agora-appbuilder-core 4.1.24-beta.1 → 4.1.24-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.24-beta.1",
3
+ "version": "4.1.24-beta.2",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -77,8 +77,8 @@ const DefaultConfig = {
77
77
  CHAT_ORG_NAME: "",
78
78
  CHAT_APP_NAME: "",
79
79
  CHAT_URL: "",
80
- CLI_VERSION: "3.1.24-beta.1",
81
- CORE_VERSION: "4.1.24-beta.1",
80
+ CLI_VERSION: "3.1.24-beta.2",
81
+ CORE_VERSION: "4.1.24-beta.2",
82
82
  DISABLE_LANDSCAPE_MODE: false,
83
83
  STT_AUTO_START: false,
84
84
  CLOUD_RECORDING_AUTO_START: false,
@@ -37,6 +37,8 @@ import {
37
37
  videoRoomGoToActiveSpeakerText,
38
38
  } from '../language/default-labels/videoCallScreenLabels';
39
39
  import {useFullScreen} from '..//utils/useFullScreen';
40
+ import {LogSource, logger} from '../logger/AppBuilderLogger';
41
+ import {getPinnedLayoutState} from './pinnedLayoutState';
40
42
  const {topPinned} = layoutProps;
41
43
 
42
44
  const PinnedVideo = ({renderData}) => {
@@ -50,12 +52,38 @@ const PinnedVideo = ({renderData}) => {
50
52
  const {width} = useWindowDimensions();
51
53
  const isDesktop = width > BREAKPOINTS.lg;
52
54
  const isSidePinnedlayout = topPinned === true ? false : isDesktop; // if either explicity set to false or auto evaluation
53
- const [maxUid, ...minUids] = renderData;
55
+ const {maximizedUid, sidebarUids} = getPinnedLayoutState(
56
+ renderData,
57
+ pinnedUid,
58
+ secondaryPinnedUid,
59
+ );
60
+ const renderDataSnapshot = JSON.stringify(renderData);
61
+ const sidebarUidsSnapshot = JSON.stringify(sidebarUids);
54
62
  const activeSpeaker = useActiveSpeaker();
55
63
  const {dispatch} = useContext(DispatchContext);
56
64
  const {videoTileInViewPortState} = useVideoCall();
57
65
  const {getWhiteboardUid = () => 0} = useWhiteboard();
58
66
  const {defaultContent, customContent} = useContent();
67
+ useEffect(() => {
68
+ logger.log(
69
+ LogSource.Internals,
70
+ 'LAYOUT',
71
+ '[PINNED_LAYOUT] pinned layout UIDs resolved',
72
+ {
73
+ renderData: JSON.parse(renderDataSnapshot),
74
+ pinnedUid: pinnedUid || null,
75
+ secondaryPinnedUid: secondaryPinnedUid || null,
76
+ maximizedUid,
77
+ sidebarUids: JSON.parse(sidebarUidsSnapshot),
78
+ },
79
+ );
80
+ }, [
81
+ renderDataSnapshot,
82
+ pinnedUid,
83
+ secondaryPinnedUid,
84
+ maximizedUid,
85
+ sidebarUidsSnapshot,
86
+ ]);
59
87
  useEffect(() => {
60
88
  if (activeSpeaker && !videoTileInViewPortState[activeSpeaker] && isOnTop) {
61
89
  dispatch({
@@ -85,21 +113,20 @@ const PinnedVideo = ({renderData}) => {
85
113
  }
86
114
  };
87
115
 
88
- const isWhiteboard = customContent && customContent[pinnedUid || maxUid];
116
+ const isWhiteboard = customContent && customContent[maximizedUid];
89
117
  const isRemoteScreenshare =
90
- (pinnedUid || maxUid) != screenUid &&
118
+ maximizedUid != screenUid &&
91
119
  defaultContent &&
92
- defaultContent[pinnedUid || maxUid]?.video &&
93
- defaultContent[pinnedUid || maxUid]?.type === 'screenshare';
120
+ defaultContent[maximizedUid]?.video &&
121
+ defaultContent[maximizedUid]?.type === 'screenshare';
94
122
 
95
123
  //fixed issue on expand/collapse for screenshare
96
124
  //screen will have the fullscreen functionality
97
125
  useEffect(() => {
98
- const maximizedUid = pinnedUid || maxUid;
99
126
  if (getWhiteboardUid() != maximizedUid && collapse) {
100
127
  setCollapse(false);
101
128
  }
102
- }, [isWhiteboard, collapse, pinnedUid, maxUid, getWhiteboardUid]);
129
+ }, [isWhiteboard, collapse, maximizedUid, getWhiteboardUid]);
103
130
 
104
131
  return (
105
132
  <View
@@ -129,9 +156,7 @@ const PinnedVideo = ({renderData}) => {
129
156
  marginBottom: 8,
130
157
  }
131
158
  }>
132
- {secondaryPinnedUid &&
133
- secondaryPinnedUid !== pinnedUid &&
134
- secondaryPinnedUid !== maxUid ? (
159
+ {secondaryPinnedUid && sidebarUids.includes(secondaryPinnedUid) ? (
135
160
  <Pressable
136
161
  disabled={true}
137
162
  style={
@@ -158,10 +183,8 @@ const PinnedVideo = ({renderData}) => {
158
183
  <></>
159
184
  )}
160
185
  {/* Pinned Video Top View(Desktop minimized and Mobile native and Mobile web) / Side View(Desktop maximized)*/}
161
- {minUids?.map((minUid, i) => {
186
+ {sidebarUids?.map(minUid => {
162
187
  if (minUid === secondaryPinnedUid) return null;
163
- if (minUid === pinnedUid) return null;
164
- if (minUid === maxUid) return null;
165
188
  //rendering minimized view
166
189
  return (
167
190
  <Pressable
@@ -194,7 +217,7 @@ const PinnedVideo = ({renderData}) => {
194
217
  marginRight: 8,
195
218
  }
196
219
  }
197
- key={'minVideo' + i}
220
+ key={'minVideo' + minUid}
198
221
  onPress={() => {}}>
199
222
  <RenderComponent uid={minUid} />
200
223
  </Pressable>
@@ -259,7 +282,7 @@ const PinnedVideo = ({renderData}) => {
259
282
  )}
260
283
  </>
261
284
  )}
262
- {(pinnedUid || maxUid) && (
285
+ {maximizedUid && (
263
286
  <View
264
287
  style={
265
288
  isSidePinnedlayout
@@ -268,7 +291,7 @@ const PinnedVideo = ({renderData}) => {
268
291
  : style.width80
269
292
  : style.flex8
270
293
  }>
271
- <View style={style.flex1} key={'maxVideo' + (pinnedUid || maxUid)}>
294
+ <View style={style.flex1} key={'maxVideo' + maximizedUid}>
272
295
  {isSidePinnedlayout && (isWhiteboard || isRemoteScreenshare) ? (
273
296
  <IconButton
274
297
  containerStyle={{
@@ -279,12 +302,9 @@ const PinnedVideo = ({renderData}) => {
279
302
  elevation: 999,
280
303
  }}
281
304
  onPress={() => {
282
- if (
283
- defaultContent &&
284
- defaultContent[pinnedUid || maxUid]?.video
285
- ) {
286
- requestFullscreen(pinnedUid || maxUid);
287
- } else if (customContent[pinnedUid || maxUid]) {
305
+ if (defaultContent && defaultContent[maximizedUid]?.video) {
306
+ requestFullscreen(maximizedUid);
307
+ } else if (customContent[maximizedUid]) {
288
308
  setCollapse(!collapse);
289
309
  }
290
310
  }}
@@ -348,7 +368,7 @@ const PinnedVideo = ({renderData}) => {
348
368
  <></>
349
369
  )} */}
350
370
  {/** Render the maximized view */}
351
- <RenderComponent uid={pinnedUid || maxUid} isMax={true} />
371
+ <RenderComponent uid={maximizedUid} isMax={true} />
352
372
  </View>
353
373
  </View>
354
374
  )}
@@ -0,0 +1,47 @@
1
+ import {
2
+ canViewInLarge,
3
+ getPinnedLayoutState,
4
+ isUidMaximized,
5
+ } from '../pinnedLayoutState';
6
+
7
+ describe('pinned layout state', () => {
8
+ it('keeps every non-maximized UID visible when active-speaker ordering moves another UID to index zero', () => {
9
+ expect(getPinnedLayoutState([200, 101, 100, 201], 101)).toEqual({
10
+ maximizedUid: 101,
11
+ sidebarUids: [200, 100, 201],
12
+ });
13
+ });
14
+
15
+ it('keeps the secondary pin first without removing any other sidebar UID', () => {
16
+ expect(getPinnedLayoutState([200, 100, 101, 201], 101, 100)).toEqual({
17
+ maximizedUid: 101,
18
+ sidebarUids: [100, 200, 201],
19
+ });
20
+ });
21
+
22
+ it('falls back to the first render UID when there is no valid primary pin', () => {
23
+ expect(getPinnedLayoutState([200, 100, 101], 999)).toEqual({
24
+ maximizedUid: 200,
25
+ sidebarUids: [100, 101],
26
+ });
27
+ });
28
+
29
+ it('does not offer View in large for either pinned position', () => {
30
+ expect(canViewInLarge(101, 101, 100)).toBe(false);
31
+ expect(canViewInLarge(100, 101, 100)).toBe(false);
32
+ expect(canViewInLarge(200, 101, 100)).toBe(true);
33
+ });
34
+
35
+ it('does not offer View in large for the fallback maximized UID', () => {
36
+ expect(canViewInLarge(101, undefined, 100, 101)).toBe(false);
37
+ expect(canViewInLarge(100, undefined, 100, 101)).toBe(false);
38
+ expect(canViewInLarge(200, undefined, 100, 101)).toBe(true);
39
+ });
40
+
41
+ it('identifies explicit and fallback maximized UIDs for tile actions', () => {
42
+ expect(isUidMaximized(101, 101, 200)).toBe(true);
43
+ expect(isUidMaximized(200, 101, 200)).toBe(false);
44
+ expect(isUidMaximized(200, undefined, 200)).toBe(true);
45
+ expect(isUidMaximized(100, undefined, 200)).toBe(false);
46
+ });
47
+ });
@@ -77,6 +77,7 @@ import {
77
77
  DEFAULT_ACTION_KEYS,
78
78
  UserActionMenuItemsConfig,
79
79
  } from '../../atoms/UserActionMenuPreset';
80
+ import {canViewInLarge, isUidMaximized} from '../pinnedLayoutState';
80
81
 
81
82
  interface UserActionMenuOptionsOptionsProps {
82
83
  user: ContentInterface;
@@ -207,7 +208,14 @@ export default function UserActionMenuOptionsOptions(
207
208
  )
208
209
  ) {
209
210
  if (enablePinForMe) {
210
- if (pinnedUid !== user.uid) {
211
+ if (
212
+ canViewInLarge(
213
+ user.uid,
214
+ pinnedUid,
215
+ secondaryPinnedUid,
216
+ activeUids?.[0],
217
+ )
218
+ ) {
211
219
  const viewInLargeKey = ActionMenuKeys.VIEW_IN_LARGE;
212
220
  const viewInLargeConfig = userActionMenuItems?.[viewInLargeKey] ?? {};
213
221
  const isPinned = pinnedUid === user.uid;
@@ -274,9 +282,14 @@ export default function UserActionMenuOptionsOptions(
274
282
  const pinToTopKey = ActionMenuKeys.PIN_TO_TOP;
275
283
  const pinToTopConfig = userActionMenuItems?.[pinToTopKey] ?? {};
276
284
  const isPinnedToTop = user.uid === secondaryPinnedUid;
285
+ const isMaximized = isUidMaximized(
286
+ user.uid,
287
+ pinnedUid,
288
+ activeUids?.[0],
289
+ );
277
290
  const isOnlyOneActive = activeUids?.length === 1;
278
291
 
279
- if (!pinToTopConfig.hide) {
292
+ if (!pinToTopConfig.hide && !isMaximized) {
280
293
  items.push({
281
294
  key: pinToTopKey,
282
295
  disabled: pinToTopConfig.disabled ?? isOnlyOneActive,
@@ -0,0 +1,43 @@
1
+ import {UidType} from '../../agora-rn-uikit';
2
+
3
+ export const getPinnedLayoutState = (
4
+ renderData: UidType[],
5
+ pinnedUid?: UidType,
6
+ secondaryPinnedUid?: UidType,
7
+ ) => {
8
+ const maximizedUid: UidType =
9
+ (pinnedUid && renderData.includes(pinnedUid) ? pinnedUid : renderData[0]) ||
10
+ 0;
11
+ const sidebarUids = renderData.filter(uid => uid !== maximizedUid);
12
+
13
+ if (
14
+ secondaryPinnedUid &&
15
+ sidebarUids.includes(secondaryPinnedUid) &&
16
+ sidebarUids[0] !== secondaryPinnedUid
17
+ ) {
18
+ return {
19
+ maximizedUid,
20
+ sidebarUids: [
21
+ secondaryPinnedUid,
22
+ ...sidebarUids.filter(uid => uid !== secondaryPinnedUid),
23
+ ],
24
+ };
25
+ }
26
+
27
+ return {maximizedUid, sidebarUids};
28
+ };
29
+
30
+ export const isUidMaximized = (
31
+ uid: UidType,
32
+ pinnedUid?: UidType,
33
+ fallbackMaximizedUid?: UidType,
34
+ ) => uid === (pinnedUid || fallbackMaximizedUid);
35
+
36
+ export const canViewInLarge = (
37
+ uid: UidType,
38
+ pinnedUid?: UidType,
39
+ secondaryPinnedUid?: UidType,
40
+ fallbackMaximizedUid?: UidType,
41
+ ) =>
42
+ !isUidMaximized(uid, pinnedUid, fallbackMaximizedUid) &&
43
+ uid !== secondaryPinnedUid;
@@ -247,10 +247,12 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
247
247
  uid={user.uid}
248
248
  hasLeadingIcon={
249
249
  currentLayout === DefaultLayouts[1].name &&
250
+ !isMax &&
250
251
  user.uid === secondaryPinnedUid
251
252
  }
252
253
  />
253
254
  {currentLayout === DefaultLayouts[1].name &&
255
+ !isMax &&
254
256
  user.uid === secondaryPinnedUid ? (
255
257
  <View
256
258
  style={{