Compare commits
55 Commits
debug-test
...
8889
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2f7b3be6c | ||
|
|
29fd5df16a | ||
|
|
f324122d93 | ||
|
|
82d4628976 | ||
|
|
8ab02d598c | ||
|
|
5b23072bd0 | ||
|
|
7b4cc552fb | ||
|
|
eb188ff02a | ||
|
|
b40c24db70 | ||
|
|
a855f76377 | ||
|
|
c481e7ede4 | ||
|
|
17b4c2156a | ||
|
|
ca6579e032 | ||
|
|
f3e99624e9 | ||
|
|
3b5c2d9b0b | ||
|
|
bab9ddbb57 | ||
|
|
8d4193ce1e | ||
|
|
7d2cf3dbf2 | ||
|
|
9309d61c00 | ||
|
|
4089702060 | ||
|
|
bf2254c753 | ||
|
|
641b52c51d | ||
|
|
083037d152 | ||
|
|
a3200a172f | ||
|
|
ebff46971d | ||
|
|
657aefefc2 | ||
|
|
683d6eb208 | ||
|
|
a62fa3f833 | ||
|
|
88f1ef27c5 | ||
|
|
95ecf73c71 | ||
|
|
a96908dd7c | ||
|
|
a103b0e5bd | ||
|
|
d67622f6f2 | ||
|
|
ecec65f7af | ||
|
|
447be3f6a9 | ||
|
|
1255b4dcf3 | ||
|
|
47e420f10e | ||
|
|
698aa8db3e | ||
|
|
50bad7bbca | ||
|
|
9d4e6c2d0d | ||
|
|
f3e1fbfdce | ||
|
|
6287c14dd3 | ||
|
|
8a7ee9bae5 | ||
|
|
38677dbe0a | ||
|
|
1900c42098 | ||
|
|
6deb0a6385 | ||
|
|
ce567955f0 | ||
|
|
9d2f1ce8e0 | ||
|
|
841ab8c052 | ||
|
|
3e4f45dc7b | ||
|
|
19cff49ab1 | ||
|
|
a06c3fe715 | ||
|
|
5580301ef7 | ||
|
|
69b0ac4686 | ||
|
|
9f7eb6b657 |
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#17A0DB</color>
|
||||
<color name="navigationBarColor">#161618</color>
|
||||
<color name="navigationBarColor">#040404</color>
|
||||
<color name="statusBarColor">#040404</color>
|
||||
</resources>
|
||||
@@ -3,6 +3,7 @@
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:navigationBarColor">@color/navigationBarColor</item>
|
||||
<item name="android:statusBarColor">@color/statusBarColor</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -73,7 +73,6 @@ dependencies {
|
||||
}
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation project(':react-native-get-random-values')
|
||||
implementation project(':react-native-immersive-mode')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-orientation-locker')
|
||||
implementation project(':react-native-pager-view')
|
||||
|
||||
@@ -62,7 +62,8 @@
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data android:name="org.jitsi.meet.sdk.JitsiInitializer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
|
||||
@@ -96,9 +96,6 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
|
||||
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
|
||||
|
||||
// Only apply if edge-to-edge is supported (API 30+) or enforced (API 35+)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return;
|
||||
|
||||
View decorView = w.getDecorView();
|
||||
|
||||
decorView.post(() -> {
|
||||
@@ -138,7 +135,12 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
JitsiMeetActivityDelegate.onHostResume(this);
|
||||
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
addTopBottomInsets(getWindow(),findViewById(android.R.id.content));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
|
||||
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
addTopBottomInsets(getWindow(), findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
this.jitsiView = findViewById(R.id.jitsiView);
|
||||
|
||||
registerForBroadcastMessages();
|
||||
|
||||
@@ -101,7 +101,6 @@ class ReactInstanceManagerHolder {
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.swmansion.gesturehandler.RNGestureHandlerPackage(),
|
||||
new org.linusu.RNGetRandomValuesPackage(),
|
||||
new com.rnimmersivemode.RNImmersiveModePackage(),
|
||||
new com.swmansion.rnscreens.RNScreensPackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
||||
|
||||
5
android/sdk/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="navigationBarColor">#040404</color>
|
||||
<color name="statusBarColor">#040404</color>
|
||||
</resources>
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
|
||||
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:navigationBarColor">@color/navigationBarColor</item>
|
||||
<item name="android:statusBarColor">@color/statusBarColor</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -24,8 +24,6 @@ include ':react-native-giphy'
|
||||
project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
|
||||
include ':react-native-immersive-mode'
|
||||
project(':react-native-immersive-mode').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive-mode/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/@sayem314/react-native-keep-awake/android')
|
||||
include ':react-native-orientation-locker'
|
||||
|
||||
@@ -805,7 +805,9 @@ var config = {
|
||||
// // By setting preCallTestEnabled, you enable the pre-call test in the prejoin page.
|
||||
// // ICE server credentials need to be provided over the preCallTestICEUrl
|
||||
// preCallTestEnabled: false,
|
||||
// preCallTestICEUrl: ''
|
||||
// preCallTestICEUrl: '',
|
||||
// // Shows the hangup button in the lobby screen.
|
||||
// showHangUp: true,
|
||||
// },
|
||||
|
||||
// When 'true', the user cannot edit the display name.
|
||||
@@ -1605,6 +1607,10 @@ var config = {
|
||||
// audio: true,
|
||||
// video: true
|
||||
// },
|
||||
// // Hides the visitor count for visitors.
|
||||
// // hideVisitorCountForVisitors: false,
|
||||
// // Whether to show the join meeting dialog when joining as a visitor.
|
||||
// // showJoinMeetingDialog: true,
|
||||
// },
|
||||
// The default type of desktop sharing sources that will be used in the electron app.
|
||||
// desktopSharingSources: ['screen', 'window'],
|
||||
|
||||
@@ -44,7 +44,6 @@ target 'JitsiMeetSDK' do
|
||||
pod 'giphy-react-native-sdk', :path => '../node_modules/@giphy/react-native-sdk'
|
||||
pod 'RNCalendarEvents', :path => '../node_modules/react-native-calendar-events'
|
||||
pod 'RNGoogleSignin', :path => '../node_modules/@react-native-google-signin/google-signin'
|
||||
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
|
||||
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
@@ -1473,7 +1473,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-webrtc (124.0.4):
|
||||
- react-native-webrtc (124.0.7):
|
||||
- JitsiWebRTC (~> 124.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (13.13.5):
|
||||
@@ -1870,8 +1870,6 @@ PODS:
|
||||
- React-Core
|
||||
- RNSVG (15.11.2):
|
||||
- React-Core
|
||||
- RNWatch (1.1.0):
|
||||
- React
|
||||
- SocketRocket (0.7.1)
|
||||
- SplashView (0.0.18):
|
||||
- DoubleConversion
|
||||
@@ -1993,7 +1991,6 @@ DEPENDENCIES:
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- RNSound (from `../node_modules/react-native-sound`)
|
||||
- RNSVG (from `../node_modules/react-native-svg`)
|
||||
- RNWatch (from `../node_modules/react-native-watch-connectivity`)
|
||||
- SplashView (from `../node_modules/react-native-splash-view`)
|
||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
@@ -2203,8 +2200,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-sound"
|
||||
RNSVG:
|
||||
:path: "../node_modules/react-native-svg"
|
||||
RNWatch:
|
||||
:path: "../node_modules/react-native-watch-connectivity"
|
||||
SplashView:
|
||||
:path: "../node_modules/react-native-splash-view"
|
||||
Yoga:
|
||||
@@ -2279,7 +2274,7 @@ SPEC CHECKSUMS:
|
||||
react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1
|
||||
react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb
|
||||
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
|
||||
react-native-webrtc: 2261a482150195092246fe70b3aff976f2e11ec5
|
||||
react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74
|
||||
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
|
||||
react-native-worklets-core: b59cf88762c8fb6132d8796babd4cec15217d6f0
|
||||
React-nativeconfig: ecf4dc92c40b97e2b3f0c619938f78bfd6507b08
|
||||
@@ -2321,11 +2316,10 @@ SPEC CHECKSUMS:
|
||||
RNScreens: 9ef996b6041d0960a4794a845f7d0808b171b4ef
|
||||
RNSound: 314cc5226453ef4a3314a196c65e8a65e5106a7b
|
||||
RNSVG: 67de7abef81f367387b708ba6d2acefe7d4f5895
|
||||
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
SplashView: ed71a114c3ffe60dc3a9e5aa2cefb352c3794a70
|
||||
Yoga: 31a098f74c16780569aebd614a0f37a907de0189
|
||||
|
||||
PODFILE CHECKSUM: eac4bba07b2f30174fc20bccbaf64f86676d9b1f
|
||||
PODFILE CHECKSUM: 7c37a89916893e11159576c8b308b7b5c25246c9
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -7,16 +7,6 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */; };
|
||||
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B70016F1F7C51CC005944F4 /* InCallController.swift */; };
|
||||
0BEA5C291F7B8F73000D0AB4 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */; };
|
||||
0BEA5C2B1F7B8F73000D0AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C2A1F7B8F73000D0AB4 /* Assets.xcassets */; };
|
||||
0BEA5C321F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0BEA5C371F7B8F73000D0AB4 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BEA5C361F7B8F73000D0AB4 /* InterfaceController.swift */; };
|
||||
0BEA5C391F7B8F73000D0AB4 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BEA5C381F7B8F73000D0AB4 /* ExtensionDelegate.swift */; };
|
||||
0BEA5C3B1F7B8F73000D0AB4 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BEA5C3A1F7B8F73000D0AB4 /* ComplicationController.swift */; };
|
||||
0BEA5C3D1F7B8F73000D0AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C3C1F7B8F73000D0AB4 /* Assets.xcassets */; };
|
||||
0BEA5C411F7B8F73000D0AB4 /* JitsiMeetCompanion.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.storyboard */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */; };
|
||||
@@ -36,27 +26,11 @@
|
||||
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
DED016F128ECBC9D009D5E8D /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DED016F028ECBC9D009D5E8D /* WebRTC.xcframework */; };
|
||||
DED016F228ECBC9D009D5E8D /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DED016F028ECBC9D009D5E8D /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
|
||||
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
|
||||
FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; };
|
||||
FD572B9927EDF32300A800FB /* GiphyUISDK.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
0BEA5C331F7B8F73000D0AB4 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0BEA5C301F7B8F73000D0AB4;
|
||||
remoteInfo = "JitsiMeetCompanion Extension";
|
||||
};
|
||||
0BEA5C3F1F7B8F73000D0AB4 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0BEA5C241F7B8F73000D0AB4;
|
||||
remoteInfo = JitsiMeetCompanion;
|
||||
};
|
||||
4EB06029260E026600F524C5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
@@ -81,24 +55,12 @@
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0BEA5C471F7B8F73000D0AB4 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
0BEA5C321F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
0BEA5C411F7B8F73000D0AB4 /* JitsiMeetCompanion.app in Embed Watch Content */,
|
||||
);
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -118,19 +80,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingRowController.swift; sourceTree = "<group>"; };
|
||||
0B70016F1F7C51CC005944F4 /* InCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCallController.swift; sourceTree = "<group>"; };
|
||||
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
|
||||
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JitsiMeetCompanion.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0BEA5C281F7B8F73000D0AB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; };
|
||||
0BEA5C2A1F7B8F73000D0AB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
0BEA5C2C1F7B8F73000D0AB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "JitsiMeetCompanion Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0BEA5C361F7B8F73000D0AB4 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = "<group>"; };
|
||||
0BEA5C381F7B8F73000D0AB4 /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = "<group>"; };
|
||||
0BEA5C3A1F7B8F73000D0AB4 /* ComplicationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = "<group>"; };
|
||||
0BEA5C3C1F7B8F73000D0AB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
0BEA5C3E1F7B8F73000D0AB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = src/Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
@@ -156,19 +106,10 @@
|
||||
DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JitsiMeetSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DED016F028ECBC9D009D5E8D /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = ../Pods/JitsiWebRTC/WebRTC.xcframework; sourceTree = "<group>"; };
|
||||
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
|
||||
E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
|
||||
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
|
||||
FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:925PGC4MV7:Giphy, Inc."; lastKnownFileType = wrapper.xcframework; name = GiphyUISDK.xcframework; path = ../Pods/Giphy/GiphySDK/GiphyUISDK.xcframework; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0BEA5C2E1F7B8F73000D0AB4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -181,13 +122,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1F021A8A5B056078665DE530 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EB06020260E026600F524C5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -216,34 +150,6 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0BEA5C261F7B8F73000D0AB4 /* Watch app */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */,
|
||||
0BEA5C2A1F7B8F73000D0AB4 /* Assets.xcassets */,
|
||||
0BEA5C2C1F7B8F73000D0AB4 /* Info.plist */,
|
||||
);
|
||||
name = "Watch app";
|
||||
path = watchos/app;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BEA5C361F7B8F73000D0AB4 /* InterfaceController.swift */,
|
||||
0BEA5C381F7B8F73000D0AB4 /* ExtensionDelegate.swift */,
|
||||
0BEA5C3A1F7B8F73000D0AB4 /* ComplicationController.swift */,
|
||||
0BEA5C3C1F7B8F73000D0AB4 /* Assets.xcassets */,
|
||||
0BEA5C3E1F7B8F73000D0AB4 /* Info.plist */,
|
||||
0B70016F1F7C51CC005944F4 /* InCallController.swift */,
|
||||
0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */,
|
||||
E58801132278944E008B0561 /* JitsiMeetContext.swift */,
|
||||
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */,
|
||||
);
|
||||
name = "WatchKit extension";
|
||||
path = watchos/extension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -279,11 +185,10 @@
|
||||
0B26BE711EC5BC4D00EEFB41 /* Frameworks */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
13B07FAE1A68108700A75B9A /* src */,
|
||||
0BEA5C261F7B8F73000D0AB4 /* Watch app */,
|
||||
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
|
||||
4EB06025260E026600F524C5 /* JitsiMeetBroadcast Extension */,
|
||||
CDD71F5E1157E9F283DF92A8 /* Pods */,
|
||||
5C1BE20ECD5DEEB48FED90B5 /* PrivacyInfo.xcprivacy */,
|
||||
DEAC44E22E97D2C200AD7BEE /* Recovered References */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@@ -293,8 +198,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
|
||||
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
|
||||
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
|
||||
4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
@@ -310,44 +213,17 @@
|
||||
path = ../Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DEAC44E22E97D2C200AD7BEE /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.storyboard */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0BEA5C481F7B8F73000D0AB4 /* Build configuration list for PBXNativeTarget "JitsiMeetCompanion" */;
|
||||
buildPhases = (
|
||||
0BEA5C231F7B8F73000D0AB4 /* Resources */,
|
||||
0BEA5C471F7B8F73000D0AB4 /* Embed App Extensions */,
|
||||
1F021A8A5B056078665DE530 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0BEA5C341F7B8F73000D0AB4 /* PBXTargetDependency */,
|
||||
);
|
||||
name = JitsiMeetCompanion;
|
||||
productName = JitsiMeetCompanion;
|
||||
productReference = 0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */;
|
||||
productType = "com.apple.product-type.application.watchapp2";
|
||||
};
|
||||
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0BEA5C461F7B8F73000D0AB4 /* Build configuration list for PBXNativeTarget "JitsiMeetCompanion Extension" */;
|
||||
buildPhases = (
|
||||
0BEA5C2D1F7B8F73000D0AB4 /* Sources */,
|
||||
0BEA5C2E1F7B8F73000D0AB4 /* Frameworks */,
|
||||
0BEA5C2F1F7B8F73000D0AB4 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "JitsiMeetCompanion Extension";
|
||||
productName = "JitsiMeetCompanion Extension";
|
||||
productReference = 0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */;
|
||||
productType = "com.apple.product-type.watchkit2-extension";
|
||||
};
|
||||
13B07F861A680F5B00A75B9A /* JitsiMeet */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
|
||||
@@ -369,7 +245,6 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
|
||||
4EB0602A260E026600F524C5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = JitsiMeet;
|
||||
@@ -404,16 +279,6 @@
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = Jitsi;
|
||||
TargetAttributes = {
|
||||
0BEA5C241F7B8F73000D0AB4 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
DevelopmentTeam = FC967L3QRG;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
0BEA5C301F7B8F73000D0AB4 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
DevelopmentTeam = FC967L3QRG;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1620;
|
||||
SystemCapabilities = {
|
||||
@@ -444,31 +309,12 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* JitsiMeet */,
|
||||
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
|
||||
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
|
||||
4EB06022260E026600F524C5 /* JitsiMeetBroadcastExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
0BEA5C231F7B8F73000D0AB4 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BEA5C2B1F7B8F73000D0AB4 /* Assets.xcassets in Resources */,
|
||||
0BEA5C291F7B8F73000D0AB4 /* Interface.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0BEA5C2F1F7B8F73000D0AB4 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BEA5C3D1F7B8F73000D0AB4 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -622,20 +468,6 @@
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
0BEA5C2D1F7B8F73000D0AB4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */,
|
||||
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */,
|
||||
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */,
|
||||
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */,
|
||||
0BEA5C391F7B8F73000D0AB4 /* ExtensionDelegate.swift in Sources */,
|
||||
0BEA5C371F7B8F73000D0AB4 /* InterfaceController.swift in Sources */,
|
||||
0BEA5C3B1F7B8F73000D0AB4 /* ComplicationController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -660,16 +492,6 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
0BEA5C341F7B8F73000D0AB4 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */;
|
||||
targetProxy = 0BEA5C331F7B8F73000D0AB4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */;
|
||||
targetProxy = 0BEA5C3F1F7B8F73000D0AB4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
4EB0602A260E026600F524C5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4EB06022260E026600F524C5 /* JitsiMeetBroadcastExtension */;
|
||||
@@ -678,14 +500,6 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
0BEA5C281F7B8F73000D0AB4 /* Base */,
|
||||
);
|
||||
name = Interface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -697,155 +511,10 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
0BEA5C421F7B8F73000D0AB4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
IBSC_MODULE = JitsiMeetCompanion_Extension;
|
||||
INFOPLIST_FILE = watchos/app/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0BEA5C431F7B8F73000D0AB4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
IBSC_MODULE = JitsiMeetCompanion_Extension;
|
||||
INFOPLIST_FILE = watchos/app/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0BEA5C441F7B8F73000D0AB4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = watchos/extension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0BEA5C451F7B8F73000D0AB4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = watchos/extension/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 756FCE06C08D9B947653C98A /* Pods-JitsiMeet.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
@@ -882,7 +551,6 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 3E0F4ED943C0B12BE77F6B45 /* Pods-JitsiMeet.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
@@ -1131,24 +799,6 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
0BEA5C461F7B8F73000D0AB4 /* Build configuration list for PBXNativeTarget "JitsiMeetCompanion Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0BEA5C441F7B8F73000D0AB4 /* Debug */,
|
||||
0BEA5C451F7B8F73000D0AB4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0BEA5C481F7B8F73000D0AB4 /* Build configuration list for PBXNativeTarget "JitsiMeetCompanion" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0BEA5C421F7B8F73000D0AB4 /* Debug */,
|
||||
0BEA5C431F7B8F73000D0AB4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "JitsiMeet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "24x24",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-24@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "notificationCenter",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "27.5x27.5",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-27.5@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "notificationCenter",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-29@2x.png",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-29@3x.png",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-40@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "appLauncher",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "44x44",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-88@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "appLauncher",
|
||||
"subtype" : "40mm"
|
||||
},
|
||||
{
|
||||
"size" : "50x50",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-100@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "appLauncher",
|
||||
"subtype" : "44mm"
|
||||
},
|
||||
{
|
||||
"size" : "86x86",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-86@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "quickLook",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "98x98",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-98@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "quickLook",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"size" : "108x108",
|
||||
"idiom" : "watch",
|
||||
"filename" : "Icon-216@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "quickLook",
|
||||
"subtype" : "44mm"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "watch-marketing",
|
||||
"filename" : "Icon-1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "hangup@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "mute-off@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "mute-on@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,85 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="14490.70" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="AgC-eL-Hgc">
|
||||
<device id="watch38" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="watchOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="14490.21"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Meetings-->
|
||||
<scene sceneID="aou-V4-d1y">
|
||||
<objects>
|
||||
<controller title="Meetings" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="JitsiMeetCompanion" customModuleProvider="target">
|
||||
<items>
|
||||
<label alignment="left" textAlignment="left" numberOfLines="0" id="OQN-sx-tDt"/>
|
||||
<table alignment="left" id="gpO-ql-Xsr">
|
||||
<items>
|
||||
<tableRow identifier="MeetingRowType" id="GGl-av-xeJ" customClass="MeetingRowController" customModule="JitsiMeetCompanion_Extension">
|
||||
<group key="rootItem" width="1" height="0.0" alignment="left" layout="vertical" id="5XE-gq-qzG">
|
||||
<items>
|
||||
<label alignment="left" text="Label" id="Sij-up-N4p"/>
|
||||
<label alignment="left" text="Label" id="V5K-sm-jEH">
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="font" style="UICTFontTextStyleFootnote"/>
|
||||
</label>
|
||||
</items>
|
||||
<connections>
|
||||
<segue destination="9RD-qP-1Z0" kind="push" id="Boa-6E-eZs"/>
|
||||
</connections>
|
||||
</group>
|
||||
<connections>
|
||||
<outlet property="roomLabel" destination="Sij-up-N4p" id="PdS-SO-ylc"/>
|
||||
<outlet property="rowGroup" destination="5XE-gq-qzG" id="GZN-2c-2Gz"/>
|
||||
<outlet property="timeLabel" destination="V5K-sm-jEH" id="fWQ-kx-vE4"/>
|
||||
</connections>
|
||||
</tableRow>
|
||||
</items>
|
||||
</table>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="infoLabel" destination="OQN-sx-tDt" id="Juv-tb-SNj"/>
|
||||
<outlet property="table" destination="gpO-ql-Xsr" id="aVV-iZ-z3l"/>
|
||||
</connections>
|
||||
</controller>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-99" y="117"/>
|
||||
</scene>
|
||||
<!--Meetings-->
|
||||
<scene sceneID="ns4-Kh-qqU">
|
||||
<objects>
|
||||
<controller identifier="InCallController" title="Meetings" hidesWhenLoading="NO" id="9RD-qP-1Z0" customClass="InCallController" customModule="JitsiMeetCompanion" customModuleProvider="target">
|
||||
<items>
|
||||
<label alignment="center" text="Label" id="vFt-lL-SNY"/>
|
||||
<timer alignment="center" textAlignment="center" previewedSeconds="0" id="W8S-uZ-MPm">
|
||||
<color key="textColor" red="0.024725984125768763" green="1" blue="0.24241188365329402" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="font" style="UICTFontTextStyleHeadline"/>
|
||||
</timer>
|
||||
<group alignment="center" verticalAlignment="bottom" spacing="10" id="Hfk-a0-uWj">
|
||||
<items>
|
||||
<button width="60" height="60" alignment="left" verticalAlignment="bottom" backgroundImage="hangup" id="8jF-SI-UHz">
|
||||
<connections>
|
||||
<action selector="hangupClicked" destination="9RD-qP-1Z0" id="cXK-lw-tsd"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button width="60" height="60" alignment="right" verticalAlignment="bottom" backgroundImage="mute-off" id="LmN-FI-aQq">
|
||||
<connections>
|
||||
<action selector="muteClicked" destination="9RD-qP-1Z0" id="dJg-kV-cqH"/>
|
||||
</connections>
|
||||
</button>
|
||||
</items>
|
||||
</group>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="mutedButton" destination="LmN-FI-aQq" id="gfi-4T-gdN"/>
|
||||
<outlet property="roomLabel" destination="vFt-lL-SNY" id="cYB-Tf-Efz"/>
|
||||
<outlet property="timer" destination="W8S-uZ-MPm" id="r7T-j1-9VJ"/>
|
||||
</connections>
|
||||
</controller>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="213" y="117"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Jitsi Meet</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>org.jitsi.meet</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "jitsi@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import ClockKit
|
||||
|
||||
|
||||
class ComplicationController: NSObject, CLKComplicationDataSource {
|
||||
|
||||
// MARK: - Timeline Configuration
|
||||
|
||||
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
|
||||
handler([])
|
||||
}
|
||||
|
||||
func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
|
||||
handler(.showOnLockScreen)
|
||||
}
|
||||
|
||||
// MARK: - Timeline Population
|
||||
|
||||
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
|
||||
// Call the handler with the current timeline entry
|
||||
getLocalizableSampleTemplate(for: complication) {template in
|
||||
guard let template = template else {
|
||||
handler(nil)
|
||||
return
|
||||
}
|
||||
handler(CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template))
|
||||
}
|
||||
}
|
||||
|
||||
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
|
||||
// Call the handler with the timeline entries prior to the given date
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
|
||||
// Call the handler with the timeline entries after to the given date
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
// MARK: - Placeholder Templates
|
||||
|
||||
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
|
||||
// This method will be called once per supported complication, and the results will be cached
|
||||
|
||||
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "jitsi")!)
|
||||
if complication.family == .circularSmall {
|
||||
let small = CLKComplicationTemplateCircularSmallRingImage()
|
||||
small.imageProvider = imageProvider
|
||||
small.ringStyle = .closed
|
||||
small.fillFraction = 0
|
||||
handler(small)
|
||||
} else if complication.family == .utilitarianSmall {
|
||||
let utilitarian = CLKComplicationTemplateUtilitarianSmallSquare()
|
||||
utilitarian.imageProvider = imageProvider
|
||||
handler(utilitarian)
|
||||
} else if complication.family == .modularSmall {
|
||||
let modular = CLKComplicationTemplateModularSmallRingImage()
|
||||
modular.imageProvider = imageProvider
|
||||
modular.ringStyle = .closed
|
||||
modular.fillFraction = 0
|
||||
handler(modular)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import WatchConnectivity
|
||||
import WatchKit
|
||||
|
||||
class ExtensionDelegate: NSObject, WCSessionDelegate, WKExtensionDelegate {
|
||||
|
||||
var currentContext : JitsiMeetContext = JitsiMeetContext()
|
||||
|
||||
static var currentJitsiMeetContext: JitsiMeetContext {
|
||||
get {
|
||||
return (WKExtension.shared().delegate as! ExtensionDelegate).currentContext
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching() {
|
||||
// Start Watch Connectivity
|
||||
if WCSession.isSupported() {
|
||||
let session = WCSession.default
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
}
|
||||
|
||||
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
|
||||
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
|
||||
for task in backgroundTasks {
|
||||
// Use a switch statement to check the task type
|
||||
switch task {
|
||||
case let backgroundTask as WKApplicationRefreshBackgroundTask:
|
||||
// Be sure to complete the background task once you’re done.
|
||||
backgroundTask.setTaskCompletedWithSnapshot(false)
|
||||
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
|
||||
// Snapshot tasks have a unique completion call, make sure to set your expiration date
|
||||
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
|
||||
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
|
||||
// Be sure to complete the connectivity task once you’re done.
|
||||
connectivityTask.setTaskCompletedWithSnapshot(false)
|
||||
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
|
||||
// Be sure to complete the URL session task once you’re done.
|
||||
urlSessionTask.setTaskCompletedWithSnapshot(false)
|
||||
default:
|
||||
// make sure to complete unhandled task types
|
||||
task.setTaskCompletedWithSnapshot(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith
|
||||
activationState: WCSessionActivationState, error: Error?) {
|
||||
if let error = error {
|
||||
print("WATCH Session activation failed with error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
print("WATCH Session activated with state: \(activationState.rawValue)")
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
|
||||
DispatchQueue.main.async {
|
||||
let newContext = JitsiMeetContext(context: applicationContext)
|
||||
|
||||
print("WATCH got new context: \(newContext.description)");
|
||||
|
||||
// Update context on the root controller which displays the recent list
|
||||
let controller = WKExtension.shared().rootInterfaceController as! InterfaceController
|
||||
controller.updateUI(newContext)
|
||||
|
||||
// If the current controller is not the in-call controller and we have a
|
||||
// conference URL, show the in-call controller
|
||||
if let currentController = WKExtension.shared().visibleInterfaceController as? InterfaceController {
|
||||
// Go to the in-call controller only if the conference URL has changed, because the user may have
|
||||
// clicked the back button
|
||||
if newContext.conferenceURL != nil
|
||||
&& self.currentContext.conferenceURL != newContext.conferenceURL {
|
||||
currentController.pushController(withName: "InCallController", context: newContext)
|
||||
}
|
||||
} else if let inCallController = WKExtension.shared().visibleInterfaceController as? InCallController {
|
||||
if newContext.conferenceURL == nil {
|
||||
inCallController.popToRootController()
|
||||
} else {
|
||||
inCallController.updateUI(newContext)
|
||||
}
|
||||
}
|
||||
|
||||
self.currentContext = newContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import WatchConnectivity
|
||||
import WatchKit
|
||||
import Foundation
|
||||
|
||||
|
||||
class InCallController: WKInterfaceController {
|
||||
@IBOutlet var mutedButton: WKInterfaceButton!
|
||||
@IBOutlet var roomLabel: WKInterfaceLabel!
|
||||
@IBOutlet var timer: WKInterfaceTimer!
|
||||
|
||||
@IBAction func hangupClicked() {
|
||||
sendCommand(JitsiMeetCommands.CMD_HANG_UP, message: nil)
|
||||
}
|
||||
|
||||
@IBAction func muteClicked() {
|
||||
if var micMuted = ExtensionDelegate.currentJitsiMeetContext.micMuted {
|
||||
micMuted = !micMuted;
|
||||
sendCommand(
|
||||
JitsiMeetCommands.CMD_SET_MUTED,
|
||||
message: [
|
||||
"muted": micMuted ? "true" : "false"
|
||||
])
|
||||
updateMutedButton(withMuted: micMuted)
|
||||
}
|
||||
}
|
||||
|
||||
func sendCommand(_ command: JitsiMeetCommands, message: [String : Any]?) {
|
||||
if WCSession.isSupported() {
|
||||
let session = WCSession.default
|
||||
var data = [String: Any]()
|
||||
|
||||
if let sessionID = ExtensionDelegate.currentJitsiMeetContext.sessionID {
|
||||
if message != nil {
|
||||
message!.forEach { data[$0] = $1 }
|
||||
}
|
||||
|
||||
data["command"] = command.rawValue;
|
||||
data["sessionID"] = sessionID;
|
||||
|
||||
session.sendMessage(data, replyHandler: nil, errorHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateUI(_ newContext: JitsiMeetContext) {
|
||||
var conferenceURL = newContext.conferenceURL
|
||||
|
||||
if let joinConferenceURL = newContext.joinConferenceURL {
|
||||
sendCommand(JitsiMeetCommands.CMD_JOIN_CONFERENCE, message: [ "data" : joinConferenceURL ])
|
||||
conferenceURL = joinConferenceURL
|
||||
}
|
||||
|
||||
let newRoomName = conferenceURL != nil ? conferenceURL!.components(separatedBy: "/").last : ""
|
||||
|
||||
roomLabel.setText(newRoomName)
|
||||
|
||||
if let newTimestamp = newContext.conferenceTimestamp {
|
||||
restartTimer(newTimestamp)
|
||||
}
|
||||
if let newMuted = newContext.micMuted {
|
||||
updateMutedButton(withMuted: newMuted)
|
||||
}
|
||||
}
|
||||
|
||||
func restartTimer(_ conferenceTimestamp: Int64) {
|
||||
if (conferenceTimestamp != 0) {
|
||||
let newDate = Date(timeIntervalSince1970: TimeInterval(conferenceTimestamp / 1000))
|
||||
timer.setDate(newDate)
|
||||
timer.start();
|
||||
print("WATCH timer set date to: \(newDate) and start")
|
||||
} else {
|
||||
print("WATCH timer stop")
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
func updateMutedButton(withMuted isMuted: Bool) {
|
||||
if isMuted {
|
||||
mutedButton.setBackgroundImageNamed("mute-on.png")
|
||||
} else {
|
||||
mutedButton.setBackgroundImageNamed("mute-off.png")
|
||||
}
|
||||
}
|
||||
|
||||
override func awake(withContext context: Any?) {
|
||||
super.awake(withContext: context)
|
||||
|
||||
if let data = context as? JitsiMeetContext {
|
||||
updateUI(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Jitsi Meet Companion Extension</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
|
||||
<key>CLKComplicationSupportedFamilies</key>
|
||||
<array>
|
||||
<string>CLKComplicationFamilyModularSmall</string>
|
||||
<string>CLKComplicationFamilyUtilitarianSmall</string>
|
||||
<string>CLKComplicationFamilyCircularSmall</string>
|
||||
</array>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>WKAppBundleIdentifier</key>
|
||||
<string>org.jitsi.meet.watchkit</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
<key>WKExtensionDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import WatchKit
|
||||
import WatchConnectivity
|
||||
import Foundation
|
||||
|
||||
|
||||
class InterfaceController: WKInterfaceController {
|
||||
|
||||
@IBOutlet var infoLabel: WKInterfaceLabel!
|
||||
@IBOutlet var table: WKInterfaceTable!
|
||||
|
||||
override func didAppear(){
|
||||
self.updateUI(ExtensionDelegate.currentJitsiMeetContext)
|
||||
}
|
||||
|
||||
func updateUI(_ newContext:JitsiMeetContext) {
|
||||
if (newContext.recentURLs == nil || newContext.recentURLs!.count == 0) {
|
||||
infoLabel.setText("There are no recent meetings. Please use the app on the phone to start a new call.")
|
||||
|
||||
table.setHidden(true)
|
||||
infoLabel.setHidden(false)
|
||||
} else {
|
||||
updateRecents(withRecents: newContext.recentURLs!, currentContext: newContext)
|
||||
|
||||
table.setHidden(false)
|
||||
infoLabel.setHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRecents(withRecents recents: NSArray, currentContext: JitsiMeetContext) {
|
||||
// Updating the # of rows only if it actually changed prevents from blinking the UI
|
||||
if (table.numberOfRows != recents.count) {
|
||||
table.setNumberOfRows(recents.count, withRowType: "MeetingRowType")
|
||||
}
|
||||
|
||||
for (index, entry) in recents.enumerated() {
|
||||
let entryDict = entry as! NSDictionary
|
||||
let roomURL = entryDict["conference"] as! NSString
|
||||
let timestamp = entryDict["date"] as! NSNumber
|
||||
|
||||
// Prepare values
|
||||
let room = roomURL.components(separatedBy: "/").last
|
||||
let date = Date(timeIntervalSince1970: timestamp.doubleValue / 1000) // timestamp is taken with Date.now() in JS, which uses milliseconds
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone.current
|
||||
dateFormatter.locale = NSLocale.current
|
||||
dateFormatter.dateFormat = "HH:mm yyyy-MM-dd"
|
||||
let strDate = dateFormatter.string(from: date)
|
||||
|
||||
// Update row controller
|
||||
let controller = table.rowController(at: index) as! MeetingRowController
|
||||
controller.room = room
|
||||
controller.roomUrl = roomURL as String
|
||||
controller.roomLabel.setText(room)
|
||||
controller.timeLabel.setText(strDate)
|
||||
|
||||
// Change the background for the active meeting
|
||||
if (controller.roomUrl == currentContext.conferenceURL) {
|
||||
controller.rowGroup.setBackgroundColor(UIColor(red: 0.125, green: 0.58, blue: 0.98, alpha: 1))
|
||||
} else {
|
||||
controller.rowGroup.setBackgroundColor(UIColor(red: 0.949, green: 0.956, blue: 1, alpha: 0.14))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String, in table: WKInterfaceTable, rowIndex: Int) -> Any? {
|
||||
let controller = table.rowController(at: rowIndex) as! MeetingRowController
|
||||
let currentContext = ExtensionDelegate.currentJitsiMeetContext
|
||||
|
||||
// Copy the current context and add the joinConferenceURL to trigger the command when the in-call screen is displayed
|
||||
let actionContext = JitsiMeetContext(jmContext: currentContext)
|
||||
actionContext.joinConferenceURL = controller.roomUrl
|
||||
|
||||
print("WATCH contextForSegue: \(actionContext.description)");
|
||||
|
||||
return actionContext;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This needs to be in sync with features/mobile/watchos/constants.js
|
||||
enum JitsiMeetCommands : String {
|
||||
typealias RawValue = String
|
||||
|
||||
case CMD_HANG_UP = "hangup";
|
||||
|
||||
case CMD_JOIN_CONFERENCE = "joinConference";
|
||||
|
||||
case CMD_SET_MUTED = "setMuted";
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class JitsiMeetContext {
|
||||
private var dictionary : [String : Any]
|
||||
|
||||
var joinConferenceURL : String? = nil;
|
||||
|
||||
init() {
|
||||
dictionary = [:]
|
||||
}
|
||||
|
||||
init(context: [String : Any]) {
|
||||
dictionary = context
|
||||
}
|
||||
|
||||
init(jmContext: JitsiMeetContext) {
|
||||
dictionary = jmContext.dictionary
|
||||
joinConferenceURL = jmContext.joinConferenceURL
|
||||
}
|
||||
|
||||
var conferenceURL : String? {
|
||||
get {
|
||||
return dictionary["conferenceURL"] as? String
|
||||
}
|
||||
}
|
||||
|
||||
var conferenceTimestamp : Int64? {
|
||||
get {
|
||||
return dictionary["conferenceTimestamp"] as? Int64;
|
||||
}
|
||||
}
|
||||
|
||||
var sessionID : Int64? {
|
||||
get {
|
||||
return dictionary["sessionID"] as? Int64;
|
||||
}
|
||||
}
|
||||
|
||||
var recentURLs : NSArray? {
|
||||
get {
|
||||
return dictionary["recentURLs"] as? NSArray
|
||||
}
|
||||
}
|
||||
|
||||
var micMuted : Bool? {
|
||||
get {
|
||||
return (dictionary["micMuted"] as? NSNumber)?.boolValue ?? nil;
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "JitsiMeetContext[conferenceURL: \(String(describing: conferenceURL)), conferenceTimestamp: \(String(describing:conferenceTimestamp)), sessionID: \(String(describing:sessionID)), recentURLs: \(String(describing:recentURLs)), joinConferenceURL: \(String(describing:joinConferenceURL)) "
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import WatchKit
|
||||
|
||||
class MeetingRowController: NSObject {
|
||||
@IBOutlet var roomLabel: WKInterfaceLabel!
|
||||
@IBOutlet var timeLabel: WKInterfaceLabel!
|
||||
@IBOutlet var rowGroup: WKInterfaceGroup!
|
||||
|
||||
var room: String!
|
||||
var roomUrl: String!
|
||||
}
|
||||
@@ -39,36 +39,6 @@ platform :ios do
|
||||
end
|
||||
)
|
||||
|
||||
# Set the (watch) app identifier
|
||||
update_app_identifier(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "watchos/app/Info.plist",
|
||||
app_identifier: "com.atlassian.JitsiMeet.ios.watchkit"
|
||||
)
|
||||
|
||||
# Set the (watch) extension identifier
|
||||
update_app_identifier(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "watchos/extension/Info.plist",
|
||||
app_identifier: "com.atlassian.JitsiMeet.ios.watchkit.extension"
|
||||
)
|
||||
|
||||
update_info_plist(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "watchos/app/Info.plist",
|
||||
block: proc do |plist|
|
||||
plist["WKCompanionAppBundleIdentifier"] = "com.atlassian.JitsiMeet.ios"
|
||||
end
|
||||
)
|
||||
|
||||
update_info_plist(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "watchos/extension/Info.plist",
|
||||
block: proc do |plist|
|
||||
plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = "com.atlassian.JitsiMeet.ios.watchkit"
|
||||
end
|
||||
)
|
||||
|
||||
# Increment the build number by 1
|
||||
increment_build_number(
|
||||
build_number: Time.now.to_i,
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# The script is based on tutorial written by Antonis Tsakiridis published at:
|
||||
# https://medium.com/@atsakiridis/continuous-deployment-for-ios-using-travis-ci-55dcea342d9
|
||||
#
|
||||
# It is intended to be executed through the Travis CI REST API call, as it
|
||||
# requires few arguments which are mandatory with no default values provided:
|
||||
# PR_REPO_SLUG - the Github name of the repo to be merged into the origin/master
|
||||
# PR_BRANCH - the branch to be merged, if set to "master" no merge will happen
|
||||
# IPA_DEPLOY_LOCATION - the location understandable by the "scp" command
|
||||
# executed at the end of the script to deploy the output .ipa file
|
||||
# LIB_JITSI_MEET_PKG (optional) - the npm package for lib-jitsi-meet which will
|
||||
# be put in place of the current version in the package.json file.
|
||||
#
|
||||
# Other than that the script requires the following env variables to be set
|
||||
# (reading the tutorial mentioned above will help in understanding the
|
||||
# variables):
|
||||
#
|
||||
# APPLE_CERT_URL - the URL pointing to Apple certificate (set to
|
||||
# http://developer.apple.com/certificationauthority/AppleWWDRCA.cer by default)
|
||||
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
|
||||
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
|
||||
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
|
||||
# in the script.
|
||||
# IOS_DEV_CERT_KEY_URL - URL pointing to provisioning profile certificate key
|
||||
# file (development-key.p12.enc from the tutorial) encrypted with the
|
||||
# $ENCRYPTION_PASSWORD.
|
||||
# IOS_DEV_CERT_URL - URL pointing to provisioning profile certificate file
|
||||
# (development-cert.cer.enc from the tutorial) encrypted with the
|
||||
# $ENCRYPTION_PASSWORD.
|
||||
# IOS_DEV_PROV_PROFILE_URL - URL pointing to provisioning profile file
|
||||
# (profile-development-olympus.mobileprovision.enc from the tutorial) encrypted
|
||||
# with the $ENCRYPTION_PASSWORD.
|
||||
# IOS_SIGNING_CERT_PASSWORD - the password to the provisioning profile
|
||||
# certificate key (used to open development-key.p12 from the tutorial).
|
||||
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
|
||||
# place of "YOUR_TEAM_ID".
|
||||
|
||||
|
||||
# Travis will not print the last echo if there's no sleep 1
|
||||
function echoSleepAndExit1() {
|
||||
echo $1
|
||||
sleep 1
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "TRAVIS_BRANCH=${TRAVIS_BRANCH}"
|
||||
echo "TRAVIS_REPO_SLUG=${TRAVIS_REPO_SLUG}"
|
||||
|
||||
if [ -z $PR_REPO_SLUG ]; then
|
||||
echoSleepAndExit1 "No PR_REPO_SLUG defined"
|
||||
fi
|
||||
if [ -z $PR_BRANCH ]; then
|
||||
echoSleepAndExit1 "No PR_BRANCH defined"
|
||||
fi
|
||||
if [ -z $IPA_DEPLOY_LOCATION ]; then
|
||||
echoSleepAndExit1 "No IPA_DEPLOY_LOCATION defined"
|
||||
fi
|
||||
|
||||
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
|
||||
|
||||
# do the merge and git log
|
||||
|
||||
if [ $PR_BRANCH != "master" ]; then
|
||||
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"
|
||||
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
git pull https://github.com/${PR_REPO_SLUG}.git $PR_BRANCH --no-edit
|
||||
fi
|
||||
|
||||
# Link this lib-jitsi-meet checkout in jitsi-meet through the package.json
|
||||
if [ ! -z ${LIB_JITSI_MEET_PKG} ];
|
||||
then
|
||||
echo "Adjusting lib-jitsi-meet package in package.json to ${LIB_JITSI_MEET_PKG}"
|
||||
# escape for the sed
|
||||
LIB_JITSI_MEET_PKG=$(echo $LIB_JITSI_MEET_PKG | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')
|
||||
sed -i.bak -e "s/\"lib-jitsi-meet.*/\"lib-jitsi-meet\"\: \"${LIB_JITSI_MEET_PKG}\",/g" package.json
|
||||
echo "Package.json lib-jitsi-meet line:"
|
||||
grep lib-jitsi-meet package.json
|
||||
else
|
||||
echo "LIB_JITSI_MEET_PKG var not set - will not modify the package.json"
|
||||
fi
|
||||
|
||||
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
|
||||
|
||||
#certificates
|
||||
CERT_DIR="ios/travis-ci/certs"
|
||||
|
||||
mkdir -p $CERT_DIR
|
||||
|
||||
./ios/ci/setup-certificates.sh $CERT_DIR
|
||||
|
||||
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
|
||||
chmod 0600 ${CERT_DIR}/id_rsa
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
npm install
|
||||
|
||||
# Ever since the Apple Watch app has been added the bitcode for WebRTC needs to be downloaded in order to build successfully
|
||||
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
|
||||
|
||||
cd ios
|
||||
pod install --repo-update --no-ansi
|
||||
cd ..
|
||||
|
||||
mkdir -p /tmp/jitsi-meet/
|
||||
|
||||
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
|
||||
|
||||
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
|
||||
|
||||
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
|
||||
|
||||
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
|
||||
|
||||
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
if [ ! -z ${SCP_PROXY_HOST} ];
|
||||
then
|
||||
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
else
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
fi
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
"messageAccessibleTitle": "{{user}} dit: ",
|
||||
"messageAccessibleTitleMe": "Je dis: ",
|
||||
"messageTo": "Message privé à {{recipient}}",
|
||||
"messagebox": "Saisissez un message",
|
||||
"messagebox": "Envoyer un message",
|
||||
"newMessages": "Nouveaux messages",
|
||||
"nickname": {
|
||||
"popover": "Choisissez un pseudonyme",
|
||||
|
||||
@@ -109,9 +109,12 @@
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"disabled": "Skicka chattmeddelande är inaktiverat",
|
||||
"enter": "Delta i mötet",
|
||||
"error": "Fel: ditt meddelande skickades inte. Orsak: {{error}}",
|
||||
"everyone": "Alla",
|
||||
"fieldPlaceHolder": "Skriv ditt meddelande här",
|
||||
"guestsChatIndicator": "(gäst)",
|
||||
"lobbyChatMessageTo": "Skicka meddelande",
|
||||
"message": "Meddelande",
|
||||
"messageAccessibleTitle": "{{user}} Säger:",
|
||||
@@ -122,7 +125,10 @@
|
||||
"nickname": {
|
||||
"popover": "Välj ett namn",
|
||||
"title": "Skriv in ett namn för att börja använda chatten",
|
||||
"titleWithPolls": "Skriv in ett namn för att börja använda chatten"
|
||||
"titleWithCC": "Skriv in ett namn för att börja använda chatten och för undertexter",
|
||||
"titleWithPolls": "Skriv in ett namn för att börja använda chatten och omröstningar",
|
||||
"titleWithPollsAndCC": "Skriv in ett namn för att börja använda chatten, omröstningar och undertexter",
|
||||
"titleWithPollsAndCCAndFileSharing": "Skriv in ett namn för att börja använda chatten, omröstningar, undertexter och fildelning"
|
||||
},
|
||||
"noMessagesMessage": "Det finns ännu inga meddelanden i mötet. Påbörja en konversation!",
|
||||
"privateNotice": "Privat meddelande till {{recipient}}",
|
||||
@@ -131,11 +137,16 @@
|
||||
"systemDisplayName": "",
|
||||
"tabs": {
|
||||
"chat": "Chatt",
|
||||
"closedCaptions": "Undertexter",
|
||||
"fileSharing": "Fildelning",
|
||||
"polls": "Omröstningar"
|
||||
},
|
||||
"title": "Chatt",
|
||||
"titleWithPolls": "Chatt",
|
||||
"you": "du"
|
||||
"titleWithCC": "Undertexter",
|
||||
"titleWithFeatures": "Chatt och",
|
||||
"titleWithFileSharing": "Filer",
|
||||
"titleWithPolls": "Omröstningar",
|
||||
"you": "dig"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Installera Chrome-tillägg",
|
||||
@@ -144,6 +155,10 @@
|
||||
"dontShowAgain": "Visa inte det här igen",
|
||||
"installExtensionText": "Installera tillägget för integration med Google Kalender och Office 365"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "Undertexter kommer vara tillgängliga när en moderator startar de",
|
||||
"startClosedCaptionsButton": "Starta undertexter"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Ansluter till mötet…"
|
||||
},
|
||||
@@ -263,7 +278,8 @@
|
||||
"Remove": "Ta bort",
|
||||
"Share": "Dela",
|
||||
"Submit": "Skicka",
|
||||
"WaitForHostMsg": "Konferensen har inte börjat än. Autentisera konferensen om du är värd. Vänta annars på att värden startar konferensen.",
|
||||
"Understand": "Jag förstår, låt min mikrofon vara avstängd tillsvidare",
|
||||
"UnderstandAndUnmute": "Jag förstår, starta min mikrofon",
|
||||
"WaitForHostNoAuthMsg": "Konferensen har ännu inte startat eftersom ingen värd har anlänt ännu. Vänligen vänta.",
|
||||
"WaitingForHostButton": "Vänta på värd",
|
||||
"WaitingForHostTitle": "Väntar på värden…",
|
||||
@@ -285,6 +301,12 @@
|
||||
"alreadySharedVideoTitle": "Endast en delad video åt gången tillåts",
|
||||
"applicationWindow": "Applikationsfönster",
|
||||
"authenticationRequired": "Autentisering krävs",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "Ta ett foto och skicka de via din mobila enhet",
|
||||
"ok": "Starta kamera",
|
||||
"reject": "Inte nu",
|
||||
"title": "Ta ett foto"
|
||||
},
|
||||
"cameraConstraintFailedError": "Din kamera uppfyller inte kraven för användning.",
|
||||
"cameraNotFoundError": "Hittar ingen kamera.",
|
||||
"cameraNotSendingData": "Vi saknar åtkomst till kameran. Kontrollera om ett annat program använder enheten, välj en annan enhet från inställningsmenyn eller försök att starta om programmet.",
|
||||
@@ -299,6 +321,7 @@
|
||||
"conferenceReloadMsg": "Vi försöker fixa problemet. Återansluter om {{seconds}} sekunder…",
|
||||
"conferenceReloadTitle": "Något gick snett.",
|
||||
"confirm": "Bekräfta",
|
||||
"confirmBack": "Bakåt",
|
||||
"confirmNo": "Nej",
|
||||
"confirmYes": "Ja",
|
||||
"connectError": "Ojdå! Något gick fel och vi kunde inte ansluta till konferensen.",
|
||||
@@ -331,11 +354,12 @@
|
||||
"internalError": "Ett fel uppstod. Fel: {{error}}",
|
||||
"internalErrorTitle": "Internt fel",
|
||||
"kickMessage": "Du kan kontakta {{participantDisplayName}} för mer information.",
|
||||
"kickParticipantButton": "Ta bort från mötet",
|
||||
"kickParticipantButton": "Ta bort deltagaren från mötet",
|
||||
"kickParticipantDialog": "Vill du ta bort denna deltagaren från mötet?",
|
||||
"kickParticipantTitle": "Tysta deltagaren?",
|
||||
"kickSystemTitle": "Du har blivit borttagen från mötet",
|
||||
"kickTitle": "{{participantDisplayName}} tog bort dig från mötet",
|
||||
"learnMore": "Läs mer",
|
||||
"linkMeeting": "Länka möte",
|
||||
"linkMeetingTitle": "Länka möte till Salesforce",
|
||||
"liveStreaming": "Streama",
|
||||
@@ -358,22 +382,34 @@
|
||||
"micTimeoutError": "Time out, kunde ej starta ljud enhet",
|
||||
"micUnknownError": "Av okänd anledning kan inte din mikrofon användas.",
|
||||
"moderationAudioLabel": "Tillåt deltagarna att slå på ljudet för sig själva",
|
||||
"moderationDesktopLabel": "Tillåt deltagare att skärmdela",
|
||||
"moderationVideoLabel": "Tillåt deltagarna att starta sin video",
|
||||
"muteEveryoneDialog": "Är du säker på att du vill tysta alla? Du kan inte slå på mikrofonen åt dem, men de kan själva slå på sin egen mikrofon när som helst.",
|
||||
"muteEveryoneDialogModerationOn": "Deltagarna kan när som helst begära att få prata.",
|
||||
"muteEveryoneElseDialog": "När någon tystats kan du inte slå på mikrofonen, men de kan själva slå på sin egen mikrofon när som helst.",
|
||||
"muteEveryoneElseTitle": "Tysta alla utom {{whom}}?",
|
||||
"muteEveryoneElsesDesktopDialog": "När delningen är stoppad kommer du inte kunna starta den igen. Men de kan dela med dig igen.",
|
||||
"muteEveryoneElsesDesktopTitle": "Stoppa allas skärmdelning utom {{whom}}?",
|
||||
"muteEveryoneElsesVideoDialog": "När kameran är inaktiverad kan den inte aktiveras igen. Däremot kan övriga deltagare aktivera sina kameror.",
|
||||
"muteEveryoneElsesVideoTitle": "Inaktivera allas kameror förutom {{whom}}",
|
||||
"muteEveryoneSelf": "Dig själv",
|
||||
"muteEveryoneStartMuted": "Alla börjar tystade",
|
||||
"muteEveryoneTitle": "Tysta alla?",
|
||||
"muteEveryonesDesktopDialog": "Deltagarna kan dela sin skärm när som helst. Du kan inte starta deras skärmdelning åt dem.",
|
||||
"muteEveryonesDesktopDialogModerationOn": "Deltagarna kan skicka en förfrågan om att starta skärmdelningen.",
|
||||
"muteEveryonesDesktopTitle": "Stoppa alla deltagares skärmdelning?",
|
||||
"muteEveryonesVideoDialog": "Är du säker du vill inaktivera allas kameror. Du kommer inte att kunna aktivera dessa igen. Däremot kommer deltagarna att kunna aktivera sin egen kamera när som.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Deltagarna kan när som helst begära att få aktivera sin kamera.",
|
||||
"muteEveryonesVideoDialogOk": "Inaktivera",
|
||||
"muteEveryonesVideoTitle": "Inaktiveras allas kameror",
|
||||
"muteParticipantBody": "Du kan inte aktivera deras mikrofoner, men de kan göra det själva.",
|
||||
"muteParticipantButton": "Tysta",
|
||||
"muteParticipantsDesktopBody": "Du kommer inte kunna starta deltagarnas skärmdelning. Men deltagare kan starta skärmdelning.",
|
||||
"muteParticipantsDesktopBodyModerationOn": "Du kommer inte kunna starta deltagarnas skärmdelning.Deltagare kommer inte kunna starta deras skärmdelning.",
|
||||
"muteParticipantsDesktopButton": "Stoppa skärmdelning",
|
||||
"muteParticipantsDesktopDialog": "Är du säker på att du vill stänga av deltagarens skärmdelning? Du kommer inte kunna slå igång den igen, utan deltagaren måste starta de igen.",
|
||||
"muteParticipantsDesktopDialogModerationOn": "Är du säker på att du vill stänga av deltagarens skärmdelning? Du kommer inte kunna slå igång den igen, deltagaren kommer inte heller kunna starta den igen.",
|
||||
"muteParticipantsDesktopTitle": "Avaktivera skärmdelning för deltagaren?",
|
||||
"muteParticipantsVideoBody": "Du kommer inte att kunna aktivera kameran igen. Däremot kan deltagaren kunna aktivera sin egen kamera när som.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Du och deltagarna kommer inte att kunna aktivera kameran igen.",
|
||||
"muteParticipantsVideoButton": "Inaktivera kamera",
|
||||
@@ -393,6 +429,10 @@
|
||||
"recentlyUsedObjects": "Dina senaste använda objekt",
|
||||
"recording": "Inspelning",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Ej möjligt medan livestreaming pågår.",
|
||||
"recordingInProgressDescription": "Mötet spelas in och analyseras av AI{{learnMore}}. Ditt ljud och din bild har stängts av, om du väljer att starta kamera eller mikrofon så accepterar du att bli inspelad.",
|
||||
"recordingInProgressDescriptionFirstHalf": "Mötet spelas in och analyseras av AI",
|
||||
"recordingInProgressDescriptionSecondHalf": "Ditt ljud och din bild har stängts av, om du väljer att starta kamera eller mikrofon så accepterar du att bli inspelad.",
|
||||
"recordingInProgressTitle": "Inspelning pågår",
|
||||
"rejoinNow": "Återanslut nu",
|
||||
"remoteControlAllowedMessage": "{{user}} godkände din begäran om fjärrstyrning.",
|
||||
"remoteControlDeniedMessage": "{{user}} avböjde din begäran om fjärrstyrning.",
|
||||
@@ -482,6 +522,7 @@
|
||||
"tokenAuthFailedWithReasons": "Förlåt, du har inte tillåtelse att gå med i det här samtalet. Troliga anledingar: {{reason}}",
|
||||
"tokenAuthUnsupported": "Token URL är inte tillåten",
|
||||
"transcribing": "Transkriberar",
|
||||
"unauthenticatedAccessDisabled": "Detta samtalet kräver identifiering. Logga in för att fortsätta.",
|
||||
"unlockRoom": "Ta bort möte $t(lockRoomPassword)",
|
||||
"user": "Användare",
|
||||
"userIdentifier": "Användar-ID",
|
||||
@@ -522,6 +563,25 @@
|
||||
"veryBad": "Mycket dåligt",
|
||||
"veryGood": "Mycket bra"
|
||||
},
|
||||
"fileSharing": {
|
||||
"downloadFailedDescription": "Försök igen.",
|
||||
"downloadFailedTitle": "Nedladdning misslyckades",
|
||||
"downloadFile": "Ladda ner",
|
||||
"downloadStarted": "Nedladdning har startat",
|
||||
"dragAndDrop": "Dra och släpp filen här eller någonstans på skärmen",
|
||||
"fileAlreadyUploaded": "Filen har redan laddats upp till mötet.",
|
||||
"fileRemovedByOther": "Filen vid namn '{{ fileName }}' har tagits bort",
|
||||
"fileTooLargeDescription": "Se till att filstorleken inte överskrider {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Den valda filen är för stor",
|
||||
"fileUploadProgress": "Överföring pågår",
|
||||
"fileUploadedSuccessfully": "Överföring lyckades",
|
||||
"newFileNotification": "{{ participantName }} delade '{{ fileName }}'",
|
||||
"removeFile": "Ta bort",
|
||||
"removeFileSuccess": "Filen togs bort",
|
||||
"uploadFailedDescription": "Snälla försök igen.",
|
||||
"uploadFailedTitle": "Överföring misslyckades",
|
||||
"uploadFile": "Dela fil"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Videominiatyrer"
|
||||
@@ -690,7 +750,8 @@
|
||||
"notificationTitle": "Väntrum",
|
||||
"passwordJoinButton": "Anslut",
|
||||
"title": "Lobby",
|
||||
"toggleLabel": "Aktivera väntrum"
|
||||
"toggleLabel": "Aktivera väntrum",
|
||||
"waitForModerator": "Konferensen har annu inte startat för ingen moderator anslutit. Om du vill bli moderator var vänlig logga in. Annars, var god dröj."
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -733,7 +794,10 @@
|
||||
"me": "jag",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Säkerhetsproblem!",
|
||||
"allowAction": "Tillåt",
|
||||
"allowAll": "Tillåt",
|
||||
"allowAudio": "Tillåt ljud",
|
||||
"allowDesktop": "Tillåt skärmdelning",
|
||||
"allowVideo": "Tillåt kamera",
|
||||
"allowedUnmute": "Du kan slå på mikrofonen, starta kameran eller dela din skärm.",
|
||||
"audioUnmuteBlockedDescription": "Aktivering av mikrofonen har tillfälligt blockerats på grund av systembegränsningar.",
|
||||
"audioUnmuteBlockedTitle": "Mikrofonen har blockerats!",
|
||||
@@ -746,8 +810,10 @@
|
||||
"dataChannelClosedDescription": "Bryggkanalen har kopplats bort och därmed är videokvaliteten begränsad till sin lägsta inställning",
|
||||
"dataChannelClosedDescriptionWithAudio": "Bryggkanalen har kopplats bort och därmed kan abvrott i ljud och bild uppstå.",
|
||||
"dataChannelClosedWithAudio": "Ljud och bild kvalite kan vara försämrad.",
|
||||
"desktopMutedRemotelyTitle": "Din skärmdelning har avslutats av {{participantDisplayName}}",
|
||||
"disabledIframe": "Inbäddning är endast avsedd för demonstrationsändamål, så det här samtalet kommer att kopplas ner om {{timeout}} minuter.",
|
||||
"disabledIframeSecondary": "Bädda in {{domain}} är bara till för demo, så detta samtal kommer att kopplas bort inom {{timeout}} minuter. Var god använd <a href='{{jaasDomain}}' rel='nooper noreferrer' target='_blank'>Jitsi som tjänst</a> för att bädda in i produktion.",
|
||||
"disabledIframeSecondaryNative": "Inbäddning {{domain}} är endast avsedd för demonstrationsändamål, så det här samtalet kommer att kopplas ner om {{timeout}} minuter.",
|
||||
"disabledIframeSecondaryWeb": "Bädda in {{domain}} är bara till för demo, så detta samtal kommer att kopplas bort inom {{timeout}} minuter. Var god använd <a href='{{jaasDomain}}' rel='nooper noreferrer' target='_blank'>Jitsi som tjänst</a> för att bädda in i produktion.",
|
||||
"disconnected": "frånkopplad",
|
||||
"displayNotifications": "Visa aviseringar för",
|
||||
"dontRemindMe": "Påminn mig inte",
|
||||
@@ -802,6 +868,7 @@
|
||||
"oldElectronClientDescription1": "Den version av Jitsi meet som används är gammal och har säkerhetsluckor. Var god uppdatera till den senaste versionen.",
|
||||
"oldElectronClientDescription2": "senast build",
|
||||
"oldElectronClientDescription3": "nu!",
|
||||
"openChat": "Öppna chatt",
|
||||
"participantWantsToJoin": "Vill vara med på mötet",
|
||||
"participantsWantToJoin": "Vill vara med på mötet",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) togs bort av en annan deltagare",
|
||||
@@ -825,6 +892,8 @@
|
||||
"suggestRecordingDescription": "Vill du starta en inspelning?",
|
||||
"suggestRecordingTitle": "Spela in detta mötet",
|
||||
"unmute": "Slå på mikrofonen",
|
||||
"unmuteScreen": "Starta skärmdelning",
|
||||
"unmuteVideo": "Starta kamera",
|
||||
"videoMutedRemotelyDescription": "Du kan alltid slå på den igen.",
|
||||
"videoMutedRemotelyTitle": "Din kamera har inaktiverats av {{participantDisplayName}}!",
|
||||
"videoUnmuteBlockedDescription": "Aktivering av kameran och delning av skrivbord har tillfälligt blockerats på grund av systembegränsningar.",
|
||||
@@ -843,11 +912,14 @@
|
||||
"admit": "Godkänn",
|
||||
"admitAll": "Godkänn alla",
|
||||
"allow": "Låt deltagarna:",
|
||||
"allowDesktop": "Tillåt skärmdelning",
|
||||
"allowVideo": "Tillåt kamera",
|
||||
"askDesktop": "Skicka skärmdelningsförfrågan",
|
||||
"askUnmute": "Be om att aktivera ljud",
|
||||
"audioModeration": "Slå på ljudet för sig själva",
|
||||
"blockEveryoneMicCamera": "Inaktivera allas mikrofon och kamera",
|
||||
"breakoutRooms": "Grupprum",
|
||||
"desktopModeration": "Starta skärmdelning",
|
||||
"goLive": "Gå live",
|
||||
"invite": "Bjud in någon",
|
||||
"lowerAllHands": "Ta ner allas händer",
|
||||
@@ -859,6 +931,8 @@
|
||||
"muteAll": "Stäng av allt ljud",
|
||||
"muteEveryoneElse": "Inaktivera ljud för alla deltagare",
|
||||
"reject": "Avvisa",
|
||||
"stopDesktop": "Stoppa skärmdelning",
|
||||
"stopEveryonesDesktop": "Stoppa allas skärmdelning",
|
||||
"stopEveryonesVideo": "Inaktivera allas video",
|
||||
"stopVideo": "Inaktivera video",
|
||||
"unblockEveryoneMicCamera": "Aktivera allas mikrofon och kamera",
|
||||
@@ -868,12 +942,15 @@
|
||||
"headings": {
|
||||
"lobby": "Väntrum ({{count}})",
|
||||
"participantsList": "Mötesdeltagare ({{count}})",
|
||||
"viewerRequests": "Deltagar förfrågningar {{count}}",
|
||||
"visitorInQueue": "(väntande {{count}})",
|
||||
"visitorRequests": "(förfrågningar {{count}})",
|
||||
"visitors": "Gäster ({{count}})",
|
||||
"visitorsList": "Gäster ({{count}})",
|
||||
"waitingLobby": "Väntar i väntrum ({{count}})"
|
||||
},
|
||||
"search": "Sök efter deltagare",
|
||||
"searchDescription": "Börja skriv för att filtrera bland deltagare",
|
||||
"title": "Deltagare"
|
||||
},
|
||||
"passwordDigitsOnly": "Ange max {{number}} siffror",
|
||||
@@ -890,6 +967,9 @@
|
||||
"by": "Av {{ name }}",
|
||||
"closeButton": "Stäng omröstning",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Skicka omröstning"
|
||||
},
|
||||
"addOption": "Lägg till alternativ",
|
||||
"answerPlaceholder": "Alternativ",
|
||||
"cancel": "Avbryt",
|
||||
@@ -898,8 +978,7 @@
|
||||
"pollQuestion": "Fråga för omröstning",
|
||||
"questionPlaceholder": "Ställ en fråga",
|
||||
"removeOption": "Ta bort alternativ",
|
||||
"save": "Spara",
|
||||
"send": "Skicka"
|
||||
"save": "Spara"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Alternativ måste vara unika"
|
||||
@@ -1100,6 +1179,7 @@
|
||||
"signedIn": "Hämtar kalenderhändelser från {{email}}. Tryck på knappen nedan för att sluta hämta kalenderhändelser.",
|
||||
"title": "Kalender"
|
||||
},
|
||||
"chatWithPermissions": "Chatt kräver högre rättigheter",
|
||||
"desktopShareFramerate": "Bildfrekvens för skrivbordsdelning",
|
||||
"desktopShareHighFpsWarning": "En högre bildhastighet för skrivbordsdelning kan påverka din bandbredd. Du måste starta om skärmdelningen för att de nya inställningarna ska träda i kraft.",
|
||||
"desktopShareWarning": "Du måste starta om skärmdelningen för att de nya inställningarna ska träda i kraft.",
|
||||
@@ -1129,6 +1209,7 @@
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Självvy",
|
||||
"shortcuts": "Genvägar",
|
||||
"showSubtitlesOnStage": "Vissa undertexter på scenen",
|
||||
"speakers": "Högtalare",
|
||||
"startAudioMuted": "Alla börjar tystade",
|
||||
"startReactionsMuted": "Stäng av reaktionsljud för alla",
|
||||
@@ -1188,6 +1269,7 @@
|
||||
"neutral": "Neutral",
|
||||
"sad": "Ledsen",
|
||||
"search": "Sök",
|
||||
"searchDescription": "Börja skriv för att filtrera bland deltagare",
|
||||
"searchHint": "Sök deltagare",
|
||||
"seconds": "{{count}} s",
|
||||
"speakerStats": "Talarstatistik",
|
||||
@@ -1224,6 +1306,7 @@
|
||||
"closeChat": "Stäng chatten",
|
||||
"closeMoreActions": "Stäng menyn för fler åtgärder",
|
||||
"closeParticipantsPane": "Stäng deltagarfönstret",
|
||||
"closedCaptions": "Undertexter",
|
||||
"collapse": "Minimera",
|
||||
"document": "Växla delat dokument",
|
||||
"documentClose": "Stäng delat dokument",
|
||||
@@ -1245,7 +1328,7 @@
|
||||
"help": "Hjälp",
|
||||
"hideWhiteboard": "Dölj whiteboard",
|
||||
"invite": "Bjud in personer",
|
||||
"kick": "Ta bort deltagare",
|
||||
"kick": "Ta bort deltagare från möte",
|
||||
"laugh": "Skratta",
|
||||
"leaveConference": "Lämna mötet",
|
||||
"like": "Tummen upp",
|
||||
@@ -1302,6 +1385,20 @@
|
||||
"videounmute": "Starta kameran"
|
||||
},
|
||||
"addPeople": "Lägg till personer i samtal",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "Akustisk eko hantering"
|
||||
},
|
||||
"agc": {
|
||||
"label": "Automatisk gain kontroll"
|
||||
},
|
||||
"ns": {
|
||||
"label": "Brusreducering"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "Stereo"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "Avsluta ljudläget",
|
||||
"audioOnlyOn": "Starta ljudläget",
|
||||
"audioRoute": "Välj ljudenhet",
|
||||
@@ -1314,6 +1411,7 @@
|
||||
"closeChat": "Stäng chatt",
|
||||
"closeParticipantsPane": "Stäng deltagarrutan",
|
||||
"closeReactionsMenu": "Stäng meny för reaktioner",
|
||||
"closedCaptions": "Undertexter",
|
||||
"disableNoiseSuppression": "Inaktivera brusreducering",
|
||||
"disableReactionSounds": "Du kan inaktivera reaktionsljud för det här mötet",
|
||||
"documentClose": "Stäng delat dokument",
|
||||
@@ -1372,6 +1470,7 @@
|
||||
"reactionHeart": "Skicka kärlek",
|
||||
"reactionLaugh": "Skratta",
|
||||
"reactionLike": "Skicka tummen upp",
|
||||
"reactionLove": "Skicka kärleksreaktion",
|
||||
"reactionSilence": "Skicka tyst reaktion",
|
||||
"reactionSurprised": "Skicka reaktionen överaskad",
|
||||
"reactions": "Reaktioner",
|
||||
@@ -1404,14 +1503,18 @@
|
||||
"ccButtonTooltip": "Aktivera / Inaktivera undertexter",
|
||||
"expandedLabel": "Transkribering är aktiverad",
|
||||
"failed": "Transkribiering misslyckades",
|
||||
"labelToolTip": "Mötet transkriberas",
|
||||
"labelTooltip": "Mötet transkriberas",
|
||||
"labelTooltipExtra": "Transkriberingen kommer finnas tillgänglig senare.",
|
||||
"openClosedCaptions": "Öppna undertexter",
|
||||
"original": "Original",
|
||||
"sourceLanguageDesc": "För närvarande är mötesspråket inställt på <b>{{sourceLanguage}}</b>. <br/> Du kan ändra det från ",
|
||||
"sourceLanguageHere": "här",
|
||||
"start": "Börja visa undertexter",
|
||||
"stop": "Sluta visa undertexter",
|
||||
"subtitles": "Undertexter",
|
||||
"subtitlesOff": "Av",
|
||||
"tr": "TR"
|
||||
"tr": "TR",
|
||||
"translateTo": "Översätt till"
|
||||
},
|
||||
"unpinParticipant": "Lossa deltagare",
|
||||
"userMedia": {
|
||||
@@ -1453,6 +1556,8 @@
|
||||
"connectionInfo": "Anslutningsinformation",
|
||||
"demote": "Gör till besökare",
|
||||
"domute": "Tysta",
|
||||
"domuteDesktop": "Stoppa skärmdelning",
|
||||
"domuteDesktopOfOthers": "Stoppa skärmdelning för alla andra",
|
||||
"domuteOthers": "Inaktivera ljud för alla andra",
|
||||
"domuteVideo": "Inaktivera kamera",
|
||||
"domuteVideoOfOthers": "Inaktivera kamera för alla andra",
|
||||
@@ -1517,6 +1622,8 @@
|
||||
"noMainParticipantsTitle": "Det är mötet har inte startat än",
|
||||
"noVisitorLobby": "Du kan inte gå med medans lobby är påslagen för mötet.",
|
||||
"notAllowedPromotion": "En deltagare behöver godkänna din förfrågan först.",
|
||||
"requestToJoin": "Håller upp handen",
|
||||
"requestToJoinDescription": "Din begäran skickades till en moderator. Var god dröj!",
|
||||
"title": "Du är en besökare i mötet"
|
||||
},
|
||||
"waitingMessage": "Du kommer att komma in i mötet när det är live!"
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"everyone": "Everyone",
|
||||
"fieldPlaceHolder": "Aa",
|
||||
"fileAccessibleTitle": "{{user}} uploaded a file",
|
||||
"fileAccessibleTitleMe": "me uploaded a file",
|
||||
"fileDeleted": "A file was deleted",
|
||||
"guestsChatIndicator": "(guest)",
|
||||
"lobbyChatMessageTo": "Lobby chat message to {{recipient}}",
|
||||
"message": "Message",
|
||||
@@ -570,10 +573,12 @@
|
||||
"downloadStarted": "File download started",
|
||||
"dragAndDrop": "Drag and drop files here or anywhere on screen",
|
||||
"fileAlreadyUploaded": "File has already been uploaded to this meeting.",
|
||||
"fileRemovedByOther": "Your file '{{ fileName }}' was removed",
|
||||
"fileTooLargeDescription": "Please make sure the file does not exceed {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "The selected file is too large",
|
||||
"fileUploadProgress": "File upload progress",
|
||||
"fileUploadedSuccessfully": "File uploaded successfully",
|
||||
"newFileNotification": "{{ participantName }} shared '{{ fileName }}'",
|
||||
"removeFile": "Remove",
|
||||
"removeFileSuccess": "File removed successfully",
|
||||
"uploadFailedDescription": "Please try again.",
|
||||
|
||||
@@ -158,10 +158,11 @@ const VideoLayout = {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const state = APP.store.getState();
|
||||
const currentContainer = largeVideo.getCurrentContainer();
|
||||
const currentContainerType = largeVideo.getCurrentContainerType();
|
||||
const isOnLarge = this.isCurrentlyOnLarge(id);
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, id);
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
const videoStream = videoTrack?.jitsiTrack;
|
||||
|
||||
69
package-lock.json
generated
@@ -66,7 +66,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -87,7 +87,6 @@
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.24.0",
|
||||
"react-native-get-random-values": "1.11.0",
|
||||
"react-native-immersive-mode": "https://github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker.git#fe095651d819cf134624f786b61fc8667862178a",
|
||||
"react-native-pager-view": "6.8.1",
|
||||
"react-native-paper": "5.10.3",
|
||||
@@ -101,8 +100,7 @@
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.13.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "124.0.4",
|
||||
"react-native-webrtc": "124.0.7",
|
||||
"react-native-webview": "13.13.5",
|
||||
"react-native-worklets-core": "https://github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
@@ -18260,8 +18258,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0FYPvOFSdg9L4ocH8bJw8doUE0rM55JnqRijXMOLS3ZOphbpeBg8tBTH33jwb+bqgo5jjmjTrvJkmkvGNF5/Jg==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.4.6",
|
||||
@@ -18535,11 +18533,6 @@
|
||||
"integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
@@ -21870,14 +21863,6 @@
|
||||
"react-native": ">=0.56"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-immersive-mode": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "git+ssh://git@github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.60.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-is-edge-to-edge": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz",
|
||||
@@ -22485,22 +22470,11 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-watch-connectivity": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-1.1.0.tgz",
|
||||
"integrity": "sha512-s+zlKOVENRXgkVQvJt5f73CyqpC6ZhKlRsXLybtaFIsR1KR3ARzExm0yTm3DAb5K9AqtCpYX+1SOd4d0Af2ZNQ==",
|
||||
"dependencies": {
|
||||
"lodash.sortby": "^4.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.1",
|
||||
"react-native": ">=0.40"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-webrtc": {
|
||||
"version": "124.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.4.tgz",
|
||||
"integrity": "sha512-ZbhSz1f+kc1v5VE0B84+v6ujIWTHa2fIuocrYzGUIFab7E5izmct7PNHb9dzzs0xhBGqh4c2rUa49jbL+P/e2w==",
|
||||
"version": "124.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.7.tgz",
|
||||
"integrity": "sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
@@ -39715,8 +39689,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0FYPvOFSdg9L4ocH8bJw8doUE0rM55JnqRijXMOLS3ZOphbpeBg8tBTH33jwb+bqgo5jjmjTrvJkmkvGNF5/Jg==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.4.6",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
@@ -39958,11 +39932,6 @@
|
||||
"integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
|
||||
},
|
||||
"lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
@@ -42365,10 +42334,6 @@
|
||||
"fast-base64-decode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-native-immersive-mode": {
|
||||
"version": "git+ssh://git@github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"from": "react-native-immersive-mode@https://github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c"
|
||||
},
|
||||
"react-native-is-edge-to-edge": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz",
|
||||
@@ -42719,18 +42684,10 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-6.13.0.tgz",
|
||||
"integrity": "sha512-eY6jgLcmYKAAlAZhsZbp8wfCVrGu7jmUYTTspn8udN8j4jqr4Fq90ROOM/QegGkwNs4waclL0IkzGuq61kT4DQ=="
|
||||
},
|
||||
"react-native-watch-connectivity": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-1.1.0.tgz",
|
||||
"integrity": "sha512-s+zlKOVENRXgkVQvJt5f73CyqpC6ZhKlRsXLybtaFIsR1KR3ARzExm0yTm3DAb5K9AqtCpYX+1SOd4d0Af2ZNQ==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0"
|
||||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "124.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.4.tgz",
|
||||
"integrity": "sha512-ZbhSz1f+kc1v5VE0B84+v6ujIWTHa2fIuocrYzGUIFab7E5izmct7PNHb9dzzs0xhBGqh4c2rUa49jbL+P/e2w==",
|
||||
"version": "124.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.7.tgz",
|
||||
"integrity": "sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==",
|
||||
"requires": {
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -93,7 +93,6 @@
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.24.0",
|
||||
"react-native-get-random-values": "1.11.0",
|
||||
"react-native-immersive-mode": "https://github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker.git#fe095651d819cf134624f786b61fc8667862178a",
|
||||
"react-native-pager-view": "6.8.1",
|
||||
"react-native-paper": "5.10.3",
|
||||
@@ -107,8 +106,7 @@
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.13.0",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "124.0.4",
|
||||
"react-native-webrtc": "124.0.7",
|
||||
"react-native-webview": "13.13.5",
|
||||
"react-native-worklets-core": "https://github.com/jitsi/react-native-worklets-core.git#8c5dfab2a5907305da8971696a781b60f0f9cb18",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
|
||||
@@ -29,7 +29,7 @@ Pod::Spec.new do |s|
|
||||
SOURCE_PATH="${PODS_TARGET_SRCROOT}/sounds/"
|
||||
TARGET_PATH=$(dirname "${CONFIGURATION_BUILD_DIR}")
|
||||
PROJECT_NAME=$(basename $(dirname $(dirname "${PROJECT_DIR}"))).app
|
||||
cp -R "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}"
|
||||
ditto "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}/sounds"
|
||||
',
|
||||
}
|
||||
end
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"react-native-device-info": "0.0.0",
|
||||
"react-native-get-random-values": "0.0.0",
|
||||
"react-native-gesture-handler": "0.0.0",
|
||||
"react-native-immersive-mode": "0.0.0",
|
||||
"react-native-pager-view": "0.0.0",
|
||||
"react-native-performance": "0.0.0",
|
||||
"react-native-orientation-locker": "0.0.0",
|
||||
|
||||
@@ -20,11 +20,6 @@ module.exports = {
|
||||
platforms: {
|
||||
ios: null
|
||||
}
|
||||
},
|
||||
'react-native-watch-connectivity': {
|
||||
platforms: {
|
||||
ios: null
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import amplitude from '@amplitude/analytics-react-native';
|
||||
import * as amplitude from '@amplitude/analytics-react-native';
|
||||
|
||||
export default amplitude;
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ import '../mobile/audio-mode/middleware';
|
||||
import '../mobile/background/middleware';
|
||||
import '../mobile/call-integration/middleware';
|
||||
import '../mobile/external-api/middleware';
|
||||
import '../mobile/full-screen/middleware';
|
||||
import '../mobile/navigation/middleware';
|
||||
import '../mobile/permissions/middleware';
|
||||
import '../mobile/proximity/middleware';
|
||||
import '../mobile/wake-lock/middleware';
|
||||
import '../mobile/react-native-sdk/middleware';
|
||||
import '../mobile/watchos/middleware';
|
||||
import '../share-room/middleware';
|
||||
import '../shared-video/middleware';
|
||||
import '../toolbox/middleware.native';
|
||||
|
||||
@@ -2,8 +2,6 @@ import '../mobile/audio-mode/reducer';
|
||||
import '../mobile/background/reducer';
|
||||
import '../mobile/call-integration/reducer';
|
||||
import '../mobile/external-api/reducer';
|
||||
import '../mobile/full-screen/reducer';
|
||||
import '../mobile/watchos/reducer';
|
||||
import '../share-room/reducer';
|
||||
|
||||
import './reducer.native';
|
||||
|
||||
@@ -52,8 +52,6 @@ import { IMobileAudioModeState } from '../mobile/audio-mode/reducer';
|
||||
import { IMobileBackgroundState } from '../mobile/background/reducer';
|
||||
import { ICallIntegrationState } from '../mobile/call-integration/reducer';
|
||||
import { IMobileExternalApiState } from '../mobile/external-api/reducer';
|
||||
import { IFullScreenState } from '../mobile/full-screen/reducer';
|
||||
import { IMobileWatchOSState } from '../mobile/watchos/reducer';
|
||||
import { INoAudioSignalState } from '../no-audio-signal/reducer';
|
||||
import { INoiseDetectionState } from '../noise-detection/reducer';
|
||||
import { INoiseSuppressionState } from '../noise-suppression/reducer';
|
||||
@@ -132,7 +130,6 @@ export interface IReduxState {
|
||||
'features/file-sharing': IFileSharingState;
|
||||
'features/filmstrip': IFilmstripState;
|
||||
'features/follow-me': IFollowMeState;
|
||||
'features/full-screen': IFullScreenState;
|
||||
'features/gifs': IGifsState;
|
||||
'features/google-api': IGoogleApiState;
|
||||
'features/invite': IInviteState;
|
||||
@@ -143,7 +140,6 @@ export interface IReduxState {
|
||||
'features/mobile/audio-mode': IMobileAudioModeState;
|
||||
'features/mobile/background': IMobileBackgroundState;
|
||||
'features/mobile/external-api': IMobileExternalApiState;
|
||||
'features/mobile/watchos': IMobileWatchOSState;
|
||||
'features/no-audio-signal': INoAudioSignalState;
|
||||
'features/noise-detection': INoiseDetectionState;
|
||||
'features/noise-suppression': INoiseSuppressionState;
|
||||
|
||||
@@ -7,6 +7,8 @@ import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { determineTranscriptionLanguage } from '../../transcribing/functions';
|
||||
import { IStateful } from '../app/types';
|
||||
import { connect } from '../connection/actions';
|
||||
import { disconnect } from '../connection/actions.any';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
@@ -22,7 +24,7 @@ import {
|
||||
safeDecodeURIComponent
|
||||
} from '../util/uri';
|
||||
|
||||
import { setObfuscatedRoom } from './actions';
|
||||
import { conferenceWillInit, setObfuscatedRoom } from './actions';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
@@ -618,3 +620,34 @@ export function updateTrackMuteState(stateful: IStateful, dispatch: IStore['disp
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the "destroyed" event of a conference and if the destroyed conference is the current one,
|
||||
* it silently reconnects to the same room.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @param {Array} params - The parameters for the destroy event.
|
||||
*
|
||||
* @returns {boolean} - True if the destroyed conference was the current one, and we are reconnecting, false otherwise.
|
||||
*/
|
||||
export function processDestroyConferenceEvent(stateful: IStateful, dispatch: IStore['dispatch'], params: Array<any>) {
|
||||
const [ jid ] = params;
|
||||
const conference = getCurrentConference(stateful);
|
||||
|
||||
// if the jid of the room is the same as the current conference, we are being
|
||||
// notified that the current conference has been destroyed, and we need to reconnect
|
||||
if (conference?.room?.roomjid === jid) {
|
||||
dispatch(disconnect(true, false))
|
||||
.then(() => {
|
||||
dispatch(conferenceWillInit());
|
||||
logger.info('Dispatching silent re-connect.');
|
||||
|
||||
return dispatch(connect());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { CONFERENCE_FAILED } from './actionTypes';
|
||||
import { conferenceLeft } from './actions.native';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
|
||||
import './middleware.any';
|
||||
import { processDestroyConferenceEvent } from './functions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch } = store;
|
||||
@@ -23,6 +23,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
if (processDestroyConferenceEvent(state, dispatch, error.params)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!notifyOnConferenceDestruction) {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
KICKED_OUT
|
||||
} from './actionTypes';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
import { processDestroyConferenceEvent } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
let screenLock: WakeLockSentinel | undefined;
|
||||
@@ -127,6 +127,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = getState();
|
||||
const { notifyOnConferenceDestruction = true } = state['features/base/config'];
|
||||
const [ reason ] = action.error.params;
|
||||
|
||||
if (processDestroyConferenceEvent(state, dispatch, action.error.params)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const titlekey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
@@ -524,6 +524,7 @@ export interface IConfig {
|
||||
hideExtraJoinButtons?: Array<string>;
|
||||
preCallTestEnabled?: boolean;
|
||||
preCallTestICEUrl?: string;
|
||||
showHangUp?: boolean;
|
||||
};
|
||||
raisedHands?: {
|
||||
disableLowerHandByModerator?: boolean;
|
||||
@@ -653,7 +654,9 @@ export interface IConfig {
|
||||
audio?: boolean;
|
||||
video?: boolean;
|
||||
};
|
||||
hideVisitorCountForVisitors?: boolean;
|
||||
queueService: string;
|
||||
showJoinMeetingDialog?: boolean;
|
||||
};
|
||||
watchRTCConfigParams?: IWatchRTCConfiguration;
|
||||
webhookProxyUrl?: string;
|
||||
|
||||
@@ -99,6 +99,7 @@ export default [
|
||||
'disabledNotifications',
|
||||
'disabledSounds',
|
||||
'disableFilmstripAutohiding',
|
||||
'disableFocus',
|
||||
'disableInitialGUM',
|
||||
'disableInviteFunctions',
|
||||
'disableIncomingMessageSound',
|
||||
@@ -237,6 +238,8 @@ export default [
|
||||
'useTurnUdp',
|
||||
'videoQuality',
|
||||
'visitors.enableMediaOnPromote',
|
||||
'visitors.hideVisitorCountForVisitors',
|
||||
'visitors.showJoinMeetingDialog',
|
||||
'watchRTCConfigParams.allowBrowserLogCollection',
|
||||
'watchRTCConfigParams.collectionInterval',
|
||||
'watchRTCConfigParams.console',
|
||||
|
||||
@@ -385,10 +385,10 @@ function _propertiesUpdate(properties: object) {
|
||||
* Closes connection.
|
||||
*
|
||||
* @param {boolean} isRedirect - Indicates if the action has been dispatched as part of visitor promotion.
|
||||
*
|
||||
* @param {boolean} shouldLeave - Indicates whether to call JitsiConference.leave().
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect(isRedirect?: boolean) {
|
||||
export function disconnect(isRedirect?: boolean, shouldLeave = true) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
|
||||
const state = getState();
|
||||
|
||||
@@ -407,20 +407,26 @@ export function disconnect(isRedirect?: boolean) {
|
||||
// intention to leave the conference.
|
||||
dispatch(conferenceWillLeave(conference_, isRedirect));
|
||||
|
||||
promise
|
||||
= conference_.leave()
|
||||
.catch((error: Error) => {
|
||||
logger.warn(
|
||||
'JitsiConference.leave() rejected with:',
|
||||
error);
|
||||
if (!shouldLeave) {
|
||||
// we are skipping JitsiConference.leave(), but will still dispatch the normal leave flow events
|
||||
dispatch(conferenceLeft(conference_));
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
promise
|
||||
= conference_.leave()
|
||||
.catch((error: Error) => {
|
||||
logger.warn(
|
||||
'JitsiConference.leave() rejected with:',
|
||||
error);
|
||||
|
||||
// The library lib-jitsi-meet failed to make the
|
||||
// JitsiConference leave. Which may be because
|
||||
// JitsiConference thinks it has already left.
|
||||
// Regardless of the failure reason, continue in
|
||||
// jitsi-meet as if the leave has succeeded.
|
||||
dispatch(conferenceLeft(conference_));
|
||||
});
|
||||
// The library lib-jitsi-meet failed to make the
|
||||
// JitsiConference leave. Which may be because
|
||||
// JitsiConference thinks it has already left.
|
||||
// Regardless of the failure reason, continue in
|
||||
// jitsi-meet as if the leave has succeeded.
|
||||
dispatch(conferenceLeft(conference_));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -78,12 +78,6 @@ export const CHAT_ENABLED = 'chat.enabled';
|
||||
*/
|
||||
export const FILMSTRIP_ENABLED = 'filmstrip.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if fullscreen (immersive) mode should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const FULLSCREEN_ENABLED = 'fullscreen.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the Help button should be enabled.
|
||||
* Default: enabled (true).
|
||||
|
||||
@@ -62,65 +62,6 @@ const AVATAR_CHECKER_FUNCTIONS = [
|
||||
];
|
||||
/* eslint-enable arrow-body-style */
|
||||
|
||||
/**
|
||||
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
|
||||
* dominant speaker is visible always on the vertical filmstrip in stage layout.
|
||||
*
|
||||
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||
* retrieve the state.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
sortedRemoteVirtualScreenshareParticipants,
|
||||
speakersList
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
let activeSpeakers = new Map(speakersList);
|
||||
|
||||
// Do not re-sort the active speakers if dominant speaker is currently visible.
|
||||
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
|
||||
return activeSpeakers;
|
||||
}
|
||||
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
|
||||
|
||||
if (activeSpeakers.has(dominantSpeaker ?? '')) {
|
||||
activeSpeakers.delete(dominantSpeaker ?? '');
|
||||
}
|
||||
|
||||
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
|
||||
// alphabetically sorted.
|
||||
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
|
||||
const updatedSpeakers = Array.from(activeSpeakers);
|
||||
|
||||
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
|
||||
activeSpeakers = new Map(updatedSpeakers);
|
||||
}
|
||||
|
||||
// Remove screenshares from the count.
|
||||
if (sortedRemoteVirtualScreenshareParticipants) {
|
||||
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
|
||||
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
|
||||
|
||||
activeSpeakers.delete(ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fake participants from the count.
|
||||
if (fakeParticipants) {
|
||||
availableSlotsForActiveSpeakers -= fakeParticipants.size;
|
||||
}
|
||||
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
|
||||
|
||||
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
|
||||
|
||||
return new Map(truncatedSpeakersList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first loadable avatar URL for a participant.
|
||||
*
|
||||
@@ -827,17 +768,30 @@ export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispat
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if private chat is enabled for the given participant.
|
||||
* Checks if private chat is enabled for the given participant or local participant.
|
||||
*
|
||||
* @param {IParticipant|IVisitorChatParticipant|undefined} participant - The participant to check.
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @param {boolean} [checkSelf=false] - Whether to check for local participant's ability to send messages.
|
||||
* @returns {boolean} - True if private chat is enabled, false otherwise.
|
||||
*/
|
||||
export function isPrivateChatEnabled(participant: IParticipant | IVisitorChatParticipant | undefined, state: IReduxState) {
|
||||
export function isPrivateChatEnabled(
|
||||
participant: IParticipant | IVisitorChatParticipant | undefined,
|
||||
state: IReduxState,
|
||||
checkSelf: boolean = false
|
||||
): boolean {
|
||||
const { remoteVideoMenu = {} } = state['features/base/config'];
|
||||
const { disablePrivateChat } = remoteVideoMenu;
|
||||
|
||||
if ((!isVisitorChatParticipant(participant) && participant?.local) || disablePrivateChat === 'all') {
|
||||
// If checking self capability (if the local participant can send messages) ignore the local participant blocking rule
|
||||
const isLocal = !isVisitorChatParticipant(participant) && participant?.local;
|
||||
|
||||
if (isLocal && !checkSelf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if private chat is disabled globally
|
||||
if (disablePrivateChat === 'all') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -857,3 +811,15 @@ export function isPrivateChatEnabled(participant: IParticipant | IVisitorChatPar
|
||||
|
||||
return !disablePrivateChat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if private chat is enabled for the local participant (can they send private messages).
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} - True if the local participant can send private messages, false otherwise.
|
||||
*/
|
||||
export function isPrivateChatEnabledSelf(state: IReduxState): boolean {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
return isPrivateChatEnabled(localParticipant, state, true);
|
||||
}
|
||||
|
||||
@@ -76,12 +76,12 @@ const DEFAULT_STATE = {
|
||||
numberOfParticipantsNotSupportingE2EE: 0,
|
||||
overwrittenNameList: {},
|
||||
pinnedParticipant: undefined,
|
||||
previousSpeakers: new Set<string>(),
|
||||
raisedHandsQueue: [],
|
||||
remote: new Map(),
|
||||
remoteVideoSources: new Set<string>(),
|
||||
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||
sortedRemoteParticipants: new Map(),
|
||||
speakersList: new Map()
|
||||
sortedRemoteParticipants: new Map()
|
||||
};
|
||||
|
||||
export interface IParticipantsState {
|
||||
@@ -94,12 +94,12 @@ export interface IParticipantsState {
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
previousSpeakers: Set<string>;
|
||||
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
|
||||
remote: Map<string, IParticipant>;
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||
speakersList: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,22 +156,10 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
const { participant } = action;
|
||||
const { id, previousSpeakers = [] } = participant;
|
||||
const { dominantSpeaker, local } = state;
|
||||
const newSpeakers = [ id, ...previousSpeakers ];
|
||||
const sortedSpeakersList: Array<Array<string>> = [];
|
||||
|
||||
for (const speaker of newSpeakers) {
|
||||
if (speaker !== local?.id) {
|
||||
const remoteParticipant = state.remote.get(speaker);
|
||||
|
||||
remoteParticipant
|
||||
&& sortedSpeakersList.push(
|
||||
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the remote speaker list sorted alphabetically.
|
||||
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
// Build chronologically ordered Set of remote speakers (excluding local)
|
||||
const previousSpeakersSet: Set<string>
|
||||
= new Set(previousSpeakers.filter((speaker: string) => speaker !== local?.id));
|
||||
|
||||
// Only one dominant speaker is allowed.
|
||||
if (dominantSpeaker) {
|
||||
@@ -181,8 +169,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
|
||||
return {
|
||||
...state,
|
||||
dominantSpeaker: id, // @ts-ignore
|
||||
speakersList: new Map(sortedSpeakersList)
|
||||
dominantSpeaker: id,
|
||||
previousSpeakers: previousSpeakersSet
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,7 +423,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
}
|
||||
|
||||
// Remove the participant from the list of speakers.
|
||||
state.speakersList.has(id) && state.speakersList.delete(id);
|
||||
state.previousSpeakers.delete(id);
|
||||
|
||||
if (pinnedParticipant === id) {
|
||||
state.pinnedParticipant = undefined;
|
||||
|
||||
@@ -282,15 +282,18 @@ const PreMeetingScreen = ({
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
||||
const { hiddenPremeetingButtons } = state['features/base/config'];
|
||||
const { hiddenPremeetingButtons, prejoinConfig } = state['features/base/config'];
|
||||
const { toolbarButtons } = state['features/toolbox'];
|
||||
const { showHangUp = true } = getLobbyConfig(state);
|
||||
const { knocking } = state['features/lobby'];
|
||||
const { showHangUp: showHangUpLobby = true } = getLobbyConfig(state);
|
||||
const { showHangUp: showHangUpPrejoin = true } = prejoinConfig || {};
|
||||
const premeetingButtons = (ownProps.thirdParty
|
||||
? THIRD_PARTY_PREJOIN_BUTTONS
|
||||
: PREMEETING_BUTTONS).filter((b: any) => !(hiddenPremeetingButtons || []).includes(b));
|
||||
|
||||
if (showHangUp && knocking && !premeetingButtons.includes('hangup')) {
|
||||
const shouldShowHangUp = knocking ? showHangUpLobby : showHangUpPrejoin;
|
||||
|
||||
if (shouldShowHangUp && !premeetingButtons.includes('hangup')) {
|
||||
premeetingButtons.push('hangup');
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class Message extends Component<IProps> {
|
||||
const content: any[] = [];
|
||||
const { gifEnabled } = this.props;
|
||||
|
||||
// check if the message is a GIF
|
||||
// Check if the message is a GIF
|
||||
if (gifEnabled && isGifMessage(text)) {
|
||||
const url = extractGifURL(text);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { VIDEO_CODEC } from '../../video-quality/constants';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import {
|
||||
@@ -57,75 +56,12 @@ export function isLargeVideoReceived({ getState }: IStore): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in AV1.
|
||||
* Returns the local video track's codec.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
* @returns {string?} The local video track's codec.
|
||||
*/
|
||||
export function isLocalCameraEncodingAv1({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.AV1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in H.264.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingH264({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.H264) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP8.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp8({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP9.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp9({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
export function getLocalCameraEncoding({ getState }: IStore): string | undefined {
|
||||
return getLocalVideoTrack(getState()['features/base/tracks'])?.codec?.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,12 +8,9 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
|
||||
|
||||
import { setConnectionState } from './actions';
|
||||
import {
|
||||
getLocalCameraEncoding,
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isLocalCameraEncodingAv1,
|
||||
isLocalCameraEncodingH264,
|
||||
isLocalCameraEncodingVp8,
|
||||
isLocalCameraEncodingVp9,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
} from './functions';
|
||||
@@ -90,10 +87,7 @@ function _bindTortureHelpers(store: IStore) {
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
isLocalCameraEncodingAv1: isLocalCameraEncodingAv1.bind(null, store),
|
||||
isLocalCameraEncodingH264: isLocalCameraEncodingH264.bind(null, store),
|
||||
isLocalCameraEncodingVp8: isLocalCameraEncodingVp8.bind(null, store),
|
||||
isLocalCameraEncodingVp9: isLocalCameraEncodingVp9.bind(null, store),
|
||||
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,11 +71,16 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
badge: {
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text04,
|
||||
padding: `0 ${theme.spacing(1)}`,
|
||||
borderRadius: '100%',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.warning01,
|
||||
marginLeft: theme.spacing(2)
|
||||
borderRadius: theme.spacing(2),
|
||||
color: theme.palette.text04,
|
||||
display: 'inline-flex',
|
||||
height: theme.spacing(3),
|
||||
justifyContent: 'center',
|
||||
marginLeft: theme.spacing(2),
|
||||
minWidth: theme.spacing(2),
|
||||
padding: `0 ${theme.spacing(1)}`
|
||||
},
|
||||
|
||||
icon: {
|
||||
|
||||
@@ -7,7 +7,9 @@ import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { closeChat, sendMessage } from '../../actions.native';
|
||||
import { getUnreadFilesCount } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
@@ -17,6 +19,21 @@ import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The number of unread file messages.
|
||||
*/
|
||||
_unreadFilesCount: number;
|
||||
|
||||
/**
|
||||
* The number of unread messages.
|
||||
*/
|
||||
_unreadMessagesCount: number;
|
||||
|
||||
/**
|
||||
* The number of unread polls.
|
||||
*/
|
||||
_unreadPollsCount: number;
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
@@ -96,21 +113,26 @@ class Chat extends Component<IProps> {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number
|
||||
* _unreadMessagesCount: number,
|
||||
* _unreadPollsCount: number,
|
||||
* _unreadFilesCount: number
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { messages, nbUnreadMessages } = state['features/chat'];
|
||||
const { messages, unreadMessagesCount } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages
|
||||
_unreadMessagesCount: unreadMessagesCount,
|
||||
_unreadPollsCount: getUnreadPollCount(state),
|
||||
_unreadFilesCount: getUnreadFilesCount(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
const { _nbUnreadMessages, dispatch, navigation, t } = props;
|
||||
const unreadMessagesNr = _nbUnreadMessages > 0;
|
||||
const { _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount, dispatch, navigation, t } = props;
|
||||
const totalUnread = _unreadMessagesCount + _unreadPollsCount + _unreadFilesCount;
|
||||
const unreadMessagesNr = totalUnread > 0;
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
@@ -121,14 +143,14 @@ export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
activeUnreadNr = { unreadMessagesNr }
|
||||
isFocused = { isFocused }
|
||||
label = { t('chat.tabs.chat') }
|
||||
nbUnread = { _nbUnreadMessages } />
|
||||
unreadCount = { totalUnread } />
|
||||
)
|
||||
});
|
||||
|
||||
return () => {
|
||||
isFocused && dispatch(closeChat());
|
||||
};
|
||||
}, [ isFocused, _nbUnreadMessages ]);
|
||||
}, [ isFocused, _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount ]);
|
||||
|
||||
return (
|
||||
<Chat { ...props } />
|
||||
|
||||
@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
@@ -70,9 +70,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
|
||||
return {
|
||||
_isPollsDisabled: arePollsDisabled(state),
|
||||
|
||||
// The toggled icon should also be available for new polls
|
||||
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),
|
||||
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state) || getUnreadFilesCount(state),
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant, getRemoteParticipants } from '../../../base/participants/functions';
|
||||
import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
|
||||
import Select from '../../../base/ui/components/web/Select';
|
||||
import Tabs from '../../../base/ui/components/web/Tabs';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
@@ -73,16 +73,21 @@ interface IProps extends AbstractProps {
|
||||
*/
|
||||
_isResizing: boolean;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_nbUnreadPolls: number;
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean;
|
||||
|
||||
/**
|
||||
* Number of unread file sharing messages.
|
||||
*/
|
||||
_unreadFilesCount: number;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_unreadPollsCount: number;
|
||||
|
||||
/**
|
||||
* The current width of the chat panel.
|
||||
*/
|
||||
@@ -216,8 +221,9 @@ const Chat = ({
|
||||
_focusedTab,
|
||||
_isResizing,
|
||||
_messages,
|
||||
_nbUnreadMessages,
|
||||
_nbUnreadPolls,
|
||||
_unreadMessagesCount,
|
||||
_unreadPollsCount,
|
||||
_unreadFilesCount,
|
||||
_showNamePrompt,
|
||||
_width,
|
||||
dispatch,
|
||||
@@ -236,6 +242,7 @@ const Chat = ({
|
||||
} = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const privateMessageRecipient = useSelector((state: IReduxState) => state['features/chat'].privateMessageRecipient);
|
||||
const participants = useSelector(getRemoteParticipants);
|
||||
const isPrivateChatAllowed = useSelector((state: IReduxState) => isPrivateChatEnabledSelf(state));
|
||||
|
||||
const options = useMemo(() => {
|
||||
const o = Array.from(participants?.values() || [])
|
||||
@@ -425,12 +432,14 @@ const Chat = ({
|
||||
<MessageContainer
|
||||
messages = { _messages } />
|
||||
<MessageRecipient />
|
||||
<Select
|
||||
containerClassName = { cx(classes.privateMessageRecipientsList) }
|
||||
id = 'select-chat-recipient'
|
||||
onChange = { onSelectedRecipientChange }
|
||||
options = { options }
|
||||
value = { privateMessageRecipient?.id || OPTION_GROUPCHAT } />
|
||||
{isPrivateChatAllowed && (
|
||||
<Select
|
||||
containerClassName = { cx(classes.privateMessageRecipientsList) }
|
||||
id = 'select-chat-recipient'
|
||||
onChange = { onSelectedRecipientChange }
|
||||
options = { options }
|
||||
value = { privateMessageRecipient?.id || OPTION_GROUPCHAT } />
|
||||
)}
|
||||
<ChatInput
|
||||
onSend = { onSendMessage } />
|
||||
</div>
|
||||
@@ -479,7 +488,7 @@ const Chat = ({
|
||||
{
|
||||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
countBadge:
|
||||
_focusedTab !== ChatTabs.CHAT && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined,
|
||||
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
|
||||
id: ChatTabs.CHAT,
|
||||
controlsId: `${ChatTabs.CHAT}-panel`,
|
||||
icon: IconMessage,
|
||||
@@ -490,7 +499,7 @@ const Chat = ({
|
||||
if (_isPollsEnabled) {
|
||||
tabs.push({
|
||||
accessibilityLabel: t('chat.tabs.polls'),
|
||||
countBadge: _focusedTab !== ChatTabs.POLLS && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined,
|
||||
countBadge: _focusedTab !== ChatTabs.POLLS && _unreadPollsCount > 0 ? _unreadPollsCount : undefined,
|
||||
id: ChatTabs.POLLS,
|
||||
controlsId: `${ChatTabs.POLLS}-panel`,
|
||||
icon: IconInfo,
|
||||
@@ -512,7 +521,7 @@ const Chat = ({
|
||||
if (_isFileSharingTabEnabled) {
|
||||
tabs.push({
|
||||
accessibilityLabel: t('chat.tabs.fileSharing'),
|
||||
countBadge: undefined,
|
||||
countBadge: _focusedTab !== ChatTabs.FILE_SHARING && _unreadFilesCount > 0 ? _unreadFilesCount : undefined,
|
||||
id: ChatTabs.FILE_SHARING,
|
||||
controlsId: `${ChatTabs.FILE_SHARING}-panel`,
|
||||
icon: IconShareDoc,
|
||||
@@ -584,16 +593,17 @@ const Chat = ({
|
||||
* _isCCTabEnabled: boolean,
|
||||
* _focusedTab: string,
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number,
|
||||
* _nbUnreadPolls: number,
|
||||
* _unreadMessagesCount: number,
|
||||
* _unreadPollsCount: number,
|
||||
* _unreadFilesCount: number,
|
||||
* _showNamePrompt: boolean,
|
||||
* _width: number,
|
||||
* _isResizing: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, focusedTab, messages, nbUnreadMessages, width, isResizing } = state['features/chat'];
|
||||
const { nbUnreadPolls } = state['features/polls'];
|
||||
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
|
||||
const { unreadPollsCount } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
@@ -604,8 +614,9 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_isFileSharingTabEnabled: isFileSharingEnabled(state),
|
||||
_focusedTab: focusedTab,
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages,
|
||||
_nbUnreadPolls: nbUnreadPolls,
|
||||
_unreadMessagesCount: unreadMessagesCount,
|
||||
_unreadPollsCount: unreadPollsCount,
|
||||
_unreadFilesCount: unreadFilesCount,
|
||||
_showNamePrompt: !_localParticipant?.name,
|
||||
_width: width?.current || CHAT_SIZE,
|
||||
_isResizing: isResizing
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ChatCounter}.
|
||||
@@ -65,7 +65,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||
|
||||
return {
|
||||
|
||||
_count: getUnreadCount(state) + getUnreadPollCount(state),
|
||||
_count: getUnreadCount(state) + getUnreadPollCount(state) + getUnreadFilesCount(state),
|
||||
_isOpen: isOpen
|
||||
|
||||
};
|
||||
|
||||
@@ -9,9 +9,10 @@ import { getParticipantById, getParticipantDisplayName, isPrivateChatEnabled } f
|
||||
import Popover from '../../../base/popover/components/Popover.web';
|
||||
import Message from '../../../base/react/components/web/Message';
|
||||
import { MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { getDisplayNameSuffix, getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
|
||||
import { getDisplayNameSuffix, getFormattedTimestamp, getMessageText, getPrivateNoticeMessage, isFileMessage } from '../../functions';
|
||||
import { IChatMessageProps } from '../../types';
|
||||
|
||||
import FileMessage from './FileMessage';
|
||||
import MessageMenu from './MessageMenu';
|
||||
import ReactButton from './ReactButton';
|
||||
|
||||
@@ -48,6 +49,22 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
marginTop: '4px',
|
||||
boxSizing: 'border-box' as const,
|
||||
|
||||
'&.file': {
|
||||
display: 'flex',
|
||||
maxWidth: '100%',
|
||||
minWidth: 0,
|
||||
|
||||
'& $replyWrapper': {
|
||||
width: '100%',
|
||||
minWidth: 0
|
||||
},
|
||||
|
||||
'& $messageContent': {
|
||||
width: '100%',
|
||||
minWidth: 0
|
||||
}
|
||||
},
|
||||
|
||||
'&.privatemessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
},
|
||||
@@ -117,8 +134,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
messageContent: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
flex: 1
|
||||
overflow: 'hidden'
|
||||
},
|
||||
optionsButtonContainer: {
|
||||
display: 'flex',
|
||||
@@ -344,6 +360,7 @@ const ChatMessage = ({
|
||||
{isHovered && <MessageMenu
|
||||
displayName = { message.displayName }
|
||||
enablePrivateChat = { Boolean(enablePrivateChat) }
|
||||
isFileMessage = { isFileMessage(message) }
|
||||
isFromVisitor = { message.isFromVisitor }
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
message = { message.message }
|
||||
@@ -356,19 +373,31 @@ const ChatMessage = ({
|
||||
classes.chatMessage,
|
||||
className,
|
||||
message.privateMessage && 'privatemessage',
|
||||
message.lobbyChat && !knocking && 'lobbymessage'
|
||||
message.lobbyChat && !knocking && 'lobbymessage',
|
||||
isFileMessage(message) && 'file'
|
||||
) }>
|
||||
<div className = { classes.replyWrapper }>
|
||||
<div className = { cx('messagecontent', classes.messageContent) }>
|
||||
{showDisplayName && _renderDisplayName()}
|
||||
<div className = { cx('usermessage', classes.userMessage) }>
|
||||
<Message
|
||||
screenReaderHelpText = { message.displayName === message.recipient
|
||||
? t<string>('chat.messageAccessibleTitleMe')
|
||||
: t<string>('chat.messageAccessibleTitle', {
|
||||
user: message.displayName
|
||||
}) }
|
||||
text = { getMessageText(message) } />
|
||||
{isFileMessage(message) ? (
|
||||
<FileMessage
|
||||
message = { message }
|
||||
screenReaderHelpText = { message.messageType === MESSAGE_TYPE_LOCAL
|
||||
? t<string>('chat.fileAccessibleTitleMe')
|
||||
: t<string>('chat.fileAccessibleTitle', {
|
||||
user: message.displayName
|
||||
})
|
||||
} />
|
||||
) : (
|
||||
<Message
|
||||
screenReaderHelpText = { message.messageType === MESSAGE_TYPE_LOCAL
|
||||
? t<string>('chat.messageAccessibleTitleMe')
|
||||
: t<string>('chat.messageAccessibleTitle', {
|
||||
user: message.displayName
|
||||
}) }
|
||||
text = { getMessageText(message) } />
|
||||
)}
|
||||
{(message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& _renderPrivateNotice()}
|
||||
<div className = { classes.chatMessageFooter }>
|
||||
@@ -400,6 +429,7 @@ const ChatMessage = ({
|
||||
{isHovered && <MessageMenu
|
||||
displayName = { message.displayName }
|
||||
enablePrivateChat = { Boolean(enablePrivateChat) }
|
||||
isFileMessage = { isFileMessage(message) }
|
||||
isFromVisitor = { message.isFromVisitor }
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
message = { message.message }
|
||||
|
||||
@@ -27,7 +27,7 @@ const useStyles = makeStyles()(theme => {
|
||||
flexDirection: 'column',
|
||||
maxWidth: '100%',
|
||||
|
||||
'&.remote': {
|
||||
'&.remote, &.file': {
|
||||
maxWidth: 'calc(100% - 40px)' // 100% - avatar and margin
|
||||
}
|
||||
},
|
||||
|
||||
143
react/features/chat/components/web/FileMessage.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { downloadFile, removeFile } from '../../../file-sharing/actions';
|
||||
import FileItem from '../../../file-sharing/components/web/FileItem';
|
||||
import { isFileUploadingEnabled } from '../../../file-sharing/functions.any';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
/**
|
||||
* Props for the FileMessage component.
|
||||
*/
|
||||
interface IFileMessageProps {
|
||||
|
||||
/**
|
||||
* Additional CSS class name.
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* The message containing file metadata.
|
||||
*/
|
||||
message: IMessage;
|
||||
|
||||
/**
|
||||
* Screen reader help text for accessibility.
|
||||
*/
|
||||
screenReaderHelpText?: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
fileMessageContainer: {
|
||||
margin: `${theme.spacing(1)} 0`,
|
||||
maxWidth: '100%',
|
||||
minWidth: 0,
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
// Override FileItem styles for compact chat display
|
||||
'& .fileItem': {
|
||||
padding: theme.spacing(2), // Reduced from 3 (24px → 16px)
|
||||
gap: theme.spacing(2), // Reduced from 3
|
||||
|
||||
// Add background to button container to hide text underneath in chat context
|
||||
'& > div:last-child': {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
paddingLeft: theme.spacing(2)
|
||||
},
|
||||
|
||||
'&:hover > div:last-child': {
|
||||
backgroundColor: theme.palette.ui03
|
||||
}
|
||||
},
|
||||
|
||||
'& .fileName': {
|
||||
...theme.typography.bodyShortRegular // Match message text font
|
||||
},
|
||||
|
||||
'& .fileSize': {
|
||||
...theme.typography.labelRegular // Keep smaller for metadata
|
||||
}
|
||||
},
|
||||
deletedFileMessage: {
|
||||
...theme.typography.bodyShortRegular,
|
||||
fontStyle: 'italic',
|
||||
color: theme.palette.text02,
|
||||
padding: theme.spacing(1, 0)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component for displaying file messages in chat.
|
||||
*
|
||||
* @param {IFileMessageProps} props - The component props.
|
||||
* @returns {JSX.Element | null} The FileMessage component or null if no file metadata.
|
||||
*/
|
||||
const FileMessage = ({ className = '', message, screenReaderHelpText }: IFileMessageProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isUploadEnabled = useSelector(isFileUploadingEnabled);
|
||||
|
||||
/**
|
||||
* Handles the download action for a file.
|
||||
*
|
||||
* @param {string} fileId - The ID of the file to download.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleDownload = useCallback((fileId: string) => {
|
||||
dispatch(downloadFile(fileId));
|
||||
}, [ dispatch ]);
|
||||
|
||||
/**
|
||||
* Handles the remove action for a file.
|
||||
*
|
||||
* @param {string} fileId - The ID of the file to remove.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleRemove = useCallback((fileId: string) => {
|
||||
dispatch(removeFile(fileId));
|
||||
}, [ dispatch ]);
|
||||
|
||||
if (!message.fileMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the file has been deleted, show a deletion message instead of the file item.
|
||||
if (message.fileMetadata.isDeleted) {
|
||||
return (
|
||||
<div className = { cx(classes.fileMessageContainer, className) }>
|
||||
<div className = { classes.deletedFileMessage }>
|
||||
{t('chat.fileDeleted')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = { cx(classes.fileMessageContainer, className) }>
|
||||
{screenReaderHelpText && (
|
||||
<span className = 'sr-only'>
|
||||
{screenReaderHelpText}
|
||||
</span>
|
||||
)}
|
||||
<FileItem
|
||||
actionsVisible = { true }
|
||||
className = 'fileItem'
|
||||
file = { message.fileMetadata }
|
||||
iconSize = { 40 }
|
||||
onDownload = { handleDownload }
|
||||
onRemove = { handleRemove }
|
||||
showAuthor = { false }
|
||||
showRemoveButton = { isUploadEnabled }
|
||||
showTimestamp = { false } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileMessage;
|
||||
@@ -17,6 +17,7 @@ export interface IProps {
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
enablePrivateChat: boolean;
|
||||
isFileMessage?: boolean;
|
||||
isFromVisitor?: boolean;
|
||||
isLobbyMessage: boolean;
|
||||
message: string;
|
||||
@@ -60,7 +61,7 @@ const useStyles = makeStyles()(theme => {
|
||||
};
|
||||
});
|
||||
|
||||
const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, enablePrivateChat, displayName }: IProps) => {
|
||||
const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, enablePrivateChat, displayName, isFileMessage }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { classes, cx } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
@@ -72,6 +73,11 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
|
||||
|
||||
const participant = useSelector((state: IReduxState) => getParticipantById(state, participantId));
|
||||
|
||||
// If no menu items will be shown, don't render the menu button.
|
||||
if (!enablePrivateChat && isFileMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleMenuClick = useCallback(() => {
|
||||
setIsPopoverOpen(true);
|
||||
}, []);
|
||||
@@ -137,11 +143,13 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
|
||||
{t('Private Message')}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className = { classes.menuItem }
|
||||
onClick = { handleCopyClick }>
|
||||
{t('Copy')}
|
||||
</div>
|
||||
{!isFileMessage && (
|
||||
<div
|
||||
className = { classes.menuItem }
|
||||
onClick = { handleCopyClick }>
|
||||
{t('Copy')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -131,6 +131,16 @@ export function getUnreadCount(state: IReduxState) {
|
||||
return messagesCount - (lastReadIndex + 1) - reactionMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unread files count.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {number} The number of unread files.
|
||||
*/
|
||||
export function getUnreadFilesCount(state: IReduxState): number {
|
||||
return state['features/chat']?.unreadFilesCount || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the chat smileys are disabled or not.
|
||||
*
|
||||
@@ -283,3 +293,13 @@ export function getDisplayNameSuffix(message: IMessage): string {
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a message is a file message by verifying the presence of file metadata.
|
||||
*
|
||||
* @param {IMessage} message - The message to check.
|
||||
* @returns {boolean} True if the message contains file metadata, false otherwise.
|
||||
*/
|
||||
export function isFileMessage(message: IMessage): boolean {
|
||||
return Boolean(message?.fileMetadata);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import { addGif } from '../gifs/actions';
|
||||
import { extractGifURL, getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
|
||||
import { showMessageNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { resetNbUnreadPollsMessages } from '../polls/actions';
|
||||
import { resetUnreadPollsCount } from '../polls/actions';
|
||||
import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
|
||||
import { pushReactions } from '../reactions/actions.any';
|
||||
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
|
||||
@@ -130,7 +130,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.API.notifyChatUpdated(unreadCount, false);
|
||||
}
|
||||
} else if (focusedTab === ChatTabs.POLLS) {
|
||||
dispatch(resetNbUnreadPollsMessages());
|
||||
dispatch(resetUnreadPollsCount());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
}
|
||||
} else if (focusedTab === ChatTabs.POLLS) {
|
||||
dispatch(resetNbUnreadPollsMessages());
|
||||
dispatch(resetUnreadPollsCount());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UPDATE_CONFERENCE_METADATA } from '../base/conference/actionTypes';
|
||||
import { ILocalParticipant, IParticipant } from '../base/participants/types';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { ADD_FILE, _FILE_LIST_RECEIVED } from '../file-sharing/actionTypes';
|
||||
import { IVisitorChatParticipant } from '../visitors/types';
|
||||
|
||||
import {
|
||||
@@ -29,7 +30,8 @@ const DEFAULT_STATE = {
|
||||
messages: [],
|
||||
notifyPrivateRecipientsChangedTimestamp: undefined,
|
||||
reactions: {},
|
||||
nbUnreadMessages: 0,
|
||||
unreadMessagesCount: 0,
|
||||
unreadFilesCount: 0,
|
||||
privateMessageRecipient: undefined,
|
||||
lobbyMessageRecipient: undefined,
|
||||
isLobbyChatActive: false,
|
||||
@@ -53,9 +55,10 @@ export interface IChatState {
|
||||
name: string;
|
||||
} | ILocalParticipant;
|
||||
messages: IMessage[];
|
||||
nbUnreadMessages: number;
|
||||
notifyPrivateRecipientsChangedTimestamp?: number;
|
||||
privateMessageRecipient?: IParticipant | IVisitorChatParticipant;
|
||||
unreadFilesCount: number;
|
||||
unreadMessagesCount: number;
|
||||
width: {
|
||||
current: number;
|
||||
userSet: number | null;
|
||||
@@ -68,6 +71,7 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
const newMessage: IMessage = {
|
||||
displayName: action.displayName,
|
||||
error: action.error,
|
||||
fileMetadata: action.fileMetadata,
|
||||
isFromGuest: Boolean(action.isFromGuest),
|
||||
isFromVisitor: Boolean(action.isFromVisitor),
|
||||
participantId: action.participantId,
|
||||
@@ -98,7 +102,7 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
...state,
|
||||
lastReadMessage:
|
||||
action.hasRead ? newMessage : state.lastReadMessage,
|
||||
nbUnreadMessages: state.focusedTab !== ChatTabs.CHAT ? state.nbUnreadMessages + 1 : state.nbUnreadMessages,
|
||||
unreadMessagesCount: state.focusedTab !== ChatTabs.CHAT ? state.unreadMessagesCount + 1 : state.unreadMessagesCount,
|
||||
messages
|
||||
};
|
||||
}
|
||||
@@ -235,7 +239,8 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
return {
|
||||
...state,
|
||||
focusedTab: action.tabId,
|
||||
nbUnreadMessages: action.tabId === ChatTabs.CHAT ? 0 : state.nbUnreadMessages
|
||||
unreadMessagesCount: action.tabId === ChatTabs.CHAT ? 0 : state.unreadMessagesCount,
|
||||
unreadFilesCount: action.tabId === ChatTabs.FILE_SHARING ? 0 : state.unreadFilesCount
|
||||
};
|
||||
|
||||
case SET_CHAT_WIDTH: {
|
||||
@@ -271,6 +276,23 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
...state,
|
||||
notifyPrivateRecipientsChangedTimestamp: action.payload
|
||||
};
|
||||
|
||||
case ADD_FILE:
|
||||
return {
|
||||
...state,
|
||||
unreadFilesCount: action.shouldIncrementUnread ? state.unreadFilesCount + 1 : state.unreadFilesCount
|
||||
};
|
||||
|
||||
case _FILE_LIST_RECEIVED: {
|
||||
const remoteFilesCount = Object.values(action.files).filter(
|
||||
(file: any) => file.authorParticipantId !== action.localParticipantId
|
||||
).length;
|
||||
|
||||
return {
|
||||
...state,
|
||||
unreadFilesCount: remoteFilesCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { IFileMetadata } from '../file-sharing/types';
|
||||
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
fileMetadata?: IFileMetadata;
|
||||
isFromGuest?: boolean;
|
||||
isFromVisitor?: boolean;
|
||||
isReaction: boolean;
|
||||
@@ -33,7 +35,7 @@ export interface IChatProps extends WithTranslation {
|
||||
/**
|
||||
* Number of unread chat messages.
|
||||
*/
|
||||
_nbUnreadMessages: number;
|
||||
_unreadMessagesCount: number;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
|
||||
@@ -3,9 +3,7 @@ import React, { useCallback } from 'react';
|
||||
import {
|
||||
BackHandler,
|
||||
NativeModules,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
@@ -16,8 +14,6 @@ import { appNavigate } from '../../../app/actions.native';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
|
||||
import { isDisplayNameVisible } from '../../../base/config/functions.native';
|
||||
import { FULLSCREEN_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import Container from '../../../base/react/components/native/Container';
|
||||
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
|
||||
import TintedView from '../../../base/react/components/native/TintedView';
|
||||
@@ -96,11 +92,6 @@ interface IProps extends AbstractProps {
|
||||
*/
|
||||
_filmstripVisible: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether fullscreen (immersive) mode is enabled.
|
||||
*/
|
||||
_fullscreenEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines if the display name is visible.
|
||||
*/
|
||||
@@ -277,7 +268,6 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
override render() {
|
||||
const {
|
||||
_brandingStyles,
|
||||
_fullscreenEnabled
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -287,13 +277,6 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
_brandingStyles
|
||||
] }>
|
||||
<BrandingImageBackground />
|
||||
{
|
||||
Platform.OS === 'android'
|
||||
&& <StatusBar
|
||||
barStyle = 'light-content'
|
||||
hidden = { _fullscreenEnabled }
|
||||
translucent = { _fullscreenEnabled } />
|
||||
}
|
||||
{ this._renderContent() }
|
||||
</Container>
|
||||
);
|
||||
@@ -590,7 +573,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
_connecting: isConnecting(state),
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
|
||||
_isDisplayNameVisible: isDisplayNameVisible(state),
|
||||
_isParticipantsPaneOpen: isOpen,
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
|
||||
@@ -39,12 +39,14 @@ export function updateFileProgress(fileId: string, progress: number) {
|
||||
* Add a file.
|
||||
*
|
||||
* @param {IFileMetadata} file - The file to add to the state.
|
||||
* @param {boolean} shouldIncrementUnread - Whether to increment the unread count.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function addFile(file: IFileMetadata) {
|
||||
export function addFile(file: IFileMetadata, shouldIncrementUnread = false) {
|
||||
return {
|
||||
type: ADD_FILE,
|
||||
file
|
||||
file,
|
||||
shouldIncrementUnread
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
369
react/features/file-sharing/components/web/FileItem.tsx
Normal file
@@ -0,0 +1,369 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconDownload, IconTrash } from '../../../base/icons/svg';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
import {
|
||||
formatFileSize,
|
||||
formatTimestamp,
|
||||
getFileIcon
|
||||
} from '../../functions.any';
|
||||
import { IFileMetadata } from '../../types';
|
||||
|
||||
/**
|
||||
* Props for the FileItem component.
|
||||
*/
|
||||
interface IFileItemProps {
|
||||
|
||||
/**
|
||||
* Whether to show action buttons (download, remove).
|
||||
*/
|
||||
actionsVisible?: boolean;
|
||||
|
||||
/**
|
||||
* Additional CSS class name.
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* The file metadata to display.
|
||||
*/
|
||||
file: IFileMetadata;
|
||||
|
||||
/**
|
||||
* Size of the file icon in pixels (default: 64).
|
||||
*/
|
||||
iconSize?: number;
|
||||
|
||||
/**
|
||||
* Callback function when download button is clicked.
|
||||
*/
|
||||
onDownload?: (fileId: string) => void;
|
||||
|
||||
/**
|
||||
* Callback function when remove button is clicked.
|
||||
*/
|
||||
onRemove?: (fileId: string) => void;
|
||||
|
||||
/**
|
||||
* Whether to show the author/uploader information.
|
||||
*/
|
||||
showAuthor?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the remove button.
|
||||
*/
|
||||
showRemoveButton?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the timestamp.
|
||||
*/
|
||||
showTimestamp?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
buttonContainer: {
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: theme.spacing(2),
|
||||
position: 'absolute',
|
||||
right: theme.spacing(3),
|
||||
top: 0
|
||||
},
|
||||
|
||||
fileIconContainer: {
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
margin: 'auto'
|
||||
},
|
||||
|
||||
fileItem: {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(3),
|
||||
justifyContent: 'space-between',
|
||||
maxWidth: '100%',
|
||||
minWidth: 0,
|
||||
padding: theme.spacing(3),
|
||||
position: 'relative',
|
||||
|
||||
'& .actionIconVisibility': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s'
|
||||
},
|
||||
|
||||
'& .timestampVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
|
||||
'& .actionIconVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'& .timestampVisibility': {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
'&.focused .actionIconVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'&.focused .timestampVisibility': {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
fileItemDetails: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
gap: theme.spacing(1),
|
||||
justifyContent: 'center',
|
||||
minWidth: 0,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
|
||||
fileName: {
|
||||
...theme.typography.labelBold,
|
||||
gap: theme.spacing(1),
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
|
||||
fileAuthorParticipant: {
|
||||
alignItems: 'center',
|
||||
display: 'inline-flex',
|
||||
gap: theme.spacing(1)
|
||||
},
|
||||
|
||||
fileAuthorParticipantName: {
|
||||
...theme.typography.labelBold,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
|
||||
fileSize: {
|
||||
...theme.typography.labelRegular
|
||||
},
|
||||
|
||||
fileTimestamp: {
|
||||
...theme.typography.labelRegular,
|
||||
display: 'flex',
|
||||
lineHeight: '1.2rem',
|
||||
marginTop: theme.spacing(1),
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
iconButton: {
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: '8px',
|
||||
padding: 0,
|
||||
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.action01}`,
|
||||
borderRadius: '4px'
|
||||
}
|
||||
},
|
||||
|
||||
progressBar: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
progressFill: {
|
||||
backgroundColor: theme.palette.action01,
|
||||
height: '100%',
|
||||
transition: 'width 0.3s ease'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component for displaying file information in a consistent way across the application.
|
||||
*
|
||||
* @param {IFileItemProps} props - The component props.
|
||||
* @returns {JSX.Element} The FileItem component.
|
||||
*/
|
||||
const FileItem = ({
|
||||
actionsVisible = true,
|
||||
className = '',
|
||||
file,
|
||||
iconSize = 64,
|
||||
onDownload,
|
||||
onRemove,
|
||||
showAuthor = true,
|
||||
showRemoveButton = false,
|
||||
showTimestamp = true
|
||||
}: IFileItemProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
const [ isFocused, setIsFocused ] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const isUploading = (file.progress ?? 100) < 100;
|
||||
|
||||
/**
|
||||
* Handles the download button click.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleDownload = useCallback(() => {
|
||||
onDownload?.(file.fileId);
|
||||
}, [ onDownload, file.fileId ]);
|
||||
|
||||
/**
|
||||
* Handles the remove button click.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleRemove = useCallback(() => {
|
||||
onRemove?.(file.fileId);
|
||||
}, [ onRemove, file.fileId ]);
|
||||
|
||||
/**
|
||||
* Handles blur event to remove focus state.
|
||||
*
|
||||
* @param {React.FocusEvent} e - The blur event.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleBlur = useCallback((e: React.FocusEvent) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Handles focus event to set focus state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleFocus = useCallback(() => {
|
||||
setIsFocused(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cx(classes.fileItem, isFocused && 'focused', className) }
|
||||
key = { file.fileId }
|
||||
onBlur = { handleBlur }
|
||||
onFocus = { handleFocus }
|
||||
tabIndex = { -1 }
|
||||
title = { file.fileName }>
|
||||
{
|
||||
!isUploading && (
|
||||
<>
|
||||
<div className = { classes.fileIconContainer }>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { iconSize }
|
||||
src = { getFileIcon(file.fileType) } />
|
||||
</div>
|
||||
<div className = { classes.fileItemDetails }>
|
||||
<div className = { cx(classes.fileName, 'fileName') }>
|
||||
{ file.fileName }
|
||||
</div>
|
||||
<div className = { cx(classes.fileSize, 'fileSize') }>
|
||||
{ formatFileSize(file.fileSize) }
|
||||
</div>
|
||||
{
|
||||
showAuthor && (
|
||||
<div className = { classes.fileAuthorParticipant }>
|
||||
<Avatar
|
||||
displayName = { file.authorParticipantName }
|
||||
participantId = { file.authorParticipantId }
|
||||
size = { 16 } />
|
||||
<div className = { classes.fileAuthorParticipantName }>
|
||||
{ file.authorParticipantName }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
showTimestamp && (
|
||||
<div className = { `${classes.fileTimestamp} timestampVisibility` }>
|
||||
<pre>
|
||||
{ formatTimestamp(file.timestamp) }
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
actionsVisible && (
|
||||
<div className = { `${classes.buttonContainer} actionIconVisibility` }>
|
||||
{
|
||||
onDownload && (
|
||||
<button
|
||||
aria-label = { `${t('fileSharing.downloadFile')} ${file.fileName}` }
|
||||
className = { classes.iconButton }
|
||||
onClick = { handleDownload }
|
||||
type = 'button'>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 24 }
|
||||
src = { IconDownload } />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showRemoveButton && onRemove && (
|
||||
<button
|
||||
aria-label = { `${t('fileSharing.removeFile')} ${file.fileName}` }
|
||||
className = { classes.iconButton }
|
||||
onClick = { handleRemove }
|
||||
type = 'button'>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 24 }
|
||||
src = { IconTrash } />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
isUploading && (
|
||||
<div
|
||||
aria-label = { t('fileSharing.fileUploadProgress') }
|
||||
aria-valuemax = { 100 }
|
||||
aria-valuemin = { 0 }
|
||||
aria-valuenow = { file.progress }
|
||||
className = { classes.progressBar }
|
||||
role = 'progressbar'>
|
||||
<div
|
||||
className = { classes.progressFill }
|
||||
style = {{ width: `${file.progress}%` }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileItem;
|
||||
@@ -4,34 +4,21 @@ import { useDispatch, useSelector, useStore } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloudUpload, IconDownload, IconTrash } from '../../../base/icons/svg';
|
||||
import { IconCloudUpload } from '../../../base/icons/svg';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.web';
|
||||
import { downloadFile, removeFile } from '../../actions';
|
||||
import {
|
||||
formatFileSize,
|
||||
formatTimestamp,
|
||||
getFileIcon,
|
||||
isFileUploadingEnabled,
|
||||
processFiles
|
||||
} from '../../functions.any';
|
||||
|
||||
import FileItem from './FileItem';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
buttonContainer: {
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: theme.spacing(2),
|
||||
position: 'absolute',
|
||||
right: theme.spacing(3),
|
||||
top: 0
|
||||
},
|
||||
|
||||
container: {
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
@@ -64,60 +51,6 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
|
||||
fileIconContainer: {
|
||||
display: 'flex',
|
||||
margin: 'auto'
|
||||
},
|
||||
|
||||
fileItem: {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(3),
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(3),
|
||||
position: 'relative',
|
||||
|
||||
'& .actionIconVisibility': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s'
|
||||
},
|
||||
|
||||
'& .timestampVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
|
||||
'& .actionIconVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'& .timestampVisibility': {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
'&.focused .actionIconVisibility': {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
'&.focused .timestampVisibility': {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
|
||||
fileItemDetails: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 2,
|
||||
gap: theme.spacing(1),
|
||||
justifyContent: 'center',
|
||||
minWidth: 0
|
||||
},
|
||||
|
||||
fileList: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
@@ -129,40 +62,13 @@ const useStyles = makeStyles()(theme => {
|
||||
marginTop: 0,
|
||||
overflowY: 'auto',
|
||||
padding: 0,
|
||||
zIndex: 1
|
||||
},
|
||||
zIndex: 1,
|
||||
|
||||
fileName: {
|
||||
...theme.typography.labelBold,
|
||||
gap: theme.spacing(1),
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
|
||||
fileAuthorParticipant: {
|
||||
alignItems: 'center',
|
||||
display: 'inline-flex',
|
||||
gap: theme.spacing(1)
|
||||
},
|
||||
|
||||
fileAuthorParticipantName: {
|
||||
...theme.typography.labelBold,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
|
||||
fileSize: {
|
||||
...theme.typography.labelRegular
|
||||
},
|
||||
|
||||
fileTimestamp: {
|
||||
...theme.typography.labelRegular,
|
||||
display: 'flex',
|
||||
lineHeight: '1.2rem',
|
||||
marginTop: theme.spacing(1),
|
||||
textAlign: 'center',
|
||||
'& > li': {
|
||||
listStyleType: 'none',
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}
|
||||
},
|
||||
|
||||
hiddenInput: {
|
||||
@@ -184,20 +90,6 @@ const useStyles = makeStyles()(theme => {
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
progressBar: {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
progressFill: {
|
||||
backgroundColor: theme.palette.action01,
|
||||
height: '100%',
|
||||
transition: 'width 0.3s ease'
|
||||
},
|
||||
|
||||
uploadButton: {
|
||||
bottom: theme.spacing(4),
|
||||
cursor: 'pointer',
|
||||
@@ -210,33 +102,6 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
uploadIcon: {
|
||||
margin: '0 auto'
|
||||
},
|
||||
|
||||
actionIcon: {
|
||||
background: 'transparent',
|
||||
border: 0,
|
||||
cursor: 'pointer',
|
||||
padding: theme.spacing(1),
|
||||
visibility: 'hidden',
|
||||
'&:focus': {
|
||||
outline: `2px solid ${theme.palette.action01}`
|
||||
}
|
||||
},
|
||||
|
||||
iconButton: {
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
marginLeft: '8px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${theme.palette.action01}`,
|
||||
borderRadius: '4px'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -244,7 +109,6 @@ const useStyles = makeStyles()(theme => {
|
||||
const FileSharing = () => {
|
||||
const { classes } = useStyles();
|
||||
const [ isDragging, setIsDragging ] = useState(false);
|
||||
const [ isFocused, setIsFocused ] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const uploadButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const { t } = useTranslation();
|
||||
@@ -349,92 +213,12 @@ const FileSharing = () => {
|
||||
<ul className = { classes.fileList }>
|
||||
{
|
||||
sortedFiles.map(file => (
|
||||
<li
|
||||
className = { `${classes.fileItem} ${isFocused ? 'focused' : ''}` }
|
||||
key = { file.fileId }
|
||||
// Only remove focus when leaving the whole fileItem, not just moving between its buttons
|
||||
onBlur = { e => !e.currentTarget.contains(e.relatedTarget as Node) && setIsFocused(false) }
|
||||
onFocus = { () => setIsFocused(true) }
|
||||
tabIndex = { -1 }
|
||||
title = { file.fileName }>
|
||||
{
|
||||
(file.progress ?? 100) === 100 && (
|
||||
<>
|
||||
<div className = { classes.fileIconContainer }>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 64 }
|
||||
src = { getFileIcon(file.fileType) } />
|
||||
</div>
|
||||
<div className = { classes.fileItemDetails }>
|
||||
<div className = { classes.fileName }>
|
||||
{ file.fileName }
|
||||
</div>
|
||||
<div className = { classes.fileSize }>
|
||||
{ formatFileSize(file.fileSize) }
|
||||
</div>
|
||||
<div className = { classes.fileAuthorParticipant }>
|
||||
<Avatar
|
||||
displayName = { file.authorParticipantName }
|
||||
participantId = { file.authorParticipantId }
|
||||
size = { 16 } />
|
||||
<div className = { classes.fileAuthorParticipantName }>
|
||||
{ file.authorParticipantName }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className = { `${classes.fileTimestamp} timestampVisibility` }>
|
||||
<pre>
|
||||
{ formatTimestamp(file.timestamp) }
|
||||
</pre>
|
||||
</div>
|
||||
<div className = { `${classes.buttonContainer} actionIconVisibility` }>
|
||||
<button
|
||||
aria-label = { `${t('fileSharing.downloadFile')} ${file.fileName}` }
|
||||
className = { `${classes.iconButton}` }
|
||||
onClick = { () => dispatch(downloadFile(file.fileId)) }
|
||||
type = 'button'>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 24 }
|
||||
src = { IconDownload } />
|
||||
</button>
|
||||
|
||||
{
|
||||
isUploadEnabled && (
|
||||
<button
|
||||
aria-label = { `${t('fileSharing.removeFile')} ${file.fileName}` }
|
||||
className = { `${classes.iconButton}` }
|
||||
onClick = { () => dispatch(removeFile(file.fileId)) }
|
||||
type = 'button'>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 24 }
|
||||
src = { IconTrash } />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(file.progress ?? 100) < 100 && (
|
||||
<>
|
||||
<div
|
||||
aria-label = { t('fileSharing.fileUploadProgress') }
|
||||
aria-valuemax = { 100 }
|
||||
aria-valuemin = { 0 }
|
||||
aria-valuenow = { file.progress }
|
||||
className = { classes.progressBar }
|
||||
role = 'progressbar'>
|
||||
<div
|
||||
className = { classes.progressFill }
|
||||
style = {{ width: `${file.progress}%` }} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<li key = { file.fileId }>
|
||||
<FileItem
|
||||
file = { file }
|
||||
onDownload = { fileId => dispatch(downloadFile(fileId)) }
|
||||
onRemove = { fileId => dispatch(removeFile(fileId)) }
|
||||
showRemoveButton = { isUploadEnabled } />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -6,10 +6,21 @@ import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { addMessage, editMessage } from '../chat/actions.any';
|
||||
import { ChatTabs, MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../chat/constants';
|
||||
import { showErrorNotification, showNotification, showSuccessNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
|
||||
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
|
||||
|
||||
import { DOWNLOAD_FILE, REMOVE_FILE, UPLOAD_FILES, _FILE_LIST_RECEIVED, _FILE_REMOVED } from './actionTypes';
|
||||
import {
|
||||
ADD_FILE,
|
||||
DOWNLOAD_FILE,
|
||||
REMOVE_FILE,
|
||||
UPDATE_FILE_UPLOAD_PROGRESS,
|
||||
UPLOAD_FILES,
|
||||
_FILE_LIST_RECEIVED,
|
||||
_FILE_REMOVED
|
||||
} from './actionTypes';
|
||||
import { addFile, removeFile, updateFileProgress } from './actions';
|
||||
import { getFileExtension } from './functions.any';
|
||||
import logger from './logger';
|
||||
@@ -23,12 +34,40 @@ import { downloadFile } from './utils';
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => state['features/base/conference'].conference,
|
||||
(conference, { dispatch }, previousConference) => {
|
||||
(conference, { dispatch, getState }, previousConference) => {
|
||||
if (conference && !previousConference) {
|
||||
conference.on(JitsiConferenceEvents.FILE_SHARING_FILE_ADDED, (file: IFileMetadata) => {
|
||||
dispatch(addFile(file));
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const isRemoteFile = file.authorParticipantId !== localParticipant?.id;
|
||||
const { isOpen, focusedTab } = state['features/chat'];
|
||||
const isFileSharingTabVisible = isOpen && focusedTab === ChatTabs.FILE_SHARING;
|
||||
|
||||
dispatch(addFile(file, isRemoteFile && !isFileSharingTabVisible));
|
||||
|
||||
if (isRemoteFile && !isFileSharingTabVisible) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'fileSharing.newFileNotification',
|
||||
titleArguments: { participantName: file.authorParticipantName, fileName: file.fileName }
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
});
|
||||
conference.on(JitsiConferenceEvents.FILE_SHARING_FILE_REMOVED, (fileId: string) => {
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { files } = state['features/file-sharing'];
|
||||
const { isOpen, focusedTab } = state['features/chat'];
|
||||
const removedFile = files.get(fileId);
|
||||
const isFileSharingTabVisible = isOpen && focusedTab === ChatTabs.FILE_SHARING;
|
||||
|
||||
if (removedFile && removedFile.authorParticipantId === localParticipant?.id && !isFileSharingTabVisible) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'fileSharing.fileRemovedByOther',
|
||||
titleArguments: { fileName: removedFile.fileName },
|
||||
appearance: NOTIFICATION_TYPE.WARNING
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: _FILE_REMOVED,
|
||||
fileId
|
||||
@@ -36,9 +75,13 @@ StateListenerRegistry.register(
|
||||
});
|
||||
|
||||
conference.on(JitsiConferenceEvents.FILE_SHARING_FILES_RECEIVED, (files: object) => {
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch({
|
||||
type: _FILE_LIST_RECEIVED,
|
||||
files
|
||||
files,
|
||||
localParticipantId: localParticipant?.id
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -52,6 +95,17 @@ StateListenerRegistry.register(
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case I_AM_VISITOR_MODE: {
|
||||
if (!action.iAmVisitor) {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
conference?.getFileSharing()?.requestFileList?.();
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
case UPLOAD_FILES: {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
@@ -65,6 +119,44 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
case UPDATE_FILE_UPLOAD_PROGRESS:
|
||||
case ADD_FILE: {
|
||||
const result = next(action);
|
||||
const state = store.getState();
|
||||
const { files } = state['features/file-sharing'];
|
||||
const file = action.type === ADD_FILE ? action.file as IFileMetadata : files.get(action.fileId);
|
||||
|
||||
if (!file) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const isLocalFile = localParticipant?.id === file.authorParticipantId;
|
||||
|
||||
// Only dispatch chat message for fully uploaded files (progress === 100).
|
||||
// Files that are still uploading have progress < 100, so we skip creating the message.
|
||||
// Once upload completes, for the local participant the file is broadcast with progress: 100 and the message
|
||||
// is created. Remote participants receive the file metadata only once the file is successfully uploaded and
|
||||
// the progress field will be undefined.
|
||||
if (file.progress === 100 || !isLocalFile) {
|
||||
store.dispatch(addMessage({
|
||||
displayName: file.authorParticipantName,
|
||||
fileMetadata: file,
|
||||
hasRead: isLocalFile,
|
||||
isReaction: false,
|
||||
lobbyChat: false,
|
||||
message: '', // Empty message as the file metadata contains all info
|
||||
messageId: file.fileId,
|
||||
messageType: isLocalFile ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
|
||||
participantId: file.authorParticipantId,
|
||||
privateMessage: false,
|
||||
timestamp: file.timestamp
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case REMOVE_FILE: {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
@@ -151,6 +243,27 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
case _FILE_REMOVED: {
|
||||
const result = next(action);
|
||||
const state = store.getState();
|
||||
const { messages } = state['features/chat'];
|
||||
|
||||
// Find the message corresponding to this file and mark it as deleted.
|
||||
const fileMessage = messages.find(msg => msg.messageId === action.fileId);
|
||||
|
||||
if (fileMessage?.fileMetadata) {
|
||||
// Replace the file metadata with just the isDeleted flag to avoid keeping unnecessary data.
|
||||
store.dispatch(editMessage({
|
||||
...fileMessage,
|
||||
fileMetadata: {
|
||||
isDeleted: true
|
||||
} as any
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import { ADD_FILE, UPDATE_FILE_UPLOAD_PROGRESS, _FILE_LIST_RECEIVED, _FILE_REMOVED } from './actionTypes';
|
||||
import {
|
||||
ADD_FILE,
|
||||
UPDATE_FILE_UPLOAD_PROGRESS,
|
||||
_FILE_LIST_RECEIVED,
|
||||
_FILE_REMOVED
|
||||
} from './actionTypes';
|
||||
import { IFileMetadata } from './types';
|
||||
|
||||
export interface IFileSharingState {
|
||||
@@ -20,6 +25,7 @@ ReducerRegistry.register<IFileSharingState>('features/file-sharing',
|
||||
newFiles.set(action.file.fileId, action.file);
|
||||
|
||||
return {
|
||||
...state,
|
||||
files: newFiles
|
||||
};
|
||||
}
|
||||
@@ -30,6 +36,7 @@ ReducerRegistry.register<IFileSharingState>('features/file-sharing',
|
||||
newFiles.delete(action.fileId);
|
||||
|
||||
return {
|
||||
...state,
|
||||
files: newFiles
|
||||
};
|
||||
}
|
||||
@@ -43,12 +50,14 @@ ReducerRegistry.register<IFileSharingState>('features/file-sharing',
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
files: newFiles
|
||||
};
|
||||
}
|
||||
|
||||
case _FILE_LIST_RECEIVED: {
|
||||
return {
|
||||
...state,
|
||||
files: new Map(Object.entries(action.files))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface IFileMetadata {
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
fileType: string;
|
||||
isDeleted?: boolean;
|
||||
progress?: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
|
||||
|
||||
import { setRemoteParticipants } from './actions';
|
||||
import { isFilmstripScrollVisible } from './functions';
|
||||
|
||||
/**
|
||||
* Returns an array containing the first `count` items from a set.
|
||||
*
|
||||
* @param {Set<T>} set - The set from which to take items.
|
||||
* @param {number} count - The number of items to take.
|
||||
* @returns {T[]} An array containing the taken items.
|
||||
* @private
|
||||
*/
|
||||
function _takeFirstN<T>(set: Set<T>, count: number): T[] {
|
||||
const result: T[] = [];
|
||||
|
||||
for (const item of set) {
|
||||
if (result.length >= count) break;
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reorderd list of the remote participants.
|
||||
*
|
||||
@@ -16,7 +32,7 @@ import { isFilmstripScrollVisible } from './functions';
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string) {
|
||||
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string): void {
|
||||
const state = store.getState();
|
||||
let reorderedParticipants = [];
|
||||
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
|
||||
@@ -33,14 +49,26 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
}
|
||||
|
||||
const {
|
||||
dominantSpeaker,
|
||||
fakeParticipants,
|
||||
previousSpeakers,
|
||||
sortedRemoteParticipants
|
||||
} = state['features/base/participants'];
|
||||
const { visibleRemoteParticipants } = state['features/filmstrip'];
|
||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
|
||||
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
|
||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||
const speakers = getActiveSpeakersToBeDisplayed(state);
|
||||
const speakers: Set<string> = new Set();
|
||||
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
|
||||
const config = state['features/base/config'];
|
||||
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
|
||||
|
||||
// Generate the remote active speakers list.
|
||||
if (dominant && !dominant.local) {
|
||||
speakers.add(dominant.id);
|
||||
}
|
||||
previousSpeakers.forEach(id => speakers.add(id));
|
||||
|
||||
for (const screenshare of screenShareParticipants) {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
@@ -57,7 +85,16 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
remoteParticipants.delete(speaker);
|
||||
}
|
||||
|
||||
// Always update the order of the thubmnails.
|
||||
// Calculate the number of slots available for active speakers and then sort them alphabetically to ensure
|
||||
// consistent order.
|
||||
const numberOfActiveSpeakerSlots
|
||||
= visibleRemoteParticipants.size - (screenShareParticipants.length * 2) - sharedVideos.length;
|
||||
const activeSpeakersDisplayed = _takeFirstN(speakers, numberOfActiveSpeakerSlots)
|
||||
.sort((a: string, b: string) => {
|
||||
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
|
||||
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
|
||||
});
|
||||
|
||||
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
|
||||
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||
|
||||
@@ -67,10 +104,11 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// Always update the order of the thumbnails.
|
||||
reorderedParticipants = [
|
||||
...participantsWithScreenShare,
|
||||
...sharedVideos,
|
||||
...Array.from(speakers.keys()),
|
||||
...activeSpeakersDisplayed,
|
||||
...Array.from(remoteParticipants.keys())
|
||||
];
|
||||
|
||||
|
||||
@@ -38,9 +38,10 @@ export const getKeyboardKey = (e: KeyboardEvent): string => {
|
||||
// If alt is pressed a different char can be returned so this takes
|
||||
// the char from the code. It also prefixes with a colon to differentiate
|
||||
// alt combo from simple keypress.
|
||||
if (altKey) {
|
||||
const replacedKey = code.replace('Key', '');
|
||||
|
||||
const replacedKey = code.replace('Key', '');
|
||||
|
||||
if (altKey) {
|
||||
return `:${replacedKey}`;
|
||||
}
|
||||
|
||||
@@ -54,6 +55,10 @@ export const getKeyboardKey = (e: KeyboardEvent): string => {
|
||||
return `-${key}`;
|
||||
}
|
||||
|
||||
if (code.startsWith('Key')) {
|
||||
return replacedKey;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
getVirtualScreenshareParticipantByOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { getAutoPinSetting } from '../video-layout/functions';
|
||||
|
||||
import {
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
SET_LARGE_VIDEO_DIMENSIONS,
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
} from './actionTypes';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Action to select the participant to be displayed in LargeVideo based on the
|
||||
@@ -34,12 +34,8 @@ export function selectParticipantInLargeVideo(participant?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep Etherpad open.
|
||||
if (state['features/etherpad'].editing) {
|
||||
// Skip large video updates when the large video container is hidden.
|
||||
if (shouldHideLargeVideo(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,18 +161,26 @@ function _electParticipantInLargeVideo(state: IReduxState) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next, pick the dominant speaker (other than self).
|
||||
// Next, pick the dominant speaker or the last active speaker if the dominant speaker is local.
|
||||
participant = getDominantSpeakerParticipant(state);
|
||||
if (participant && !participant.local) {
|
||||
// Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
|
||||
// auto_pin_latest_screen_share setting is disabled.
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
|
||||
let speakerId: string | undefined;
|
||||
|
||||
return screenshareParticipant?.id ?? participant.id;
|
||||
if (participant?.local) {
|
||||
const { previousSpeakers } = state['features/base/participants'];
|
||||
|
||||
if (previousSpeakers?.size) {
|
||||
speakerId = previousSpeakers.keys().next().value;
|
||||
}
|
||||
} else if (participant) {
|
||||
speakerId = participant.id;
|
||||
}
|
||||
|
||||
// In case this is the local participant.
|
||||
participant = undefined;
|
||||
// Return the screensharing participant id associated with this endpoint.
|
||||
if (speakerId) {
|
||||
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, speakerId);
|
||||
|
||||
return screenshareParticipant?.id ?? speakerId;
|
||||
}
|
||||
|
||||
// Next, pick the most recent participant with video.
|
||||
const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
|
||||
/**
|
||||
* Selector for the participant currently displaying on the large video.
|
||||
@@ -12,3 +14,17 @@ export function getLargeVideoParticipant(state: IReduxState) {
|
||||
|
||||
return getParticipantById(state, participantId ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the large video container should be hidden.
|
||||
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
|
||||
* or when editing etherpad.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} True if large video should be hidden, false otherwise.
|
||||
*/
|
||||
export function shouldHideLargeVideo(state: IReduxState): boolean {
|
||||
return shouldDisplayTileView(state)
|
||||
|| isStageFilmstripAvailable(state, 2)
|
||||
|| Boolean(state['features/etherpad']?.editing);
|
||||
}
|
||||
|
||||
26
react/features/large-video/subscriber.any.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
|
||||
import { selectParticipantInLargeVideo } from './actions.any';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Updates the large video when transitioning from a hidden state to visible state.
|
||||
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
|
||||
* whiteboard, or etherpad editing modes.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => shouldHideLargeVideo(state),
|
||||
/* listener */ (isHidden, { dispatch }) => {
|
||||
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
|
||||
// Otherwise set it to undefined because we don't show the large video.
|
||||
if (!isHidden) {
|
||||
dispatch(selectParticipantInLargeVideo());
|
||||
} else {
|
||||
dispatch({
|
||||
type: SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
participantId: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
import './subscriber.any';
|
||||
|
||||
@@ -4,6 +4,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
|
||||
|
||||
import { getLargeVideoParticipant } from './functions';
|
||||
import './subscriber.any';
|
||||
|
||||
/**
|
||||
* Updates the on stage participant video.
|
||||
|
||||