diff --git a/src/components/CallView/CallView.vue b/src/components/CallView/CallView.vue index fc271255420..87014d3515a 100644 --- a/src/components/CallView/CallView.vue +++ b/src/components/CallView/CallView.vue @@ -447,19 +447,19 @@ export default { callParticipantModelsWithScreen(newValue, previousValue) { // Everytime a new screen is shared, switch to promoted view if (newValue.length > previousValue.length) { - this.callViewStore.startPresentation() + this.callViewStore.startPresentation(this.token) } else if (newValue.length === 0 && previousValue.length > 0 && !this.hasLocalScreen && !this.selectedVideoPeerId) { // last screen share stopped and no selected video, restoring previous state - this.callViewStore.stopPresentation() + this.callViewStore.stopPresentation(this.token) } }, showLocalScreen(showLocalScreen) { // Everytime the local screen is shared, switch to promoted view if (showLocalScreen) { - this.callViewStore.startPresentation() + this.callViewStore.startPresentation(this.token) } else if (this.callParticipantModelsWithScreen.length === 0 && !this.selectedVideoPeerId) { // last screen share stopped and no selected video, restoring previous state - this.callViewStore.stopPresentation() + this.callViewStore.stopPresentation(this.token) } }, hasLocalVideo(newValue) { @@ -681,12 +681,13 @@ export default { if (this.callViewStore.presentationStarted) { this.callViewStore.setCallViewMode({ + token: this.token, isGrid: false, isStripeOpen: false, clearLast: false, }) } else { - this.callViewStore.startPresentation() + this.callViewStore.startPresentation(this.token) } this.callViewStore.setSelectedVideoPeerId(null) this.screens.splice(index, 1) @@ -718,7 +719,7 @@ export default { return } this.callViewStore.setSelectedVideoPeerId(peerId) - this.callViewStore.startPresentation() + this.callViewStore.startPresentation(this.token) }, handleClickLocalVideo() { // DO nothing if no video @@ -727,7 +728,7 @@ export default { } // Deselect possible selected video this.callViewStore.setSelectedVideoPeerId('local') - this.callViewStore.startPresentation() + this.callViewStore.startPresentation(this.token) }, async fetchPeers() { diff --git a/src/components/CallView/Grid/Grid.vue b/src/components/CallView/Grid/Grid.vue index b1464dde93b..78e49b90cc8 100644 --- a/src/components/CallView/Grid/Grid.vue +++ b/src/components/CallView/Grid/Grid.vue @@ -558,7 +558,7 @@ export default { return this.isStripe }, set(value) { - this.callViewStore.setCallViewMode({ isGrid: !value, clearLast: false }) + this.callViewStore.setCallViewMode({ token: this.token, isGrid: !value, clearLast: false }) }, }, }, @@ -853,7 +853,7 @@ export default { }, handleClickStripeCollapse() { - this.callViewStore.setCallViewMode({ isStripeOpen: !this.stripeOpen, clearLast: false }) + this.callViewStore.setCallViewMode({ token: this.token, isStripeOpen: !this.stripeOpen, clearLast: false }) }, handleMovement() { diff --git a/src/components/CallView/shared/LocalVideo.vue b/src/components/CallView/shared/LocalVideo.vue index 61e6beb318d..40e50bce5f5 100644 --- a/src/components/CallView/shared/LocalVideo.vue +++ b/src/components/CallView/shared/LocalVideo.vue @@ -339,7 +339,7 @@ export default { handleStopFollowing() { this.callViewStore.setSelectedVideoPeerId(null) - this.callViewStore.stopPresentation() + this.callViewStore.stopPresentation(this.token) }, updateContainerAspectRatio([{ target }]) { diff --git a/src/components/CallView/shared/VideoBottomBar.spec.js b/src/components/CallView/shared/VideoBottomBar.spec.js index ea598bca939..6ebbe97a80a 100644 --- a/src/components/CallView/shared/VideoBottomBar.spec.js +++ b/src/components/CallView/shared/VideoBottomBar.spec.js @@ -527,7 +527,7 @@ describe('VideoBottomBar.vue', () => { test('method is called after click', async () => { callViewStore.setSelectedVideoPeerId(PEER_ID) - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) expect(callViewStore.selectedVideoPeerId).toBe(PEER_ID) expect(callViewStore.presentationStarted).toBeTruthy() diff --git a/src/components/CallView/shared/VideoBottomBar.vue b/src/components/CallView/shared/VideoBottomBar.vue index 2f9a2814ef4..30a3bf2e0d4 100644 --- a/src/components/CallView/shared/VideoBottomBar.vue +++ b/src/components/CallView/shared/VideoBottomBar.vue @@ -286,7 +286,7 @@ export default { }, handleStopFollowing() { - this.callViewStore.stopPresentation() + this.callViewStore.stopPresentation(this.token) this.callViewStore.setSelectedVideoPeerId(null) }, }, diff --git a/src/components/TopBar/TopBarMenu.vue b/src/components/TopBar/TopBarMenu.vue index a5000e480b4..058b46d2bc5 100644 --- a/src/components/TopBar/TopBarMenu.vue +++ b/src/components/TopBar/TopBarMenu.vue @@ -452,7 +452,7 @@ export default { }, changeView() { - this.callViewStore.setCallViewMode({ isGrid: !this.isGrid, clearLast: false }) + this.callViewStore.setCallViewMode({ token: this.token, isGrid: !this.isGrid, clearLast: false }) this.callViewStore.setSelectedVideoPeerId(null) }, diff --git a/src/store/participantsStore.js b/src/store/participantsStore.js index 66b3ef4ca3b..09fc6f91f93 100644 --- a/src/store/participantsStore.js +++ b/src/store/participantsStore.js @@ -845,7 +845,7 @@ const actions = { }, 10000) const callViewStore = useCallViewStore() - callViewStore.handleJoinCall({ token }) + callViewStore.handleJoinCall(getters.conversation(token)) }, async leaveCall({ commit, getters }, { token, participantIdentifier, all = false }) { diff --git a/src/store/participantsStore.spec.js b/src/store/participantsStore.spec.js index 36fd7494d88..70dc8baac55 100644 --- a/src/store/participantsStore.spec.js +++ b/src/store/participantsStore.spec.js @@ -540,6 +540,10 @@ describe('participantsStore', () => { const flags = PARTICIPANT.CALL_FLAG.WITH_AUDIO | PARTICIPANT.CALL_FLAG.WITH_VIDEO beforeEach(async () => { + testStoreConfig.getters.conversation = () => jest.fn().mockReturnValue({ + token: TOKEN, + type: 3, + }) store = new Vuex.Store(testStoreConfig) store.dispatch('addParticipant', { token: TOKEN, @@ -876,6 +880,10 @@ describe('participantsStore', () => { attendeeId: 1, sessionId: 'session-id-1', }) + testStoreConfig.getters.conversation = () => jest.fn().mockReturnValue({ + token: TOKEN, + type: 3, + }) store = new Vuex.Store(testStoreConfig) store.dispatch('addParticipant', { diff --git a/src/stores/__tests__/callView.spec.js b/src/stores/__tests__/callView.spec.js index 96a7198f084..6231c864582 100644 --- a/src/stores/__tests__/callView.spec.js +++ b/src/stores/__tests__/callView.spec.js @@ -38,11 +38,12 @@ describe('callViewStore', () => { function testDefaultGridState(type, state, browserStorageState = null) { // Arrange BrowserStorage.getItem.mockReturnValueOnce(browserStorageState) + const conversation = { token: TOKEN, type } // using commit instead of dispatch because the action also processes participants - vuexStore.commit('addConversation', { token: TOKEN, type }) + vuexStore.commit('addConversation', conversation) // Act - callViewStore.handleJoinCall({ token: TOKEN }) + callViewStore.handleJoinCall(conversation) // Assert expect(BrowserStorage.getItem).toHaveBeenCalledWith(BROWSER_STORAGE_KEY) @@ -50,6 +51,13 @@ describe('callViewStore', () => { expect(callViewStore.isStripeOpen).toBeTruthy() } + it('does not proceed without conversation object', () => { + // Act + callViewStore.handleJoinCall() + // Assert + expect(BrowserStorage.getItem).not.toHaveBeenCalledWith(BROWSER_STORAGE_KEY) + }) + it('restores grid state from BrowserStorage when joining call (true)', () => { // Arrange testDefaultGridState(CONVERSATION.TYPE.GROUP, true, 'true') @@ -72,9 +80,8 @@ describe('callViewStore', () => { }) it('switching call view mode saves in local storage', () => { - vuexStore.dispatch('updateToken', TOKEN) - callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: false, }) @@ -83,6 +90,7 @@ describe('callViewStore', () => { expect(BrowserStorage.setItem).toHaveBeenCalledWith(BROWSER_STORAGE_KEY, true) callViewStore.setCallViewMode({ + token: TOKEN, isGrid: false, isStripeOpen: true, }) @@ -93,19 +101,21 @@ describe('callViewStore', () => { it('start presentation switches off grid view and restores when it ends', () => { [{ + token: TOKEN, isGrid: true, isStripeOpen: true, }, { + token: TOKEN, isGrid: false, isStripeOpen: false, }].forEach((testState) => { callViewStore.setCallViewMode(testState) - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) expect(callViewStore.isGrid).toBeFalsy() expect(callViewStore.isStripeOpen).toBeFalsy() - callViewStore.stopPresentation() + callViewStore.stopPresentation(TOKEN) expect(callViewStore.isGrid).toEqual(testState.isGrid) expect(callViewStore.isStripeOpen).toEqual(testState.isStripeOpen) }) @@ -113,17 +123,19 @@ describe('callViewStore', () => { it('switching modes during presentation does not resets it after it ends', () => { callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: true, }) - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) // switch during presentation callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: true, }) - callViewStore.stopPresentation() + callViewStore.stopPresentation(TOKEN) // state kept, not restored expect(callViewStore.isGrid).toBeTruthy() @@ -132,26 +144,28 @@ describe('callViewStore', () => { it('starting presentation twice does not mess up remembered state', () => { callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: true, }) expect(callViewStore.presentationStarted).toBeFalsy() - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) expect(callViewStore.presentationStarted).toBeTruthy() // switch during presentation callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: true, }) - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) // state kept expect(callViewStore.presentationStarted).toBeTruthy() expect(callViewStore.isGrid).toBeTruthy() expect(callViewStore.isStripeOpen).toBeTruthy() - callViewStore.stopPresentation() + callViewStore.stopPresentation(TOKEN) expect(callViewStore.presentationStarted).toBeFalsy() // state kept, not restored expect(callViewStore.isGrid).toBeTruthy() @@ -160,24 +174,26 @@ describe('callViewStore', () => { it('stopping presentation twice does not mess up remembered state', () => { callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: true, }) expect(callViewStore.presentationStarted).toBeFalsy() - callViewStore.startPresentation() + callViewStore.startPresentation(TOKEN) expect(callViewStore.presentationStarted).toBeTruthy() - callViewStore.stopPresentation() + callViewStore.stopPresentation(TOKEN) expect(callViewStore.presentationStarted).toBeFalsy() expect(callViewStore.isGrid).toBeTruthy() expect(callViewStore.isStripeOpen).toBeTruthy() callViewStore.setCallViewMode({ + token: TOKEN, isGrid: false, isStripeOpen: false, }) - callViewStore.stopPresentation() + callViewStore.stopPresentation(TOKEN) expect(callViewStore.presentationStarted).toBeFalsy() // state kept, not reset expect(callViewStore.isGrid).toBeFalsy() @@ -188,6 +204,7 @@ describe('callViewStore', () => { expect(callViewStore.lastIsGrid).toEqual(null) expect(callViewStore.lastIsStripeOpen).toEqual(null) callViewStore.setCallViewMode({ + token: TOKEN, isGrid: true, isStripeOpen: false, }) @@ -195,6 +212,7 @@ describe('callViewStore', () => { expect(callViewStore.lastIsStripeOpen).toBeTruthy() callViewStore.setCallViewMode({ + token: TOKEN, clearLast: false, }) expect(callViewStore.lastIsGrid).toBeFalsy() diff --git a/src/stores/callView.js b/src/stores/callView.js index 2cec27a8ea4..360be6eafcf 100644 --- a/src/stores/callView.js +++ b/src/stores/callView.js @@ -7,7 +7,6 @@ import { defineStore } from 'pinia' import { CONVERSATION } from '../constants.js' import BrowserStorage from '../services/BrowserStorage.js' -import store from '../store/index.js' export const useCallViewStore = defineStore('callView', { state: () => ({ @@ -44,13 +43,16 @@ export const useCallViewStore = defineStore('callView', { this.selectedVideoPeerId = value }, - handleJoinCall({ token }) { - const gridPreference = BrowserStorage.getItem(`callprefs-${token}-isgrid`) + handleJoinCall(conversation) { + if (!conversation) { + return + } + const gridPreference = BrowserStorage.getItem(`callprefs-${conversation.token}-isgrid`) const isGrid = gridPreference === null // not defined yet, default to grid view for group/public calls, otherwise speaker view - ? [CONVERSATION.TYPE.GROUP, CONVERSATION.TYPE.PUBLIC].includes(store.getters.conversations[token].type) + ? [CONVERSATION.TYPE.GROUP, CONVERSATION.TYPE.PUBLIC].includes(conversation.type) : gridPreference === 'true' - this.setCallViewMode({ isGrid, isStripeOpen: true }) + this.setCallViewMode({ token: conversation.token, isGrid, isStripeOpen: true }) }, /** @@ -58,11 +60,12 @@ export const useCallViewStore = defineStore('callView', { * If clearLast is false, also remembers it in separate properties. * * @param {object} data the wrapping object; + * @param {string} data.token current conversation token; * @param {boolean|null} [data.isGrid=null] true for enabled grid mode, false for speaker view; * @param {boolean|null} [data.isStripeOpen=null] true for visible striped mode, false for speaker view; * @param {boolean} [data.clearLast=true] set false to not reset last temporary remembered state; */ - setCallViewMode({ isGrid = null, isStripeOpen = null, clearLast = true }) { + setCallViewMode({ token, isGrid = null, isStripeOpen = null, clearLast = true }) { if (clearLast) { this.lastIsGrid = null this.lastIsStripeOpen = null @@ -70,7 +73,7 @@ export const useCallViewStore = defineStore('callView', { if (isGrid !== null) { this.lastIsGrid = this.isGrid - BrowserStorage.setItem(`callprefs-${store.getters.getToken()}-isgrid`, isGrid) + BrowserStorage.setItem(`callprefs-${token}-isgrid`, isGrid) this.isGrid = isGrid } @@ -85,15 +88,16 @@ export const useCallViewStore = defineStore('callView', { * * Switches off grid mode and closes the stripe. * Remembers the call view state for after the end of the presentation. + * @param {string} token current conversation token. */ - startPresentation() { + startPresentation(token) { // don't start twice, this would prevent multiple screen shares to clear the last call view state if (this.presentationStarted) { return } this.presentationStarted = true - this.setCallViewMode({ isGrid: false, isStripeOpen: false, clearLast: false }) + this.setCallViewMode({ token, isGrid: false, isStripeOpen: false, clearLast: false }) }, /** @@ -101,8 +105,9 @@ export const useCallViewStore = defineStore('callView', { * * Restores call view state from before starting the presentation, * given that the last state was not cleared manually. + * @param {string} token current conversation token. */ - stopPresentation() { + stopPresentation(token) { if (!this.presentationStarted) { return } @@ -110,7 +115,7 @@ export const useCallViewStore = defineStore('callView', { if (!this.isGrid && !this.isStripeOpen) { // User didn't pick grid view during presentation, restore previous state - this.setCallViewMode({ isGrid: this.lastIsGrid, isStripeOpen: this.lastIsStripeOpen, clearLast: false }) + this.setCallViewMode({ token, isGrid: this.lastIsGrid, isStripeOpen: this.lastIsStripeOpen, clearLast: false }) } },