From 939a9a45d3fc5ec29798a16dcda9b934bf48f6f6 Mon Sep 17 00:00:00 2001 From: Calinteodor Date: Tue, 22 Oct 2024 16:19:31 +0300 Subject: [PATCH] feat(mobile/background): use reducedUI only when app is not active and log appState (#15167) * feat(mobile/background): use reducedUI only when app is not active and log appState changes --- react/features/app/types.ts | 4 ++-- react/features/base/lastn/middleware.ts | 6 ++++-- react/features/calendar-sync/middleware.ts | 3 +-- .../conference/components/native/Conference.tsx | 8 ++++++-- react/features/mobile/background/logger.ts | 3 +++ .../mobile/background/middleware.native.ts | 5 ++++- react/features/mobile/background/reducer.ts | 7 ++++--- .../mobile/call-integration/middleware.ts | 16 +++++++++++----- react/features/mobile/full-screen/middleware.ts | 2 +- 9 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 react/features/mobile/background/logger.ts diff --git a/react/features/app/types.ts b/react/features/app/types.ts index 272d5bd2ca..6891d99a8e 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -48,7 +48,7 @@ import { IKeyboardShortcutsState } from '../keyboard-shortcuts/types'; import { ILargeVideoState } from '../large-video/reducer'; import { ILobbyState } from '../lobby/reducer'; import { IMobileAudioModeState } from '../mobile/audio-mode/reducer'; -import { IBackgroundState } from '../mobile/background/reducer'; +import { IMobileBackgroundState } from '../mobile/background/reducer'; import { ICallIntegrationState } from '../mobile/call-integration/reducer'; import { IMobileExternalApiState } from '../mobile/external-api/reducer'; import { IFullScreenState } from '../mobile/full-screen/reducer'; @@ -93,7 +93,6 @@ export interface IReduxState { 'features/analytics': IAnalyticsState; 'features/authentication': IAuthenticationState; 'features/av-moderation': IAVModerationState; - 'features/background': IBackgroundState; 'features/base/app': IAppState; 'features/base/audio-only': IAudioOnlyState; 'features/base/color-scheme': any; @@ -141,6 +140,7 @@ export interface IReduxState { 'features/large-video': ILargeVideoState; 'features/lobby': ILobbyState; 'features/mobile/audio-mode': IMobileAudioModeState; + 'features/mobile/background': IMobileBackgroundState; 'features/mobile/external-api': IMobileExternalApiState; 'features/mobile/watchos': IMobileWatchOSState; 'features/no-audio-signal': INoAudioSignalState; diff --git a/react/features/base/lastn/middleware.ts b/react/features/base/lastn/middleware.ts index c530aff354..26f37595c7 100644 --- a/react/features/base/lastn/middleware.ts +++ b/react/features/base/lastn/middleware.ts @@ -33,7 +33,7 @@ const _updateLastN = debounce(({ dispatch, getState }: IStore) => { } const { enabled: audioOnly } = state['features/base/audio-only']; - const { appState } = state['features/background'] || {}; + const { appState } = state['features/mobile/background'] || {}; const { enabled: filmStripEnabled } = state['features/filmstrip']; const config = state['features/base/config']; const { carMode } = state['features/video-layout']; @@ -44,7 +44,9 @@ const _updateLastN = debounce(({ dispatch, getState }: IStore) => { // 3. -1 as the default value. let lastNSelected = config.startLastN ?? (config.channelLastN ?? -1); - if (appState === 'background' || carMode) { + // Because this is shared, on web appState is always undefined, + // meaning that it is never active + if (navigator.product === 'ReactNative' && (appState !== 'active' || carMode)) { lastNSelected = 0; } else if (audioOnly) { const { remoteScreenShares, tileViewEnabled } = state['features/video-layout']; diff --git a/react/features/calendar-sync/middleware.ts b/react/features/calendar-sync/middleware.ts index 4ec4de2e88..539e963073 100644 --- a/react/features/calendar-sync/middleware.ts +++ b/react/features/calendar-sync/middleware.ts @@ -71,6 +71,5 @@ MiddlewareRegistry.register(store => next => action => { * @returns {void} */ function _maybeClearAccessStatus(store: IStore, { appState }: { appState: string; }) { - appState === 'background' - && store.dispatch(setCalendarAuthorization(undefined)); + appState === 'background' && store.dispatch(setCalendarAuthorization(undefined)); } diff --git a/react/features/conference/components/native/Conference.tsx b/react/features/conference/components/native/Conference.tsx index a67522e95a..fc2a4a553e 100644 --- a/react/features/conference/components/native/Conference.tsx +++ b/react/features/conference/components/native/Conference.tsx @@ -559,7 +559,7 @@ class Conference extends AbstractConference { * @returns {IProps} */ function _mapStateToProps(state: IReduxState, _ownProps: any) { - const { appState } = state['features/background']; + const { appState } = state['features/mobile/background']; const { isOpen } = state['features/participants-pane']; const { aspectRatio, reducedUI } = state['features/base/responsive-ui']; const { backgroundColor } = state['features/dynamic-branding']; @@ -581,7 +581,11 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) { _isParticipantsPaneOpen: isOpen, _largeVideoParticipantId: state['features/large-video'].participantId, _pictureInPictureEnabled: isPipEnabled(state), - _reducedUI: reducedUI || appState === 'background', + + // [iOS] - Transitioning between foreground & background, appState goes from active -> inactive -> background + // and during periods of inactivity such as entering the multitasking view, + // opening the Notification Center or in the event of an incoming call. + _reducedUI: reducedUI || appState !== 'active', _showLobby: getIsLobbyVisible(state), _startCarMode: startCarMode, _toolboxVisible: isToolboxVisible(state) diff --git a/react/features/mobile/background/logger.ts b/react/features/mobile/background/logger.ts new file mode 100644 index 0000000000..645c269270 --- /dev/null +++ b/react/features/mobile/background/logger.ts @@ -0,0 +1,3 @@ +import { getLogger } from '../../base/logging/functions'; + +export default getLogger('features/mobile/background'); diff --git a/react/features/mobile/background/middleware.native.ts b/react/features/mobile/background/middleware.native.ts index 21f7eb6e7b..e57b0801a8 100644 --- a/react/features/mobile/background/middleware.native.ts +++ b/react/features/mobile/background/middleware.native.ts @@ -5,6 +5,7 @@ import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes'; import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry'; import { _setAppStateSubscription, appStateChanged } from './actions'; +import logger from './logger'; /** * Middleware that captures App lifetime actions and subscribes to application @@ -45,6 +46,8 @@ MiddlewareRegistry.register(store => next => action => { */ function _onAppStateChange(dispatch: IStore['dispatch'], appState: string) { dispatch(appStateChanged(appState)); + + logger.debug(`appState changed to: ${appState}`); } /** @@ -59,7 +62,7 @@ function _onAppStateChange(dispatch: IStore['dispatch'], appState: string) { * @returns {Object} The value returned by {@code next(action)}. */ function _setAppStateListener({ dispatch, getState }: IStore, listener: any) { - const { subscription } = getState()['features/background']; + const { subscription } = getState()['features/mobile/background']; subscription?.remove(); diff --git a/react/features/mobile/background/reducer.ts b/react/features/mobile/background/reducer.ts index 8187524325..2a1152c48e 100644 --- a/react/features/mobile/background/reducer.ts +++ b/react/features/mobile/background/reducer.ts @@ -4,7 +4,7 @@ import ReducerRegistry from '../../base/redux/ReducerRegistry'; import { APP_STATE_CHANGED, _SET_APP_STATE_SUBSCRIPTION } from './actionTypes'; -export interface IBackgroundState { +export interface IMobileBackgroundState { appState: string; subscription?: NativeEventSubscription; } @@ -13,10 +13,11 @@ export interface IBackgroundState { * The default/initial redux state of the feature background. */ const DEFAULT_STATE = { - appState: 'active' + appState: '' }; -ReducerRegistry.register('features/background', (state = DEFAULT_STATE, action): IBackgroundState => { +// eslint-disable-next-line max-len +ReducerRegistry.register('features/mobile/background', (state = DEFAULT_STATE, action): IMobileBackgroundState => { switch (action.type) { case _SET_APP_STATE_SUBSCRIPTION: diff --git a/react/features/mobile/call-integration/middleware.ts b/react/features/mobile/call-integration/middleware.ts index 451f163b94..41ea1f9d6d 100644 --- a/react/features/mobile/call-integration/middleware.ts +++ b/react/features/mobile/call-integration/middleware.ts @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { createTrackMutedEvent } from '../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../analytics/functions'; -import { appNavigate } from '../../app/actions'; +import { appNavigate } from '../../app/actions.native'; import { IReduxState, IStore } from '../../app/types'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes'; import { SET_AUDIO_ONLY } from '../../base/audio-only/actionTypes'; @@ -30,7 +30,7 @@ import { TRACK_REMOVED, TRACK_UPDATED } from '../../base/tracks/actionTypes'; -import { isLocalTrackMuted } from '../../base/tracks/functions.any'; +import { isLocalTrackMuted } from '../../base/tracks/functions.native'; import CallKit from './CallKit'; import ConnectionService from './ConnectionService'; @@ -158,6 +158,7 @@ function _conferenceFailed({ getState }: IStore, next: Function, action: AnyActi if (callUUID) { delete action.conference.callUUID; + CallIntegration.reportCallFailed(callUUID); } } @@ -234,6 +235,7 @@ function _conferenceLeft({ getState }: IStore, next: Function, action: AnyAction if (callUUID) { delete action.conference.callUUID; + CallIntegration.endCall(callUUID); } @@ -272,7 +274,7 @@ function _conferenceWillJoin({ dispatch, getState }: IStore, next: Function, act return result; } - // When assigning the call UUID, do so in upper case, since iOS will return + // When assigning the callUUID, do so in upper case, since iOS will return // it upper-cased. conference.callUUID = (callUUID || uuidv4()).toUpperCase(); @@ -300,6 +302,7 @@ function _conferenceWillJoin({ dispatch, getState }: IStore, next: Function, act // We're not tracking the call anymore - it doesn't exist on // the native side. delete conference.callUUID; + dispatch(appNavigate(undefined)); Alert.alert( 'Call aborted', @@ -362,11 +365,12 @@ function _onPerformEndCallAction({ callUUID }: { callUUID: string; }) { const { dispatch, getState } = this; // eslint-disable-line @typescript-eslint/no-invalid-this const conference = getCurrentConference(getState); - if (conference && conference.callUUID === callUUID) { + if (conference?.callUUID === callUUID) { // We arrive here when a call is ended by the system, for example, when // another incoming call is received and the user selects "End & // Accept". delete conference.callUUID; + dispatch(appNavigate(undefined)); } } @@ -383,10 +387,12 @@ function _onPerformSetMutedCallAction({ callUUID, muted }: { callUUID: string; m const { dispatch, getState } = this; // eslint-disable-line @typescript-eslint/no-invalid-this const conference = getCurrentConference(getState); - if (conference && conference.callUUID === callUUID) { + if (conference?.callUUID === callUUID) { muted = Boolean(muted); // eslint-disable-line no-param-reassign + sendAnalytics( createTrackMutedEvent('audio', 'call-integration', muted)); + dispatch(setAudioMuted(muted, /* ensureTrack */ true)); } } diff --git a/react/features/mobile/full-screen/middleware.ts b/react/features/mobile/full-screen/middleware.ts index 058e771544..22b587d90a 100644 --- a/react/features/mobile/full-screen/middleware.ts +++ b/react/features/mobile/full-screen/middleware.ts @@ -60,7 +60,7 @@ StateListenerRegistry.register( */ function _onImmersiveChange({ getState }: IStore) { const state = getState(); - const { appState } = state['features/background']; + const { appState } = state['features/mobile/background']; if (appState === 'active') { _setFullScreen(shouldUseFullScreen(state));