feature-flags: initial implementation
The welcomePageEnabled and pictureInPictureEnabled props on mobile have been converted to feature flags.
This commit is contained in:
parent
d798f93614
commit
cf7b10d53d
|
@ -54,6 +54,11 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
*/
|
*/
|
||||||
private Bundle colorScheme;
|
private Bundle colorScheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||||
|
*/
|
||||||
|
private Bundle featureFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
||||||
* only mode respectively.
|
* only mode respectively.
|
||||||
|
@ -62,12 +67,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
private Boolean audioOnly;
|
private Boolean audioOnly;
|
||||||
private Boolean videoMuted;
|
private Boolean videoMuted;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
|
|
||||||
* since the host application decides which meeting to join.
|
|
||||||
*/
|
|
||||||
private Boolean welcomePageEnabled;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||||
*/
|
*/
|
||||||
|
@ -78,14 +77,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
private Bundle colorScheme;
|
private Bundle colorScheme;
|
||||||
|
private Bundle featureFlags;
|
||||||
|
|
||||||
private Boolean audioMuted;
|
private Boolean audioMuted;
|
||||||
private Boolean audioOnly;
|
private Boolean audioOnly;
|
||||||
private Boolean videoMuted;
|
private Boolean videoMuted;
|
||||||
|
|
||||||
private Boolean welcomePageEnabled;
|
|
||||||
|
|
||||||
public Builder() {
|
public Builder() {
|
||||||
|
featureFlags = new Bundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**\
|
/**\
|
||||||
|
@ -186,7 +185,25 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||||
*/
|
*/
|
||||||
public Builder setWelcomePageEnabled(boolean enabled) {
|
public Builder setWelcomePageEnabled(boolean enabled) {
|
||||||
this.welcomePageEnabled = enabled;
|
this.featureFlags.putBoolean("welcomepage.enabled", enabled);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFeatureFlag(String flag, boolean value) {
|
||||||
|
this.featureFlags.putBoolean(flag, value);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFeatureFlag(String flag, String value) {
|
||||||
|
this.featureFlags.putString(flag, value);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFeatureFlag(String flag, int value) {
|
||||||
|
this.featureFlags.putInt(flag, value);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -204,10 +221,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
options.subject = this.subject;
|
options.subject = this.subject;
|
||||||
options.token = this.token;
|
options.token = this.token;
|
||||||
options.colorScheme = this.colorScheme;
|
options.colorScheme = this.colorScheme;
|
||||||
|
options.featureFlags = this.featureFlags;
|
||||||
options.audioMuted = this.audioMuted;
|
options.audioMuted = this.audioMuted;
|
||||||
options.audioOnly = this.audioOnly;
|
options.audioOnly = this.audioOnly;
|
||||||
options.videoMuted = this.videoMuted;
|
options.videoMuted = this.videoMuted;
|
||||||
options.welcomePageEnabled = this.welcomePageEnabled;
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -221,30 +238,29 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
subject = in.readString();
|
subject = in.readString();
|
||||||
token = in.readString();
|
token = in.readString();
|
||||||
colorScheme = in.readBundle();
|
colorScheme = in.readBundle();
|
||||||
|
featureFlags = in.readBundle();
|
||||||
byte tmpAudioMuted = in.readByte();
|
byte tmpAudioMuted = in.readByte();
|
||||||
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
||||||
byte tmpAudioOnly = in.readByte();
|
byte tmpAudioOnly = in.readByte();
|
||||||
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
||||||
byte tmpVideoMuted = in.readByte();
|
byte tmpVideoMuted = in.readByte();
|
||||||
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
||||||
byte tmpWelcomePageEnabled = in.readByte();
|
|
||||||
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle asProps() {
|
Bundle asProps() {
|
||||||
Bundle props = new Bundle();
|
Bundle props = new Bundle();
|
||||||
|
|
||||||
|
// Android always has the PiP flag set by default.
|
||||||
|
if (!featureFlags.containsKey("pip.enabled")) {
|
||||||
|
featureFlags.putBoolean("pip.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.putBundle("flags", featureFlags);
|
||||||
|
|
||||||
if (colorScheme != null) {
|
if (colorScheme != null) {
|
||||||
props.putBundle("colorScheme", colorScheme);
|
props.putBundle("colorScheme", colorScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (welcomePageEnabled != null) {
|
|
||||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: get rid of this.
|
|
||||||
props.putBoolean("pictureInPictureEnabled", true);
|
|
||||||
|
|
||||||
Bundle config = new Bundle();
|
Bundle config = new Bundle();
|
||||||
|
|
||||||
if (audioMuted != null) {
|
if (audioMuted != null) {
|
||||||
|
@ -305,10 +321,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||||
dest.writeString(subject);
|
dest.writeString(subject);
|
||||||
dest.writeString(token);
|
dest.writeString(token);
|
||||||
dest.writeBundle(colorScheme);
|
dest.writeBundle(colorScheme);
|
||||||
|
dest.writeBundle(featureFlags);
|
||||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||||
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
||||||
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
||||||
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -96,4 +96,10 @@
|
||||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
|
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
- (void)enterPictureInPicture:(NSDictionary *)data {
|
||||||
|
[self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -41,6 +41,11 @@
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly, nonnull) NSDictionary *featureFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to YES to join the conference with audio / video muted or to start in audio
|
* Set to YES to join the conference with audio / video muted or to start in audio
|
||||||
* only mode respectively.
|
* only mode respectively.
|
||||||
|
@ -55,6 +60,9 @@
|
||||||
*/
|
*/
|
||||||
@property (nonatomic) BOOL welcomePageEnabled;
|
@property (nonatomic) BOOL welcomePageEnabled;
|
||||||
|
|
||||||
|
- (void)setFeatureFlag:(NSString *_Nonnull)flag withBoolean:(BOOL)value;
|
||||||
|
- (void)setFeatureFlag:(NSString *_Nonnull)flag withValue:(id _Nonnull)value;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface JitsiMeetConferenceOptions : NSObject
|
@interface JitsiMeetConferenceOptions : NSObject
|
||||||
|
@ -66,6 +74,7 @@
|
||||||
@property (nonatomic, copy, nullable, readonly) NSString *token;
|
@property (nonatomic, copy, nullable, readonly) NSString *token;
|
||||||
|
|
||||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||||
|
@property (nonatomic, readonly, nonnull) NSDictionary *featureFlags;
|
||||||
|
|
||||||
@property (nonatomic, readonly) BOOL audioOnly;
|
@property (nonatomic, readonly) BOOL audioOnly;
|
||||||
@property (nonatomic, readonly) BOOL audioMuted;
|
@property (nonatomic, readonly) BOOL audioMuted;
|
||||||
|
|
|
@ -18,11 +18,17 @@
|
||||||
|
|
||||||
#import "JitsiMeetConferenceOptions+Private.h"
|
#import "JitsiMeetConferenceOptions+Private.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backwards compatibility: turn the boolean property into a feature flag.
|
||||||
|
*/
|
||||||
|
static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||||
|
|
||||||
|
|
||||||
@implementation JitsiMeetConferenceOptionsBuilder {
|
@implementation JitsiMeetConferenceOptionsBuilder {
|
||||||
NSNumber *_audioOnly;
|
NSNumber *_audioOnly;
|
||||||
NSNumber *_audioMuted;
|
NSNumber *_audioMuted;
|
||||||
NSNumber *_videoMuted;
|
NSNumber *_videoMuted;
|
||||||
NSNumber *_welcomePageEnabled;
|
NSMutableDictionary *_featureFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@dynamic audioOnly;
|
@dynamic audioOnly;
|
||||||
|
@ -38,17 +44,24 @@
|
||||||
_token = nil;
|
_token = nil;
|
||||||
|
|
||||||
_colorScheme = nil;
|
_colorScheme = nil;
|
||||||
|
_featureFlags = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
_audioOnly = nil;
|
_audioOnly = nil;
|
||||||
_audioMuted = nil;
|
_audioMuted = nil;
|
||||||
_videoMuted = nil;
|
_videoMuted = nil;
|
||||||
|
|
||||||
_welcomePageEnabled = nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setFeatureFlag:(NSString *)flag withBoolean:(BOOL)value {
|
||||||
|
[self setFeatureFlag:flag withValue:[NSNumber numberWithBool:value]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setFeatureFlag:(NSString *)flag withValue:(id)value {
|
||||||
|
_featureFlags[flag] = value;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Dynamic properties
|
#pragma mark - Dynamic properties
|
||||||
|
|
||||||
- (void)setAudioOnly:(BOOL)audioOnly {
|
- (void)setAudioOnly:(BOOL)audioOnly {
|
||||||
|
@ -76,11 +89,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setWelcomePageEnabled:(BOOL)welcomePageEnabled {
|
- (void)setWelcomePageEnabled:(BOOL)welcomePageEnabled {
|
||||||
_welcomePageEnabled = [NSNumber numberWithBool:welcomePageEnabled];
|
[self setFeatureFlag:WelcomePageEnabledFeatureFlag
|
||||||
|
withBoolean:welcomePageEnabled];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)welcomePageEnabled {
|
- (BOOL)welcomePageEnabled {
|
||||||
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
|
NSNumber *n = _featureFlags[WelcomePageEnabledFeatureFlag];
|
||||||
|
|
||||||
|
return n != nil ? [n boolValue] : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Private API
|
#pragma mark - Private API
|
||||||
|
@ -97,17 +113,13 @@
|
||||||
return _videoMuted;
|
return _videoMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSNumber *)getWelcomePageEnabled {
|
|
||||||
return _welcomePageEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation JitsiMeetConferenceOptions {
|
@implementation JitsiMeetConferenceOptions {
|
||||||
NSNumber *_audioOnly;
|
NSNumber *_audioOnly;
|
||||||
NSNumber *_audioMuted;
|
NSNumber *_audioMuted;
|
||||||
NSNumber *_videoMuted;
|
NSNumber *_videoMuted;
|
||||||
NSNumber *_welcomePageEnabled;
|
NSDictionary *_featureFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@dynamic audioOnly;
|
@dynamic audioOnly;
|
||||||
|
@ -130,7 +142,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)welcomePageEnabled {
|
- (BOOL)welcomePageEnabled {
|
||||||
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
|
NSNumber *n = _featureFlags[WelcomePageEnabledFeatureFlag];
|
||||||
|
|
||||||
|
return n != nil ? [n boolValue] : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Internal initializer
|
#pragma mark - Internal initializer
|
||||||
|
@ -148,7 +162,7 @@
|
||||||
_audioMuted = [builder getAudioMuted];
|
_audioMuted = [builder getAudioMuted];
|
||||||
_videoMuted = [builder getVideoMuted];
|
_videoMuted = [builder getVideoMuted];
|
||||||
|
|
||||||
_welcomePageEnabled = [builder getWelcomePageEnabled];
|
_featureFlags = [NSDictionary dictionaryWithDictionary:builder.featureFlags];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -167,14 +181,12 @@
|
||||||
- (NSDictionary *)asProps {
|
- (NSDictionary *)asProps {
|
||||||
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
|
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
props[@"flags"] = [NSMutableDictionary dictionaryWithDictionary:_featureFlags];
|
||||||
|
|
||||||
if (_colorScheme != nil) {
|
if (_colorScheme != nil) {
|
||||||
props[@"colorScheme"] = self.colorScheme;
|
props[@"colorScheme"] = self.colorScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_welcomePageEnabled != nil) {
|
|
||||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
||||||
if (_audioOnly != nil) {
|
if (_audioOnly != nil) {
|
||||||
config[@"startAudioOnly"] = @(self.audioOnly);
|
config[@"startAudioOnly"] = @(self.audioOnly);
|
||||||
|
|
|
@ -24,6 +24,12 @@
|
||||||
#import "RNRootView.h"
|
#import "RNRootView.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||||
|
*/
|
||||||
|
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||||
|
|
||||||
|
|
||||||
@implementation JitsiMeetView {
|
@implementation JitsiMeetView {
|
||||||
/**
|
/**
|
||||||
* The unique identifier of this `JitsiMeetView` within the process for the
|
* The unique identifier of this `JitsiMeetView` within the process for the
|
||||||
|
@ -122,11 +128,15 @@ static void initializeViewsMap() {
|
||||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||||
|
|
||||||
props[@"externalAPIScope"] = externalAPIScope;
|
// Set the PiP flag if it wasn't manually set.
|
||||||
|
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||||
|
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||||
|
featureFlags[PiPEnabledFeatureFlag]
|
||||||
|
= [NSNumber numberWithBool:
|
||||||
|
self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]];
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: put this in some 'flags' field
|
props[@"externalAPIScope"] = externalAPIScope;
|
||||||
props[@"pictureInPictureEnabled"]
|
|
||||||
= @(self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]);
|
|
||||||
|
|
||||||
// This method is supposed to be imperative i.e. a second
|
// This method is supposed to be imperative i.e. a second
|
||||||
// invocation with one and the same URL is expected to join the respective
|
// invocation with one and the same URL is expected to join the respective
|
||||||
|
|
|
@ -6,6 +6,7 @@ import '../../analytics';
|
||||||
import '../../authentication';
|
import '../../authentication';
|
||||||
import { setColorScheme } from '../../base/color-scheme';
|
import { setColorScheme } from '../../base/color-scheme';
|
||||||
import { DialogContainer } from '../../base/dialog';
|
import { DialogContainer } from '../../base/dialog';
|
||||||
|
import { updateFlags } from '../../base/flags';
|
||||||
import '../../base/jwt';
|
import '../../base/jwt';
|
||||||
import { Platform } from '../../base/react';
|
import { Platform } from '../../base/react';
|
||||||
import {
|
import {
|
||||||
|
@ -47,18 +48,9 @@ type Props = AbstractAppProps & {
|
||||||
externalAPIScope: string,
|
externalAPIScope: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar button
|
* An object with the feature flags.
|
||||||
* is rendered in the {@link Conference} view to afford entering
|
|
||||||
* Picture-in-Picture.
|
|
||||||
*/
|
*/
|
||||||
pictureInPictureEnabled: boolean,
|
flags: Object
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the Welcome page is enabled. If {@code true}, the Welcome page is
|
|
||||||
* rendered when the {@link App} is not at a location (URL) identifying
|
|
||||||
* a Jitsi Meet conference/room.
|
|
||||||
*/
|
|
||||||
welcomePageEnabled: boolean
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,6 +91,9 @@ export class App extends AbstractApp {
|
||||||
// We set the color scheme early enough so then we avoid any
|
// We set the color scheme early enough so then we avoid any
|
||||||
// unnecessary re-renders.
|
// unnecessary re-renders.
|
||||||
this.state.store.dispatch(setColorScheme(this.props.colorScheme));
|
this.state.store.dispatch(setColorScheme(this.props.colorScheme));
|
||||||
|
|
||||||
|
// Ditto for feature flags.
|
||||||
|
this.state.store.dispatch(updateFlags(this.props.flags));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* The type of Redux action which updates the feature flags.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: UPDATE_FLAGS,
|
||||||
|
* flags: Object
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const UPDATE_FLAGS = 'UPDATE_FLAGS';
|
|
@ -0,0 +1,19 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { UPDATE_FLAGS } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current features flags with the given ones. They will be merged.
|
||||||
|
*
|
||||||
|
* @param {Object} flags - The new flags object.
|
||||||
|
* @returns {{
|
||||||
|
* type: UPDATE_FLAGS,
|
||||||
|
* flags: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function updateFlags(flags: Object) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_FLAGS,
|
||||||
|
flags
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating if Picture-in-Picture should be enabled.
|
||||||
|
* Default: auto-detected.
|
||||||
|
*/
|
||||||
|
export const PIP_ENABLED = 'pip.enabled';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating if the welcome page should be enabled.
|
||||||
|
* Default: disabled (false).
|
||||||
|
*/
|
||||||
|
export const WELCOME_PAGE_ENABLED = 'welcomepage.enabled';
|
|
@ -0,0 +1,32 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { getAppProp } from '../app';
|
||||||
|
import { toState } from '../redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of a specific feature flag.
|
||||||
|
*
|
||||||
|
* @param {Function|Object} stateful - The redux store or {@code getState}
|
||||||
|
* function.
|
||||||
|
* @param {string} flag - The name of the React {@code Component} prop of
|
||||||
|
* the currently mounted {@code App} to get.
|
||||||
|
* @param {*} defaultValue - A default value for the flag, in case it's not defined.
|
||||||
|
* @returns {*} The value of the specified React {@code Compoennt} prop of the
|
||||||
|
* currently mounted {@code App}.
|
||||||
|
*/
|
||||||
|
export function getFeatureFlag(stateful: Function | Object, flag: string, defaultValue: any) {
|
||||||
|
const state = toState(stateful)['features/base/flags'];
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
const value = state[flag];
|
||||||
|
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe the value hasn't made it to the redux store yet, check the app props.
|
||||||
|
const flags = getAppProp(stateful, 'flags') || {};
|
||||||
|
|
||||||
|
return flags[flag] || defaultValue;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
|
export * from './constants';
|
||||||
|
export * from './functions';
|
||||||
|
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,33 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { ReducerRegistry } from '../redux';
|
||||||
|
|
||||||
|
import { UPDATE_FLAGS } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default state value for the feature flags.
|
||||||
|
*/
|
||||||
|
const DEFAULT_STATE = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces redux actions which handle feature flags.
|
||||||
|
*
|
||||||
|
* @param {State} state - The current redux state.
|
||||||
|
* @param {Action} action - The redux action to reduce.
|
||||||
|
* @param {string} action.type - The type of the redux action to reduce.
|
||||||
|
* @returns {State} The next redux state that is the result of reducing the
|
||||||
|
* specified action.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/base/flags', (state = DEFAULT_STATE, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case UPDATE_FLAGS: {
|
||||||
|
const newState = _.merge({}, state, action.flags);
|
||||||
|
|
||||||
|
return _.isEqual(state, newState) ? state : newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
||||||
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||||
|
|
||||||
import { appNavigate } from '../../../app';
|
import { appNavigate } from '../../../app';
|
||||||
import { getAppProp } from '../../../base/app';
|
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||||
import { getParticipantCount } from '../../../base/participants';
|
import { getParticipantCount } from '../../../base/participants';
|
||||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
|
@ -452,7 +452,7 @@ function _mapStateToProps(state) {
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
_pictureInPictureEnabled: getAppProp(state, 'pictureInPictureEnabled'),
|
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The indicator which determines whether the UI is reduced (to
|
* The indicator which determines whether the UI is reduced (to
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { getAppProp } from '../../base/app';
|
import { PIP_ENABLED, getFeatureFlag } from '../../base/flags';
|
||||||
import { Platform } from '../../base/react';
|
import { Platform } from '../../base/react';
|
||||||
|
|
||||||
import { ENTER_PICTURE_IN_PICTURE } from './actionTypes';
|
import { ENTER_PICTURE_IN_PICTURE } from './actionTypes';
|
||||||
|
@ -25,7 +25,7 @@ export function enterPictureInPicture() {
|
||||||
// XXX At the time of this writing this action can only be dispatched by
|
// XXX At the time of this writing this action can only be dispatched by
|
||||||
// the button which is on the conference view, which means that it's
|
// the button which is on the conference view, which means that it's
|
||||||
// fine to enter PiP mode.
|
// fine to enter PiP mode.
|
||||||
if (getAppProp(getState, 'pictureInPictureEnabled')) {
|
if (getFeatureFlag(getState, PIP_ENABLED)) {
|
||||||
const { PictureInPicture } = NativeModules;
|
const { PictureInPicture } = NativeModules;
|
||||||
const p
|
const p
|
||||||
= Platform.OS === 'android'
|
= Platform.OS === 'android'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getAppProp } from '../../../base/app';
|
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||||
import { translate } from '../../../base/i18n';
|
import { translate } from '../../../base/i18n';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { AbstractButton } from '../../../base/toolbox';
|
import { AbstractButton } from '../../../base/toolbox';
|
||||||
|
@ -62,7 +62,7 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state): Object {
|
function _mapStateToProps(state): Object {
|
||||||
return {
|
return {
|
||||||
_enabled: Boolean(getAppProp(state, 'pictureInPictureEnabled'))
|
_enabled: Boolean(getFeatureFlag(state, PIP_ENABLED))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getAppProp } from '../base/app';
|
import { WELCOME_PAGE_ENABLED, getFeatureFlag } from '../base/flags';
|
||||||
import { toState } from '../base/redux';
|
import { toState } from '../base/redux';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
@ -24,7 +24,7 @@ export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
||||||
// - Enabling/disabling the Welcome page on Web historically
|
// - Enabling/disabling the Welcome page on Web historically
|
||||||
// automatically redirects to a random room and that does not make sense
|
// automatically redirects to a random room and that does not make sense
|
||||||
// on mobile (right now).
|
// on mobile (right now).
|
||||||
return Boolean(getAppProp(stateful, 'welcomePageEnabled'));
|
return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue