Compare commits

..

1 Commits

Author SHA1 Message Date
damencho
5ff3219935 debug: Drop. 2025-10-02 14:40:39 -05:00
254 changed files with 3975 additions and 3682 deletions

View File

@@ -14,12 +14,3 @@ trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[*.{java,kt}]
indent_size = 4
[*.xml]
indent_size = 2
[*.{swift,m,mm,h}]
indent_size = 4

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#17A0DB</color>
<color name="navigationBarColor">#040404</color>
<color name="statusBarColor">#040404</color>
<color name="navigationBarColor">#161618</color>
</resources>

View File

@@ -3,7 +3,6 @@
<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>

View File

@@ -73,6 +73,7 @@ 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')

View File

@@ -62,8 +62,7 @@
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
android:exported="false">
<meta-data android:name="org.jitsi.meet.sdk.JitsiInitializer"
android:value="androidx.startup" />
</provider>

View File

@@ -96,6 +96,9 @@ 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(() -> {
@@ -135,12 +138,7 @@ public class JitsiMeetActivity extends AppCompatActivity
JitsiMeetActivityDelegate.onHostResume(this);
setContentView(R.layout.activity_jitsi_meet);
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));
}
addTopBottomInsets(getWindow(),findViewById(android.R.id.content));
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages();

View File

@@ -37,6 +37,7 @@ public class JitsiMeetActivityDelegate {
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* Tells whether or not the permissions request is currently in progress.
@@ -141,6 +142,11 @@ public class JitsiMeetActivityDelegate {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
if (permissionsCallback != null) {
permissionsCallback.invoke();
permissionsCallback = null;
}
}
/**
@@ -163,10 +169,15 @@ public class JitsiMeetActivityDelegate {
public static void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
// Invoke the callback immediately
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
permissionsCallback = new Callback() {
@Override
public void invoke(Object... args) {
if (permissionListener != null
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
}
};
}
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {

View File

@@ -99,7 +99,6 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
Activity activity = (Activity) context;
PermissionListener listener = new PermissionListener() {
@Override
@@ -135,7 +134,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
activity,
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
listener
@@ -160,20 +159,12 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
} catch (Exception e) {
// Handle ForegroundServiceStartNotAllowedException when app is in background and cannot start foreground service.
// See: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start#wiu-restrictions
JitsiMeetLogger.w(TAG + " Failed to start foreground service", e);
stopSelf();
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}

View File

@@ -101,6 +101,7 @@ 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(),

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="navigationBarColor">#040404</color>
<color name="statusBarColor">#040404</color>
</resources>

View File

@@ -1,6 +1,3 @@
<resources>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:navigationBarColor">@color/navigationBarColor</item>
<item name="android:statusBarColor">@color/statusBarColor</item>
</style>
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
</resources>

View File

@@ -24,6 +24,8 @@ 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'

View File

@@ -1375,7 +1375,7 @@ export default {
}
APP.store.dispatch(updateRemoteParticipantFeatures(user));
logger.log(`USER ${id} connected`);
logger.log(`USER ${id} connected:`, user);
APP.UI.addUser(user);
});

View File

@@ -139,9 +139,6 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables chat feature entirely including notifications, sounds, and private messages.
// disableChat: false,
// Disables demote button from self-view
// disableSelfDemote: false,
@@ -808,9 +805,7 @@ 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: '',
// // Shows the hangup button in the lobby screen.
// showHangUp: true,
// preCallTestICEUrl: ''
// },
// When 'true', the user cannot edit the display name.
@@ -1610,10 +1605,6 @@ 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'],

View File

@@ -44,6 +44,7 @@ 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
#

View File

@@ -1473,7 +1473,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-webrtc (124.0.7):
- react-native-webrtc (124.0.4):
- JitsiWebRTC (~> 124.0.0)
- React-Core
- react-native-webview (13.13.5):
@@ -1870,6 +1870,8 @@ PODS:
- React-Core
- RNSVG (15.11.2):
- React-Core
- RNWatch (1.1.0):
- React
- SocketRocket (0.7.1)
- SplashView (0.0.18):
- DoubleConversion
@@ -1991,6 +1993,7 @@ 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`)
@@ -2200,6 +2203,8 @@ 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:
@@ -2274,7 +2279,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1
react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74
react-native-webrtc: 2261a482150195092246fe70b3aff976f2e11ec5
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
react-native-worklets-core: b59cf88762c8fb6132d8796babd4cec15217d6f0
React-nativeconfig: ecf4dc92c40b97e2b3f0c619938f78bfd6507b08
@@ -2316,10 +2321,11 @@ SPEC CHECKSUMS:
RNScreens: 9ef996b6041d0960a4794a845f7d0808b171b4ef
RNSound: 314cc5226453ef4a3314a196c65e8a65e5106a7b
RNSVG: 67de7abef81f367387b708ba6d2acefe7d4f5895
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SplashView: ed71a114c3ffe60dc3a9e5aa2cefb352c3794a70
Yoga: 31a098f74c16780569aebd614a0f37a907de0189
PODFILE CHECKSUM: 7c37a89916893e11159576c8b308b7b5c25246c9
PODFILE CHECKSUM: eac4bba07b2f30174fc20bccbaf64f86676d9b1f
COCOAPODS: 1.16.2

View File

@@ -7,6 +7,16 @@
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 */; };
@@ -26,11 +36,27 @@
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 */;
@@ -55,12 +81,24 @@
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;
@@ -80,7 +118,19 @@
/* 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>"; };
@@ -106,10 +156,19 @@
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;
@@ -122,6 +181,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
1F021A8A5B056078665DE530 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4EB06020260E026600F524C5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -150,6 +216,34 @@
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 = (
@@ -185,10 +279,11 @@
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>";
@@ -198,6 +293,8 @@
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */,
);
name = Products;
@@ -213,17 +310,44 @@
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" */;
@@ -245,6 +369,7 @@
buildRules = (
);
dependencies = (
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
4EB0602A260E026600F524C5 /* PBXTargetDependency */,
);
name = JitsiMeet;
@@ -279,6 +404,16 @@
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 = {
@@ -309,12 +444,31 @@
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;
@@ -468,6 +622,20 @@
/* 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;
@@ -492,6 +660,16 @@
/* 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 */;
@@ -500,6 +678,14 @@
/* 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 = (
@@ -511,10 +697,155 @@
/* 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;
@@ -551,6 +882,7 @@
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;
@@ -799,6 +1131,24 @@
/* 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 = (

View File

@@ -0,0 +1,92 @@
{
"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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "hangup@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "mute-off@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "mute-on@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,85 @@
<?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>

View File

@@ -0,0 +1,33 @@
<?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>

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "jitsi@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,81 @@
/*
* 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)
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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 youre 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 youre done.
connectivityTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once youre 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;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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)
}
}
}

View File

@@ -0,0 +1,44 @@
<?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>

View File

@@ -0,0 +1,94 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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";
}

View File

@@ -0,0 +1,71 @@
/*
* 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)) "
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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!
}

View File

@@ -39,6 +39,36 @@ 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,

127
ios/travis-ci/build-ipa.sh Executable file
View File

@@ -0,0 +1,127 @@
#!/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

View File

@@ -120,7 +120,7 @@
"messageAccessibleTitle": "{{user}} dit: ",
"messageAccessibleTitleMe": "Je dis: ",
"messageTo": "Message privé à {{recipient}}",
"messagebox": "Envoyer un message",
"messagebox": "Saisissez un message",
"newMessages": "Nouveaux messages",
"nickname": {
"popover": "Choisissez un pseudonyme",

View File

@@ -114,9 +114,6 @@
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
"everyone": "Tutti",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"fileAccessibleTitle": "{{user}} ha caricato un file",
"fileAccessibleTitleMe": "ho caricato un file",
"fileDeleted": "Un file è stato eliminato",
"guestsChatIndicator": "(ospite)",
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
"message": "Messaggio",
@@ -283,6 +280,7 @@
"Submit": "Invia",
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
"WaitForHostMsg": "La riunione non è ancora iniziata. Se sei l'organizzatore, per favore autenticati. Altrimenti, attendi l'arrivo dell'organizzatore.",
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
"WaitingForHostButton": "Attendi l'organizzatore",
"WaitingForHostTitle": "In attesa dell'organizzatore…",
@@ -525,7 +523,6 @@
"tokenAuthFailedWithReasons": "Non sei autorizzato a partecipare a questa chiamata. Possibile motivo: {{reason}}",
"tokenAuthUnsupported": "Il token URL non è supportato.",
"transcribing": "Trascrizione in corso",
"unauthenticatedAccessDisabled": "Questa chiamata richiede l'autenticazione. Si prega di accedere per procedere.",
"unlockRoom": "Rimuovi la $t(lockRoomPassword) alla riunione",
"user": "Utente",
"userIdentifier": "Identificatore utente",
@@ -573,12 +570,10 @@
"downloadStarted": "Download del file iniziato",
"dragAndDrop": "Trascina e rilascia i file qui o da qualsiasi altra parte nella schermata",
"fileAlreadyUploaded": "Questo file è già stato caricato nella riunione.",
"fileRemovedByOther": "Il tuo file '{{ fileName }}' è stato rimosso",
"fileTooLargeDescription": "Assicurati che il file non superi {{ maxFileSize }}.",
"fileTooLargeTitle": "Il file selezionato è troppo grande",
"fileUploadProgress": "Caricamento del file in corso",
"fileUploadedSuccessfully": "Il file è stato caricato con successo",
"newFileNotification": "{{ participantName }} ha condiviso '{{ fileName }}'",
"removeFile": "Rimuovi",
"removeFileSuccess": "File rimosso con successo",
"uploadFailedDescription": "Si prega di riprovare.",
@@ -753,8 +748,7 @@
"notificationTitle": "Sala d'attesa",
"passwordJoinButton": "Entra",
"title": "Sala d'attesa",
"toggleLabel": "Attiva sala d'attesa",
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
"toggleLabel": "Attiva sala d'attesa"
},
"localRecording": {
"clientState": {
@@ -970,9 +964,6 @@
"by": "Da {{ name }}",
"closeButton": "Chiudi sondaggio",
"create": {
"accessibilityLabel": {
"send": "Invia sondaggio"
},
"addOption": "Aggiungi opzione",
"answerPlaceholder": "Opzione {{index}}",
"cancel": "Annulla",
@@ -981,7 +972,8 @@
"pollQuestion": "Domanda del sondaggio",
"questionPlaceholder": "Fai una domanda",
"removeOption": "Elimina opzione",
"save": "Salva"
"save": "Salva",
"send": "Invia"
},
"errors": {
"notUniqueOption": "Le opzioni devono essere uniche"
@@ -1625,8 +1617,6 @@
"noMainParticipantsTitle": "La riunione non è ancora iniziata.",
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
"requestToJoin": "Mano alzata",
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
"title": "Sei uno spettatore nella riunione"
},
"waitingMessage": "Ti unirai alla riunione quando inizierà!"

View File

@@ -109,12 +109,9 @@
}
},
"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:",
@@ -125,10 +122,7 @@
"nickname": {
"popover": "Välj ett namn",
"title": "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"
"titleWithPolls": "Skriv in ett namn för att börja använda chatten"
},
"noMessagesMessage": "Det finns ännu inga meddelanden i mötet. Påbörja en konversation!",
"privateNotice": "Privat meddelande till {{recipient}}",
@@ -137,16 +131,11 @@
"systemDisplayName": "",
"tabs": {
"chat": "Chatt",
"closedCaptions": "Undertexter",
"fileSharing": "Fildelning",
"polls": "Omröstningar"
},
"title": "Chatt",
"titleWithCC": "Undertexter",
"titleWithFeatures": "Chatt och",
"titleWithFileSharing": "Filer",
"titleWithPolls": "Omröstningar",
"you": "dig"
"titleWithPolls": "Chatt",
"you": "du"
},
"chromeExtensionBanner": {
"buttonText": "Installera Chrome-tillägg",
@@ -155,10 +144,6 @@
"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…"
},
@@ -278,8 +263,7 @@
"Remove": "Ta bort",
"Share": "Dela",
"Submit": "Skicka",
"Understand": "Jag förstår, låt min mikrofon vara avstängd tillsvidare",
"UnderstandAndUnmute": "Jag förstår, starta min mikrofon",
"WaitForHostMsg": "Konferensen har inte börjat än. Autentisera konferensen om du är värd. Vänta annars på att värden startar konferensen.",
"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…",
@@ -301,12 +285,6 @@
"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.",
@@ -321,7 +299,6 @@
"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.",
@@ -354,12 +331,11 @@
"internalError": "Ett fel uppstod. Fel: {{error}}",
"internalErrorTitle": "Internt fel",
"kickMessage": "Du kan kontakta {{participantDisplayName}} för mer information.",
"kickParticipantButton": "Ta bort deltagaren från mötet",
"kickParticipantButton": "Ta bort 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",
@@ -382,34 +358,22 @@
"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",
@@ -429,10 +393,6 @@
"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.",
@@ -522,7 +482,6 @@
"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",
@@ -563,25 +522,6 @@
"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"
@@ -750,8 +690,7 @@
"notificationTitle": "Väntrum",
"passwordJoinButton": "Anslut",
"title": "Lobby",
"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."
"toggleLabel": "Aktivera väntrum"
},
"localRecording": {
"clientState": {
@@ -794,10 +733,7 @@
"me": "jag",
"notify": {
"OldElectronAPPTitle": "Säkerhetsproblem!",
"allowAll": "Tillåt",
"allowAudio": "Tillåt ljud",
"allowDesktop": "Tillåt skärmdelning",
"allowVideo": "Tillåt kamera",
"allowAction": "Tillåt",
"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!",
@@ -810,10 +746,8 @@
"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.",
"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.",
"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.",
"disconnected": "frånkopplad",
"displayNotifications": "Visa aviseringar för",
"dontRemindMe": "Påminn mig inte",
@@ -868,7 +802,6 @@
"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",
@@ -892,8 +825,6 @@
"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.",
@@ -912,14 +843,11 @@
"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",
@@ -931,8 +859,6 @@
"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",
@@ -942,15 +868,12 @@
"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",
@@ -967,9 +890,6 @@
"by": "Av {{ name }}",
"closeButton": "Stäng omröstning",
"create": {
"accessibilityLabel": {
"send": "Skicka omröstning"
},
"addOption": "Lägg till alternativ",
"answerPlaceholder": "Alternativ",
"cancel": "Avbryt",
@@ -978,7 +898,8 @@
"pollQuestion": "Fråga för omröstning",
"questionPlaceholder": "Ställ en fråga",
"removeOption": "Ta bort alternativ",
"save": "Spara"
"save": "Spara",
"send": "Skicka"
},
"errors": {
"notUniqueOption": "Alternativ måste vara unika"
@@ -1179,7 +1100,6 @@
"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.",
@@ -1209,7 +1129,6 @@
"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",
@@ -1269,7 +1188,6 @@
"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",
@@ -1306,7 +1224,6 @@
"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",
@@ -1328,7 +1245,7 @@
"help": "Hjälp",
"hideWhiteboard": "Dölj whiteboard",
"invite": "Bjud in personer",
"kick": "Ta bort deltagare från möte",
"kick": "Ta bort deltagare",
"laugh": "Skratta",
"leaveConference": "Lämna mötet",
"like": "Tummen upp",
@@ -1385,20 +1302,6 @@
"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",
@@ -1411,7 +1314,6 @@
"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",
@@ -1470,7 +1372,6 @@
"reactionHeart": "Skicka kärlek",
"reactionLaugh": "Skratta",
"reactionLike": "Skicka tummen upp",
"reactionLove": "Skicka kärleksreaktion",
"reactionSilence": "Skicka tyst reaktion",
"reactionSurprised": "Skicka reaktionen överaskad",
"reactions": "Reaktioner",
@@ -1503,18 +1404,14 @@
"ccButtonTooltip": "Aktivera / Inaktivera undertexter",
"expandedLabel": "Transkribering är aktiverad",
"failed": "Transkribiering misslyckades",
"labelTooltip": "Mötet transkriberas",
"labelTooltipExtra": "Transkriberingen kommer finnas tillgänglig senare.",
"openClosedCaptions": "Öppna undertexter",
"original": "Original",
"labelToolTip": "Mötet transkriberas",
"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",
"translateTo": "Översätt till"
"tr": "TR"
},
"unpinParticipant": "Lossa deltagare",
"userMedia": {
@@ -1556,8 +1453,6 @@
"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",
@@ -1622,8 +1517,6 @@
"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!"

View File

@@ -112,12 +112,7 @@
"disabled": "聊天已禁用",
"enter": "加入会议室",
"error": "错误:你的消息未发送。原因:{{error}}",
"everyone": "所有人",
"fieldPlaceHolder": "在这里输入你的信息",
"fileAccessibleTitle": "{{user}}上传了一个文件",
"fileAccessibleTitleMe": "我上传了一个文件",
"fileDeleted": "文件已被删除",
"guestsChatIndicator": "(访客)",
"lobbyChatMessageTo": "等候室聊天消息发送至{{recipient}}",
"message": "信息",
"messageAccessibleTitle": "{{user}}",
@@ -305,12 +300,6 @@
"alreadySharedVideoTitle": "同一时间只允许一个视频分享",
"applicationWindow": "应用程序窗口",
"authenticationRequired": "需要身份验证",
"cameraCaptureDialog": {
"description": "使用手机摄像头拍照并发送",
"ok": "打开相机",
"reject": "暂不使用",
"title": "拍照"
},
"cameraConstraintFailedError": "你的摄像头未满足某些必要条件。",
"cameraNotFoundError": "找不到摄像头",
"cameraNotSendingData": "我们无法访问你的摄像头,请检查是否有其他应用程序正在使用此设备,从设置菜单中选择另一个设备,或尝试重新加载应用程序。",
@@ -386,34 +375,22 @@
"micTimeoutError": "无法开启音频设备,发生超时!",
"micUnknownError": "由于未知原因,无法使用麦克风。",
"moderationAudioLabel": "允许参会者自己解除静音",
"moderationDesktopLabel": "允许非主持人共享屏幕",
"moderationVideoLabel": "允许参会者自己开启视频",
"muteEveryoneDialog": "参会者可以在任何时候解除自己的静音。",
"muteEveryoneDialogModerationOn": "参会者可以在任何时候请求发言。",
"muteEveryoneElseDialog": "静音后,你将无法为其解除静音,但是他们可以随时解除自己的静音。",
"muteEveryoneElseTitle": "除了{{whom}}以外的将所有人静音?",
"muteEveryoneElsesDesktopDialog": "一旦停止共享,你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的屏幕共享?",
"muteEveryoneElsesVideoDialog": "一旦关闭,你将无法重新开启他们的摄像头,但他们随时可以重新开启。",
"muteEveryoneElsesVideoTitle": "除了{{whom}}以外,关闭所有人的摄像头?",
"muteEveryoneSelf": "你自己",
"muteEveryoneStartMuted": "现在所有人都已静音",
"muteEveryoneTitle": "静音所有人?",
"muteEveryonesDesktopDialog": "参会者可以随时共享他们的屏幕",
"muteEveryonesDesktopDialogModerationOn": "参会者可以随时请求共享他们的屏幕",
"muteEveryonesDesktopTitle": "停止所有人的屏幕共享?",
"muteEveryonesVideoDialog": "参会者可以随时开启他们的摄像头",
"muteEveryonesVideoDialogModerationOn": "参会者可以随时请求开启他们的摄像头",
"muteEveryonesVideoDialogOk": "关闭",
"muteEveryonesVideoTitle": "关闭所有人的摄像头?",
"muteParticipantBody": "你将无法为他们解除静音,但是他们可以随时解除自己的静音。",
"muteParticipantButton": "静音",
"muteParticipantsDesktopBody": "你无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteParticipantsDesktopBodyModerationOn": "你和他们都无法重新开启屏幕共享",
"muteParticipantsDesktopButton": "停止屏幕共享",
"muteParticipantsDesktopDialog": "你确定要停止这个参会者的屏幕共享吗?你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteParticipantsDesktopDialogModerationOn": "你确定要停止这个参会者的屏幕共享吗?你和他们都无法重新开启屏幕共享。",
"muteParticipantsDesktopTitle": "停止这个参会者的屏幕共享?",
"muteParticipantsVideoBody": "你无法重新开启摄像头,但他们随时可以重新开启。",
"muteParticipantsVideoBodyModerationOn": "你和他们都无法重新开启摄像头",
"muteParticipantsVideoButton": "关闭摄像头",
@@ -526,7 +503,6 @@
"tokenAuthFailedWithReasons": "抱歉,你无法加入此通话,原因:",
"tokenAuthUnsupported": "Token地址不支持",
"transcribing": "转录中",
"unauthenticatedAccessDisabled": "此会议需要身份验证,请先登录后继续。",
"unlockRoom": "移除会议$t(lockRoomPassword)",
"user": "用户",
"userIdentifier": "用户ID",
@@ -571,17 +547,11 @@
"downloadFailedDescription": "请稍后重试",
"downloadFailedTitle": "下载失败",
"downloadFile": "下载",
"downloadStarted": "文件下载已开始",
"dragAndDrop": "拖拽文件到此处上传",
"fileAlreadyUploaded": "文件已上传至本次会议",
"fileRemovedByOther": "你的文件{{fileName}}已被移除",
"fileTooLargeDescription": "请确保文件不超过 {{ maxFileSize }}",
"fileTooLargeTitle": "文件太大",
"fileUploadProgress": "文件上传进度",
"fileUploadedSuccessfully": "文件上传成功",
"newFileNotification": "{{participantName}}分享了{{fileName}}",
"removeFile": "移除",
"removeFileSuccess": "文件移除成功",
"uploadFailedDescription": "请稍后重试",
"uploadFailedTitle": "上传失败",
"uploadFile": "文件共享"
@@ -754,8 +724,7 @@
"notificationTitle": "等候室",
"passwordJoinButton": "加入",
"title": "等候室",
"toggleLabel": "开启等候室模式",
"waitForModerator": "会议尚未开始,暂无主持人入会。如需成为主持人请先登录,或耐心等待会议开始。"
"toggleLabel": "开启等候室模式"
},
"localRecording": {
"clientState": {
@@ -798,10 +767,8 @@
"me": "我",
"notify": {
"OldElectronAPPTitle": "安全漏洞!",
"allowAll": "允许全部",
"allowAudio": "允许开启麦克风",
"allowBoth": "允许音视频",
"allowDesktop": "允许屏幕共享",
"allowVideo": "允许开启摄像头",
"allowedUnmute": "你可以解除麦克风静音、启动摄像头或共享屏幕。",
"audioUnmuteBlockedDescription": "由于系统限制,麦克风解除静音操作被暂时阻止。",
@@ -815,7 +782,6 @@
"dataChannelClosedDescription": "桥接通道已断开,视频质量可能会被限制为最低设置",
"dataChannelClosedDescriptionWithAudio": "桥接通道已断开,音视频可能会出现卡顿或中断",
"dataChannelClosedWithAudio": "音视频质量可能受影响",
"desktopMutedRemotelyTitle": "你的屏幕共享已被{{participantDisplayName}}停止",
"disabledIframe": "嵌入仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
"disabledIframeSecondaryNative": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
"disabledIframeSecondaryWeb": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开。如需在正式环境嵌入,请使用<a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi服务</a>",
@@ -873,7 +839,6 @@
"oldElectronClientDescription1": "你似乎正在使用存在已知安全漏洞的旧版Jitsi Meet客户端请确保您更新到我们的",
"oldElectronClientDescription2": "最新版本",
"oldElectronClientDescription3": "",
"openChat": "打开聊天",
"participantWantsToJoin": "想要加入会议",
"participantsWantToJoin": "想要加入会议",
"passwordRemovedRemotely": "其他参会者移除了$t(lockRoomPasswordUppercase)",
@@ -897,7 +862,6 @@
"suggestRecordingDescription": "是否需要录制本次会议?",
"suggestRecordingTitle": "录制会议",
"unmute": "解除静音",
"unmuteScreen": "开始屏幕共享",
"unmuteVideo": "开启摄像头",
"videoMutedRemotelyDescription": "你可随时重新开启视频",
"videoMutedRemotelyTitle": "{{participantDisplayName}}已关闭你的视频",
@@ -917,14 +881,11 @@
"admit": "同意加入",
"admitAll": "全部同意加入",
"allow": "允许参会者:",
"allowDesktop": "允许屏幕共享",
"allowVideo": "允许开启摄像头",
"askDesktop": "请求共享屏幕",
"askUnmute": "请求取消静音",
"audioModeration": "自行解除静音",
"blockEveryoneMicCamera": "禁用所有人的麦克风和摄像头",
"breakoutRooms": "分组讨论室",
"desktopModeration": "开始屏幕共享",
"goLive": "开始直播",
"invite": "邀请其他人",
"lowerAllHands": "取消全部举手",
@@ -936,8 +897,6 @@
"muteAll": "全体静音",
"muteEveryoneElse": "静音其他人",
"reject": "拒绝",
"stopDesktop": "停止屏幕共享",
"stopEveryonesDesktop": "停止所有人的屏幕共享",
"stopEveryonesVideo": "关闭所有人摄像头",
"stopVideo": "关闭摄像头",
"unblockEveryoneMicCamera": "允许所有人开启麦克风和摄像头",
@@ -947,11 +906,9 @@
"headings": {
"lobby": "等候室(({{count}}人)",
"participantsList": "会议参会者({{count}}人)",
"viewerRequests": "观众请求({{count}}人)",
"visitorInQueue": "(排队中:{{count}}人)",
"visitorRequests": "(请求加入:{{count}}人)",
"visitors": "观众(({{count}}人)",
"visitorsList": "观众({{count}}人)",
"waitingLobby": "在等候室等待({{count}}人)"
},
"search": "搜索参会者",
@@ -972,9 +929,6 @@
"by": "由{{ name }}发起",
"closeButton": "结束投票",
"create": {
"accessibilityLabel": {
"send": "发送投票"
},
"addOption": "添加选项",
"answerPlaceholder": "选项{{index}}",
"cancel": "取消",
@@ -1391,20 +1345,6 @@
"videounmute": "打开摄像头"
},
"addPeople": "添加成员到通话中",
"advancedAudioSettings": {
"aec": {
"label": "回声消除"
},
"agc": {
"label": "自动增益控制"
},
"ns": {
"label": "降噪"
},
"stereo": {
"label": "立体声"
}
},
"audioOnlyOff": "关闭省流模式",
"audioOnlyOn": "开启省流模式",
"audioRoute": "选择音频设备",
@@ -1476,7 +1416,6 @@
"reactionHeart": "发送爱心",
"reactionLaugh": "发送大笑",
"reactionLike": "发送点赞",
"reactionLove": "发送爱心",
"reactionSilence": "发送沉默",
"reactionSurprised": "发送惊讶",
"reactions": "互动表情",
@@ -1562,8 +1501,6 @@
"connectionInfo": "连接信息",
"demote": "设为观众",
"domute": "静音",
"domuteDesktop": "停止屏幕共享",
"domuteDesktopOfOthers": "停止屏幕共享给其他人",
"domuteOthers": "静音其他人",
"domuteVideo": "关闭摄像头",
"domuteVideoOfOthers": "关闭其他人摄像头",
@@ -1628,8 +1565,6 @@
"noMainParticipantsTitle": "会议尚未开始",
"noVisitorLobby": "当前会议已开启等候室,暂无法加入",
"notAllowedPromotion": "需由会议成员同意才能参与讨论",
"requestToJoin": "举手请求",
"requestToJoinDescription": "你的请求已发送给主持人,请稍候!",
"title": "你当前为会议观众"
},
"waitingMessage": "会议开始后将自动加入"

View File

@@ -112,12 +112,7 @@
"disabled": "聊天訊息已停用",
"enter": "加入聊天室",
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
"everyone": "所有人",
"fieldPlaceHolder": "在此輸入您的訊息",
"fileAccessibleTitle": "{{user}}上傳了一個檔案",
"fileAccessibleTitleMe": "我上傳了一個檔案",
"fileDeleted": "檔案已被刪除",
"guestsChatIndicator": "(訪客)",
"lobbyChatMessageTo": "大廳聊天訊息傳送至 {{recipient}}",
"message": "訊息",
"messageAccessibleTitle": "{{user}}",
@@ -305,12 +300,6 @@
"alreadySharedVideoTitle": "同一時間只允許一位影像分享",
"applicationWindow": "應用程式視窗",
"authenticationRequired": "需要驗證",
"cameraCaptureDialog": {
"description": "使用手機攝影機拍照並傳送",
"ok": "開啟相機",
"reject": "暫不使用",
"title": "拍照"
},
"cameraConstraintFailedError": "您的網路攝影機不符合要求。",
"cameraNotFoundError": "找不到網路攝影機。",
"cameraNotSendingData": "我們無法存取您的網路攝影機,請檢查是否有其他應用程式正在使用這個裝置,並從裝置選單裡選擇其他設備或者重新載入。",
@@ -386,34 +375,22 @@
"micTimeoutError": "無法啟動音訊裝置,連線逾時!",
"micUnknownError": "不明原因造成麥克風無法使用。",
"moderationAudioLabel": "允許與會者自我解除靜音",
"moderationDesktopLabel": "允許非主持人共享螢幕",
"moderationVideoLabel": "允許與會者開啟視訊",
"muteEveryoneDialog": "與會者可以隨時解除自己的靜音狀態。",
"muteEveryoneDialogModerationOn": "與會者可以隨時請求發言。",
"muteEveryoneElseDialog": "靜音後,您就不能再解除對方的靜音,但對方可以隨時解除自己的靜音狀態。",
"muteEveryoneElseTitle": "是否要讓除了 {{whom}} 以外的人靜音?",
"muteEveryoneElsesDesktopDialog": "一旦停止共享,您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的螢幕共享?",
"muteEveryoneElsesVideoDialog": "一旦停用,您就不能再重新開啟對方的網路攝影機,但對方隨時能重新開啟自己的網路攝影機。",
"muteEveryoneElsesVideoTitle": "是否要關閉除了 {{whom}} 以外的人的網路攝影機?",
"muteEveryoneSelf": "您自己",
"muteEveryoneStartMuted": "現在所有人皆已靜音",
"muteEveryoneTitle": "要將所有人靜音嗎?",
"muteEveryonesDesktopDialog": "與會者可以隨時共享他們的螢幕",
"muteEveryonesDesktopDialogModerationOn": "與會者可以隨時請求共享他們的螢幕",
"muteEveryonesDesktopTitle": "停止所有人的螢幕共享?",
"muteEveryonesVideoDialog": "與會者隨時可以重新開啟自己的網路攝影機。",
"muteEveryonesVideoDialogModerationOn": "與會者可以隨時傳送開啟視訊請求。",
"muteEveryonesVideoDialogOk": "停用",
"muteEveryonesVideoTitle": "要關閉所有人的網路攝影機嗎?",
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
"muteParticipantButton": "靜音",
"muteParticipantsDesktopBody": "您無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteParticipantsDesktopBodyModerationOn": "您和他們都無法重新開啟螢幕共享",
"muteParticipantsDesktopButton": "停止螢幕分享",
"muteParticipantsDesktopDialog": "您確定要停止這位與會者的螢幕共享嗎?您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteParticipantsDesktopDialogModerationOn": "您確定要停止這位與會者的螢幕共享嗎?您和他們都無法重新開啟螢幕共享。",
"muteParticipantsDesktopTitle": "停止這位與會者的螢幕共享?",
"muteParticipantsVideoBody": "您無法重新開啟,只有對方能自己重新開啟。",
"muteParticipantsVideoBodyModerationOn": "您和他都無法再將視訊重新開啟。",
"muteParticipantsVideoButton": "停用網路攝影機",
@@ -526,7 +503,6 @@
"tokenAuthFailedWithReasons": "抱歉,您無法參加這個通話,可能原因:{{reason}}",
"tokenAuthUnsupported": "不支援權杖網址。",
"transcribing": "轉錄中",
"unauthenticatedAccessDisabled": "此會議需要身份驗證,請先登入後繼續。",
"unlockRoom": "移除會議 $t(lockRoomPassword)",
"user": "使用者",
"userIdentifier": "使用者 ID",
@@ -571,17 +547,11 @@
"downloadFailedDescription": "請重試",
"downloadFailedTitle": "下載失敗",
"downloadFile": "下載",
"downloadStarted": "檔案下載已開始",
"dragAndDrop": "將檔案拖曳至此或畫面任一處上傳",
"fileAlreadyUploaded": "檔案已上傳至此會議",
"fileRemovedByOther": "您的檔案「{{fileName}}」已被移除",
"fileTooLargeDescription": "請確認檔案未超過 {{ maxFileSize }}",
"fileTooLargeTitle": "檔案過大",
"fileUploadProgress": "檔案上傳進度",
"fileUploadedSuccessfully": "檔案上傳成功",
"newFileNotification": "{{participantName}}分享了「{{fileName}}」",
"removeFile": "移除",
"removeFileSuccess": "檔案移除成功",
"uploadFailedDescription": "請重試",
"uploadFailedTitle": "上傳失敗",
"uploadFile": "分享檔案"
@@ -754,8 +724,7 @@
"notificationTitle": "大廳",
"passwordJoinButton": "加入",
"title": "大廳",
"toggleLabel": "啟用大廳模式",
"waitForModerator": "會議尚未開始,暫無主持人入會。如需成為主持人請先登入,或耐心等待會議開始。"
"toggleLabel": "啟用大廳模式"
},
"localRecording": {
"clientState": {
@@ -798,10 +767,8 @@
"me": "我",
"notify": {
"OldElectronAPPTitle": "安全漏洞!",
"allowAll": "允許全部",
"allowAudio": "允許音訊",
"allowBoth": "允許音訊與視訊",
"allowDesktop": "允許螢幕分享",
"allowVideo": "允許視訊",
"allowedUnmute": "您可以將麥克風解除靜音、開啟視訊,或是分享您的螢幕。",
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
@@ -815,7 +782,6 @@
"dataChannelClosedDescription": "橋接通道已斷開,視訊品質降至最低設定。",
"dataChannelClosedDescriptionWithAudio": "橋接通道已斷開,音訊和視訊可能會受到影響。",
"dataChannelClosedWithAudio": "音訊和視訊品質可能會降低。",
"desktopMutedRemotelyTitle": "您的螢幕分享已被{{participantDisplayName}}停止",
"disabledIframe": "嵌入僅供示範使用,此通話將於 {{timeout}} 分鐘後中斷連線。",
"disabledIframeSecondaryNative": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷。",
"disabledIframeSecondaryWeb": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷,請使用 <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi 服務</a> 來進行正式嵌入!",
@@ -873,7 +839,6 @@
"oldElectronClientDescription1": "您似乎正在使用存在已知安全漏洞的過時 Jitsi Meet 用戶端,請盡快更新至我們的",
"oldElectronClientDescription2": "最新版本",
"oldElectronClientDescription3": "",
"openChat": "開啟聊天",
"participantWantsToJoin": "希望加入會議",
"participantsWantToJoin": "希望加入會議",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他與會者移除",
@@ -897,7 +862,6 @@
"suggestRecordingDescription": "是否要開始錄製這場會議?",
"suggestRecordingTitle": "錄製此會議",
"unmute": "取消靜音",
"unmuteScreen": "開始螢幕分享",
"unmuteVideo": "啟用視訊",
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
"videoMutedRemotelyTitle": "您的視訊已被 {{participantDisplayName}} 停用",
@@ -917,14 +881,11 @@
"admit": "準許",
"admitAll": "準許所有人",
"allow": "允許與會者能夠:",
"allowDesktop": "允許螢幕分享",
"allowVideo": "允許視訊",
"askDesktop": "請求共享螢幕",
"askUnmute": "要求解除靜音",
"audioModeration": "自我解除靜音",
"blockEveryoneMicCamera": "停用所有人的麥克風和網路攝影機",
"breakoutRooms": "分組討論室",
"desktopModeration": "開始螢幕分享",
"goLive": "開始直播",
"invite": "邀請他人",
"lowerAllHands": "全部取消舉手",
@@ -936,8 +897,6 @@
"muteAll": "靜音所有人",
"muteEveryoneElse": "靜音其他人",
"reject": "拒絕",
"stopDesktop": "停止螢幕分享",
"stopEveryonesDesktop": "停止所有人的螢幕分享",
"stopEveryonesVideo": "停用所有人的視訊",
"stopVideo": "停用視訊",
"unblockEveryoneMicCamera": "解除封鎖所有人的麥克風及網路攝影機",
@@ -947,11 +906,9 @@
"headings": {
"lobby": "大廳({{count}} 人)",
"participantsList": "會議與會者({{count}} 人)",
"viewerRequests": "觀眾請求({{count}}人)",
"visitorInQueue": "{{count}} 人等候中)",
"visitorRequests": "{{count}} 人申請",
"visitors": "訪客({{count}} 人)",
"visitorsList": "觀眾({{count}}人)",
"waitingLobby": "於大廳等候({{count}} 人)"
},
"search": "搜尋與會者",
@@ -972,9 +929,6 @@
"by": "由 {{ name }}",
"closeButton": "結束投票",
"create": {
"accessibilityLabel": {
"send": "傳送投票"
},
"addOption": "新增選項",
"answerPlaceholder": "選項 {{index}}",
"cancel": "取消",
@@ -1391,20 +1345,6 @@
"videounmute": "啟用網路攝影機"
},
"addPeople": "新增人員到您的通話中",
"advancedAudioSettings": {
"aec": {
"label": "回聲消除"
},
"agc": {
"label": "自動增益控制"
},
"ns": {
"label": "降噪"
},
"stereo": {
"label": "立體聲"
}
},
"audioOnlyOff": "停用低頻寬模式",
"audioOnlyOn": "啟用低頻寬模式",
"audioRoute": "選擇音訊裝置",
@@ -1476,7 +1416,6 @@
"reactionHeart": "傳送愛心反應",
"reactionLaugh": "傳送大笑反應",
"reactionLike": "傳送比讚反應",
"reactionLove": "傳送愛心",
"reactionSilence": "傳送沉默反應",
"reactionSurprised": "傳送驚訝反應",
"reactions": "反應",
@@ -1562,8 +1501,6 @@
"connectionInfo": "連線資訊",
"demote": "轉為訪客",
"domute": "靜音",
"domuteDesktop": "停止螢幕分享",
"domuteDesktopOfOthers": "停止螢幕分享給其他人",
"domuteOthers": "靜音其他人",
"domuteVideo": "停用網路攝影機",
"domuteVideoOfOthers": "停用其他人的網路攝影機",
@@ -1628,8 +1565,6 @@
"noMainParticipantsTitle": "會議尚未開始",
"noVisitorLobby": "此會議啟用大廳,暫時無法加入",
"notAllowedPromotion": "需由與會者同意您的申請",
"requestToJoin": "舉手請求",
"requestToJoinDescription": "您的請求已傳送給主持人,請稍候!",
"title": "您是會議中的訪客"
},
"waitingMessage": "會議開始後您將自動加入!"

View File

@@ -114,9 +114,6 @@
"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",
@@ -126,16 +123,8 @@
"messagebox": "Type a message",
"newMessages": "New messages",
"nickname": {
"featureChat": "chat",
"featureClosedCaptions": "closed captions",
"featureFileSharing": "file sharing",
"featurePolls": "polls",
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat",
"titleWith1Features": "Enter a nickname to use {{feature1}}",
"titleWith2Features": "Enter a nickname to use {{feature1}} and {{feature2}}",
"titleWith3Features": "Enter a nickname to use {{feature1}}, {{feature2}} and {{feature3}}",
"titleWith4Features": "Enter a nickname to use {{feature1}}, {{feature2}}, {{feature3}} and {{feature4}}",
"titleWithCC": "Enter a nickname to use chat and closed captions",
"titleWithPolls": "Enter a nickname to use chat and polls",
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",
@@ -581,12 +570,10 @@
"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.",
@@ -1437,7 +1424,6 @@
"exitFullScreen": "Exit full screen",
"exitTileView": "Exit tile view",
"feedback": "Leave feedback",
"fileSharing": "File sharing",
"giphy": "Toggle GIPHY menu",
"hangup": "Leave the meeting",
"help": "Help",
@@ -1473,7 +1459,6 @@
"openReactionsMenu": "Open reactions menu",
"participants": "Participants",
"pip": "Enter Picture-in-Picture mode",
"polls": "Polls",
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise your hand",

View File

@@ -158,11 +158,10 @@ 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;

84
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@giphy/react-components": "6.9.4",
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.6.7",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
@@ -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/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -87,6 +87,7 @@
"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",
@@ -100,7 +101,8 @@
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.13.0",
"react-native-webrtc": "124.0.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.4",
"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",
@@ -4544,10 +4546,9 @@
}
},
"node_modules/@jitsi/js-utils": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
"license": "Apache-2.0",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
@@ -18259,8 +18260,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
"integrity": "sha512-0FYPvOFSdg9L4ocH8bJw8doUE0rM55JnqRijXMOLS3ZOphbpeBg8tBTH33jwb+bqgo5jjmjTrvJkmkvGNF5/Jg==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.4.6",
@@ -18534,6 +18535,11 @@
"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",
@@ -21864,6 +21870,14 @@
"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",
@@ -22471,11 +22485,22 @@
"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.7",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.7.tgz",
"integrity": "sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==",
"license": "MIT",
"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==",
"dependencies": {
"base64-js": "1.5.1",
"debug": "4.3.4",
@@ -30017,9 +30042,9 @@
"integrity": "sha512-8fAv3cVEuoukSyu5RBZg0YWcrGEMjSKsdeQJuMmeOL2vVXIBWo0TpaHqys4HNCGRmZKzkhYccqxtmNSTxlBgkQ=="
},
"@jitsi/js-utils": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"requires": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
@@ -39690,8 +39715,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
"integrity": "sha512-0FYPvOFSdg9L4ocH8bJw8doUE0rM55JnqRijXMOLS3ZOphbpeBg8tBTH33jwb+bqgo5jjmjTrvJkmkvGNF5/Jg==",
"requires": {
"@jitsi/js-utils": "2.4.6",
"@jitsi/logger": "2.1.1",
@@ -39933,6 +39958,11 @@
"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",
@@ -42335,6 +42365,10 @@
"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",
@@ -42685,10 +42719,18 @@
"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.7",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.7.tgz",
"integrity": "sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==",
"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==",
"requires": {
"base64-js": "1.5.1",
"debug": "4.3.4",

View File

@@ -25,7 +25,7 @@
"@giphy/react-components": "6.9.4",
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.6.7",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
@@ -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/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2099.0.0+89536686/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -93,6 +93,7 @@
"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",
@@ -106,7 +107,8 @@
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.13.0",
"react-native-webrtc": "124.0.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.4",
"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",

View File

@@ -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
ditto "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}/sounds"
cp -R "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}"
',
}
end

View File

@@ -73,6 +73,7 @@
"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",

View File

@@ -20,6 +20,11 @@ module.exports = {
platforms: {
ios: null
}
},
'react-native-watch-connectivity': {
platforms: {
ios: null
}
}
}
};

View File

@@ -600,6 +600,31 @@ export function createRemoteVideoMenuButtonEvent(buttonName: string, attributes
};
}
/**
* The rtcstats websocket onclose event. We send this to amplitude in order
* to detect trace ws prematurely closing.
*
* @param {Object} closeEvent - The event with which the websocket closed.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createRTCStatsTraceCloseEvent(closeEvent: { code: string; reason: string; }) {
const event: {
action: string;
code?: string;
reason?: string;
source: string;
} = {
action: 'trace.onclose',
source: 'rtcstats'
};
event.code = closeEvent.code;
event.reason = closeEvent.reason;
return event;
}
/**
* Creates an event indicating that an action related to screen sharing
* occurred (e.g. It was started or stopped).

View File

@@ -1,4 +1,4 @@
import * as amplitude from '@amplitude/analytics-react-native';
import amplitude from '@amplitude/analytics-react-native';
export default amplitude;

View File

@@ -4,11 +4,13 @@ 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';

View File

@@ -2,6 +2,8 @@ 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';

View File

@@ -52,6 +52,8 @@ 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';
@@ -130,6 +132,7 @@ 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;
@@ -140,6 +143,7 @@ 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;

View File

@@ -55,28 +55,6 @@ function getFirstGraphemeUpper(word: string) {
return splitter.splitGraphemes(word)[0].toUpperCase();
}
/**
* Strips bracketed annotations from a display name. Handles multiple bracket types like (),
* [], and {}.
*
* @param {string} name - The display name to clean.
* @returns {string} The cleaned display name without bracketed annotations.
*/
function stripBracketedAnnotations(name: string): string {
// Match content within any of the bracket types at the end of the string
// This regex matches: (...) or [...] or {...} at the end
const bracketRegex = /\s*[([{][^)\]}]*[)\]}]$/;
let cleaned = name;
// Remove all trailing bracketed annotations (handle multiple occurrences)
while (bracketRegex.test(cleaned)) {
cleaned = cleaned.replace(bracketRegex, '');
}
return cleaned.trim();
}
/**
* Generates initials for a simple string.
*
@@ -86,15 +64,7 @@ function stripBracketedAnnotations(name: string): string {
export function getInitials(s?: string) {
// We don't want to use the domain part of an email address, if it is one
const initialsBasis = split(s, '@')[0];
// Strip bracketed annotations (e.g., "(Department)", "[Team]", "{Org}")
// to prevent them from being considered as name parts
const cleanedName = stripBracketedAnnotations(initialsBasis);
// Fallback to original if cleaned name is empty
const nameForInitials = cleanedName || initialsBasis;
const [ firstWord, ...remainingWords ] = nameForInitials.split(wordSplitRegex).filter(Boolean);
const [ firstWord, ...remainingWords ] = initialsBasis.split(wordSplitRegex).filter(Boolean);
return getFirstGraphemeUpper(firstWord) + getFirstGraphemeUpper(remainingWords.pop() || '');
}

View File

@@ -7,8 +7,6 @@ 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';
@@ -24,7 +22,7 @@ import {
safeDecodeURIComponent
} from '../util/uri';
import { conferenceWillInit, setObfuscatedRoom } from './actions';
import { setObfuscatedRoom } from './actions';
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
@@ -620,34 +618,3 @@ 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;
}

View File

@@ -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,10 +23,6 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
if (processDestroyConferenceEvent(state, dispatch, error.params)) {
break;
}
if (!notifyOnConferenceDestruction) {
dispatch(conferenceLeft(action.conference));
dispatch(appNavigate(undefined));

View File

@@ -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,11 +127,6 @@ 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)
];

View File

@@ -285,7 +285,6 @@ export interface IConfig {
disableAudioLevels?: boolean;
disableBeforeUnloadHandlers?: boolean;
disableCameraTintForeground?: boolean;
disableChat?: boolean;
disableChatSmileys?: boolean;
disableDeepLinking?: boolean;
disableFilmstripAutohiding?: boolean;
@@ -394,7 +393,6 @@ export interface IConfig {
disabled?: boolean;
initialWidth?: number;
minParticipantCountForTopPanel?: number;
stageFilmstripParticipants?: number;
};
flags?: {
ssrcRewritingEnabled: boolean;
@@ -526,7 +524,6 @@ export interface IConfig {
hideExtraJoinButtons?: Array<string>;
preCallTestEnabled?: boolean;
preCallTestICEUrl?: string;
showHangUp?: boolean;
};
raisedHands?: {
disableLowerHandByModerator?: boolean;
@@ -656,9 +653,7 @@ export interface IConfig {
audio?: boolean;
video?: boolean;
};
hideVisitorCountForVisitors?: boolean;
queueService: string;
showJoinMeetingDialog?: boolean;
};
watchRTCConfigParams?: IWatchRTCConfiguration;
webhookProxyUrl?: string;

View File

@@ -94,13 +94,11 @@ export default [
'disableAudioLevels',
'disableBeforeUnloadHandlers',
'disableCameraTintForeground',
'disableChat',
'disableChatSmileys',
'disableDeepLinking',
'disabledNotifications',
'disabledSounds',
'disableFilmstripAutohiding',
'disableFocus',
'disableInitialGUM',
'disableInviteFunctions',
'disableIncomingMessageSound',
@@ -239,8 +237,6 @@ export default [
'useTurnUdp',
'videoQuality',
'visitors.enableMediaOnPromote',
'visitors.hideVisitorCountForVisitors',
'visitors.showJoinMeetingDialog',
'watchRTCConfigParams.allowBrowserLogCollection',
'watchRTCConfigParams.collectionInterval',
'watchRTCConfigParams.console',

View File

@@ -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, shouldLeave = true) {
export function disconnect(isRedirect?: boolean) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
const state = getState();
@@ -407,26 +407,20 @@ export function disconnect(isRedirect?: boolean, shouldLeave = true) {
// intention to leave the conference.
dispatch(conferenceWillLeave(conference_, isRedirect));
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);
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();
}

View File

@@ -78,6 +78,12 @@ 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).

View File

@@ -80,7 +80,7 @@ const options: i18next.InitOptions = {
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
load: 'all',
load: 'languageOnly',
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
react: {
// re-render when a new resource bundle is added

View File

@@ -186,9 +186,6 @@ function _initLogging({ dispatch, getState }: IStore,
Logger.addGlobalTransport(debugLogCollector);
JitsiMeetJS.addGlobalLogTransport(debugLogCollector);
debugLogCollector.start();
Logger.removeGlobalTransport(console);
JitsiMeetJS.removeGlobalLogTransport(console);
}
} else if (logCollector && loggingConfig.disableLogCollector) {
Logger.removeGlobalTransport(logCollector);

View File

@@ -827,30 +827,17 @@ export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispat
};
/**
* Checks if private chat is enabled for the given participant or local participant.
* Checks if private chat is enabled for the given 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,
checkSelf: boolean = false
): boolean {
export function isPrivateChatEnabled(participant: IParticipant | IVisitorChatParticipant | undefined, state: IReduxState) {
const { remoteVideoMenu = {} } = state['features/base/config'];
const { disablePrivateChat } = remoteVideoMenu;
// 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') {
if ((!isVisitorChatParticipant(participant) && participant?.local) || disablePrivateChat === 'all') {
return false;
}
@@ -870,15 +857,3 @@ export function isPrivateChatEnabled(
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);
}

View File

@@ -27,7 +27,6 @@ import {
isRemoteScreenshareParticipant,
isScreenShareParticipant
} from './functions';
import logger from './logger';
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
/**
@@ -365,8 +364,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant joined', id);
}
// Exclude the screenshare participant from the fake participant count to avoid duplicates.
@@ -451,8 +448,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
sortedRemoteVirtualScreenshareParticipants.delete(id);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant left', id);
}
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {

View File

@@ -21,7 +21,6 @@ import {
getRemoteScreensharesBasedOnPresence,
getVirtualScreenshareParticipantOwnerId
} from './functions';
import logger from './logger';
import { FakeParticipant } from './types';
StateListenerRegistry.register(
@@ -70,19 +69,14 @@ function _createOrRemoveVirtualParticipants(
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => {
logger.debug('Dispatching participantLeft for virtual screenshare', id);
dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
}));
});
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
})));
}
if (addedScreenshareSourceNames.length) {
addedScreenshareSourceNames.forEach(id => {
logger.debug('Creating virtual screenshare participant', id);
dispatch(createVirtualScreenshareParticipant(id, false, conference));
});
addedScreenshareSourceNames.forEach(id => dispatch(
createVirtualScreenshareParticipant(id, false, conference)));
}
}

View File

@@ -282,18 +282,15 @@ const PreMeetingScreen = ({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { hiddenPremeetingButtons, prejoinConfig } = state['features/base/config'];
const { hiddenPremeetingButtons } = 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));
const shouldShowHangUp = knocking ? showHangUpLobby : showHangUpPrejoin;
if (shouldShowHangUp && !premeetingButtons.includes('hangup')) {
if (showHangUp && knocking && !premeetingButtons.includes('hangup')) {
premeetingButtons.push('hangup');
}

View File

@@ -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);

View File

@@ -1,5 +1,6 @@
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 {
@@ -56,12 +57,75 @@ export function isLargeVideoReceived({ getState }: IStore): boolean {
}
/**
* Returns the local video track's codec.
* Returns whether the local video track is encoded in AV1.
*
* @returns {string?} The local video track's codec.
* @param {IStore} store - The redux store.
* @returns {boolean}
*/
export function getLocalCameraEncoding({ getState }: IStore): string | undefined {
return getLocalVideoTrack(getState()['features/base/tracks'])?.codec?.toLowerCase();
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;
}
/**

View File

@@ -8,9 +8,12 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
import { setConnectionState } from './actions';
import {
getLocalCameraEncoding,
getRemoteVideoType,
isLargeVideoReceived,
isLocalCameraEncodingAv1,
isLocalCameraEncodingH264,
isLocalCameraEncodingVp8,
isLocalCameraEncodingVp9,
isRemoteVideoReceived,
isTestModeEnabled
} from './functions';
@@ -87,7 +90,10 @@ function _bindTortureHelpers(store: IStore) {
getJitsiMeetGlobalNS().testing = {
getRemoteVideoType: getRemoteVideoType.bind(null, store),
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
isLocalCameraEncodingAv1: isLocalCameraEncodingAv1.bind(null, store),
isLocalCameraEncodingH264: isLocalCameraEncodingH264.bind(null, store),
isLocalCameraEncodingVp8: isLocalCameraEncodingVp8.bind(null, store),
isLocalCameraEncodingVp9: isLocalCameraEncodingVp9.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
};
}

View File

@@ -211,21 +211,18 @@ export function getLocalJitsiAudioTrackSettings(state: IReduxState) {
const jitsiTrack = getLocalJitsiAudioTrack(state);
if (!jitsiTrack) {
const {
audioQuality,
disableAEC = false,
disableAGC = false,
disableAP = false,
disableNS = false
} = state['features/base/config'] || {};
const enableStereo = Boolean(audioQuality?.stereo);
const config = state['features/base/config'];
const disableAP = Boolean(config?.disableAP);
const disableAGC = Boolean(config?.disableAGC);
const disableAEC = Boolean(config?.disableAEC);
const disableNS = Boolean(config?.disableNS);
const stereo = Boolean(config?.audioQuality?.stereo);
return {
autoGainControl: enableStereo ? false : !disableAP && !disableAGC,
channelCount: enableStereo ? 2 : 1,
echoCancellation: enableStereo ? false : !disableAP && !disableAEC,
noiseSuppression: enableStereo ? false : !disableAP && !disableNS
autoGainControl: !disableAP && !disableAGC,
channelCount: stereo ? 2 : 1,
echoCancellation: !disableAP && !disableAEC,
noiseSuppression: !disableAP && !disableNS
};
}

View File

@@ -71,16 +71,11 @@ const useStyles = makeStyles()(theme => {
badge: {
...theme.typography.labelBold,
alignItems: 'center',
backgroundColor: theme.palette.warning01,
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)}`
padding: `0 ${theme.spacing(1)}`,
borderRadius: '100%',
backgroundColor: theme.palette.warning01,
marginLeft: theme.spacing(2)
},
icon: {

View File

@@ -220,34 +220,6 @@ export function openCCPanel() {
};
}
/**
* Opens the chat panel with polls tab active.
*
* @returns {Object} The redux action.
*/
export function openPollsPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.POLLS));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Opens the chat panel with file sharing tab active.
*
* @returns {Object} The redux action.
*/
export function openFileSharingPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Initiates the sending of messages between a moderator and a lobby attendee.

View File

@@ -1,34 +1,30 @@
import { IStore } from '../app/types';
import { IParticipant } from '../base/participants/types';
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';
import { setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
export * from './actions.any';
/**
* Displays the chat panel with the CHAT tab active.
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @param {boolean} disablePolls - Checks if polls are disabled.
*
* @returns {Function}
* @returns {{
* participant: participant,
* type: OPEN_CHAT
* }}
*/
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
return (dispatch: IStore['dispatch']) => {
if (disablePolls) {
navigate(screen.conference.chat);
} else {
navigate(screen.conference.chatandpolls.main);
}
if (disablePolls) {
navigate(screen.conference.chat);
}
navigate(screen.conference.chatandpolls.main);
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT
});
return {
participant,
type: OPEN_CHAT
};
}

View File

@@ -8,13 +8,12 @@ import {
SET_CHAT_WIDTH,
SET_USER_CHAT_WIDTH
} from './actionTypes';
import { closeChat, setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
import { closeChat } from './actions.any';
export * from './actions.any';
/**
* Displays the chat panel with the CHAT tab active.
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @param {Object} _disablePolls - Used on native.
@@ -25,7 +24,6 @@ export * from './actions.any';
*/
export function openChat(participant?: Object, _disablePolls?: boolean) {
return function(dispatch: IStore['dispatch']) {
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT

View File

@@ -7,9 +7,7 @@ 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';
@@ -19,21 +17,6 @@ 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).
*/
@@ -113,26 +96,21 @@ class Chat extends Component<IProps> {
* @private
* @returns {{
* _messages: Array<Object>,
* _unreadMessagesCount: number,
* _unreadPollsCount: number,
* _unreadFilesCount: number
* _nbUnreadMessages: number
* }}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { messages, unreadMessagesCount } = state['features/chat'];
const { messages, nbUnreadMessages } = state['features/chat'];
return {
_messages: messages,
_unreadMessagesCount: unreadMessagesCount,
_unreadPollsCount: getUnreadPollCount(state),
_unreadFilesCount: getUnreadFilesCount(state)
_nbUnreadMessages: nbUnreadMessages
};
}
export default translate(connect(_mapStateToProps)((props: IProps) => {
const { _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount, dispatch, navigation, t } = props;
const totalUnread = _unreadMessagesCount + _unreadPollsCount + _unreadFilesCount;
const unreadMessagesNr = totalUnread > 0;
const { _nbUnreadMessages, dispatch, navigation, t } = props;
const unreadMessagesNr = _nbUnreadMessages > 0;
const isFocused = useIsFocused();
@@ -143,14 +121,14 @@ export default translate(connect(_mapStateToProps)((props: IProps) => {
activeUnreadNr = { unreadMessagesNr }
isFocused = { isFocused }
label = { t('chat.tabs.chat') }
unreadCount = { totalUnread } />
nbUnread = { _nbUnreadMessages } />
)
});
return () => {
isFocused && dispatch(closeChat());
};
}, [ isFocused, _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount ]);
}, [ isFocused, _nbUnreadMessages ]);
return (
<Chat { ...props } />

View File

@@ -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, getUnreadFilesCount, isChatDisabled } from '../../functions';
import { getUnreadCount } from '../../functions';
interface IProps extends AbstractButtonProps {
@@ -65,12 +65,14 @@ class ChatButton extends AbstractButton<IProps> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_isPollsDisabled: arePollsDisabled(state),
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state) || getUnreadFilesCount(state),
// The toggled icon should also be available for new polls
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),
visible
};
}

View File

@@ -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, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
import { getLocalParticipant, getRemoteParticipants } 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';
@@ -24,7 +24,7 @@ import {
toggleChat
} from '../../actions.web';
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
import { getChatMaxSize } from '../../functions';
import { IChatProps as AbstractProps } from '../../types';
import ChatHeader from './ChatHeader';
@@ -41,18 +41,13 @@ interface IProps extends AbstractProps {
/**
* The currently focused tab.
*/
_focusedTab?: ChatTabs;
_focusedTab: ChatTabs;
/**
* True if the CC tab is enabled and false otherwise.
*/
_isCCTabEnabled: boolean;
/**
* True if chat is disabled.
*/
_isChatDisabled: boolean;
/**
* True if file sharing tab is enabled.
*/
@@ -78,21 +73,16 @@ 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.
*/
@@ -222,24 +212,17 @@ const Chat = ({
_isOpen,
_isPollsEnabled,
_isCCTabEnabled,
_isChatDisabled,
_isFileSharingTabEnabled,
_focusedTab,
_isResizing,
_messages,
_unreadMessagesCount,
_unreadPollsCount,
_unreadFilesCount,
_nbUnreadMessages,
_nbUnreadPolls,
_showNamePrompt,
_width,
dispatch,
t
}: IProps) => {
// If no tabs are available, don't render the chat panel at all.
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
return null;
}
const { classes, cx } = useStyles({ _isResizing, width: _width });
const [ isMouseDown, setIsMouseDown ] = useState(false);
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
@@ -253,7 +236,6 @@ 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() || [])
@@ -427,7 +409,7 @@ const Chat = ({
return (
<>
{renderTabs()}
{!_isChatDisabled && (<div
<div
aria-labelledby = { ChatTabs.CHAT }
className = { cx(
classes.chatPanel,
@@ -443,17 +425,15 @@ const Chat = ({
<MessageContainer
messages = { _messages } />
<MessageRecipient />
{isPrivateChatAllowed && (
<Select
containerClassName = { cx(classes.privateMessageRecipientsList) }
id = 'select-chat-recipient'
onChange = { onSelectedRecipientChange }
options = { options }
value = { privateMessageRecipient?.id || OPTION_GROUPCHAT } />
)}
<Select
containerClassName = { cx(classes.privateMessageRecipientsList) }
id = 'select-chat-recipient'
onChange = { onSelectedRecipientChange }
options = { options }
value = { privateMessageRecipient?.id || OPTION_GROUPCHAT } />
<ChatInput
onSend = { onSendMessage } />
</div>) }
</div>
{ _isPollsEnabled && (
<>
<div
@@ -495,32 +475,22 @@ const Chat = ({
* @returns {ReactElement}
*/
function renderTabs() {
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
// executed because Chat component won't render anything. This should never happen but adding the check
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
if (!_focusedTab) {
return null;
}
let tabs = [];
// Only add chat tab if chat is not disabled.
if (!_isChatDisabled) {
tabs.push({
let tabs = [
{
accessibilityLabel: t('chat.tabs.chat'),
countBadge:
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
_focusedTab !== ChatTabs.CHAT && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined,
id: ChatTabs.CHAT,
controlsId: `${ChatTabs.CHAT}-panel`,
icon: IconMessage,
title: t('chat.tabs.chat')
});
}
}
];
if (_isPollsEnabled) {
tabs.push({
accessibilityLabel: t('chat.tabs.polls'),
countBadge: _focusedTab !== ChatTabs.POLLS && _unreadPollsCount > 0 ? _unreadPollsCount : undefined,
countBadge: _focusedTab !== ChatTabs.POLLS && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined,
id: ChatTabs.POLLS,
controlsId: `${ChatTabs.POLLS}-panel`,
icon: IconInfo,
@@ -542,7 +512,7 @@ const Chat = ({
if (_isFileSharingTabEnabled) {
tabs.push({
accessibilityLabel: t('chat.tabs.fileSharing'),
countBadge: _focusedTab !== ChatTabs.FILE_SHARING && _unreadFilesCount > 0 ? _unreadFilesCount : undefined,
countBadge: undefined,
id: ChatTabs.FILE_SHARING,
controlsId: `${ChatTabs.FILE_SHARING}-panel`,
icon: IconShareDoc,
@@ -585,8 +555,6 @@ const Chat = ({
{_showNamePrompt
? <DisplayNameForm
isCCTabEnabled = { _isCCTabEnabled }
isChatDisabled = { _isChatDisabled }
isFileSharingEnabled = { _isFileSharingTabEnabled }
isPollsEnabled = { _isPollsEnabled } />
: renderChat()}
<div
@@ -616,17 +584,16 @@ const Chat = ({
* _isCCTabEnabled: boolean,
* _focusedTab: string,
* _messages: Array<Object>,
* _unreadMessagesCount: number,
* _unreadPollsCount: number,
* _unreadFilesCount: number,
* _nbUnreadMessages: number,
* _nbUnreadPolls: number,
* _showNamePrompt: boolean,
* _width: number,
* _isResizing: boolean
* }}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { unreadPollsCount } = state['features/polls'];
const { isOpen, focusedTab, messages, nbUnreadMessages, width, isResizing } = state['features/chat'];
const { nbUnreadPolls } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
return {
@@ -634,13 +601,11 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_isOpen: isOpen,
_isPollsEnabled: !arePollsDisabled(state),
_isCCTabEnabled: isCCTabEnabled(state),
_isChatDisabled: isChatDisabled(state),
_isFileSharingTabEnabled: isFileSharingEnabled(state),
_focusedTab: getFocusedTab(state),
_focusedTab: focusedTab,
_messages: messages,
_unreadMessagesCount: unreadMessagesCount,
_unreadPollsCount: unreadPollsCount,
_unreadFilesCount: unreadFilesCount,
_nbUnreadMessages: nbUnreadMessages,
_nbUnreadPolls: nbUnreadPolls,
_showNamePrompt: !_localParticipant?.name,
_width: width?.current || CHAT_SIZE,
_isResizing: isResizing

View File

@@ -9,7 +9,6 @@ import { IconMessage } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
import { toggleChat } from '../../actions.web';
import { isChatDisabled } from '../../functions';
import ChatCounter from './ChatCounter';
@@ -92,8 +91,7 @@ class ChatButton extends AbstractButton<IProps> {
*/
const mapStateToProps = (state: IReduxState) => {
return {
_chatOpen: state['features/chat'].isOpen,
visible: !isChatDisabled(state)
_chatOpen: state['features/chat'].isOpen
};
};

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
import { getUnreadCount } 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) + getUnreadFilesCount(state),
_count: getUnreadCount(state) + getUnreadPollCount(state),
_isOpen: isOpen
};

View File

@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconCloseLarge } from '../../../base/icons/svg';
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
import { toggleChat } from '../../actions.web';
import { ChatTabs } from '../../constants';
import { getFocusedTab, isChatDisabled } from '../../functions';
interface IProps {
@@ -40,8 +40,7 @@ interface IProps {
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const _isChatDisabled = useSelector(isChatDisabled);
const focusedTab = useSelector(getFocusedTab);
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
const onCancel = useCallback(() => {
@@ -57,7 +56,7 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
let title = 'chat.title';
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
if (focusedTab === ChatTabs.CHAT) {
title = 'chat.tabs.chat';
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
title = 'chat.tabs.polls';
@@ -65,11 +64,6 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
title = 'chat.tabs.closedCaptions';
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
title = 'chat.tabs.fileSharing';
} else {
// If the focused tab is not enabled, don't render the header.
// This should not happen in normal circumstances since Chat.tsx already checks
// if any tabs are available before rendering.
return null;
}
return (

View File

@@ -9,10 +9,9 @@ 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, isFileMessage } from '../../functions';
import { getDisplayNameSuffix, getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
import { IChatMessageProps } from '../../types';
import FileMessage from './FileMessage';
import MessageMenu from './MessageMenu';
import ReactButton from './ReactButton';
@@ -49,22 +48,6 @@ 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
},
@@ -134,7 +117,8 @@ const useStyles = makeStyles()((theme: Theme) => {
},
messageContent: {
maxWidth: '100%',
overflow: 'hidden'
overflow: 'hidden',
flex: 1
},
optionsButtonContainer: {
display: 'flex',
@@ -360,7 +344,6 @@ const ChatMessage = ({
{isHovered && <MessageMenu
displayName = { message.displayName }
enablePrivateChat = { Boolean(enablePrivateChat) }
isFileMessage = { isFileMessage(message) }
isFromVisitor = { message.isFromVisitor }
isLobbyMessage = { message.lobbyChat }
message = { message.message }
@@ -373,31 +356,19 @@ const ChatMessage = ({
classes.chatMessage,
className,
message.privateMessage && 'privatemessage',
message.lobbyChat && !knocking && 'lobbymessage',
isFileMessage(message) && 'file'
message.lobbyChat && !knocking && 'lobbymessage'
) }>
<div className = { classes.replyWrapper }>
<div className = { cx('messagecontent', classes.messageContent) }>
{showDisplayName && _renderDisplayName()}
<div className = { cx('usermessage', classes.userMessage) }>
{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
screenReaderHelpText = { message.displayName === message.recipient
? t<string>('chat.messageAccessibleTitleMe')
: t<string>('chat.messageAccessibleTitle', {
user: message.displayName
}) }
text = { getMessageText(message) } />
{(message.privateMessage || (message.lobbyChat && !knocking))
&& _renderPrivateNotice()}
<div className = { classes.chatMessageFooter }>
@@ -429,7 +400,6 @@ const ChatMessage = ({
{isHovered && <MessageMenu
displayName = { message.displayName }
enablePrivateChat = { Boolean(enablePrivateChat) }
isFileMessage = { isFileMessage(message) }
isFromVisitor = { message.isFromVisitor }
isLobbyMessage = { message.lobbyChat }
message = { message.message }

View File

@@ -27,7 +27,7 @@ const useStyles = makeStyles()(theme => {
flexDirection: 'column',
maxWidth: '100%',
'&.remote, &.file': {
'&.remote': {
maxWidth: 'calc(100% - 40px)' // 100% - avatar and margin
}
},

View File

@@ -25,16 +25,6 @@ interface IProps extends WithTranslation {
*/
isCCTabEnabled: boolean;
/**
* Whether chat is disabled.
*/
isChatDisabled: boolean;
/**
* Whether file sharing is enabled.
*/
isFileSharingEnabled: boolean;
/**
* Whether the polls feature is enabled or not.
*/
@@ -84,31 +74,18 @@ class DisplayNameForm extends Component<IProps, IState> {
* @returns {ReactElement}
*/
override render() {
const { isCCTabEnabled, isChatDisabled, isFileSharingEnabled, isPollsEnabled, t } = this.props;
const { isCCTabEnabled, isPollsEnabled, t } = this.props;
// Build array of enabled feature names (translated).
const features = [
!isChatDisabled ? t('chat.nickname.featureChat') : '',
isPollsEnabled ? t('chat.nickname.featurePolls') : '',
isFileSharingEnabled ? t('chat.nickname.featureFileSharing') : '',
isCCTabEnabled ? t('chat.nickname.featureClosedCaptions') : ''
].filter(Boolean);
let title = 'chat.nickname.title';
// Return null if no features available - component won't render.
if (features.length === 0) {
return null;
if (isCCTabEnabled && isPollsEnabled) {
title = 'chat.nickname.titleWithPollsAndCC';
} else if (isCCTabEnabled) {
title = 'chat.nickname.titleWithCC';
} else if (isPollsEnabled) {
title = 'chat.nickname.titleWithPolls';
}
// Build translation arguments dynamically: { feature1: "chat", feature2: "polls", ... }
const translationArgs = features.reduce((acc, feature, index) => {
acc[`feature${index + 1}`] = feature;
return acc;
}, {} as Record<string, string>);
// Use dynamic translation key: "titleWith1Features", "titleWith2Features", etc.
const title = t(`chat.nickname.titleWith${features.length}Features`, translationArgs);
return (
<div id = 'nickname'>
<form onSubmit = { this._onSubmit }>

View File

@@ -1,143 +0,0 @@
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;

Some files were not shown because too many files have changed in this diff Show More