feat(dynamic-branding) add initial mobile SDK customization

* feat(dynamic-branding) sdk customization

* feat(dynamic-branding) unsetDynamicBranding when we disconnect

* feat(dynamic-branding) added branding colors to conference

* feat(dynamic-branding) extracted logger to its own file

* feat(dynamic-branding) reverted style change

* feat(dynamic-branding) unset branding if connection failed

* feat(dynamic-branding) removed index.js, updated imports, added ImageBackground component

* feat(dynamic-branding) created logger feature object

* feat(dynamic-branding) moved brandingStyles to mapStateToProps, used SvGUri

* feat(dynamic-branding) created BrandingImageBackground component, fixed styles

* feat(dynamic-branding) moved BrandingImageBackground to dynamic-branding feature

* feat(dynamic-branding) fixed linter

* feat(dynamic-branding) added style comment
This commit is contained in:
Calinteodor 2022-05-23 18:02:14 +03:00 committed by GitHub
parent 543f273792
commit f3f9cd3d05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 212 additions and 54 deletions

View File

@ -1,6 +1,7 @@
// @flow
import '../authentication/middleware';
import '../dynamic-branding/middleware';
import '../gifs/middleware';
import '../mobile/audio-mode/middleware';
import '../mobile/background/middleware';

View File

