Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2763c2f5c9 | ||
|
|
1ec8f70d55 | ||
|
|
916208a5ff | ||
|
|
43e655b619 | ||
|
|
1ab0f1993a | ||
|
|
4cb7ebce70 | ||
|
|
0a5910f0b3 | ||
|
|
d91c546a1e | ||
|
|
b6f7f8fba7 | ||
|
|
6ebe2c2809 | ||
|
|
067610b3fd | ||
|
|
6f5534fcb6 | ||
|
|
dd8b220ff9 | ||
|
|
80d789879c | ||
|
|
d49c5a6d8c | ||
|
|
9268255ca8 | ||
|
|
a0806716ae | ||
|
|
3677a2f769 | ||
|
|
3881da5db9 | ||
|
|
35a586df3c | ||
|
|
dc5a776123 | ||
|
|
a3c6e690dd | ||
|
|
a1c197c73c | ||
|
|
e8c0c03e49 | ||
|
|
4798e0271b | ||
|
|
cd29f10fa8 | ||
|
|
97dc07810c |
@@ -25,5 +25,5 @@ android.enableDexingArtifactTransform.desugaring=false
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=20.6.0
|
||||
sdkVersion=2.12.0
|
||||
appVersion=21.0.0
|
||||
sdkVersion=3.0.0
|
||||
|
||||
@@ -47,7 +47,14 @@ dependencies {
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
implementation(project(':react-native-device-info')) {
|
||||
exclude group: 'com.google.firebase'
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.installreferrer'
|
||||
}
|
||||
} else {
|
||||
implementation project(':react-native-device-info')
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'androidx'
|
||||
@@ -59,7 +66,6 @@ dependencies {
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation project(':react-native-community_netinfo')
|
||||
implementation project(':react-native-default-preference')
|
||||
implementation project(':react-native-device-info')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
|
||||
@@ -472,8 +472,8 @@ export default {
|
||||
*/
|
||||
createInitialLocalTracks(options = {}) {
|
||||
const errors = {};
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
const initialDevices = config.disableInitialGUM ? [] : [ 'audio' ];
|
||||
const requestedAudio = !config.disableInitialGUM;
|
||||
let requestedVideo = false;
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
@@ -484,19 +484,22 @@ export default {
|
||||
this.muteAudio(true, true);
|
||||
}
|
||||
|
||||
if (!options.startWithVideoMuted
|
||||
if (!config.disableInitialGUM
|
||||
&& !options.startWithVideoMuted
|
||||
&& !options.startAudioOnly
|
||||
&& !options.startScreenSharing) {
|
||||
initialDevices.push('video');
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
|
||||
browserName =>
|
||||
APP.store.dispatch(
|
||||
mediaPermissionPromptVisibilityChanged(true, browserName))
|
||||
);
|
||||
if (!config.disableInitialGUM) {
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
|
||||
browserName =>
|
||||
APP.store.dispatch(
|
||||
mediaPermissionPromptVisibilityChanged(true, browserName))
|
||||
);
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
|
||||
72
config.js
@@ -336,6 +336,10 @@ var config = {
|
||||
// will be joined when no room is specified.
|
||||
enableWelcomePage: true,
|
||||
|
||||
// Disable initial browser getUserMedia requests.
|
||||
// This is useful for scenarios where users might want to start a conference for screensharing only
|
||||
// disableInitialGUM: false,
|
||||
|
||||
// Enabling the close page will ignore the welcome page redirection when
|
||||
// a call is hangup.
|
||||
// enableClosePage: false,
|
||||
@@ -623,9 +627,18 @@ var config = {
|
||||
// otherwise the app doesn't render it.
|
||||
// moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com',
|
||||
|
||||
// If true, tile view will not be enabled automatically when the participants count threshold is reached.
|
||||
// disableTileView: true,
|
||||
|
||||
// Hides the conference subject
|
||||
// hideConferenceSubject: true
|
||||
|
||||
// Hides the conference timer.
|
||||
// hideConferenceTimer: true,
|
||||
|
||||
// Hides the participants stats
|
||||
// hideParticipantsStats: true
|
||||
|
||||
// Sets the conference subject
|
||||
// subject: 'Conference Subject',
|
||||
|
||||
@@ -682,6 +695,65 @@ var config = {
|
||||
ignoreStartMuted
|
||||
*/
|
||||
|
||||
/**
|
||||
Use this array to configure which notifications will be shown to the user
|
||||
The items correspond to the title or description key of that notification
|
||||
Some of these notifications also depend on some other internal logic to be displayed or not,
|
||||
so adding them here will not ensure they will always be displayed
|
||||
|
||||
A falsy value for this prop will result in having all notifications enabled (e.g null, undefined, false)
|
||||
*/
|
||||
// notifications: [
|
||||
// 'connection.CONNFAIL', // shown when the connection fails,
|
||||
// 'dialog.cameraNotSendingData', // shown when there's no feed from user's camera
|
||||
// 'dialog.kickTitle', // shown when user has been kicked
|
||||
// 'dialog.liveStreaming', // livestreaming notifications (pending, on, off, limits)
|
||||
// 'dialog.lockTitle', // shown when setting conference password fails
|
||||
// 'dialog.maxUsersLimitReached', // shown when maximmum users limit has been reached
|
||||
// 'dialog.micNotSendingData', // shown when user's mic is not sending any audio
|
||||
// 'dialog.passwordNotSupportedTitle', // shown when setting conference password fails due to password format
|
||||
// 'dialog.recording', // recording notifications (pending, on, off, limits)
|
||||
// 'dialog.remoteControlTitle', // remote control notifications (allowed, denied, start, stop, error)
|
||||
// 'dialog.reservationError',
|
||||
// 'dialog.serviceUnavailable', // shown when server is not reachable
|
||||
// 'dialog.sessTerminated', // shown when there is a failed conference session
|
||||
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
|
||||
// 'dialog.transcribing', // transcribing notifications (pending, off)
|
||||
// 'dialOut.statusMessage', // shown when dial out status is updated.
|
||||
// 'liveStreaming.busy', // shown when livestreaming service is busy
|
||||
// 'liveStreaming.failedToStart', // shown when livestreaming fails to start
|
||||
// 'liveStreaming.unavailableTitle', // shown when livestreaming service is not reachable
|
||||
// 'lobby.joinRejectedMessage', // shown when while in a lobby, user's request to join is rejected
|
||||
// 'lobby.notificationTitle', // shown when lobby is toggled and when join requests are allowed / denied
|
||||
// 'localRecording.localRecording', // shown when a local recording is started
|
||||
// 'notify.disconnected', // shown when a participant has left
|
||||
// 'notify.grantedTo', // shown when moderator rights were granted to a participant
|
||||
// 'notify.invitedOneMember', // shown when 1 participant has been invited
|
||||
// 'notify.invitedThreePlusMembers', // shown when 3+ participants have been invited
|
||||
// 'notify.invitedTwoMembers', // shown when 2 participants have been invited
|
||||
// 'notify.kickParticipant', // shown when a participant is kicked
|
||||
// 'notify.mutedRemotelyTitle', // shown when user is muted by a remote party
|
||||
// 'notify.mutedTitle', // shown when user has been muted upon joining,
|
||||
// 'notify.newDeviceAudioTitle', // prompts the user to use a newly detected audio device
|
||||
// 'notify.newDeviceCameraTitle', // prompts the user to use a newly detected camera
|
||||
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
|
||||
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
|
||||
// 'notify.raisedHand', // shown when a partcipant used raise hand,
|
||||
// 'notify.startSilentTitle', // shown when user joined with no audio
|
||||
// 'prejoin.errorDialOut',
|
||||
// 'prejoin.errorDialOutDisconnected',
|
||||
// 'prejoin.errorDialOutFailed',
|
||||
// 'prejoin.errorDialOutStatus',
|
||||
// 'prejoin.errorStatusCode',
|
||||
// 'prejoin.errorValidation',
|
||||
// 'recording.busy', // shown when recording service is busy
|
||||
// 'recording.failedToStart', // shown when recording fails to start
|
||||
// 'recording.unavailableTitle', // shown when recording service is not reachable
|
||||
// 'toolbar.noAudioSignalTitle', // shown when a broken mic is detected
|
||||
// 'toolbar.noisyAudioInputTitle', // shown when noise is detected for the current microphone
|
||||
// 'toolbar.talkWhileMutedPopup', // shown when user tries to speak while muted
|
||||
// 'transcribing.failedToStart' // shown when transcribing fails to start
|
||||
// ]
|
||||
|
||||
// Allow all above example options to include a trailing comma and
|
||||
// prevent fear when commenting out the last value.
|
||||
|
||||
@@ -17,6 +17,7 @@ textarea {
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -201,74 +202,3 @@ form {
|
||||
background: rgba(0, 0, 0, .5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.desktop-browser {
|
||||
@media only screen and (max-width: $smallScreen) {
|
||||
.watermark {
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
.vertical-filmstrip .filmstrip {
|
||||
display: none;
|
||||
}
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.settings-button-small-icon {
|
||||
display: none;
|
||||
}
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chrome-extension-banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,3 +379,31 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin-top: -5px; // Margin set by atlaskit.
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 16px 24px;
|
||||
width: calc(100% - 32px);
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
|
||||
.jitsi-icon > svg {
|
||||
cursor: pointer;
|
||||
fill: #A4B8D1;
|
||||
}
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,73 @@
|
||||
@mixin small-button-size() {
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin very-small-button-size() {
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.settings-button-small-icon {
|
||||
display: none;
|
||||
}
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
display: block;
|
||||
@@ -65,3 +135,65 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-browser {
|
||||
@media only screen and (max-width: $smallScreen) {
|
||||
@include small-button-size();
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
@include very-small-button-size();
|
||||
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
.vertical-filmstrip .filmstrip {
|
||||
display: none;
|
||||
}
|
||||
.chrome-extension-banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.shift-right {
|
||||
@media only screen and (max-width: $smallScreen + $sidebarWidth) {
|
||||
@include small-button-size()
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen + $sidebarWidth) {
|
||||
@include very-small-button-size();
|
||||
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
.vertical-filmstrip .filmstrip {
|
||||
display: none;
|
||||
}
|
||||
.chrome-extension-banner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right [class^="Modal__PositionerAbsolute"] {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right [class^="Modal__Dialog-"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right [class^="Modal__PositionerAbsolute"] {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right [class^="Modal__Dialog-"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
debian/jitsi-meet-prosody.postinst
vendored
@@ -142,6 +142,21 @@ case "$1" in
|
||||
echo -e " admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\", \"jvb@auth.$JVB_HOSTNAME\" }" >> $PROSODY_HOST_CONFIG
|
||||
fi
|
||||
|
||||
# Convert the old focus component config to the new one.
|
||||
# Old:
|
||||
# Component "focus.jitmeet.example.com"
|
||||
# component_secret = "focusSecret"
|
||||
# New:
|
||||
# Component "focus.jitmeet.example.com" "client_proxy"
|
||||
# target_address = "focus@auth.jitmeet.example.com"
|
||||
if grep -q "Component \"focus.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG && ! grep "Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"" $PROSODY_HOST_CONFIG ;then
|
||||
sed -i -e "s/Component \"focus.$JVB_HOSTNAME\"/Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"\n target_address = \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\"/" $PROSODY_HOST_CONFIG
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
|
||||
# Make sure the focus@auth user's roster includes the proxy component (this is idempotent)
|
||||
prosodyctl mod_roster_command subscribe focus.$JVB_HOSTNAME $JICOFO_AUTH_USER@auth.$JVB_HOSTNAME
|
||||
|
||||
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
|
||||
# prosodyctl takes care for the permissions
|
||||
# echo for using all default values
|
||||
|
||||
@@ -77,8 +77,9 @@ Component "internal.auth.jitmeet.example.com" "muc"
|
||||
VirtualHost "auth.jitmeet.example.com"
|
||||
authentication = "internal_plain"
|
||||
|
||||
Component "focus.jitmeet.example.com"
|
||||
component_secret = "focusSecret"
|
||||
-- Proxy to jicofo's user JID, so that it doesn't have to register as a component.
|
||||
Component "focus.jitmeet.example.com" "client_proxy"
|
||||
target_address = "focusUser@auth.jitmeet.example.com"
|
||||
|
||||
Component "speakerstats.jitmeet.example.com" "speakerstats_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
@@ -354,7 +354,7 @@ PODS:
|
||||
- React
|
||||
- RNDefaultPreference (1.4.2):
|
||||
- React
|
||||
- RNDeviceInfo (7.3.1):
|
||||
- RNDeviceInfo (8.0.0):
|
||||
- React-Core
|
||||
- RNGoogleSignin (3.0.1):
|
||||
- GoogleSignIn (~> 5.0.0)
|
||||
@@ -364,7 +364,7 @@ PODS:
|
||||
- RNSound/Core (= 0.11.0)
|
||||
- RNSound/Core (0.11.0):
|
||||
- React
|
||||
- RNSVG (10.1.0):
|
||||
- RNSVG (12.1.0):
|
||||
- React
|
||||
- RNWatch (0.4.3):
|
||||
- React
|
||||
@@ -577,10 +577,10 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: 18014e1d98dbeb9141e935cfe35fc93bd511ffb6
|
||||
RNCAsyncStorage: bc2f81cc1df90c267ce9ed30bb2dbc93b945a8ee
|
||||
RNDefaultPreference: 56a405ce61033ac77b95004dccd7ac54c2eb50d1
|
||||
RNDeviceInfo: 57bb2806fb7bd982a1434e9f0b4e6a6ab1f6702e
|
||||
RNDeviceInfo: 72ded653ce636b3f03571e90bed99309a714944e
|
||||
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
|
||||
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
|
||||
RNSVG: 069864be08c9fe065a2cf7e63656a34c78653c99
|
||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
|
||||
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
|
||||
|
||||
|
||||
@@ -715,6 +715,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
@@ -744,6 +745,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FC040BBED70876444D89E91C /* Pods-JitsiMeet.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.6.0</string>
|
||||
<string>21.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.6.0</string>
|
||||
<string>21.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>20.6.0</string>
|
||||
<string>21.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"kab": "Cabil",
|
||||
"ko": "Corean",
|
||||
"lt": "Lituanian",
|
||||
"ml": "Malaialam",
|
||||
"lv": "Leton",
|
||||
"nl": "Neerlandés",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polonés",
|
||||
|
||||
@@ -380,7 +380,7 @@
|
||||
"getStreamKeyManually": "We weren’t able to fetch any live streams. Try getting your live stream key from YouTube.",
|
||||
"invalidStreamKey": "Live stream key may be incorrect.",
|
||||
"off": "Live Streaming stopped",
|
||||
"on": "Live Streaming",
|
||||
"on": "Live Streaming started",
|
||||
"pending": "Starting Live Stream…",
|
||||
"serviceName": "Live Streaming service",
|
||||
"signedInAs": "You are currently signed in as:",
|
||||
@@ -497,7 +497,7 @@
|
||||
"live": "LIVE",
|
||||
"loggedIn": "Logged in as {{userName}}",
|
||||
"off": "Recording stopped",
|
||||
"on": "Recording",
|
||||
"on": "Recording started",
|
||||
"pending": "Preparing to record the meeting…",
|
||||
"rec": "REC",
|
||||
"serviceDescription": "Your recording will be saved by the recording service",
|
||||
|
||||
@@ -314,7 +314,8 @@
|
||||
"e2eeDescription": "Lo chiframent del cap a la fin es actualament EXPERIMENTALA. Mercés de gardar a l'esperit qu'activar lo chiframent del cap a la fin desactivarà en efièch los servicis costat servidor coma : l'enregistrament, la difusion en dirècte e las participacions telefonicas. Remembratz tanben que la conferéncia foncionarà pas que per lo monde que participan amb un navigador compatible amb los fluxes inseribles.",
|
||||
"screenSharingFailed": "Ops ! Quicòm a trucat, avèm pas pogut començar lo partiment d'ecran!",
|
||||
"e2eeWarning": "AVERTIMENT : pas totes los participants d'aquesta conferéncia semblan poder suportar lo chiframent del cap a la fin. Se l'activatz poiràn pas vos veire nimai vos entendre.",
|
||||
"muteEveryoneElseDialog": "Un còp mut, poiretz pas mai lo tornar la paraula, mas la se pòdon tornar quora vòlgan."
|
||||
"muteEveryoneElseDialog": "Un còp mut, poiretz pas mai lo tornar la paraula, mas la se pòdon tornar quora vòlgan.",
|
||||
"user": "utilizaire"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "ara es {{status}}"
|
||||
@@ -549,7 +550,8 @@
|
||||
"signIn": "Connexion",
|
||||
"signOut": "Se desconnectar",
|
||||
"unavailable": "Ops ! Lo {{serviceName}} es pas disponible pel moment. Sèm a reglar aqueste problèma. Mercés de tornar ensajar mai tard.",
|
||||
"unavailableTitle": "Enregistrament indisponible"
|
||||
"unavailableTitle": "Enregistrament indisponible",
|
||||
"serviceDescriptionCloud": "Enregistrament distant"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Tirar per actualizar"
|
||||
@@ -616,7 +618,8 @@
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "{{app}} a besonh d'utilizar vòstre microfòn e camèra."
|
||||
"title": "{{app}} a besonh d'utilizar vòstre microfòn e camèra.",
|
||||
"genericTitle": "Aquesta reünion requerís l'utilizacion del microfòn e de la camèra."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "Tornar participar",
|
||||
@@ -666,7 +669,8 @@
|
||||
"security": "Opcions de seguretat",
|
||||
"embedMeeting": "Conferéncia integrada",
|
||||
"grantModerator": "Passar moderator",
|
||||
"lobbyButton": "Activar/Desactivar mòde sala d'espèra"
|
||||
"lobbyButton": "Activar/Desactivar mòde sala d'espèra",
|
||||
"toggleFilmstrip": "Bascular mòde benda film"
|
||||
},
|
||||
"addPeople": "Ajustar de monde a vòstra sonada",
|
||||
"audioOnlyOff": "Desactivar lo mòde connexion febla",
|
||||
@@ -791,7 +795,9 @@
|
||||
"remoteControl": "Contraròtle alonhat",
|
||||
"show": "Mostrar davant",
|
||||
"videomute": "Lo participant a arrestat la camèra",
|
||||
"domuteOthers": "Rendre mut totes los autres"
|
||||
"domuteOthers": "Rendre mut totes los autres",
|
||||
"connectionInfo": "Info connexion",
|
||||
"grantModerator": "Nomenar moderator"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -827,7 +833,8 @@
|
||||
"jitsiOnMobile": "Jitsi sus mobil –telecargatz nòstra aplicacion e començatz de conferéncias de pertot",
|
||||
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">reservatz una URL de conferéncia</a> a l'avança ont sètz l'unic moderator.",
|
||||
"jitsiMeet": "Jitsi Meet",
|
||||
"secureMeetings": "Conferéncias seguras e de nauta qualitat"
|
||||
"secureMeetings": "Conferéncias seguras e de nauta qualitat",
|
||||
"headerTitle": "Jitsi Meet"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Centre d’ajuda"
|
||||
@@ -884,7 +891,14 @@
|
||||
"videoFreezing": "Nos esperam a veire vòstra vidèo se gelar, venir negra e se pixelizar."
|
||||
},
|
||||
"premeeting": "Preconferéncia",
|
||||
"errorMissingName": "Mercés de picar vòstre nom per rejónher la conferéncia"
|
||||
"errorMissingName": "Mercés de picar vòstre nom per rejónher la conferéncia",
|
||||
"errorDialOutDisconnected": "Impossible de sonar aqueste numèro. Desconnectat",
|
||||
"errorDialOutStatus": "Error en recuperant l’estat de la sonada sortissenta",
|
||||
"errorDialOut": "Impossible de sonar aqueste numèro",
|
||||
"errorDialOutFailed": "Impossible de sonar aqueste numèro. La sonada a fracassat",
|
||||
"dialInMeeting": "Participatz a la reünion",
|
||||
"dialInPin": "Participar a la reünion e picar lo còdi PIN :",
|
||||
"iWantToDialIn": "Vòli me connectar"
|
||||
},
|
||||
"lobby": {
|
||||
"reject": "Regetar",
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
"invalidStreamKey": "Canlı akış anahtarı yanlış olabilir.",
|
||||
"off": "Canlı Akış durduruldu",
|
||||
"offBy": "{{name}} canlı akışı durdurdu",
|
||||
"on": "Canlı Akış",
|
||||
"on": "Canlı Akış başlatıldı",
|
||||
"onBy": "{{name}} canlı akışı başlattı",
|
||||
"pending": "Canlı Akış başlatılıyor...",
|
||||
"serviceName": "Canlı Akış hizmeti",
|
||||
@@ -564,7 +564,7 @@
|
||||
"live": "CANLI",
|
||||
"loggedIn": "{{userName}} olarak giriş yapıldı",
|
||||
"off": "Kayıt durdu",
|
||||
"on": "Kaydediliyor",
|
||||
"on": "Kayıt başladı",
|
||||
"pending": "Toplantıyı kaydetmeye hazırlanıyor ...",
|
||||
"rec": "KAYIT",
|
||||
"serviceDescription": "Kaydınız kayıt hizmeti tarafından kaydedilecektir",
|
||||
@@ -844,7 +844,7 @@
|
||||
"sendFeedback": "Geri bildirim gönder",
|
||||
"terms": "Kurallar",
|
||||
"title": "Güvenli, tüm özelliklere erişimli ve tamamen ücretsiz görüntülü arama"
|
||||
|
||||
|
||||
},
|
||||
|
||||
"lonelyMeetingExperience": {
|
||||
|
||||
@@ -416,7 +416,7 @@
|
||||
"invalidStreamKey": "Live stream key may be incorrect.",
|
||||
"off": "Live Streaming stopped",
|
||||
"offBy": "{{name}} stopped the live streaming",
|
||||
"on": "Live Streaming",
|
||||
"on": "Live Streaming started",
|
||||
"onBy": "{{name}} started the live streaming",
|
||||
"pending": "Starting Live Stream...",
|
||||
"serviceName": "Live Streaming service",
|
||||
@@ -596,7 +596,7 @@
|
||||
"loggedIn": "Logged in as {{userName}}",
|
||||
"off": "Recording stopped",
|
||||
"offBy": "{{name}} stopped the recording",
|
||||
"on": "Recording",
|
||||
"on": "Recording started",
|
||||
"onBy": "{{name}} started the recording",
|
||||
"pending": "Preparing to record the meeting...",
|
||||
"rec": "REC",
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { pinParticipant } from '../../react/features/base/participants';
|
||||
import { pinParticipant, getParticipantById } from '../../react/features/base/participants';
|
||||
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
@@ -330,6 +331,24 @@ function initCommands() {
|
||||
} else {
|
||||
logger.error('No recording or streaming session found');
|
||||
}
|
||||
},
|
||||
'initiate-private-chat': participantId => {
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, participantId);
|
||||
|
||||
if (participant) {
|
||||
const { isOpen: isChatOpen } = state['features/chat'];
|
||||
|
||||
if (!isChatOpen) {
|
||||
APP.UI.toggleChat();
|
||||
}
|
||||
APP.store.dispatch(setPrivateMessageRecipient(participant));
|
||||
} else {
|
||||
logger.error('No participant found for the given participantId');
|
||||
}
|
||||
},
|
||||
'cancel-private-chat': () => {
|
||||
APP.store.dispatch(setPrivateMessageRecipient());
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
@@ -1022,6 +1041,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that user updated their hand raised.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @param {boolean} handRaised - Whether user has raised hand.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyRaiseHandUpdated(id: string, handRaised: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'raise-hand-updated',
|
||||
handRaised,
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
3
modules/API/external/external_api.js
vendored
@@ -28,11 +28,13 @@ const ALWAYS_ON_TOP_FILENAMES = [
|
||||
*/
|
||||
const commands = {
|
||||
avatarUrl: 'avatar-url',
|
||||
cancelPrivateChat: 'cancel-private-chat',
|
||||
displayName: 'display-name',
|
||||
e2eeKey: 'e2ee-key',
|
||||
email: 'email',
|
||||
toggleLobby: 'toggle-lobby',
|
||||
hangup: 'video-hangup',
|
||||
intiatePrivateChat: 'initiate-private-chat',
|
||||
muteEveryone: 'mute-everyone',
|
||||
password: 'password',
|
||||
pinParticipant: 'pin-participant',
|
||||
@@ -79,6 +81,7 @@ const events = {
|
||||
'participant-role-changed': 'participantRoleChanged',
|
||||
'password-required': 'passwordRequired',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
'video-ready-to-close': 'readyToClose',
|
||||
'video-conference-joined': 'videoConferenceJoined',
|
||||
'video-conference-left': 'videoConferenceLeft',
|
||||
|
||||
30
package-lock.json
generated
@@ -7198,18 +7198,18 @@
|
||||
}
|
||||
},
|
||||
"css-tree": {
|
||||
"version": "1.0.0-alpha.39",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz",
|
||||
"integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
|
||||
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
|
||||
"requires": {
|
||||
"mdn-data": "2.0.6",
|
||||
"mdn-data": "2.0.14",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mdn-data": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz",
|
||||
"integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA=="
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
@@ -10816,8 +10816,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"from": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"version": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
|
||||
"from": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
@@ -14199,9 +14199,9 @@
|
||||
"integrity": "sha512-kNhBLv8s6kO2gJJFEKM7qew7oRvJnygjgG1CU2ZEY6SlG5qsRX8z1Ms7z1Oo/XB7fVfyXrAoZDGhIvy+uiByrg=="
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-7.3.1.tgz",
|
||||
"integrity": "sha512-RQP3etbmXsOlcaxHeHNug68nRli02S9iGC7TbaXpkvyyevIuRogfnrI71sWtqmlT91kdpYAOYKmNfRL9LOSKVw=="
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-8.0.0.tgz",
|
||||
"integrity": "sha512-7/DOEhg8GtyW1hpVtWf8F6RvGLaFaOGmex+IkmiBWQC2uW4NFDcfXm+lMMZnduFavTyUTX7AF6lAM3y286cEfA=="
|
||||
},
|
||||
"react-native-immersive": {
|
||||
"version": "2.0.0",
|
||||
@@ -14228,9 +14228,9 @@
|
||||
"integrity": "sha512-Ls9qiNZzW/OLFoI25wfjjAcrf2DZ975hn2vr6U9gyuxi2nooVbzQeFoQS5vQcbCt9QX5NY8ASEEAtlLdIa6KVg=="
|
||||
},
|
||||
"react-native-svg": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-10.1.0.tgz",
|
||||
"integrity": "sha512-mgo6CshQIQrDDBVUPqJK/iOsJEdlagk7N4q8fyo1sqCiSUP2efpt+AQ1IRXZtHXut210/7TliAamvM59NV0Bzg==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.1.0.tgz",
|
||||
"integrity": "sha512-1g9qBRci7man8QsHoXn6tP3DhCDiypGgc6+AOWq+Sy+PmP6yiyf8VmvKuoqrPam/tf5x+ZaBT2KI0gl7bptZ7w==",
|
||||
"requires": {
|
||||
"css-select": "^2.1.0",
|
||||
"css-tree": "^1.0.0-alpha.39"
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#87c6e374755718fdb0804c2c798ea4bc832f4fca",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
@@ -75,13 +75,13 @@
|
||||
"react-native-callstats": "3.61.0",
|
||||
"react-native-collapsible": "1.5.1",
|
||||
"react-native-default-preference": "1.4.2",
|
||||
"react-native-device-info": "7.3.1",
|
||||
"react-native-device-info": "8.0.0",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-sound": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190",
|
||||
"react-native-splash-screen": "3.2.0",
|
||||
"react-native-svg": "10.1.0",
|
||||
"react-native-svg": "12.1.0",
|
||||
"react-native-svg-transformer": "0.14.3",
|
||||
"react-native-url-polyfill": "1.2.0",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
|
||||
@@ -80,6 +80,7 @@ export default [
|
||||
'disableAP',
|
||||
'disableAudioLevels',
|
||||
'disableDeepLinking',
|
||||
'disableInitialGUM',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableInviteFunctions',
|
||||
@@ -91,6 +92,7 @@ export default [
|
||||
'disableRtx',
|
||||
'disableSimulcast',
|
||||
'disableThirdPartyRequests',
|
||||
'disableTileView',
|
||||
'displayJids',
|
||||
'doNotStoreRoom',
|
||||
'e2eping',
|
||||
@@ -117,6 +119,8 @@ export default [
|
||||
'forceTurnRelay',
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hideConferenceSubject',
|
||||
'hideParticipantsStats',
|
||||
'hideConferenceTimer',
|
||||
'hiddenDomain',
|
||||
'hideLobbyButton',
|
||||
@@ -128,6 +132,7 @@ export default [
|
||||
'liveStreamingEnabled',
|
||||
'localRecording',
|
||||
'maxFullResolutionParticipants',
|
||||
'notifications',
|
||||
'openBridgeChannel',
|
||||
'openSharedDocumentOnJoin',
|
||||
'opusMaxAverageBitrate',
|
||||
|
||||
@@ -267,6 +267,12 @@ export function setAudioOutputDeviceId(
|
||||
|
||||
logger.debug(`setAudioOutputDevice: ${String(newLabel)}[${newId}]`);
|
||||
|
||||
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
||||
logger.warn('Adjusting audio output is not supported');
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
||||
.then(() => {
|
||||
const newSettings = {
|
||||
|
||||
@@ -171,10 +171,6 @@ ColorSchemeRegistry.register('BottomSheet', {
|
||||
underlayColor: ColorPalette.overflowMenuItemUnderlay
|
||||
},
|
||||
|
||||
expandIcon: {
|
||||
color: schemeColor('icon')
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom sheet's base style.
|
||||
*/
|
||||
|
||||
@@ -448,6 +448,10 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne
|
||||
raisedHand
|
||||
}));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRaiseHandUpdated(participantId, raisedHand);
|
||||
}
|
||||
|
||||
if (raisedHand) {
|
||||
dispatch(showNotification({
|
||||
titleArguments: {
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
import { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { isMobileBrowser } from '../../base/environment/utils';
|
||||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { sendMessage, toggleChat } from '../actions';
|
||||
import { DESKTOP_SMALL_WIDTH_THRESHOLD, MOBILE_SMALL_WIDTH_THRESHOLD } from '../constants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChat}.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Whether the chat is opened in a modal or not (computed based on window width).
|
||||
*/
|
||||
_isModal: boolean,
|
||||
|
||||
/**
|
||||
* True if the chat window should be rendered.
|
||||
*/
|
||||
@@ -106,6 +113,9 @@ export function _mapStateToProps(state: Object) {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
_isModal: isMobileBrowser()
|
||||
? window.innerWidth <= MOBILE_SMALL_WIDTH_THRESHOLD
|
||||
: window.innerWidth <= DESKTOP_SMALL_WIDTH_THRESHOLD,
|
||||
_isOpen: isOpen,
|
||||
_messages: messages,
|
||||
_showNamePrompt: !_localParticipant.name
|
||||
|
||||
@@ -11,6 +11,7 @@ import AbstractChat, {
|
||||
type Props
|
||||
} from '../AbstractChat';
|
||||
|
||||
import ChatDialog from './ChatDialog';
|
||||
import ChatInput from './ChatInput';
|
||||
import DisplayNameForm from './DisplayNameForm';
|
||||
import MessageContainer from './MessageContainer';
|
||||
@@ -151,16 +152,25 @@ class Chat extends AbstractChat<Props> {
|
||||
* @returns {ReactElement | null}
|
||||
*/
|
||||
_renderPanelContent() {
|
||||
const { _isOpen, _showNamePrompt } = this.props;
|
||||
const ComponentToRender = _isOpen
|
||||
? (
|
||||
<>
|
||||
{ this._renderChatHeader() }
|
||||
{ _showNamePrompt
|
||||
? <DisplayNameForm /> : this._renderChat() }
|
||||
</>
|
||||
)
|
||||
: null;
|
||||
const { _isModal, _isOpen, _showNamePrompt } = this.props;
|
||||
let ComponentToRender = null;
|
||||
|
||||
if (_isOpen) {
|
||||
if (_isModal) {
|
||||
ComponentToRender = (
|
||||
<ChatDialog>
|
||||
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
|
||||
</ChatDialog>
|
||||
);
|
||||
} else {
|
||||
ComponentToRender = (
|
||||
<>
|
||||
{ this._renderChatHeader() }
|
||||
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
let className = '';
|
||||
|
||||
if (_isOpen) {
|
||||
|
||||
37
react/features/chat/components/web/ChatDialog.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
|
||||
import Header from './ChatDialogHeader';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children of the component.
|
||||
*/
|
||||
children: React$Node
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders the content of the chat in a modal.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function ChatDialog({ children }: Props) {
|
||||
return (
|
||||
<Dialog
|
||||
customHeader = { Header }
|
||||
disableEnter = { true }
|
||||
hideCancelButton = { true }
|
||||
submitDisabled = { true }
|
||||
titleKey = 'chat.title'>
|
||||
<div className = 'chat-dialog'>
|
||||
{children}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatDialog;
|
||||
42
react/features/chat/components/web/ChatDialogHeader.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../../chat';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Function to be called when pressing the close button.
|
||||
*/
|
||||
onCancel: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom header of the {@code ChatDialog}.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function Header({ onCancel, t }: Props) {
|
||||
return (
|
||||
<div
|
||||
className = 'chat-dialog-header'>
|
||||
{ t('chat.title') }
|
||||
<Icon
|
||||
onClick = { onCancel }
|
||||
src = { IconClose } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { onCancel: toggleChat };
|
||||
|
||||
export default translate(connect(null, mapDispatchToProps)(Header));
|
||||
@@ -29,3 +29,7 @@ export const MESSAGE_TYPE_LOCAL = 'local';
|
||||
* The {@code messageType} of remote messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_REMOTE = 'remote';
|
||||
|
||||
export const DESKTOP_SMALL_WIDTH_THRESHOLD = 580;
|
||||
|
||||
export const MOBILE_SMALL_WIDTH_THRESHOLD = 680;
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
import { isButtonEnabled } from '../toolbox/functions';
|
||||
|
||||
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
|
||||
import { addMessage, clearMessages, toggleChat } from './actions';
|
||||
@@ -152,10 +151,9 @@ StateListenerRegistry.register(
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addChatMsgListener(conference, store) {
|
||||
if ((typeof APP !== 'undefined' && !isButtonEnabled('chat'))
|
||||
|| store.getState()['features/base/config'].iAmRecorder) {
|
||||
// We don't register anything on web if the chat button is not enabled in interfaceConfig
|
||||
// or we are in iAmRecorder mode
|
||||
|
||||
if (store.getState()['features/base/config'].iAmRecorder) {
|
||||
// We don't register anything on web if we are in iAmRecorder mode
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,11 +95,14 @@ class NavigationBar extends Component<Props> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { hideConferenceTimer, hideConferenceSubject } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_conferenceTimerEnabled:
|
||||
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !state['features/base/config'].hideConferenceTimer,
|
||||
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !hideConferenceTimer,
|
||||
_meetingName: getConferenceName(state),
|
||||
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
|
||||
_meetingNameEnabled:
|
||||
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject,
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ type Props = {
|
||||
*/
|
||||
_showParticipantCount: boolean,
|
||||
|
||||
/**
|
||||
* Whether the conference subject should be shown or not.
|
||||
*/
|
||||
_showSubject: boolean,
|
||||
|
||||
/**
|
||||
* The subject or the of the conference.
|
||||
* Falls back to conference name.
|
||||
@@ -51,11 +56,11 @@ class Subject extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _hideConferenceTimer, _showParticipantCount, _subject, _visible } = this.props;
|
||||
const { _hideConferenceTimer, _showParticipantCount, _showSubject, _subject, _visible } = this.props;
|
||||
|
||||
return (
|
||||
<div className = { `subject ${_visible ? 'visible' : ''}` }>
|
||||
<span className = 'subject-text'>{ _subject }</span>
|
||||
{ _showSubject && <span className = 'subject-text'>{ _subject }</span>}
|
||||
{ _showParticipantCount && <ParticipantsCount /> }
|
||||
{ !_hideConferenceTimer && <ConferenceTimer /> }
|
||||
</div>
|
||||
@@ -72,16 +77,19 @@ class Subject extends Component<Props> {
|
||||
* @returns {{
|
||||
* _hideConferenceTimer: boolean,
|
||||
* _showParticipantCount: boolean,
|
||||
* _showSubject: boolean,
|
||||
* _subject: string,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participantCount = getParticipantCount(state);
|
||||
const { hideConferenceTimer, hideConferenceSubject, hideParticipantsStats } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_hideConferenceTimer: Boolean(state['features/base/config'].hideConferenceTimer),
|
||||
_showParticipantCount: participantCount > 2,
|
||||
_hideConferenceTimer: Boolean(hideConferenceTimer),
|
||||
_showParticipantCount: participantCount > 2 && !hideParticipantsStats,
|
||||
_showSubject: !hideConferenceSubject,
|
||||
_subject: getConferenceName(state),
|
||||
_visible: isToolboxVisible(state) && participantCount > 1
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { dockToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled } from '../../../toolbox/functions.web';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import { setFilmstripHovered, setFilmstripVisible } from '../../actions';
|
||||
import { shouldRemoteVideosBeVisible } from '../../functions';
|
||||
@@ -197,7 +198,7 @@ class Filmstrip extends Component <Props> {
|
||||
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._hideToolbar) {
|
||||
if (!this.props._hideToolbar && isButtonEnabled('filmstrip')) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => {
|
||||
|
||||
recordingController.onWarning = (messageKey, messageParams) => {
|
||||
dispatch(showNotification({
|
||||
title: i18next.t('localRecording.localRecording'),
|
||||
titleKey: 'localRecording.localRecording',
|
||||
description: i18next.t(messageKey, messageParams)
|
||||
}, 10000));
|
||||
};
|
||||
|
||||
recordingController.onNotify = (messageKey, messageParams) => {
|
||||
dispatch(showNotification({
|
||||
title: i18next.t('localRecording.localRecording'),
|
||||
titleKey: 'localRecording.localRecording',
|
||||
description: i18next.t(messageKey, messageParams)
|
||||
}, 10000));
|
||||
};
|
||||
|
||||
@@ -108,20 +108,20 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
|
||||
};
|
||||
}
|
||||
|
||||
const notification = showNotification({
|
||||
const notification = await dispatch(showNotification({
|
||||
titleKey: 'toolbar.noAudioSignalTitle',
|
||||
description: <DialInLink />,
|
||||
descriptionKey,
|
||||
customActionNameKey,
|
||||
customActionHandler
|
||||
});
|
||||
|
||||
dispatch(notification);
|
||||
}));
|
||||
|
||||
dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));
|
||||
|
||||
// Store the current notification uid so we can check for this state and hide it in case
|
||||
// a new track was added, thus changing the context of the notification
|
||||
dispatch(setNoAudioSignalNotificationUid(notification.uid));
|
||||
if (notification) {
|
||||
// Store the current notification uid so we can check for this state and hide it in case
|
||||
// a new track was added, thus changing the context of the notification
|
||||
dispatch(setNoAudioSignalNotificationUid(notification.uid));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,17 +37,18 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
});
|
||||
conference.on(
|
||||
JitsiConferenceEvents.NOISY_MIC, () => {
|
||||
const notification = showNotification({
|
||||
JitsiConferenceEvents.NOISY_MIC, async () => {
|
||||
const notification = await dispatch(showNotification({
|
||||
titleKey: 'toolbar.noisyAudioInputTitle',
|
||||
descriptionKey: 'toolbar.noisyAudioInputDesc'
|
||||
});
|
||||
}));
|
||||
|
||||
dispatch(notification);
|
||||
dispatch(playSound(NOISY_AUDIO_INPUT_SOUND_ID));
|
||||
|
||||
// we store the last notification id so we can hide it if the mic is muted
|
||||
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
|
||||
if (notification) {
|
||||
// we store the last notification id so we can hide it if the mic is muted
|
||||
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -76,19 +76,23 @@ export function showErrorNotification(props: Object) {
|
||||
* @param {Object} props - The props needed to show the notification component.
|
||||
* @param {number} timeout - How long the notification should display before
|
||||
* automatically being hidden.
|
||||
* @returns {{
|
||||
* type: SHOW_NOTIFICATION,
|
||||
* props: Object,
|
||||
* timeout: number,
|
||||
* uid: number
|
||||
* }}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showNotification(props: Object = {}, timeout: ?number) {
|
||||
return {
|
||||
type: SHOW_NOTIFICATION,
|
||||
props,
|
||||
timeout,
|
||||
uid: window.Date.now()
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
const { notifications } = getState()['features/base/config'];
|
||||
const shouldDisplay = !notifications
|
||||
|| notifications.includes(props.descriptionKey)
|
||||
|| notifications.includes(props.titleKey);
|
||||
|
||||
if (shouldDisplay) {
|
||||
return dispatch({
|
||||
type: SHOW_NOTIFICATION,
|
||||
props,
|
||||
timeout,
|
||||
uid: window.Date.now()
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export function setLiveStreamKey(streamKey: string) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showPendingRecordingNotification(streamType: string) {
|
||||
return (dispatch: Function) => {
|
||||
return async (dispatch: Function) => {
|
||||
const isLiveStreaming
|
||||
= streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
const dialogProps = isLiveStreaming ? {
|
||||
@@ -85,15 +85,14 @@ export function showPendingRecordingNotification(streamType: string) {
|
||||
descriptionKey: 'recording.pending',
|
||||
titleKey: 'dialog.recording'
|
||||
};
|
||||
const showNotificationAction = showNotification({
|
||||
const notification = await dispatch(showNotification({
|
||||
isDismissAllowed: false,
|
||||
...dialogProps
|
||||
});
|
||||
}));
|
||||
|
||||
dispatch(showNotificationAction);
|
||||
|
||||
dispatch(_setPendingRecordingNotificationUid(
|
||||
showNotificationAction.uid, streamType));
|
||||
if (notification) {
|
||||
dispatch(_setPendingRecordingNotificationUid(notification.uid, streamType));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,11 +146,11 @@ export function showStartedRecordingNotification(streamType: string, participant
|
||||
= streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
const descriptionArguments = { name: participantName };
|
||||
const dialogProps = isLiveStreaming ? {
|
||||
descriptionKey: 'liveStreaming.onBy',
|
||||
descriptionKey: participantName ? 'liveStreaming.onBy' : 'liveStreaming.on',
|
||||
descriptionArguments,
|
||||
titleKey: 'dialog.liveStreaming'
|
||||
} : {
|
||||
descriptionKey: 'recording.onBy',
|
||||
descriptionKey: participantName ? 'recording.onBy' : 'recording.on',
|
||||
descriptionArguments,
|
||||
titleKey: 'dialog.recording'
|
||||
};
|
||||
|
||||
@@ -155,13 +155,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
|
||||
if (updatedSessionData.status === ON
|
||||
&& (!oldSessionData || oldSessionData.status !== ON)) {
|
||||
if (initiator) {
|
||||
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
|
||||
|
||||
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
|
||||
} else if (typeof recordingLimit === 'object') {
|
||||
if (typeof recordingLimit === 'object') {
|
||||
// Show notification with additional information to the initiator.
|
||||
dispatch(showRecordingLimitNotification(mode));
|
||||
} else {
|
||||
dispatch(showStartedRecordingNotification(
|
||||
mode, initiator && getParticipantDisplayName(getState, initiator.getId())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { IconMicrophoneEmpty, IconVolumeEmpty } from '../../../../base/icons';
|
||||
import JitsiMeetJS from '../../../../base/lib-jitsi-meet';
|
||||
import { equals } from '../../../../base/redux';
|
||||
import { createLocalAudioTracks } from '../../../functions';
|
||||
|
||||
@@ -11,6 +12,8 @@ import AudioSettingsHeader from './AudioSettingsHeader';
|
||||
import MicrophoneEntry from './MicrophoneEntry';
|
||||
import SpeakerEntry from './SpeakerEntry';
|
||||
|
||||
const browser = JitsiMeetJS.util.browser;
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
@@ -169,6 +172,14 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
async _setTracks() {
|
||||
if (browser.isSafari()) {
|
||||
|
||||
// It appears that at the time of this writing, creating audio tracks blocks the browser's main thread for
|
||||
// long time on safari. Wasn't able to confirm which part of track creation does the blocking exactly, but
|
||||
// not creating the tracks seems to help and makes the UI much more responsive.
|
||||
return;
|
||||
}
|
||||
|
||||
this._disposeTracks(this.state.audioTracks);
|
||||
|
||||
const audioTracks = await createLocalAudioTracks(
|
||||
@@ -244,9 +255,11 @@ class AudioSettingsContent extends Component<Props, State> {
|
||||
{this.state.audioTracks.map((data, i) =>
|
||||
this._renderMicrophoneEntry(data, i),
|
||||
)}
|
||||
<AudioSettingsHeader
|
||||
IconComponent = { IconVolumeEmpty }
|
||||
text = { t('settings.speakers') } />
|
||||
{ outputDevices.length > 0 && (
|
||||
<AudioSettingsHeader
|
||||
IconComponent = { IconVolumeEmpty }
|
||||
text = { t('settings.speakers') } />)
|
||||
}
|
||||
{outputDevices.map((data, i) =>
|
||||
this._renderSpeakerEntry(data, i),
|
||||
)}
|
||||
|
||||
@@ -140,7 +140,7 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
compmonentWillUnmount() {
|
||||
componentWillUnmount() {
|
||||
this._stopListening(this.props.jitsiTrack);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { children, hasError, isSelected } = this.props;
|
||||
const { children, hasError, isSelected, jitsiTrack } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -161,10 +161,11 @@ export default class MicrophoneEntry extends Component<Props, State> {
|
||||
isSelected = { isSelected }>
|
||||
{children}
|
||||
</AudioSettingsEntry>
|
||||
<Meter
|
||||
{ Boolean(jitsiTrack) && <Meter
|
||||
className = 'audio-preview-meter-mic'
|
||||
isDisabled = { hasError }
|
||||
level = { this.state.level } />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ export default class JitsiStreamPresenterEffect {
|
||||
this._videoElement.autoplay = true;
|
||||
this._videoElement.srcObject = videoStream;
|
||||
|
||||
// autoplay is not enough to start the video on Safari, it's fine to call play() on other platforms as well
|
||||
this._videoElement.play();
|
||||
|
||||
// set the style attribute of the div to make it invisible
|
||||
videoDiv.style.display = 'none';
|
||||
|
||||
@@ -132,6 +135,10 @@ export default class JitsiStreamPresenterEffect {
|
||||
this._desktopElement.height = parseInt(height, 10);
|
||||
this._desktopElement.autoplay = true;
|
||||
this._desktopElement.srcObject = desktopStream;
|
||||
|
||||
// autoplay is not enough to start the video on Safari, it's fine to call play() on other platforms as well
|
||||
this._desktopElement.play();
|
||||
|
||||
this._canvas.width = parseInt(width, 10);
|
||||
this._canvas.height = parseInt(height, 10);
|
||||
this._videoFrameTimerWorker = new Worker(timerWorkerScript, { name: 'Presenter effect worker' });
|
||||
|
||||
@@ -40,20 +40,20 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
});
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
||||
const notification = showNotification({
|
||||
JitsiConferenceEvents.TALK_WHILE_MUTED, async () => {
|
||||
const notification = await dispatch(showNotification({
|
||||
titleKey: 'toolbar.talkWhileMutedPopup',
|
||||
customActionNameKey: 'notify.unmute',
|
||||
customActionHandler: () => dispatch(setAudioMuted(false))
|
||||
});
|
||||
|
||||
dispatch(notification);
|
||||
}));
|
||||
|
||||
dispatch(playSound(TALK_WHILE_MUTED_SOUND_ID));
|
||||
|
||||
// we store the last start muted notification id that we showed,
|
||||
// so we can hide it when unmuted mic is detected
|
||||
dispatch(setCurrentNotificationUid(notification.uid));
|
||||
if (notification) {
|
||||
// we store the last start muted notification id that we showed,
|
||||
// so we can hide it when unmuted mic is detected
|
||||
dispatch(setCurrentNotificationUid(notification.uid));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -166,8 +166,9 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||
styles.expandMenuContainer
|
||||
] }>
|
||||
<TouchableOpacity onPress = { this._onToggleMenu }>
|
||||
{ /* $FlowFixMeProps */ }
|
||||
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
|
||||
{ /* $FlowFixMe */ }
|
||||
<IconDragHandle
|
||||
fill = { this.props._bottomSheetStyles.buttons.iconStyle.color } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -470,6 +470,7 @@ class Toolbox extends Component<Props, State> {
|
||||
*/
|
||||
_doToggleRaiseHand() {
|
||||
const { _localParticipantID, _raisedHand } = this.props;
|
||||
const newRaisedStatus = !_raisedHand;
|
||||
|
||||
this.props.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
@@ -480,8 +481,10 @@ class Toolbox extends Component<Props, State> {
|
||||
|
||||
id: _localParticipantID,
|
||||
local: true,
|
||||
raisedHand: !_raisedHand
|
||||
raisedHand: newRaisedStatus
|
||||
}));
|
||||
|
||||
APP.API.notifyRaiseHandUpdated(_localParticipantID, newRaisedStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { hasAvailableDevices } from '../base/devices';
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -49,10 +48,8 @@ export function isToolboxVisible(state: Object) {
|
||||
visible
|
||||
} = state['features/toolbox'];
|
||||
const { audioSettingsVisible, videoSettingsVisible } = state['features/settings'];
|
||||
const { isOpen } = state['features/chat'];
|
||||
const isMobileChatOpen = isMobileBrowser() && isOpen;
|
||||
|
||||
return Boolean(!isMobileChatOpen && !iAmSipGateway && (timeoutID || visible || alwaysVisible
|
||||
return Boolean(!iAmSipGateway && (timeoutID || visible || alwaysVisible
|
||||
|| audioSettingsVisible || videoSettingsVisible));
|
||||
}
|
||||
|
||||
|
||||
@@ -69,17 +69,16 @@ export function potentialTranscriberJoined(participantId: string) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showPendingTranscribingNotification() {
|
||||
return (dispatch: Function) => {
|
||||
const showNotificationAction = showNotification({
|
||||
return async (dispatch: Function) => {
|
||||
const notification = await dispatch(showNotification({
|
||||
descriptionKey: 'transcribing.pending',
|
||||
isDismissAllowed: false,
|
||||
titleKey: 'dialog.transcribing'
|
||||
});
|
||||
}));
|
||||
|
||||
dispatch(showNotificationAction);
|
||||
|
||||
dispatch(setPendingTranscribingNotificationUid(
|
||||
showNotificationAction.uid));
|
||||
if (notification) {
|
||||
dispatch(setPendingTranscribingNotificationUid(notification.uid));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ export function shouldDisplayTileView(state: Object = {}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { disableTileView } = state['features/base/config'];
|
||||
|
||||
if (disableTileView) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { tileViewEnabled } = state['features/video-layout'];
|
||||
|
||||
if (tileViewEnabled !== undefined) {
|
||||
|
||||
202
resources/prosody-plugins/mod_client_proxy.lua
Normal file
@@ -0,0 +1,202 @@
|
||||
if module:get_host_type() ~= "component" then
|
||||
error("proxy_component should be loaded as component", 0);
|
||||
end
|
||||
|
||||
local jid_split = require "util.jid".split;
|
||||
local jid_bare = require "util.jid".bare;
|
||||
local jid_prep = require "util.jid".prep;
|
||||
local st = require "util.stanza";
|
||||
local array = require "util.array";
|
||||
|
||||
local target_address = module:get_option_string("target_address");
|
||||
|
||||
sessions = array{};
|
||||
local sessions = sessions;
|
||||
|
||||
local function handle_target_presence(stanza)
|
||||
local type = stanza.attr.type;
|
||||
module:log("debug", "received presence from destination: %s", type)
|
||||
local _, _, resource = jid_split(stanza.attr.from);
|
||||
if type == "error" then
|
||||
-- drop all known sessions
|
||||
for k in pairs(sessions) do
|
||||
sessions[k] = nil
|
||||
end
|
||||
module:log(
|
||||
"debug",
|
||||
"received error presence, dropping all target sessions",
|
||||
resource
|
||||
)
|
||||
elseif type == "unavailable" then
|
||||
for k in pairs(sessions) do
|
||||
if sessions[k] == resource then
|
||||
sessions[k] = nil
|
||||
module:log(
|
||||
"debug",
|
||||
"dropped target session: %s",
|
||||
resource
|
||||
)
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif not type then
|
||||
-- available
|
||||
local found = false;
|
||||
for k in pairs(sessions) do
|
||||
if sessions[k] == resource then
|
||||
found = true;
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
module:log(
|
||||
"debug",
|
||||
"registered new target session: %s",
|
||||
resource
|
||||
)
|
||||
sessions:push(resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_from_target(stanza)
|
||||
local type = stanza.attr.type
|
||||
module:log(
|
||||
"debug",
|
||||
"non-presence stanza from target: name = %s, type = %s",
|
||||
stanza.name,
|
||||
type
|
||||
)
|
||||
if stanza.name == "iq" then
|
||||
if type == "error" or type == "result" then
|
||||
-- de-NAT message
|
||||
local _, _, denatted_to_unprepped = jid_split(stanza.attr.to);
|
||||
local denatted_to = jid_prep(denatted_to_unprepped);
|
||||
if not denatted_to then
|
||||
module:log(
|
||||
"debug",
|
||||
"cannot de-NAT stanza, invalid to: %s",
|
||||
denatted_to_unprepped
|
||||
)
|
||||
return
|
||||
end
|
||||
local denatted_from = module:get_host();
|
||||
|
||||
module:log(
|
||||
"debug",
|
||||
"de-NAT-ed stanza: from: %s -> %s, to: %s -> %s",
|
||||
stanza.attr.from,
|
||||
denatted_from,
|
||||
stanza.attr.to,
|
||||
denatted_to
|
||||
)
|
||||
|
||||
stanza.attr.from = denatted_from
|
||||
stanza.attr.to = denatted_to
|
||||
|
||||
module:send(stanza)
|
||||
else
|
||||
-- FIXME: we don’t support NATing outbund requests atm.
|
||||
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
|
||||
end
|
||||
elseif stanza.name == "message" then
|
||||
-- not implemented yet, we need a way to ensure that routing doesn’t
|
||||
-- break
|
||||
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_to_target(stanza)
|
||||
local type = stanza.attr.type;
|
||||
module:log(
|
||||
"debug",
|
||||
"stanza to target: name = %s, type = %s",
|
||||
stanza.name, type
|
||||
)
|
||||
if stanza.name == "presence" then
|
||||
if type ~= "error" then
|
||||
module:send(st.error_reply(stanza, "cancel", "bad-request"))
|
||||
return
|
||||
end
|
||||
elseif stanza.name == "iq" then
|
||||
if type == "get" or type == "set" then
|
||||
if #sessions == 0 then
|
||||
-- no sessions available to send to
|
||||
module:log("debug", "no sessions to send to!")
|
||||
module:send(st.error_reply(stanza, "cancel", "service-unavailable"))
|
||||
return
|
||||
end
|
||||
|
||||
-- find a target session
|
||||
local target_session = sessions:random()
|
||||
local target = target_address .. "/" .. target_session
|
||||
|
||||
-- encode sender JID in resource
|
||||
local natted_from = module:get_host() .. "/" .. stanza.attr.from;
|
||||
|
||||
module:log(
|
||||
"debug",
|
||||
"NAT-ed stanza: from: %s -> %s, to: %s -> %s",
|
||||
stanza.attr.from,
|
||||
natted_from,
|
||||
stanza.attr.to,
|
||||
target
|
||||
)
|
||||
|
||||
stanza.attr.from = natted_from
|
||||
stanza.attr.to = target
|
||||
|
||||
module:send(stanza)
|
||||
end
|
||||
-- FIXME: handle and forward result/error correctly
|
||||
elseif stanza.name == "message" then
|
||||
-- not implemented yet, we need a way to ensure that routing doesn’t
|
||||
-- break
|
||||
module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
|
||||
end
|
||||
end
|
||||
|
||||
local function stanza_handler(event)
|
||||
local origin, stanza = event.origin, event.stanza
|
||||
module:log("debug", "received stanza from %s session", origin.type)
|
||||
|
||||
local bare_from = jid_bare(stanza.attr.from);
|
||||
local _, _, to = jid_split(stanza.attr.to);
|
||||
if bare_from == target_address then
|
||||
-- from our target, to whom?
|
||||
if not to then
|
||||
-- directly to component
|
||||
if stanza.name == "presence" then
|
||||
handle_target_presence(stanza)
|
||||
else
|
||||
module:send(st.error_reply(stanza, "cancel", "bad-request"))
|
||||
return true
|
||||
end
|
||||
else
|
||||
-- to someone else
|
||||
handle_from_target(stanza)
|
||||
end
|
||||
else
|
||||
handle_to_target(stanza)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
module:hook("iq/bare", stanza_handler, -1);
|
||||
module:hook("message/bare", stanza_handler, -1);
|
||||
module:hook("presence/bare", stanza_handler, -1);
|
||||
module:hook("iq/full", stanza_handler, -1);
|
||||
module:hook("message/full", stanza_handler, -1);
|
||||
module:hook("presence/full", stanza_handler, -1);
|
||||
module:hook("iq/host", stanza_handler, -1);
|
||||
module:hook("message/host", stanza_handler, -1);
|
||||
module:hook("presence/host", stanza_handler, -1);
|
||||
|
||||
module:log("debug", "loaded proxy on %s", module:get_host())
|
||||
|
||||
subscription_request = st.presence({
|
||||
type = "subscribe",
|
||||
to = target_address,
|
||||
from = module:get_host()}
|
||||
)
|
||||
module:send(subscription_request)
|
||||
165
resources/prosody-plugins/mod_roster_command.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
-----------------------------------------------------------
|
||||
-- mod_roster_command: Manage rosters through prosodyctl
|
||||
-- version 0.02
|
||||
-----------------------------------------------------------
|
||||
-- Copyright (C) 2011 Matthew Wild
|
||||
-- Copyright (C) 2011 Adam Nielsen
|
||||
--
|
||||
-- This project is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
-----------------------------------------------------------
|
||||
|
||||
if module.host ~= "*" then
|
||||
module:log("error", "Do not load this module in Prosody, for correct usage see: https://modules.prosody.im/mod_roster_command.html");
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
-- Workaround for lack of util.startup...
|
||||
local prosody = _G.prosody;
|
||||
local hosts = prosody.hosts;
|
||||
prosody.bare_sessions = prosody.bare_sessions or {};
|
||||
_G.bare_sessions = _G.bare_sessions or prosody.bare_sessions;
|
||||
|
||||
local usermanager = require "core.usermanager";
|
||||
local rostermanager = require "core.rostermanager";
|
||||
local storagemanager = require "core.storagemanager";
|
||||
local jid = require "util.jid";
|
||||
local warn = require"util.prosodyctl".show_warning;
|
||||
|
||||
-- Make a *one-way* subscription. User will see when contact is online,
|
||||
-- contact will not see when user is online.
|
||||
function subscribe(user_jid, contact_jid)
|
||||
local user_username, user_host = jid.split(user_jid);
|
||||
local contact_username, contact_host = jid.split(contact_jid);
|
||||
if not hosts[user_host] then
|
||||
warn("The host '%s' is not configured for this server.", user_host);
|
||||
return;
|
||||
end
|
||||
if hosts[user_host].users.name == "null" then
|
||||
storagemanager.initialize_host(user_host);
|
||||
usermanager.initialize_host(user_host);
|
||||
end
|
||||
-- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters.
|
||||
if user_username ~= nil then
|
||||
rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
|
||||
end
|
||||
if hosts[contact_host] then
|
||||
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
|
||||
storagemanager.initialize_host(contact_host);
|
||||
usermanager.initialize_host(contact_host);
|
||||
end
|
||||
-- Update contact's roster to say subscription request is pending...
|
||||
rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid);
|
||||
-- Update contact's roster to say subscription request approved...
|
||||
rostermanager.subscribed(contact_username, contact_host, user_jid);
|
||||
-- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters.
|
||||
if user_username ~= nil then
|
||||
rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Make a mutual subscription between jid1 and jid2. Each JID will see
|
||||
-- when the other one is online.
|
||||
function subscribe_both(jid1, jid2)
|
||||
subscribe(jid1, jid2);
|
||||
subscribe(jid2, jid1);
|
||||
end
|
||||
|
||||
-- Unsubscribes user from contact (not contact from user, if subscribed).
|
||||
function unsubscribe(user_jid, contact_jid)
|
||||
local user_username, user_host = jid.split(user_jid);
|
||||
local contact_username, contact_host = jid.split(contact_jid);
|
||||
if not hosts[user_host] then
|
||||
warn("The host '%s' is not configured for this server.", user_host);
|
||||
return;
|
||||
end
|
||||
if hosts[user_host].users.name == "null" then
|
||||
storagemanager.initialize_host(user_host);
|
||||
usermanager.initialize_host(user_host);
|
||||
end
|
||||
-- Update user's roster to say subscription is cancelled...
|
||||
rostermanager.unsubscribe(user_username, user_host, contact_jid);
|
||||
if hosts[contact_host] then
|
||||
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
|
||||
storagemanager.initialize_host(contact_host);
|
||||
usermanager.initialize_host(contact_host);
|
||||
end
|
||||
-- Update contact's roster to say subscription is cancelled...
|
||||
rostermanager.unsubscribed(contact_username, contact_host, user_jid);
|
||||
end
|
||||
end
|
||||
|
||||
-- Cancel any subscription in either direction.
|
||||
function unsubscribe_both(jid1, jid2)
|
||||
unsubscribe(jid1, jid2);
|
||||
unsubscribe(jid2, jid1);
|
||||
end
|
||||
|
||||
-- Set the name shown and group used in the contact list
|
||||
function rename(user_jid, contact_jid, contact_nick, contact_group)
|
||||
local user_username, user_host = jid.split(user_jid);
|
||||
if not hosts[user_host] then
|
||||
warn("The host '%s' is not configured for this server.", user_host);
|
||||
return;
|
||||
end
|
||||
if hosts[user_host].users.name == "null" then
|
||||
storagemanager.initialize_host(user_host);
|
||||
usermanager.initialize_host(user_host);
|
||||
end
|
||||
|
||||
-- Load user's roster and find the contact
|
||||
local roster = rostermanager.load_roster(user_username, user_host);
|
||||
local item = roster[contact_jid];
|
||||
if item then
|
||||
if contact_nick then
|
||||
item.name = contact_nick;
|
||||
end
|
||||
if contact_group then
|
||||
item.groups = {}; -- Remove from all current groups
|
||||
item.groups[contact_group] = true;
|
||||
end
|
||||
rostermanager.save_roster(user_username, user_host, roster);
|
||||
end
|
||||
end
|
||||
|
||||
function remove(user_jid, contact_jid)
|
||||
unsubscribe_both(user_jid, contact_jid);
|
||||
local user_username, user_host = jid.split(user_jid);
|
||||
local roster = rostermanager.load_roster(user_username, user_host);
|
||||
roster[contact_jid] = nil;
|
||||
rostermanager.save_roster(user_username, user_host, roster);
|
||||
end
|
||||
|
||||
function module.command(arg)
|
||||
local command = arg[1];
|
||||
if not command then
|
||||
warn("Valid subcommands: (un)subscribe(_both) | rename");
|
||||
return 0;
|
||||
end
|
||||
table.remove(arg, 1);
|
||||
if command == "subscribe" then
|
||||
subscribe(arg[1], arg[2]);
|
||||
return 0;
|
||||
elseif command == "subscribe_both" then
|
||||
subscribe_both(arg[1], arg[2]);
|
||||
return 0;
|
||||
elseif command == "unsubscribe" then
|
||||
unsubscribe(arg[1], arg[2]);
|
||||
return 0;
|
||||
elseif command == "unsubscribe_both" then
|
||||
unsubscribe_both(arg[1], arg[2]);
|
||||
return 0;
|
||||
elseif command == "remove" then
|
||||
remove(arg[1], arg[2]);
|
||||
return 0;
|
||||
elseif command == "rename" then
|
||||
rename(arg[1], arg[2], arg[3], arg[4]);
|
||||
return 0;
|
||||
else
|
||||
warn("Unknown command: %s", command);
|
||||
return 1;
|
||||
end
|
||||
return 0;
|
||||
end
|
||||
47
resources/prosody-plugins/mod_roster_command.patch
Normal file
@@ -0,0 +1,47 @@
|
||||
# HG changeset patch
|
||||
# User Boris Grozev <boris@jitsi.org>
|
||||
# Date 1609874100 21600
|
||||
# Tue Jan 05 13:15:00 2021 -0600
|
||||
# Node ID f646babfc401494ff33f2126ef6c4df541ebf846
|
||||
# Parent 456b9f608fcf9667cfba1bd7bf9eba2151af50d0
|
||||
mod_roster_command: Fix subscription when the "user JID" is a bare domain.
|
||||
|
||||
Do not attempt to update the roster when the user is bare domain (e.g. a
|
||||
component), since they don't have rosters and the attempt results in an error:
|
||||
|
||||
$ prosodyctl mod_roster_command subscribe proxy.example.com contact@example.com
|
||||
xxxxxxxxxxFailed to execute command: Error: /usr/lib/prosody/core/rostermanager.lua:104: attempt to concatenate local 'username' (a nil value)
|
||||
stack traceback:
|
||||
/usr/lib/prosody/core/rostermanager.lua:104: in function 'load_roster'
|
||||
/usr/lib/prosody/core/rostermanager.lua:305: in function 'set_contact_pending_out'
|
||||
mod_roster_command.lua:44: in function 'subscribe'
|
||||
|
||||
diff -r 456b9f608fcf -r f646babfc401 mod_roster_command/mod_roster_command.lua
|
||||
--- a/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:49:50 2021 +0000
|
||||
+++ b/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:15:00 2021 -0600
|
||||
@@ -40,8 +40,10 @@
|
||||
storagemanager.initialize_host(user_host);
|
||||
usermanager.initialize_host(user_host);
|
||||
end
|
||||
- -- Update user's roster to say subscription request is pending...
|
||||
- rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
|
||||
+ -- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters.
|
||||
+ if user_username ~= nil then
|
||||
+ rostermanager.set_contact_pending_out(user_username, user_host, contact_jid);
|
||||
+ end
|
||||
if hosts[contact_host] then
|
||||
if contact_host ~= user_host and hosts[contact_host].users.name == "null" then
|
||||
storagemanager.initialize_host(contact_host);
|
||||
@@ -51,8 +53,10 @@
|
||||
rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid);
|
||||
-- Update contact's roster to say subscription request approved...
|
||||
rostermanager.subscribed(contact_username, contact_host, user_jid);
|
||||
- -- Update user's roster to say subscription request approved...
|
||||
- rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
|
||||
+ -- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters.
|
||||
+ if user_username ~= nil then
|
||||
+ rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid);
|
||||
+ end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,20 +46,14 @@ def twaManifest = [
|
||||
enableSiteSettingsShortcut: 'true',
|
||||
]
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
|
||||
// This lets us upload a new build at most every 10 seconds for the
|
||||
// next ~680 years.
|
||||
// https://stackoverflow.com/a/38643838
|
||||
def vcode = (int) (((new Date().getTime() / 1000) - 1546297200) / 10)
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
applicationId "org.jitsi.meet"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode vcode
|
||||
versionName "20.5.0"
|
||||
versionCode 2000000000
|
||||
versionName "1.0.0"
|
||||
|
||||
// The name for the application
|
||||
resValue "string", "appName", twaManifest.name
|
||||
@@ -71,12 +65,12 @@ android {
|
||||
def launchUrl = "https://" + twaManifest.hostName + twaManifest.launchUrl
|
||||
resValue "string", "launchUrl", launchUrl
|
||||
|
||||
|
||||
|
||||
// The URL the Web Manifest for the Progressive Web App that the TWA points to. This
|
||||
// is used by Chrome OS to open the Web version of the PWA instead of the TWA, as it
|
||||
// will probably give a better user experience for non-mobile devices.
|
||||
resValue "string", "webManifestUrl", 'https://meet.jit.si/static/pwa/manifest.json'
|
||||
|
||||
resValue "string", "webManifestUrl", 'https://meet.jit.si/manifest.json'
|
||||
|
||||
|
||||
// The hostname is used when building the intent-filter, so the TWA is able to
|
||||
// handle Intents to open https://svgomg.firebaseapp.com.
|
||||
@@ -106,13 +100,13 @@ android {
|
||||
// compile. If not set, the navigation bar color defaults to #000000 - black.
|
||||
resValue "color", "navigationColorDark", twaManifest.navigationColorDark
|
||||
|
||||
// This attribute sets the navbar divider color for the TWA. It can be either
|
||||
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
|
||||
// This attribute sets the navbar divider color for the TWA. It can be either
|
||||
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
|
||||
// will not compile. If not set, the divider color defaults to #00000000 - transparent.
|
||||
resValue "color", "navigationDividerColor", twaManifest.navigationDividerColor
|
||||
|
||||
// This attribute sets the dark navbar divider color for the TWA. It can be either
|
||||
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
|
||||
// This attribute sets the dark navbar divider color for the TWA. It can be either
|
||||
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
|
||||
//app will not compile. If not set, the divider color defaults to #000000 - black.
|
||||
resValue "color", "navigationDividerColorDark", twaManifest.navigationDividerColorDark
|
||||
|
||||
@@ -192,11 +186,11 @@ task generateShorcutsFile {
|
||||
preBuild.dependsOn(generateShorcutsFile)
|
||||
|
||||
repositories {
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
|
||||
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.0.0'
|
||||
|
||||
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.0.0'
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -1 +1 @@
|
||||
{"android_package_name":"org.jitsi.meet","prefer_related_applications":true,"related_applications":[{"id":"org.jitsi.meet","platform":"chromeos_play"}],"short_name":"Jitsi Meet","name":"Jitsi Meet","icons":[{"src":"icons/icon192.png","type":"image/png","sizes":"192x192"},{"src":"icons/icon512.png","type":"image/png","sizes":"512x512"},{"src":"icons/iconMask.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":"/","background_color":"#17A0DB","display":"standalone","scope":"/","theme_color":"#17A0DB"}
|
||||
{"android_package_name":"org.jitsi.meet","prefer_related_applications":true,"related_applications":[{"id":"org.jitsi.meet","platform":"chromeos_play"}],"short_name":"Jitsi Meet","name":"Jitsi Meet","icons":[{"src":"static/pwa/icons/icon192.png","type":"image/png","sizes":"192x192"},{"src":"static/pwa/icons/icon512.png","type":"image/png","sizes":"512x512"},{"src":"static/pwa/icons/iconMask.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":"/","background_color":"#17A0DB","display":"standalone","scope":"/","theme_color":"#17A0DB"}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#66A8DD</color>
|
||||
</resources>
|
||||
@@ -1 +1,16 @@
|
||||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<shortcuts xmlns:android='http://schemas.android.com/apk/res/android' />
|
||||
|
||||
3
twa/gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Wed Oct 14 15:52:23 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
"enableNotifications": false,
|
||||
"startUrl": "/",
|
||||
"iconUrl": "https://meet.jit.si/static/pwa/icons/icon512.png",
|
||||
"maskableIconUrl": "https://meet.jit.si/static/pwa/icons/iconMask.png",
|
||||
"splashScreenFadeOutDuration": 300,
|
||||
"appVersionName": "20.5.0",
|
||||
"appVersionCode": 1,
|
||||
"appVersionName": "1.0.0",
|
||||
"appVersionCode": 2000000000,
|
||||
"shortcuts": [],
|
||||
"generatorApp": "bubblewrap-cli",
|
||||
"webManifestUrl": "https://meet.jit.si/static/pwa/manifest.json",
|
||||
"webManifestUrl": "https://meet.jit.si/manifest.json",
|
||||
"fallbackType": "customtabs",
|
||||
"features": {},
|
||||
"enableSiteSettingsShortcut": true,
|
||||
"isChromeOSOnly": true,
|
||||
"appVersion": "20.5.0"
|
||||
"appVersion": "1.0.0"
|
||||
}
|
||||