diff --git a/src/AppBar.module.css b/src/AppBar.module.css
index 5f7888d2..13e3b759 100644
--- a/src/AppBar.module.css
+++ b/src/AppBar.module.css
@@ -1,5 +1,21 @@
.bar {
flex-shrink: 0;
+ position: relative;
+}
+
+/* Pseudo-element for the gradient background */
+.bar::before {
+ content: "";
+ position: absolute;
+ inset-inline: 0;
+ /* Extend the gradient beyond the bottom of the header for readability */
+ inset-block: -24px;
+ z-index: var(--call-view-header-footer-layer);
+ background: linear-gradient(
+ 0deg,
+ rgba(0, 0, 0, 0) 0%,
+ var(--cpd-color-bg-canvas-default) 100%
+ );
}
.bar > header {
diff --git a/src/grid/OneOnOnePortraitLayout.tsx b/src/grid/OneOnOnePortraitLayout.tsx
index 2f302349..a4c30462 100644
--- a/src/grid/OneOnOnePortraitLayout.tsx
+++ b/src/grid/OneOnOnePortraitLayout.tsx
@@ -31,7 +31,7 @@ export const makeOneOnOnePortraitLayout: CallLayout<
diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx
index 7ee9b589..0f6d38f2 100644
--- a/src/room/InCallView.tsx
+++ b/src/room/InCallView.tsx
@@ -240,6 +240,7 @@ export const InCallView: FC = ({
const edgeToEdge = useBehavior(vm.edgeToEdge$);
const tileStoreGeneration = useBehavior(vm.tileStoreGeneration$);
const [debugTileLayout] = useSetting(debugTileLayoutSetting);
+ const showNameTags = useBehavior(vm.showNameTags$);
const gridMode = useBehavior(vm.gridMode$);
const showHeader = useBehavior(vm.showHeader$);
const showFooter = useBehavior(vm.showFooter$);
@@ -432,12 +433,11 @@ export const InCallView: FC = ({
}: TileProps): ReactNode {
const spotlightExpanded = useBehavior(vm.spotlightExpanded$);
const onToggleExpanded = useBehavior(vm.toggleSpotlightExpanded$);
- const showSpeakingIndicatorsValue = useBehavior(
- vm.showSpeakingIndicators$,
- );
- const showSpotlightIndicatorsValue = useBehavior(
+ const showSpotlightIndicators = useBehavior(
vm.showSpotlightIndicators$,
);
+ const showSpeakingIndicators = useBehavior(vm.showSpeakingIndicators$);
+ const showNameTags = useBehavior(vm.showNameTags$);
return model instanceof GridTileViewModel ? (
= ({
targetHeight={targetHeight}
className={classNames(className, styles.tile)}
style={style}
- showSpeakingIndicators={showSpeakingIndicatorsValue}
+ showSpeakingIndicators={showSpeakingIndicators}
+ showNameTags={showNameTags}
focusable={!contentObscured}
/>
) : (
@@ -459,7 +460,8 @@ export const InCallView: FC = ({
onToggleExpanded={onToggleExpanded}
targetWidth={targetWidth}
targetHeight={targetHeight}
- showIndicators={showSpotlightIndicatorsValue}
+ showIndicators={showSpotlightIndicators}
+ showNameTags={showNameTags}
focusable={!contentObscured}
className={classNames(className, styles.tile)}
style={style}
@@ -492,6 +494,7 @@ export const InCallView: FC = ({
targetWidth={gridBounds.width}
targetHeight={gridBounds.height}
showIndicators={false}
+ showNameTags={showNameTags}
focusable={!contentObscured}
aria-hidden={contentObscured}
/>
@@ -505,10 +508,8 @@ export const InCallView: FC = ({
className={styles.fixedGrid}
style={{
insetBlockStart:
- edgeToEdge || headerBounds.height === 0
- ? bounds.top
- : headerBounds.bottom,
- height: gridBounds.height,
+ edgeToEdge || headerBounds.height === 0 ? 0 : headerBounds.bottom,
+ height: edgeToEdge ? "100%" : gridBounds.height,
}}
model={layout}
Layout={layers.fixed}
diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts
index a9fd18fc..1b795d7d 100644
--- a/src/state/CallViewModel/CallViewModel.ts
+++ b/src/state/CallViewModel/CallViewModel.ts
@@ -341,6 +341,7 @@ export interface CallViewModel {
tileStoreGeneration$: Behavior;
showSpotlightIndicators$: Behavior;
showSpeakingIndicators$: Behavior;
+ showNameTags$: Behavior;
spotlightExpanded$: Behavior;
toggleSpotlightExpanded$: Behavior<(() => void) | null>;
gridMode$: Behavior;
@@ -1273,6 +1274,31 @@ export function createCallViewModel$(
),
);
+ const showNameTags$ = scope.behavior(
+ layoutMedia$.pipe(
+ switchMap((l) =>
+ l.type === "pip" || l.type === "one-on-one-portrait"
+ ? matrixRoomMembers$.pipe(
+ map(
+ (members) =>
+ // Hide name tags by default in these layouts. For safety we
+ // still need to show them in case it wouldn't be clear who
+ // the spotlight media belongs to.
+ // TODO: Respect io.element.functional_members
+ // TODO: Only hide name tags in DMs, not group chats that just
+ // happen to have only 2 users
+ members.size > 2 ||
+ !(l.type === "pip"
+ ? l.spotlight.length <= 1 &&
+ members.has(l.spotlight[0].userId)
+ : members.has(l.spotlight.userId)),
+ ),
+ )
+ : of(true),
+ ),
+ ),
+ );
+
const toggleSpotlightExpanded$ = scope.behavior<(() => void) | null>(
windowMode$.pipe(
switchMap((mode) =>
@@ -1716,6 +1742,7 @@ export function createCallViewModel$(
tileStoreGeneration$: tileStoreGeneration$,
showSpotlightIndicators$: showSpotlightIndicators$,
showSpeakingIndicators$: showSpeakingIndicators$,
+ showNameTags$,
showHeader$: showHeader$,
showFooter$: showFooter$,
edgeToEdge$,
diff --git a/src/state/OneOnOnePortraitLayout.ts b/src/state/OneOnOnePortraitLayout.ts
index 8f4e3b8c..10365f45 100644
--- a/src/state/OneOnOnePortraitLayout.ts
+++ b/src/state/OneOnOnePortraitLayout.ts
@@ -25,16 +25,16 @@ export function oneOnOnePortraitLayout(
pipAlignment$: BehaviorSubject,
prevTiles: TileStore,
): [OneOnOnePortraitLayout, TileStore] {
- const update = prevTiles.from(media.pip === undefined ? 1 : 2);
- if (media.pip !== undefined) update.registerGridTile(media.pip, false);
- update.registerGridTile(media.spotlight, false, true);
+ const update = prevTiles.from(media.pip === undefined ? 0 : 1);
+ update.registerSpotlight([media.spotlight], true);
+ if (media.pip !== undefined) update.registerGridTile(media.pip);
const tiles = update.build();
return [
{
type: media.type,
foreground: "fixed",
- spotlight: tiles.gridTilesByMedia.get(media.spotlight)!,
+ spotlight: tiles.spotlightTile!,
pip: media.pip && tiles.gridTilesByMedia.get(media.pip),
pipSize$,
pipAlignment$,
diff --git a/src/state/TileStore.ts b/src/state/TileStore.ts
index ca9381bc..300e6bd2 100644
--- a/src/state/TileStore.ts
+++ b/src/state/TileStore.ts
@@ -59,37 +59,11 @@ class GridTileData {
this.media$.next(value);
}
- private readonly showNameTag$: BehaviorSubject;
- public get showNameTag(): boolean {
- return this.showNameTag$.value;
- }
- public set showNameTag(value: boolean) {
- this.showNameTag$.next(value);
- }
-
- private readonly edgeToEdge$: BehaviorSubject;
- public get edgeToEdge(): boolean {
- return this.edgeToEdge$.value;
- }
- public set edgeToEdge(value: boolean) {
- this.edgeToEdge$.next(value);
- }
-
public readonly vm: GridTileViewModel;
- public constructor(
- media: UserMediaViewModel | RingingMediaViewModel,
- showNameTag: boolean,
- edgeToEdge: boolean,
- ) {
+ public constructor(media: UserMediaViewModel | RingingMediaViewModel) {
this.media$ = new BehaviorSubject(media);
- this.showNameTag$ = new BehaviorSubject(showNameTag);
- this.edgeToEdge$ = new BehaviorSubject(edgeToEdge);
- this.vm = new GridTileViewModel(
- this.media$,
- this.showNameTag$,
- this.edgeToEdge$,
- );
+ this.vm = new GridTileViewModel(this.media$);
}
}
@@ -209,8 +183,6 @@ export class TileStoreBuilder {
*/
public registerGridTile(
media: UserMediaViewModel | RingingMediaViewModel,
- showNameTag = true,
- edgeToEdge = false,
): void {
if (DEBUG_ENABLED)
logger.debug(
@@ -248,8 +220,6 @@ export class TileStoreBuilder {
this.stationaryGridEntries[prevIndex] = entry;
// Do the media swap
entry.media = media;
- entry.showNameTag = showNameTag;
- entry.edgeToEdge = edgeToEdge;
this.prevGridByMedia.delete(this.spotlight.media[0]);
this.prevGridByMedia.set(media, prev);
} else {
@@ -258,7 +228,7 @@ export class TileStoreBuilder {
(nowVisible
? this.visibleGridEntries
: this.invisibleGridEntries
- ).push(new GridTileData(media, showNameTag, edgeToEdge));
+ ).push(new GridTileData(media));
}
this.numGridEntries++;
@@ -274,12 +244,10 @@ export class TileStoreBuilder {
(this.numGridEntries < this.visibleTiles
? this.visibleGridEntries
: this.invisibleGridEntries
- ).push(new GridTileData(media, showNameTag, edgeToEdge));
+ ).push(new GridTileData(media));
} else {
// Reuse the existing tile
const [entry, prevIndex] = prev;
- entry.showNameTag = showNameTag;
- entry.edgeToEdge = edgeToEdge;
const previouslyVisible = prevIndex < this.visibleTiles;
const nowVisible = this.numGridEntries < this.visibleTiles;
// If it doesn't need to move between the visible/invisible sections of
@@ -302,7 +270,7 @@ export class TileStoreBuilder {
* method will more eagerly try to reuse an existing tile, replacing its
* media, than registerGridTile would.
*/
- public registerPipTile(media: UserMediaViewModel, showNameTag = true): void {
+ public registerPipTile(media: UserMediaViewModel): void {
if (DEBUG_ENABLED)
logger.debug(
`[TileStore, ${this.generation}] register PiP tile: ${media.displayName$.value}`,
@@ -314,12 +282,10 @@ export class TileStoreBuilder {
this.stationaryGridEntries[0] = entry;
// Do the media swap
entry.media = media;
- entry.showNameTag = showNameTag;
- entry.edgeToEdge = false;
this.prevGridByMedia.delete(entry.media);
this.prevGridByMedia.set(media, [entry, 0]);
} else {
- this.visibleGridEntries.push(new GridTileData(media, showNameTag, false));
+ this.visibleGridEntries.push(new GridTileData(media));
}
this.numGridEntries++;
diff --git a/src/state/TileViewModel.ts b/src/state/TileViewModel.ts
index 39b0f5a7..eeec0c88 100644
--- a/src/state/TileViewModel.ts
+++ b/src/state/TileViewModel.ts
@@ -22,8 +22,6 @@ export class GridTileViewModel {
public readonly media$: Behavior<
UserMediaViewModel | RingingMediaViewModel
>,
- public readonly showNameTag$: Behavior,
- public readonly edgeToEdge$: Behavior,
) {}
}
diff --git a/src/state/layout-types.ts b/src/state/layout-types.ts
index 90130865..04905efd 100644
--- a/src/state/layout-types.ts
+++ b/src/state/layout-types.ts
@@ -123,7 +123,7 @@ export interface OneOnOneLandscapeLayout {
export interface OneOnOnePortraitLayout {
type: "one-on-one-portrait";
foreground: "fixed";
- spotlight: GridTileViewModel;
+ spotlight: SpotlightTileViewModel;
pip?: GridTileViewModel;
pipSize$: Behavior<"sm" | "lg">;
pipAlignment$: BehaviorSubject;
diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx
index be4c7828..88754b9d 100644
--- a/src/tile/GridTile.tsx
+++ b/src/tile/GridTile.tsx
@@ -62,7 +62,7 @@ interface TileProps {
targetHeight: number;
displayName: string;
mxcAvatarUrl: string | undefined;
- showNameTag: boolean;
+ showNameTags: boolean;
focusable: boolean;
}
@@ -399,6 +399,7 @@ interface GridTileProps {
className?: string;
style?: ComponentProps["style"];
showSpeakingIndicators: boolean;
+ showNameTags: boolean;
focusable: boolean;
}
@@ -407,7 +408,6 @@ export const GridTile: FC = ({
vm,
showSpeakingIndicators,
onOpenProfile,
- className,
...props
}) => {
const ourRef = useRef(null);
@@ -415,20 +415,14 @@ export const GridTile: FC = ({
const media = useBehavior(vm.media$);
const displayName = useBehavior(media.displayName$);
const mxcAvatarUrl = useBehavior(media.mxcAvatarUrl$);
- const showNameTag = useBehavior(vm.showNameTag$);
- const edgeToEdge = useBehavior(vm.edgeToEdge$);
-
- const classes = classNames(className, { [styles.edgeToEdge]: edgeToEdge });
if (media.type === "ringing") {
return (
);
@@ -436,13 +430,11 @@ export const GridTile: FC = ({
return (
);
@@ -450,12 +442,10 @@ export const GridTile: FC = ({
return (
);
diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx
index 508c027d..6ff97f7a 100644
--- a/src/tile/MediaView.tsx
+++ b/src/tile/MediaView.tsx
@@ -44,11 +44,7 @@ interface Props extends ComponentProps {
videoEnabled: boolean;
unencryptedWarning: boolean;
status?: { text: string; Icon: ComponentType> };
- /**
- * Whether to show the participant's name tag.
- * @default true
- */
- showNameTag?: boolean;
+ showNameTags: boolean;
nameTagLeadingIcon?: ReactNode;
displayName: string;
mxcAvatarUrl: string | undefined;
@@ -77,7 +73,7 @@ export const MediaView: FC = ({
userId,
videoEnabled,
unencryptedWarning,
- showNameTag = true,
+ showNameTags,
nameTagLeadingIcon,
displayName,
mxcAvatarUrl,
@@ -207,7 +203,7 @@ export const MediaView: FC = ({
)*/}
- {showNameTag ? (
+ {showNameTags && targetWidth >= 100 ? (
{nameTagLeadingIcon}
;
/**
@@ -258,6 +260,7 @@ const SpotlightItem: FC = ({
vm,
targetWidth,
targetHeight,
+ showNameTags,
focusable,
intersectionObserver$,
snap,
@@ -293,6 +296,7 @@ const SpotlightItem: FC = ({
userId: vm.userId,
displayName,
mxcAvatarUrl,
+ showNameTags,
focusable,
"aria-hidden": ariaHidden,
};
@@ -381,6 +385,7 @@ interface Props {
targetWidth: number;
targetHeight: number;
showIndicators: boolean;
+ showNameTags: boolean;
focusable: boolean;
className?: string;
style?: ComponentProps["style"];
@@ -394,6 +399,7 @@ export const SpotlightTile: FC = ({
targetWidth,
targetHeight,
showIndicators,
+ showNameTags,
focusable = true,
className,
style,
@@ -504,6 +510,7 @@ export const SpotlightTile: FC = ({
vm={vm}
targetWidth={targetWidth}
targetHeight={targetHeight}
+ showNameTags={showNameTags}
focusable={focusable}
intersectionObserver$={intersectionObserver$}
// This is how we get the container to scroll to the right media