@ -13,6 +13,7 @@ import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { TestConnectionInfo } from '../../../base/testing';
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
import { DisplayNameLabel } from '../../../display-name';
import { BrandingImageBackground } from '../../../dynamic-branding';
import {
FILMSTRIP_SIZE,
Filmstrip,
@ -55,6 +56,16 @@ type Props = AbstractProps & {
*/
_aspectRatio: Symbol,
/**
* Branding styles for conference.
*/
_brandingStyles: Object,
/**
* Branding image background.
*/
_brandingImageBackgroundUrl: string,
/**
* Wherther the calendar feature is enabled or not.
*/
@ -214,10 +225,20 @@ class Conference extends AbstractConference<Props, State> {
* @returns {ReactElement}
*/
render() {
const { _fullscreenEnabled } = this.props;
const {
_brandingImageBackgroundUrl,
_brandingStyles,
_fullscreenEnabled
} = this.props;
return (
<Container style = { styles.conference }>
<Container
style = { [
styles.conference,
_brandingStyles
] }>
<BrandingImageBackground
uri = { _brandingImageBackgroundUrl } />
<StatusBar
barStyle = 'light-content'
hidden = { _fullscreenEnabled }
@ -499,11 +520,17 @@ class Conference extends AbstractConference<Props, State> {
function _mapStateToProps(state) {
const { isOpen } = state['features/participants-pane'];
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const participantCount = getParticipantCount(state);
const brandingStyles = backgroundColor ? {
backgroundColor
} : undefined;
return {
...abstractMapStateToProps(state),
_aspectRatio: aspectRatio,
_brandingStyles: brandingStyles,
_brandingImageBackgroundUrl: backgroundImageUrl,
_calendarEnabled: isCalendarEnabled(state),
_connecting: isConnecting(state),
_filmstripVisible: isFilmstripVisible(state),

View File

@ -12,3 +12,8 @@ export const SET_DYNAMIC_BRANDING_FAILED = 'SET_DYNAMIC_BRANDING_FAILED';
* Action used to signal the branding elements are ready to be displayed
*/
export const SET_DYNAMIC_BRANDING_READY = 'SET_DYNAMIC_BRANDING_READY';
/**
* Action used to unset branding elements
*/
export const UNSET_DYNAMIC_BRANDING = 'UNSET_DYNAMIC_BRANDING';

View File

@ -1,7 +1,3 @@
// @flow
import { getLogger } from '@jitsi/logger';
import { doGetJSON } from '../base/util';
import {
@ -10,8 +6,7 @@ import {
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { getDynamicBrandingUrl } from './functions.any';
const logger = getLogger(__filename);
import logger from './logger';
/**
@ -52,7 +47,7 @@ export function fetchCustomBrandingData() {
* @param {Object} value - The custom data to be set.
* @returns {Object}
*/
function setDynamicBrandingData(value) {
export function setDynamicBrandingData(value) {
return {
type: SET_DYNAMIC_BRANDING_DATA,
value
@ -64,7 +59,7 @@ function setDynamicBrandingData(value) {
*
* @returns {Object}
*/
function setDynamicBrandingReady() {
export function setDynamicBrandingReady() {
return {
type: SET_DYNAMIC_BRANDING_READY
};
@ -75,7 +70,7 @@ function setDynamicBrandingReady() {
*
* @returns {Object}
*/
function setDynamicBrandingFailed() {
export function setDynamicBrandingFailed() {
return {
type: SET_DYNAMIC_BRANDING_FAILED
};

View File

@ -0,0 +1,54 @@
import { doGetJSON } from '../base/util';
import { UNSET_DYNAMIC_BRANDING } from './actionTypes';
import {
setDynamicBrandingData,
setDynamicBrandingFailed,
setDynamicBrandingReady
} from './actions.any';
import logger from './logger';
/**
* Fetches custom branding data.
* If there is no data or the request fails, sets the `customizationReady` flag
* so the defaults can be displayed.
*
* @returns {Function}
*/
export function fetchCustomBrandingData() {
return async function(dispatch: Function, getState: Function) {
const state = getState();
const { dynamicBrandingUrl } = state['features/base/config'];
if (dynamicBrandingUrl) {
try {
return dispatch(
setDynamicBrandingData(
await doGetJSON(dynamicBrandingUrl))
);
} catch (err) {
logger.error('Error fetching branding data', err);
return dispatch(
setDynamicBrandingFailed()
);
}
} else {
dispatch(unsetDynamicBranding());
}
dispatch(setDynamicBrandingReady());
};
}
/**
* Action used to unset branding elements.
*
* @returns {Object}
*/
export function unsetDynamicBranding() {
return {
type: UNSET_DYNAMIC_BRANDING
};
}

View File

@ -0,0 +1 @@
export * from './native/index';

View File

@ -0,0 +1,45 @@
import React from 'react';
import { Image } from 'react-native';
import { SvgUri } from 'react-native-svg';
import styles from './styles';
interface Props {
uri: any;
}
/**
* Component that displays a branding background image.
*
* @param {Props} props - The props of the component.
* @returns {ReactElement}
*/
const BrandingImageBackground: React.FC<Props> = ({ uri }:Props) => {
const imageType = uri?.substr(uri.lastIndexOf('/') + 1);
const imgSrc = uri ? uri : undefined;
let backgroundImage;
if (imageType?.includes('.svg')) {
backgroundImage
= (
<SvgUri
height = '100%'
style = { styles.brandingImageBackgroundSvg }
uri = { imgSrc }
width = '100%' />
);
} else {
backgroundImage
= (
<Image
source = {{ uri: imgSrc }}
style = { styles.brandingImageBackground } />
);
}
return backgroundImage;
};
export default BrandingImageBackground;

View File

@ -0,0 +1 @@
export { default as BrandingImageBackground } from './BrandingImageBackground';

View File

@ -0,0 +1,15 @@
export default {
/**
* {@code BrandingImageBackground} Style.
*/
brandingImageBackgroundSvg: {
position: 'absolute' as 'absolute'
},
brandingImageBackground: {
height: '100%',
position: 'absolute' as 'absolute',
width: '100%'
}
};

View File

@ -1,2 +0,0 @@
export * from './actions';
export * from './functions.any';

View File

@ -0,0 +1 @@
export * from './components/index.native';

View File

@ -0,0 +1,5 @@
// @flow
import { getLogger } from '../base/logging/functions';
export default getLogger('features/dynamic-branding');

View File

@ -0,0 +1,36 @@
import { SET_CONFIG } from '../base/config';
import { MiddlewareRegistry } from '../base/redux';
import { SET_DYNAMIC_BRANDING_DATA } from './actionTypes';
import { fetchCustomBrandingData } from './actions.native';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_CONFIG: {
const result = next(action);
store.dispatch(fetchCustomBrandingData());
return result;
}
case SET_DYNAMIC_BRANDING_DATA: {
const {
avatarBackgrounds,
backgroundColor,
backgroundImageUrl
} = action.value;
action.value = {
...action.value,
avatarBackgrounds,
backgroundColor,
backgroundImageUrl
};
break;
}
}
return next(action);
});

View File

@ -1,12 +1,11 @@
// @flow
import { APP_WILL_MOUNT } from '../base/app';
import { MiddlewareRegistry } from '../base/redux';
import { SET_DYNAMIC_BRANDING_DATA } from './actionTypes';
import { fetchCustomBrandingData } from './actions';
import { fetchCustomBrandingData } from './actions.any';
import { createMuiBrandingTheme } from './functions.web';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT: {

View File

@ -6,9 +6,11 @@ import { type Image } from '../virtual-background/constants';
import {
SET_DYNAMIC_BRANDING_DATA,
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
SET_DYNAMIC_BRANDING_READY,
UNSET_DYNAMIC_BRANDING
} from './actionTypes';
/**
* The name of the redux store/state property which is the root of the redux
* state of the feature {@code dynamic-branding}.
@ -194,6 +196,9 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
...state,
customizationReady: true
};
case UNSET_DYNAMIC_BRANDING:
return DEFAULT_STATE;
}
return state;

View File

@ -1,6 +1,6 @@
// @flow
import { getLocalParticipant } from '../base/participants';
import { extractFqnFromPath } from '../dynamic-branding';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { DETECT_FACE, FACE_BOX_EVENT_TYPE, SEND_IMAGE_INTERVAL_MS } from './constants';
import logger from './logger';

View File

@ -4,7 +4,7 @@ import type { Dispatch } from 'redux';
import { FEEDBACK_REQUEST_IN_PROGRESS } from '../../../modules/UI/UIErrors';
import { openDialog } from '../base/dialog';
import { extractFqnFromPath } from '../dynamic-branding';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { isVpaasMeeting } from '../jaas/functions';
import {

View File

@ -8,7 +8,7 @@ import { Dialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
import { connect } from '../../../../base/redux';
import { isDynamicBrandingDataLoaded } from '../../../../dynamic-branding';
import { isDynamicBrandingDataLoaded } from '../../../../dynamic-branding/functions.any';
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
import { isVpaasMeeting } from '../../../../jaas/functions';
import { getActiveSession } from '../../../../recording';

View File

@ -2,14 +2,13 @@
import React, { PureComponent } from 'react';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { ParticipantView, getParticipantById } from '../../base/participants';
import { connect } from '../../base/redux';
import { StyleType } from '../../base/styles';
import { isLocalVideoTrackDesktop } from '../../base/tracks/functions';
import { AVATAR_SIZE } from './styles';
/**
* The type of the React {@link Component} props of {@link LargeVideo}.
*/
@ -32,11 +31,6 @@ type Props = {
*/
_participantId: string,
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Application's viewport height.
*/
@ -120,7 +114,6 @@ class LargeVideo extends PureComponent<Props, State> {
const {
_disableVideo,
_participantId,
_styles,
onClick
} = this.props;
@ -130,7 +123,6 @@ class LargeVideo extends PureComponent<Props, State> {
disableVideo = { _disableVideo }
onPress = { onClick }
participantId = { _participantId }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
@ -160,7 +152,6 @@ function _mapStateToProps(state) {
_disableVideo: disableVideo,
_height: height,
_participantId: participantId,
_styles: ColorSchemeRegistry.get(state, 'LargeVideo'),
_width: width
};
}

View File

@ -1,25 +1,4 @@
import { StyleSheet } from 'react-native';
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
/**
* Size for the Avatar.
*/
export const AVATAR_SIZE = 200;
/**
* Color schemed styles for the @{LargeVideo} component.
*/
ColorSchemeRegistry.register('LargeVideo', {
/**
* Large video container style.
*/
largeVideo: {
...StyleSheet.absoluteFillObject,
alignItems: 'stretch',
backgroundColor: schemeColor('background'),
flex: 1,
justifyContent: 'center'
}
});

View File

@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { getFeatureFlag, REACTIONS_ENABLED } from '../base/flags';
import { getLocalParticipant } from '../base/participants';
import { extractFqnFromPath } from '../dynamic-branding';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { REACTIONS, SOUNDS_THRESHOLDS } from './constants';
import logger from './logger';

View File

@ -4,7 +4,7 @@ import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { isEnabled as isDropboxEnabled } from '../dropbox';
import { extractFqnFromPath } from '../dynamic-branding';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { RECORDING_STATUS_PRIORITIES, RECORDING_TYPES } from './constants';
import logger from './logger';

View File

@ -7,7 +7,7 @@ import './createImageBitmap';
import { createScreensharingCaptureTakenEvent, sendAnalytics } from '../analytics';
import { getCurrentConference } from '../base/conference';
import { getLocalParticipant, getRemoteParticipants } from '../base/participants';
import { extractFqnFromPath } from '../dynamic-branding';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import {
CLEAR_INTERVAL,