Compare commits

...

10 Commits

Author SHA1 Message Date
Alex Bumbu
1893ced556 iOS: toggle audio mute & leave meeting lock screen widgets (#12581) 2022-11-18 13:25:48 +02:00
Calin-Teodor
7d9c13a618 feat(prejoin): updated styles 2022-11-18 11:02:05 +02:00
Hristo Terezov
48ed3b7dc6 fix(dialog): cancel hide timeout on openDialog
Since we unmount the dialog after a timeout because of an animation we
need to cancel the timeout in case we need to render new dialog.
Otherwise the actual hiding can be executed after we render the new
dialog.
2022-11-17 17:00:02 -06:00
Hristo Terezov
04abfe1a3b feat(dialog): add disableAutoHideOnSubmit prop
Needed for shared video dialog.
2022-11-17 17:00:02 -06:00
Alex Bumbu
d45decc393 Update JitsiMeet.h (#12574) 2022-11-17 16:31:37 +02:00
Saúl Ibarra Corretgé
88f8f48465 fix(participants) don't treat Jigasi like a fake participant
We only really want to know if a participant is Jigasi for displaying a
specific icon, for all other intents and purposes it's a normal
participant.
2022-11-17 15:13:54 +01:00
Saúl Ibarra Corretgé
8127ea2479 feat(redux) throw exception in case invalid listeners are registered
This allows catching mistakes early.
2022-11-17 11:07:57 +01:00
Saúl Ibarra Corretgé
b22915c169 fix(rn,filmstrip) fix invalid selector registered as state listener 2022-11-17 11:07:57 +01:00
Saúl Ibarra Corretgé
14fcd153e5 fix(rn,connection-indicator) align rendering with web
Use the same way for calculating que perceived quality and display it.
2022-11-17 11:07:57 +01:00
Robert Pintilii
48efe36cdf fix(dial-in) Fix warning (#12571)
Only show warning if the feature is enabled
2022-11-16 12:14:58 +02:00
55 changed files with 1483 additions and 225 deletions

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "end_call_button.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "microphone_off_button.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "microphone_on_button.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,101 @@
//
// DarwinNotificationsObserver.swift
// WidgetsExtension
//
// Created by Alex Bumbu on 17.10.2022.
//
import Foundation
enum DarwinNotification: String {
case meetingMutedChanged = "iOS_MeetingMutedChanged"
}
extension DarwinNotification {
var name: String { rawValue }
}
class DarwinNotificationsObserver {
private static var observers = Array<ProxyObserver>()
private let queue = DispatchQueue(label: "org.jitsi.meet.darwinNotificationObserver", qos: .default, autoreleaseFrequency: .workItem)
private var notificationCenter: CFNotificationCenter
init() {
notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
}
func observe(notification: DarwinNotification, handler: @escaping () -> Void) {
let proxyObserver = ProxyObserver(observer: self, notificationName: notification.name, handler: handler)
queue.async {
DarwinNotificationsObserver.observers.append(proxyObserver)
}
let callback: CFNotificationCallback = { _, observer, name, _, _ in
guard let observer = observer else {
return
}
// Extract pointer to `observer` from void pointer:
let proxyObserver = Unmanaged<ProxyObserver>.fromOpaque(observer).takeUnretainedValue()
var observers = DarwinNotificationsObserver.observers
if !proxyObserver.forwardNotification(), let index = observers.firstIndex(of: proxyObserver) {
// cleanup if `forwardNotification` fails
observers.remove(at: index)
}
}
CFNotificationCenterAddObserver(notificationCenter,
Unmanaged.passUnretained(proxyObserver).toOpaque(),
callback,
notification.name as CFString,
nil,
.deliverImmediately)
}
func stopObserving(notification: DarwinNotification) {
queue.sync {
DarwinNotificationsObserver.observers.removeAll { $0.observer == nil }
}
if let index = DarwinNotificationsObserver.observers.firstIndex(where: { $0.observer === self && $0.notificationName == notification.name }) {
let proxyObserver = DarwinNotificationsObserver.observers[index]
CFNotificationCenterRemoveObserver(notificationCenter,
Unmanaged.passUnretained(proxyObserver).toOpaque(),
CFNotificationName(notification.name as CFString),
nil)
queue.async {
DarwinNotificationsObserver.observers.remove(at: index)
}
}
}
}
private class ProxyObserver: Equatable {
let notificationName: String
weak var observer: AnyObject?
private let handler: () -> (Void)
static func == (lhs: ProxyObserver, rhs: ProxyObserver) -> Bool {
lhs.observer === rhs.observer && lhs.notificationName == rhs.notificationName
}
init(observer: AnyObject? = nil, notificationName: String, handler: @escaping () -> Void) {
self.notificationName = notificationName
self.handler = handler
self.observer = observer
}
func forwardNotification() -> Bool {
guard observer != nil else {
return false
}
handler()
return true
}
}

View File

@@ -0,0 +1,11 @@
<?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>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,54 @@
//
// LockScreenLeaveMeetingWidget.swift
// WidgetsExtension
//
// Created by Alex Bumbu on 31.10.2022.
//
import SwiftUI
import WidgetKit
struct LockScreenLeaveMeetingWidget: Widget {
let kind: String = "LockScreenLeaveMeetingWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetsEntryView(entry: entry)
}
.configurationDisplayName("Leave Jitsi Meeting Widget")
.description("This is a lockscreen widget for leaving the ongoing Jitsi meeting.")
.supportedFamilies([.accessoryCircular])
}
}
//struct LockScreenLeaveMeetingWidget_Preview: PreviewProvider {
// static var previews: some View {
// let meetingState = MeetingState(audioMuted: true)
//
// WidgetsEntryView(entry: CurrentMeetingEntry(date: Date(), meetingState: meetingState))
// .previewContext(WidgetPreviewContext(family: .accessoryCircular))
// .previewDisplayName("Circular")
// }
//}
private struct WidgetsEntryView: View {
@Environment(\.widgetFamily) var widgetFamily
var entry: Provider.Entry
var body: some View {
if entry.meetingState != nil, widgetFamily == .accessoryCircular {
AccessoryCircularWidgetView()
} else {
EmptyView()
}
}
}
private struct AccessoryCircularWidgetView: View {
var body: some View {
Image("leave_meeting")
.resizable()
.aspectRatio(contentMode: .fit)
.widgetURL(URL(string: "meet/leaveMeeting")!)
}
}

View File

@@ -0,0 +1,89 @@
//
// LockScreenMuteAudioWidget.swift
// WidgetsExtension
//
// Created by Alex Bumbu on 31.10.2022.
//
import SwiftUI
import WidgetKit
struct LockScreenMuteAudioWidget: Widget {
let kind: String = "LockScreenMuteAudioWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetsEntryView(entry: entry)
}
.configurationDisplayName("Mute Jitsi Audio Widget")
.description("This is a lockscreen widget for muting or unmuting the audio for the ongoing Jitsi meeting.")
.supportedFamilies([.accessoryCircular])
}
}
//struct LockScreenMuteAudioWidget_Preview: PreviewProvider {
// static var previews: some View {
// let meetingState = MeetingState(audioMuted: true)
//
// WidgetsEntryView(entry: CurrentMeetingEntry(date: Date(), meetingState: meetingState))
// .previewContext(WidgetPreviewContext(family: .accessoryInline))
// .previewDisplayName("Inline")
//
// WidgetsEntryView(entry: CurrentMeetingEntry(date: Date(), meetingState: meetingState))
// .previewContext(WidgetPreviewContext(family: .accessoryCircular))
// .previewDisplayName("Circular")
//
// WidgetsEntryView(entry: CurrentMeetingEntry(date: Date(), meetingState: meetingState))
// .previewContext(WidgetPreviewContext(family: .accessoryRectangular))
// .previewDisplayName("Rectangular")
// }
//}
private struct WidgetsEntryView: View {
@Environment(\.widgetFamily) var widgetFamily
var entry: Provider.Entry
var body: some View {
if let meetingState = entry.meetingState {
switch widgetFamily {
case .accessoryInline:
Text("Some meeting name")
case .accessoryRectangular:
AccessoryCircularWidgetView(audioMuted: meetingState.audioMuted)
case .accessoryCircular:
AccessoryCircularWidgetView(audioMuted: meetingState.audioMuted)
default:
EmptyView()
}
} else {
EmptyView()
}
}
}
private struct AccessoryRectangularWidgetView: View {
var audioMuted: Bool
var body: some View {
let imageName: String = audioMuted ? "microphone_on" : "microphone_off"
let caption: String = audioMuted ? "Unmute \naudio" : "Mute \naudio"
HStack {
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
Text(caption)
}.widgetURL(URL(string: "meet/toggleAudioMute")!)
}
}
private struct AccessoryCircularWidgetView: View {
var audioMuted: Bool
var body: some View {
let imageName: String = audioMuted ? "microphone_on" : "microphone_off"
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.widgetURL(URL(string: "meet/toggleAudioMute")!)
}
}

View File

@@ -0,0 +1,30 @@
//
// MeetingState.swift
// WidgetsExtension
//
// Created by Alex Bumbu on 28.10.2022.
//
import Foundation
struct MeetingState: Decodable {
var audioMuted: Bool
}
extension MeetingState {
private static var stateFileURL: URL? {
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.org.jitsi.meet.appgroup")?.appending(component: "widgetState")
}
static func load() -> MeetingState? {
guard
let stateFileURL = stateFileURL,
let data = try? Data(contentsOf: stateFileURL)
else {
return nil
}
let decoder = PropertyListDecoder()
return try? decoder.decode(MeetingState.self, from: data)
}
}

View File

@@ -0,0 +1,44 @@
//
// Provider.swift
// WidgetsExtension
//
// Created by Alex Bumbu on 31.10.2022.
//
import WidgetKit
import SwiftUI
struct CurrentMeetingEntry: TimelineEntry {
let date: Date
var meetingState: MeetingState?
}
class Provider: TimelineProvider {
private var currentMeetingState: MeetingState? {
return MeetingState.load()
}
func placeholder(in context: Context) -> CurrentMeetingEntry {
CurrentMeetingEntry(date: Date(),
meetingState: MeetingState(audioMuted: false))
}
func getSnapshot(in context: Context, completion: @escaping (CurrentMeetingEntry) -> ()) {
var meetingState = currentMeetingState
if context.isPreview {
meetingState = MeetingState(audioMuted: false)
}
let entry = CurrentMeetingEntry(date: Date(), meetingState: meetingState)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<CurrentMeetingEntry>) -> ()) {
var entries: [CurrentMeetingEntry] = []
let entry = CurrentMeetingEntry(date: Date(), meetingState: currentMeetingState)
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}

View File

@@ -0,0 +1,19 @@
//
// Widgets.swift
// Widgets
//
// Created by Alex Bumbu on 17.10.2022.
// Copyright © 2022 Facebook. All rights reserved.
//
import WidgetKit
import SwiftUI
@main
struct Widgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
LockScreenMuteAudioWidget()
LockScreenLeaveMeetingWidget()
}
}

View File

@@ -0,0 +1,10 @@
<?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>com.apple.security.application-groups</key>
<array>
<string>group.org.jitsi.meet.appgroup</string>
</array>
</dict>
</plist>

View File

@@ -23,14 +23,26 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */; };
4E46D952290FF39E00761DEF /* LockScreenMuteAudioWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E46D951290FF39E00761DEF /* LockScreenMuteAudioWidget.swift */; };
4E46D954290FF55600761DEF /* LockScreenLeaveMeetingWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E46D953290FF55600761DEF /* LockScreenLeaveMeetingWidget.swift */; };
4E6920B828FD84D700645D9E /* DarwinNotificationsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6920B728FD84D700645D9E /* DarwinNotificationsObserver.swift */; };
4E6A3E17291024B900E6B0B5 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6A3E16291024B900E6B0B5 /* Provider.swift */; };
4E90F9402632D1AB001102D4 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F93F2632D1AB001102D4 /* Atomic.swift */; };
4EA73DA3290C1D6C00A16FF8 /* MeetingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA73DA2290C1D6C00A16FF8 /* MeetingState.swift */; };
4EB06024260E026600F524C5 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EC49B8625BED71300E76218 /* ReplayKit.framework */; };
4EB06027260E026600F524C5 /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB06026260E026600F524C5 /* SampleHandler.swift */; };
4EB0602B260E026600F524C5 /* JitsiMeetBroadcastExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4EB0603C260E09D000F524C5 /* SocketConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB06039260E09D000F524C5 /* SocketConnection.swift */; };
4EB0603D260E09D000F524C5 /* DarwinNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0603A260E09D000F524C5 /* DarwinNotificationCenter.swift */; };
4EB0603E260E09D000F524C5 /* SampleUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0603B260E09D000F524C5 /* SampleUploader.swift */; };
4EBB458A28FFFD4100855769 /* RoutesHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBB458928FFFD4100855769 /* RoutesHandler.m */; };
4EBB458E2902E85B00855769 /* WidgetKitHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBB458D2902E85B00855769 /* WidgetKitHelper.swift */; };
4ECA496628FD590000085365 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4ECA496528FD590000085365 /* WidgetKit.framework */; };
4ECA496828FD590000085365 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4ECA496728FD590000085365 /* SwiftUI.framework */; };
4ECA496B28FD590000085365 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECA496A28FD590000085365 /* Widgets.swift */; };
4ECA496D28FD590000085365 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4ECA496C28FD590000085365 /* Assets.xcassets */; };
4ECA497128FD590000085365 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4ECA496428FD590000085365 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C2116A7673E01A1CCD5DC1F4 /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF9FEBA4DEAB800AD735681 /* libPods-JitsiMeet.a */; };
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */; };
DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; };
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -64,6 +76,13 @@
remoteGlobalIDString = 4EB06022260E026600F524C5;
remoteInfo = "JitsiMeetBroadcast Extension";
};
4ECA496F28FD590000085365 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4ECA496328FD590000085365;
remoteInfo = WidgetsExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -108,6 +127,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
4ECA497128FD590000085365 /* WidgetsExtension.appex in Embed App Extensions */,
4EB0602B260E026600F524C5 /* JitsiMeetBroadcastExtension.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
@@ -134,6 +154,7 @@
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>"; };
0BF9FEBA4DEAB800AD735681 /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -141,8 +162,12 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
3E0F4ED943C0B12BE77F6B45 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
4E46D951290FF39E00761DEF /* LockScreenMuteAudioWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenMuteAudioWidget.swift; sourceTree = "<group>"; };
4E46D953290FF55600761DEF /* LockScreenLeaveMeetingWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenLeaveMeetingWidget.swift; sourceTree = "<group>"; };
4E6920B728FD84D700645D9E /* DarwinNotificationsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarwinNotificationsObserver.swift; sourceTree = "<group>"; };
4E6A3E16291024B900E6B0B5 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = "<group>"; };
4E90F93F2632D1AB001102D4 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
4EA73DA2290C1D6C00A16FF8 /* MeetingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingState.swift; sourceTree = "<group>"; };
4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JitsiMeetBroadcastExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
4EB06026260E026600F524C5 /* SampleHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleHandler.swift; sourceTree = "<group>"; };
4EB06028260E026600F524C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -150,10 +175,21 @@
4EB06039260E09D000F524C5 /* SocketConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketConnection.swift; sourceTree = "<group>"; };
4EB0603A260E09D000F524C5 /* DarwinNotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarwinNotificationCenter.swift; sourceTree = "<group>"; };
4EB0603B260E09D000F524C5 /* SampleUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleUploader.swift; sourceTree = "<group>"; };
4EBB458828FFFD4100855769 /* RoutesHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutesHandler.h; sourceTree = "<group>"; };
4EBB458928FFFD4100855769 /* RoutesHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoutesHandler.m; sourceTree = "<group>"; };
4EBB458B2902A94700855769 /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = "<group>"; };
4EBB458C2902E85B00855769 /* JitsiMeet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeet-Bridging-Header.h"; sourceTree = "<group>"; };
4EBB458D2902E85B00855769 /* WidgetKitHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetKitHelper.swift; sourceTree = "<group>"; };
4EC49B8625BED71300E76218 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
756FCE06C08D9B947653C98A /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
4ECA496428FD590000085365 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
4ECA496528FD590000085365 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
4ECA496728FD590000085365 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
4ECA496A28FD590000085365 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = "<group>"; };
4ECA496C28FD590000085365 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
4ECA496E28FD590000085365 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7052390E12D7319D36D8E4CA /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
8CAA3C5A38E868335D1C1EC1 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DE050388256E904600DEE3A5 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/apple/WebRTC.xcframework"; sourceTree = "<group>"; };
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUtilities.m; sourceTree = "<group>"; };
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRUtilities.h; sourceTree = "<group>"; };
@@ -180,7 +216,7 @@
DED016F128ECBC9D009D5E8D /* WebRTC.xcframework in Frameworks */,
DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */,
FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */,
2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */,
C2116A7673E01A1CCD5DC1F4 /* libPods-JitsiMeet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -199,6 +235,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4ECA496128FD590000085365 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4ECA496828FD590000085365 /* SwiftUI.framework in Frameworks */,
4ECA496628FD590000085365 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -213,7 +258,9 @@
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
4EC49B8625BED71300E76218 /* ReplayKit.framework */,
D6152FF9E9F7B0E86F70A21D /* libPods-JitsiMeet.a */,
4ECA496528FD590000085365 /* WidgetKit.framework */,
4ECA496728FD590000085365 /* SwiftUI.framework */,
0BF9FEBA4DEAB800AD735681 /* libPods-JitsiMeet.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -261,6 +308,10 @@
0BBD021F212EB69D00CCB19F /* Types.h */,
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
4EBB458828FFFD4100855769 /* RoutesHandler.h */,
4EBB458928FFFD4100855769 /* RoutesHandler.m */,
4EBB458D2902E85B00855769 /* WidgetKitHelper.swift */,
4EBB458C2902E85B00855769 /* JitsiMeet-Bridging-Header.h */,
);
path = src;
sourceTree = "<group>";
@@ -282,9 +333,25 @@
sourceTree = "<group>";
tabWidth = 4;
};
4ECA496928FD590000085365 /* Widgets Extension */ = {
isa = PBXGroup;
children = (
4ECA496A28FD590000085365 /* Widgets.swift */,
4E46D951290FF39E00761DEF /* LockScreenMuteAudioWidget.swift */,
4E46D953290FF55600761DEF /* LockScreenLeaveMeetingWidget.swift */,
4E6A3E16291024B900E6B0B5 /* Provider.swift */,
4EA73DA2290C1D6C00A16FF8 /* MeetingState.swift */,
4E6920B728FD84D700645D9E /* DarwinNotificationsObserver.swift */,
4ECA496C28FD590000085365 /* Assets.xcassets */,
4ECA496E28FD590000085365 /* Info.plist */,
);
path = "Widgets Extension";
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
4EBB458B2902A94700855769 /* WidgetsExtension.entitlements */,
B3B083EB1D4955FF0069CEE7 /* app.entitlements */,
0B26BE711EC5BC4D00EEFB41 /* Frameworks */,
83CBBA001A601CBA00E9B192 /* Products */,
@@ -292,11 +359,12 @@
0BEA5C261F7B8F73000D0AB4 /* Watch app */,
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
4EB06025260E026600F524C5 /* JitsiMeetBroadcast Extension */,
CDD71F5E1157E9F283DF92A8 /* Pods */,
4ECA496928FD590000085365 /* Widgets Extension */,
BD4E28FA984EA7018FD927DF /* Pods */,
);
indentWidth = 2;
indentWidth = 4;
sourceTree = "<group>";
tabWidth = 2;
tabWidth = 4;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
@@ -305,15 +373,16 @@
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */,
4ECA496428FD590000085365 /* WidgetsExtension.appex */,
);
name = Products;
sourceTree = "<group>";
};
CDD71F5E1157E9F283DF92A8 /* Pods */ = {
BD4E28FA984EA7018FD927DF /* Pods */ = {
isa = PBXGroup;
children = (
756FCE06C08D9B947653C98A /* Pods-JitsiMeet.debug.xcconfig */,
3E0F4ED943C0B12BE77F6B45 /* Pods-JitsiMeet.release.xcconfig */,
7052390E12D7319D36D8E4CA /* Pods-JitsiMeet.debug.xcconfig */,
8CAA3C5A38E868335D1C1EC1 /* Pods-JitsiMeet.release.xcconfig */,
);
name = Pods;
path = ../Pods;
@@ -361,7 +430,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
buildPhases = (
69BC5020DBE393B56BD76636 /* [CP] Check Pods Manifest.lock */,
58E2CB346F2C2A873294F481 /* [CP] Check Pods Manifest.lock */,
0BBA83C41EC9F7600075A103 /* Run React packager */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
@@ -380,6 +449,7 @@
dependencies = (
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
4EB0602A260E026600F524C5 /* PBXTargetDependency */,
4ECA497028FD590000085365 /* PBXTargetDependency */,
);
name = JitsiMeet;
productName = "Jitsi Meet";
@@ -403,15 +473,32 @@
productReference = 4EB06023260E026600F524C5 /* JitsiMeetBroadcastExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
4ECA496328FD590000085365 /* WidgetsExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4ECA497428FD590100085365 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */;
buildPhases = (
4ECA496028FD590000085365 /* Sources */,
4ECA496128FD590000085365 /* Frameworks */,
4ECA496228FD590000085365 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = WidgetsExtension;
productName = WidgetsExtension;
productReference = 4ECA496428FD590000085365 /* WidgetsExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1240;
LastSwiftUpdateCheck = 1400;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = Facebook;
ORGANIZATIONNAME = "";
TargetAttributes = {
0BEA5C241F7B8F73000D0AB4 = {
CreatedOnToolsVersion = 9.0;
@@ -424,6 +511,7 @@
ProvisioningStyle = Automatic;
};
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1400;
SystemCapabilities = {
com.apple.SafariKeychain = {
enabled = 1;
@@ -436,6 +524,9 @@
4EB06022260E026600F524C5 = {
CreatedOnToolsVersion = 12.4;
};
4ECA496328FD590000085365 = {
CreatedOnToolsVersion = 14.0.1;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
@@ -455,6 +546,7 @@
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
4EB06022260E026600F524C5 /* JitsiMeetBroadcastExtension */,
4ECA496328FD590000085365 /* WidgetsExtension */,
);
};
/* End PBXProject section */
@@ -494,6 +586,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4ECA496228FD590000085365 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4ECA496D28FD590000085365 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -543,7 +643,7 @@
shellPath = /bin/sh;
shellScript = "if test \"$PRODUCT_BUNDLE_IDENTIFIER\" = \"com.atlassian.JitsiMeet.ios\"; then\n ENTITLEMENTS_PLIST=\"$PROJECT_DIR/app.entitlements\"\n \n /usr/libexec/PlistBuddy -c \"Add :com.apple.developer.avfoundation.multitasking-camera-access bool 1\" $ENTITLEMENTS_PLIST\nfi\n";
};
69BC5020DBE393B56BD76636 /* [CP] Check Pods Manifest.lock */ = {
58E2CB346F2C2A873294F481 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -642,8 +742,10 @@
files = (
0B412F1F1EDEE6E800B1A0A6 /* ViewController.m in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
4EBB458E2902E85B00855769 /* WidgetKitHelper.swift in Sources */,
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
4EBB458A28FFFD4100855769 /* RoutesHandler.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -659,6 +761,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4ECA496028FD590000085365 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4ECA496B28FD590000085365 /* Widgets.swift in Sources */,
4E6920B828FD84D700645D9E /* DarwinNotificationsObserver.swift in Sources */,
4E46D954290FF55600761DEF /* LockScreenLeaveMeetingWidget.swift in Sources */,
4EA73DA3290C1D6C00A16FF8 /* MeetingState.swift in Sources */,
4E6A3E17291024B900E6B0B5 /* Provider.swift in Sources */,
4E46D952290FF39E00761DEF /* LockScreenMuteAudioWidget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -677,6 +792,11 @@
target = 4EB06022260E026600F524C5 /* JitsiMeetBroadcastExtension */;
targetProxy = 4EB06029260E026600F524C5 /* PBXContainerItemProxy */;
};
4ECA497028FD590000085365 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4ECA496328FD590000085365 /* WidgetsExtension */;
targetProxy = 4ECA496F28FD590000085365 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -845,11 +965,12 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 756FCE06C08D9B947653C98A /* Pods-JitsiMeet.debug.xcconfig */;
baseConfigurationReference = 7052390E12D7319D36D8E4CA /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@@ -871,16 +992,20 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "src/JitsiMeet-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3E0F4ED943C0B12BE77F6B45 /* Pods-JitsiMeet.release.xcconfig */;
baseConfigurationReference = 8CAA3C5A38E868335D1C1EC1 /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@@ -901,6 +1026,8 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "src/JitsiMeet-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -973,6 +1100,91 @@
};
name = Release;
};
4ECA497228FD590100085365 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Widgets Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Facebook. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.widgets.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4ECA497328FD590100085365 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Widgets Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Facebook. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.widgets.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1126,6 +1338,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4ECA497428FD590100085365 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4ECA497228FD590100085365 /* Debug */,
4ECA497328FD590100085365 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4ECA496328FD590000085365"
BuildableName = "WidgetsExtension.appex"
BlueprintName = "WidgetsExtension"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.springboard">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4ECA496328FD590000085365"
BuildableName = "WidgetsExtension.appex"
BlueprintName = "WidgetsExtension"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = "LockScreenLeaveMeetingWidget"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "medium"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet.app"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -17,6 +17,7 @@
#import "AppDelegate.h"
#import "FIRUtilities.h"
#import "RoutesHandler.h"
#import "Types.h"
#import "ViewController.h"
@@ -69,7 +70,7 @@
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
// 1. Attempt to handle Universal Links through Firebase in order to support
// its Dynamic Links (which we utilize for the purposes of deferred deep
@@ -107,6 +108,10 @@
if ([[url absoluteString] containsString:@"google/link/?dismiss=1&is_weak_match=1"]) {
return NO;
}
if ([[RoutesHandler sharedInstance] routeURL:url]) {
return YES;
}
NSURL *openUrl = url;

View File

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

View File

@@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View File

@@ -0,0 +1,27 @@
//
// RoutesHandler.h
// JitsiMeet
//
// Created by Alex Bumbu on 19.10.2022.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RouteObserving <NSObject>
@property (nonatomic, readonly) void (^didRouteCallback)(NSString *route);
@end
@interface RoutesHandler : NSObject
+ (instancetype)sharedInstance;
- (void)registerObserver:(id<RouteObserving>)observer forRoute:(NSString *)route;
- (void)unregisterObserver:(id<RouteObserving>)observer;
- (BOOL)routeURL:(NSURL *)url;
@end
NS_ASSUME_NONNULL_END

123
ios/app/src/RoutesHandler.m Normal file
View File

@@ -0,0 +1,123 @@
//
// RoutesHandler.m
// JitsiMeet
//
// Created by Alex Bumbu on 19.10.2022.
//
#import "RoutesHandler.h"
@protocol Routing <NSObject>
@property (nonatomic, readonly) NSString *route;
@property (nonatomic, readonly) id<RouteObserving> observer;
@end
@interface Route: NSObject <Routing>
@property (nonatomic, readonly) NSString *route;
@property (nonatomic, readonly, weak) id<RouteObserving> observer;
+ (instancetype)routeWithString:(nonnull NSString *)route observer:(id<RouteObserving>)observer;
@end
#pragma mark -
@implementation RoutesHandler {
NSMutableArray *routes;
}
+ (instancetype)sharedInstance {
static RoutesHandler *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
routes = [[NSMutableArray alloc] init];
}
return self;
}
- (void)registerObserver:(id<RouteObserving>)observer forRoute:(NSString *)route {
[routes addObject:[Route routeWithString:route observer:observer]];
}
- (void)unregisterObserver:(id<RouteObserving>)observer {
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id<Routing> _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return evaluatedObject.observer == nil || evaluatedObject.observer == observer;
}];
NSArray *routesToClear = [routes filteredArrayUsingPredicate:predicate];
[routes removeObjectsInArray:routesToClear];
}
- (BOOL)routeURL:(NSURL *)url {
[self clearRoutes];
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:false];
if (!components) {
return false;
}
NSString *route = components.path;
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id<Routing> _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [evaluatedObject.route isEqualToString:route];
}];
NSArray *routesToHandle = [routes filteredArrayUsingPredicate:predicate];
if ([routesToHandle count] == 0) {
return false;
}
for (id<Routing> route in routesToHandle) {
route.observer.didRouteCallback(route.route);
}
return true;
}
- (void)clearRoutes {
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id<Routing> _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return evaluatedObject.observer == nil;
}];
NSArray *routesToClear = [routes filteredArrayUsingPredicate:predicate];
[routes removeObjectsInArray:routesToClear];
}
@end
#pragma mark -
@interface Route()
@property (nonatomic, nonnull, copy) NSString *route;
@property (nonatomic, weak) id<RouteObserving> observer;
@end
@implementation Route
+ (instancetype)routeWithString:(nonnull NSString *)route observer:(nonnull id<RouteObserving>)observer {
return [[Route alloc] initWithString:route observer:observer];
}
- (instancetype)initWithString:(nonnull NSString *)route observer:(nonnull id<RouteObserving>)observer {
self = [super init];
if (self) {
self.route = route;
self.observer = observer;
}
return self;
}
@end

View File

@@ -21,8 +21,16 @@
@import JitsiMeetSDK;
#import "Types.h"
#import "RoutesHandler.h"
#import "ViewController.h"
#import "jitsi_meet-Swift.h"
@interface ViewController() <RouteObserving>
@property (nonatomic, nonnull, copy) void (^didRouteCallback)(NSString *);
@property (nonatomic, assign) BOOL audioMuted;
@end
@implementation ViewController
@@ -33,6 +41,8 @@
view.delegate = self;
[view join:[[JitsiMeet sharedInstance] getInitialConferenceOptions]];
[self registerRouteObserver];
}
// JitsiMeetViewDelegate
@@ -53,6 +63,10 @@
- (void)conferenceJoined:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
self.audioMuted = [[data objectForKey:@"isAudioMuted"] boolValue];
[self refreshWidgetState:self.audioMuted];
// Register a NSUserActivity for this conference so it can be invoked as a
// Siri shortcut.
@@ -82,6 +96,12 @@
- (void)conferenceTerminated:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_TERMINATED" withData:data];
NSURL *sharedContainer = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.org.jitsi.meet.appgroup"];
NSURL *widgetStateFileURL = [sharedContainer URLByAppendingPathComponent:@"widgetState"];
[[NSFileManager defaultManager] removeItemAtURL:widgetStateFileURL error:nil];
[WidgetKitHelper reloadAllWidgets];
}
- (void)conferenceWillJoin:(NSDictionary *)data {
@@ -107,7 +127,16 @@
}
- (void)audioMutedChanged:(NSDictionary *)data {
NSLog(@"%@%@", @"Audio muted changed: ", data[@"muted"]);
NSLog(@"%@%@", @"Audio muted changed: ", data[@"muted"]);
// CFNotificationCenterRef notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
// CFNotificationCenterPostNotification(notificationCenter,
// (__bridge CFStringRef)@"iOS_MeetingMutedChanged",
// NULL,
// NULL,
// true);
self.audioMuted = [[data objectForKey:@"muted"] boolValue];
[self refreshWidgetState:self.audioMuted];
}
- (void)endpointTextMessageReceived:(NSDictionary *)data {
@@ -132,9 +161,40 @@
#pragma mark - Helpers
- (void)registerRouteObserver {
__weak typeof(self) weakSelf = self;
__weak JitsiMeetView *view = (JitsiMeetView *)self.view;
self.didRouteCallback = ^(NSString *route) {
if ([route isEqual:@"meet/toggleAudioMute"]) {
weakSelf.audioMuted = !weakSelf.audioMuted;
[view setAudioMuted:weakSelf.audioMuted];
} else if ([route isEqualToString:@"meet/leaveMeeting"]) {
[weakSelf terminate];
}
};
[[RoutesHandler sharedInstance] registerObserver:self forRoute:@"meet/toggleAudioMute"];
[[RoutesHandler sharedInstance] registerObserver:self forRoute:@"meet/leaveMeeting"];
}
- (void)terminate {
JitsiMeetView *view = (JitsiMeetView *) self.view;
[view leave];
}
- (void)refreshWidgetState:(BOOL)audioMuted {
// let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
// return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
NSURL *sharedContainer = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.org.jitsi.meet.appgroup"];
NSURL *widgetStateFileURL = [sharedContainer URLByAppendingPathComponent:@"widgetState"];
NSDictionary *meetingState = @{@"audioMuted": @(audioMuted)};
if (![meetingState writeToURL:widgetStateFileURL atomically:true]) {
NSLog(@"error saving state file");
}
[WidgetKitHelper reloadAllWidgets];
}
@end

View File

@@ -0,0 +1,20 @@
//
// WidgetKitHelper.swift
// JitsiMeet
//
// Created by Alex Bumbu on 21.10.2022.
//
import WidgetKit
@available(iOS 14.0, *)
@objcMembers final class WidgetKitHelper: NSObject {
class func reloadAllWidgets(){
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.reloadAllTimelines()
#endif
}
}

View File

@@ -57,8 +57,8 @@
openURL:(NSURL *_Nonnull)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *_Nonnull)options;
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window;
- (UIInterfaceOrientationMask)application:(UIApplication *_Nonnull)application
supportedInterfaceOrientationsForWindow:(UIWindow *_Nonnull)window;
#pragma mark - Utility methods

View File

@@ -32,7 +32,7 @@ const AVATAR_CHECKED_URLS = new Map();
/* eslint-disable arrow-body-style, no-unused-vars */
const AVATAR_CHECKER_FUNCTIONS = [
(participant: IParticipant) => {
return isJigasiParticipant(participant) ? JIGASI_PARTICIPANT_ICON : null;
return participant?.isJigasi ? JIGASI_PARTICIPANT_ICON : null;
},
(participant: IParticipant) => {
return isWhiteboardParticipant(participant) ? WHITEBOARD_PARTICIPANT_ICON : null;
@@ -281,16 +281,6 @@ export function getFakeParticipants(stateful: IStateful) {
return toState(stateful)['features/base/participants'].fakeParticipants;
}
/**
* Returns whether the fake participant is Jigasi.
*
* @param {IParticipant|undefined} participant - The participant entity.
* @returns {boolean} - True if it's a Jigasi participant.
*/
function isJigasiParticipant(participant?: IParticipant): boolean {
return participant?.fakeParticipant === FakeParticipant.Jigasi;
}
/**
* Returns whether the fake participant is a local screenshare.
*

View File

@@ -80,7 +80,7 @@ import {
} from './functions';
import logger from './logger';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
import { FakeParticipant, IJitsiParticipant } from './types';
import { IJitsiParticipant } from './types';
import './subscriber';
@@ -438,7 +438,7 @@ StateListenerRegistry.register(
store.dispatch(participantUpdated({
conference,
id: participant.getId(),
fakeParticipant: value ? FakeParticipant.Jigasi : undefined
isJigasi: value
})),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'features_screen-sharing': (participant: IJitsiParticipant, value: string) =>

View File

@@ -1,5 +1,4 @@
export enum FakeParticipant {
Jigasi = 'Jigasi',
LocalScreenShare = 'LocalScreenShare',
RemoteScreenShare = 'RemoteScreenShare',
SharedVideo = 'SharedVideo',
@@ -21,6 +20,7 @@ export interface IParticipant {
};
getId?: Function;
id: string;
isJigasi?: boolean;
isReplaced?: boolean;
isReplacing?: number;
jwtId?: string;

View File

@@ -136,6 +136,10 @@ class StateListenerRegistry {
* @returns {void}
*/
register(selector: Selector, listener: Listener, options?: RegistrationOptions) {
if (typeof selector !== 'function' || typeof listener !== 'function') {
throw new Error('Invalid selector or listener!');
}
this._selectorListeners.add({
listener,
selector,

View File

@@ -1,3 +1,8 @@
/**
* IMPORTANT: this file is deprecated. All of these colors should be moved to
* the theme instead.
*/
/**
* The application's definition of the default color black.
*/
@@ -18,29 +23,12 @@ export const ColorPalette = {
* the sake of consistency.
*/
black: BLACK,
blackBlue: 'rgb(0, 3, 6)',
blue: '#17A0DB',
blueHighlight: '#1081b2',
buttonUnderlay: '#495258',
darkGrey: '#555555',
darkBackground: 'rgb(19,21,25)',
green: '#40b183',
lightGrey: '#AAAAAA',
overflowMenuItemUnderlay: '#EEEEEE',
red: '#D00000',
transparent: 'rgba(0, 0, 0, 0)',
toggled: 'rgba(255,255,255,.15)',
warning: 'rgb(215, 121, 118)',
white: '#FFFFFF',
/**
* These are colors from the atlaskit to be used on mobile, when needed.
*
* FIXME: Maybe a better solution would be good, or a native packaging of
* the respective atlaskit components.
*/
G400: '#00875A', // Slime
N500: '#42526E', // McFanning
R400: '#DE350B', // Red dirt
Y200: '#FFC400' // Pub mix
white: '#FFFFFF'
};

View File

@@ -2,7 +2,7 @@
import React, { Component, Fragment } from 'react';
import { statsEmitter } from '../../../connection-indicator';
import statsEmitter from '../../../connection-indicator/statsEmitter';
import { getLocalParticipant } from '../../participants';
import { connect } from '../../redux';
import { isTestModeEnabled } from '../functions';

View File

@@ -190,6 +190,7 @@ interface IDialogProps {
children?: React.ReactNode;
className?: string;
description?: string;
disableAutoHideOnSubmit?: boolean;
disableBackdropClose?: boolean;
disableEnter?: boolean;
hideCloseButton?: boolean;
@@ -211,6 +212,7 @@ const Dialog = ({
children,
className,
description,
disableAutoHideOnSubmit = false,
disableBackdropClose,
hideCloseButton,
disableEnter,
@@ -232,7 +234,7 @@ const Dialog = ({
}, [ onCancel ]);
const submit = useCallback(() => {
dispatch(hideDialog());
!disableAutoHideOnSubmit && dispatch(hideDialog());
onSubmit?.();
}, [ onSubmit ]);

View File

@@ -2,18 +2,29 @@ import React, { ReactElement, useEffect, useState } from 'react';
export const DialogTransitionContext = React.createContext({ isUnmounting: false });
type TimeoutType = ReturnType<typeof setTimeout>;
const DialogTransition = ({ children }: { children: ReactElement | null; }) => {
const [ childrenToRender, setChildrenToRender ] = useState(children);
const [ isUnmounting, setIsUnmounting ] = useState(false);
const [ timeoutID, setTimeoutID ] = useState<TimeoutType | undefined>(undefined);
useEffect(() => {
if (children === null) {
setIsUnmounting(true);
setTimeout(() => {
setChildrenToRender(children);
setIsUnmounting(false);
}, 150);
if (typeof timeoutID === 'undefined') {
setTimeoutID(setTimeout(() => {
setChildrenToRender(children);
setIsUnmounting(false);
setTimeoutID(undefined);
}, 150));
}
} else {
if (typeof timeoutID !== 'undefined') {
clearTimeout(timeoutID);
setTimeoutID(undefined);
setIsUnmounting(false);
}
setChildrenToRender(children);
}
}, [ children ]);

View File

@@ -1,4 +1,5 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { StyleProp, Text, View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux';
@@ -57,7 +58,9 @@ const TitleBar = (props: IProps): JSX.Element => {
<VideoQualityLabel />
</View>
<ConnectionIndicator
// @ts-ignore
iconStyle = { styles.connectionIndicatorIcon }
// @ts-ignore
participantId = { localParticipantId } />
<View style = { styles.headerLabels as StyleProp<ViewStyle> }>
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />

View File

@@ -209,7 +209,7 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
*/
export function mapStateToProps(state: Object) {
return {
_autoHideTimeout: state['features/base/config'].connectionIndicators.autoHideTimeout ?? defaultAutoHideTimeout
_autoHideTimeout: state['features/base/config'].connectionIndicators?.autoHideTimeout ?? defaultAutoHideTimeout
};
}

View File

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

View File

@@ -1,3 +0,0 @@
// @flow
export * from './web';

View File

@@ -1,68 +0,0 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { IconConnection } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import indicatorStyles from '../../../filmstrip/components/native/styles';
import AbstractConnectionIndicator, {
type Props,
type State
} from '../AbstractConnectionIndicator';
import { CONNECTOR_INDICATOR_COLORS, iconStyle } from './styles';
/**
* Implements an indicator to show the quality of the connection of a participant.
*/
class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
autoHideTimeout: undefined,
showIndicator: false,
stats: {}
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { showIndicator, stats } = this.state;
const { percent } = stats;
if (!showIndicator || typeof percent === 'undefined') {
return null;
}
// Signal level on a scale 0..2
const signalLevel = Math.floor(percent / 33.4);
return (
<View
style = {{
...indicatorStyles.indicatorContainer,
backgroundColor: CONNECTOR_INDICATOR_COLORS[signalLevel]
}}>
<BaseIndicator
icon = { IconConnection }
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
}
}
export default connect()(ConnectionIndicator);

View File

@@ -0,0 +1,216 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { View } from 'react-native';
import { IReduxState } from '../../../app/types';
import { IconConnection } from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getLocalParticipant,
getParticipantById,
isScreenShareParticipant
} from '../../../base/participants/functions';
// @ts-ignore
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import { connect } from '../../../base/redux/functions';
import {
getTrackByMediaTypeAndParticipant
} from '../../../base/tracks/functions.native';
// @ts-ignore
import indicatorStyles from '../../../filmstrip/components/native/styles';
import {
isTrackStreamingStatusInactive,
isTrackStreamingStatusInterrupted
} from '../../functions';
import AbstractConnectionIndicator, {
type Props as AbstractProps,
mapStateToProps as _abstractMapStateToProps
// @ts-ignore
} from '../AbstractConnectionIndicator';
import {
CONNECTOR_INDICATOR_COLORS,
CONNECTOR_INDICATOR_LOST,
CONNECTOR_INDICATOR_OTHER,
iconStyle
} from './styles';
type IProps = AbstractProps & {
/**
* Whether connection indicators are disabled or not.
*/
_connectionIndicatorDisabled: boolean;
/**
* Whether the inactive connection indicator is disabled or not.
*/
_connectionIndicatorInactiveDisabled: boolean;
/**
* Whether the connection is inactive or not.
*/
_isConnectionStatusInactive: boolean;
/**
* Whether the connection is interrupted or not.
*/
_isConnectionStatusInterrupted: boolean;
/**
* Whether the current participant is a virtual screenshare.
*/
_isVirtualScreenshareParticipant: boolean;
/**
* Redux dispatch function.
*/
dispatch: Function;
/**
* Icon style override.
*/
iconStyle: any;
};
type IState = {
autoHideTimeout: number | undefined;
showIndicator: boolean;
stats: any;
};
/**
* Implements an indicator to show the quality of the connection of a participant.
*/
class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// @ts-ignore
this.state = {
autoHideTimeout: undefined,
showIndicator: false,
stats: {}
};
}
/**
* Get the icon configuration from CONNECTOR_INDICATOR_COLORS which has a percentage
* that matches or exceeds the passed in percentage. The implementation
* assumes CONNECTOR_INDICATOR_COLORS is already sorted by highest to lowest
* percentage.
*
* @param {number} percent - The connection percentage, out of 100, to find
* the closest matching configuration for.
* @private
* @returns {Object}
*/
_getDisplayConfiguration(percent: number): any {
return CONNECTOR_INDICATOR_COLORS.find(x => percent >= x.percent) || {};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
_connectionIndicatorInactiveDisabled,
_connectionIndicatorDisabled,
_isVirtualScreenshareParticipant,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
// @ts-ignore
} = this.props;
const {
showIndicator,
stats
// @ts-ignore
} = this.state;
const { percent } = stats;
if (!showIndicator || typeof percent === 'undefined'
|| _connectionIndicatorDisabled || _isVirtualScreenshareParticipant) {
return null;
}
let indicatorColor;
if (_isConnectionStatusInactive) {
if (_connectionIndicatorInactiveDisabled) {
return null;
}
indicatorColor = CONNECTOR_INDICATOR_OTHER;
} else if (_isConnectionStatusInterrupted) {
indicatorColor = CONNECTOR_INDICATOR_LOST;
} else {
const displayConfig = this._getDisplayConfiguration(percent);
if (!displayConfig) {
return null;
}
indicatorColor = displayConfig.color;
}
return (
<View
style = {{
...indicatorStyles.indicatorContainer,
backgroundColor: indicatorColor
}}>
<BaseIndicator
icon = { IconConnection }
// @ts-ignore
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {IProps} ownProps - The own props of the component.
* @returns {IProps}
*/
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const { participantId } = ownProps;
const tracks = state['features/base/tracks'];
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
const _isVirtualScreenshareParticipant = isScreenShareParticipant(participant);
let _isConnectionStatusInactive;
let _isConnectionStatusInterrupted;
if (!_isVirtualScreenshareParticipant) {
const _videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
_isConnectionStatusInactive = isTrackStreamingStatusInactive(_videoTrack);
_isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
}
return {
..._abstractMapStateToProps(state),
_connectionIndicatorInactiveDisabled:
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
_connectionIndicatorDisabled:
Boolean(state['features/base/config'].connectionIndicators?.disabled),
_isVirtualScreenshareParticipant,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
};
}
// @ts-ignore
export default connect(_mapStateToProps)(ConnectionIndicator);

View File

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

View File

@@ -1,13 +0,0 @@
// @flow
import { ColorPalette } from '../../../base/styles';
export const CONNECTOR_INDICATOR_COLORS = [
ColorPalette.red,
ColorPalette.Y200,
ColorPalette.green
];
export const iconStyle = {
fontSize: 14
};

View File

@@ -0,0 +1,32 @@
/* eslint-disable lines-around-comment */
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
// @ts-ignore
import { INDICATOR_DISPLAY_THRESHOLD } from '../AbstractConnectionIndicator';
export const CONNECTOR_INDICATOR_LOST = BaseTheme.palette.ui05;
export const CONNECTOR_INDICATOR_OTHER = BaseTheme.palette.action01;
export const CONNECTOR_INDICATOR_COLORS = [
// Full (3 bars)
{
color: BaseTheme.palette.success01,
percent: INDICATOR_DISPLAY_THRESHOLD
},
// 2 bars.
{
color: BaseTheme.palette.warning01,
percent: 10
},
// 1 bar.
{
color: BaseTheme.palette.iconError,
percent: 0
}
];
export const iconStyle = {
fontSize: 14
};

View File

@@ -27,7 +27,8 @@ import {
import AbstractConnectionIndicator, {
type Props as AbstractProps,
type State as AbstractState,
INDICATOR_DISPLAY_THRESHOLD
INDICATOR_DISPLAY_THRESHOLD,
mapStateToProps as _abstractMapStateToProps
// @ts-ignore
} from '../AbstractConnectionIndicator';
@@ -386,6 +387,7 @@ export function _mapStateToProps(state: IReduxState, ownProps: Props) {
const _isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
return {
..._abstractMapStateToProps(state),
_connectionIndicatorInactiveDisabled:
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),

View File

@@ -1,5 +0,0 @@
// @flow
export * from './components';
export { default as statsEmitter } from './statsEmitter';

View File

@@ -25,7 +25,7 @@ import {
getVideoTrackByParticipant,
trackStreamingStatusChanged
} from '../../../base/tracks';
import { ConnectionIndicator } from '../../../connection-indicator';
import ConnectionIndicator from '../../../connection-indicator/components/native/ConnectionIndicator';
import { DisplayNameLabel } from '../../../display-name';
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
import {
@@ -191,6 +191,8 @@ class Thumbnail extends PureComponent<Props> {
dispatch(showSharedVideoMenu(_participantId));
}
// TODO: add support for getting info about the virtual screen shares.
if (!_fakeParticipant) {
dispatch(showContextMenuDetails(_participantId, _local));
}

View File

@@ -85,6 +85,16 @@ export function getActiveParticipantsIds(_state: any) {
return [];
}
/**
* Not implemented on mobile.
*
* @param {any} _state - Redux state.
* @returns {Array<Object>}
*/
export function getPinnedActiveParticipants(_state: any) {
return [];
}
/**
* Returns the number of participants displayed in tile view.
*

View File

@@ -183,7 +183,7 @@ function AddPeopleDialog({
&& <DialInSection phoneNumber = { _phoneNumber } />
}
{
!_dialInVisible && _isVpaasMeeting && <DialInLimit />
!_phoneNumber && _dialInVisible && _isVpaasMeeting && <DialInLimit />
}
</div>
</Dialog>

View File

@@ -143,7 +143,7 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
return (
<JitsiScreen
addBottomPadding = { false }
safeAreaInsets = { [ 'left' ] }
safeAreaInsets = { [ 'left', 'right' ] }
style = { contentWrapperStyles }>
<BrandingImageBackground />
{

View File

@@ -3,11 +3,12 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
export default {
joinButton: {
marginVertical: BaseTheme.spacing[3]
marginTop: BaseTheme.spacing[3]
},
joinLowBandwidthLabel: {
color: BaseTheme.palette.text01,
marginTop: BaseTheme.spacing[3],
textAlign: 'center'
},
@@ -76,7 +77,7 @@ export default {
flexDirection: 'row',
height: 60,
justifyContent: 'space-between',
marginVertical: BaseTheme.spacing[3],
marginBottom: BaseTheme.spacing[3],
paddingHorizontal: BaseTheme.spacing[2],
width: 148
},

View File

@@ -2,7 +2,6 @@
import React, { Component } from 'react';
import { hideDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n';
import { getParticipantById } from '../../base/participants';
import { connect } from '../../base/redux';
@@ -138,7 +137,6 @@ class RemoteControlAuthorizationDialog extends Component<Props> {
_onSubmit() {
const { dispatch, participantId } = this.props;
dispatch(hideDialog());
dispatch(grant(participantId));
return false;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { hideDialog } from '../../../base/dialog/actions';
import { translate } from '../../../base/i18n/functions';
import { connect } from '../../../base/redux/functions';
import Dialog from '../../../base/ui/components/web/Dialog';
@@ -52,7 +53,9 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<any> {
_onSubmitValue() {
const result = super._onSetVideoLink(this.state.value);
if (!result) {
if (result) {
this.props.dispatch(hideDialog());
} else {
this.setState({
error: true
});
@@ -72,6 +75,7 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<any> {
return (
<Dialog
disableAutoHideOnSubmit = { true }
ok = {{
disabled: this.state.okDisabled,
translationKey: 'dialog.Share'

View File

@@ -1,17 +1,34 @@
import React, { Component } from 'react';
/* eslint-disable lines-around-comment */
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { withTheme } from 'react-native-paper';
import { Avatar } from '../../../base/avatar';
import { BottomSheet, hideSheet } from '../../../base/dialog';
import { IReduxState } from '../../../app/types';
// @ts-ignore
import Avatar from '../../../base/avatar/components/Avatar';
import { hideSheet } from '../../../base/dialog/actions';
// @ts-ignore
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
// @ts-ignore
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
import { translate } from '../../../base/i18n';
import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons';
import { getParticipantDisplayName } from '../../../base/participants';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import { translate } from '../../../base/i18n/functions';
import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import { getParticipantDisplayName } from '../../../base/participants/functions';
// @ts-ignore
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import { connect } from '../../../base/redux/functions';
import {
getTrackByMediaTypeAndParticipant
} from '../../../base/tracks/functions.native';
import {
isTrackStreamingStatusInactive,
isTrackStreamingStatusInterrupted
} from '../../../connection-indicator/functions';
import statsEmitter from '../../../connection-indicator/statsEmitter';
// @ts-ignore
import styles from './styles';
/**
@@ -20,62 +37,87 @@ import styles from './styles';
const AVATAR_SIZE = 25;
const CONNECTION_QUALITY = [
'Low',
'Medium',
'Good'
// Full (3 bars)
{
msg: 'connectionindicator.quality.good',
percent: 30 // INDICATOR_DISPLAY_THRESHOLD
},
// 2 bars.
{
msg: 'connectionindicator.quality.nonoptimal',
percent: 10
},
// 1 bar.
{
msg: 'connectionindicator.quality.poor',
percent: 0
}
];
export type Props = {
type IProps = {
/**
* The Redux dispatch function.
* Whether this participant's connection is inactive.
*/
dispatch: Function,
_isConnectionStatusInactive: boolean;
/**
* The ID of the participant that this button is supposed to pin.
* Whether this participant's connection is interrupted.
*/
participantID: string,
_isConnectionStatusInterrupted: boolean;
/**
* True if the menu is currently open, false otherwise.
*/
_isOpen: boolean,
_isOpen: boolean;
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string,
_participantDisplayName: string;
/**
* The Redux dispatch function.
*/
dispatch: Function;
/**
* The ID of the participant that this button is supposed to pin.
*/
participantID: string;
/**
* The function to be used to translate i18n labels.
*/
t: Function,
t: Function;
/**
* Theme used for styles.
*/
theme: Object
}
theme: any;
};
/**
* The type of the React {@code Component} state of {@link ConnectionStatusComponent}.
*/
type State = {
resolutionString: string,
downloadString: string,
uploadString: string,
packetLostDownloadString: string,
packetLostUploadString: string,
serverRegionString: string,
codecString: string,
connectionString: string
type IState = {
codecString: string;
connectionString: string;
downloadString: string;
packetLostDownloadString: string;
packetLostUploadString: string;
resolutionString: string;
serverRegionString: string;
uploadString: string;
};
/**
* Class to implement a popup menu that show the connection statistics.
*/
class ConnectionStatusComponent extends Component<Props, State> {
class ConnectionStatusComponent extends PureComponent<IProps, IState> {
/**
* Constructor of the component.
@@ -85,7 +127,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onStatsUpdated = this._onStatsUpdated.bind(this);
@@ -121,15 +163,15 @@ class ConnectionStatusComponent extends Component<Props, State> {
<View style = { styles.statsWrapper }>
<View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.status')} ` }
{ t('connectionindicator.status') }
</Text>
<Text style = { styles.statsInfoText }>
{ this.state.connectionString }
{ t(this.state.connectionString) }
</Text>
</View>
<View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.bitrate')}` }
{ t('connectionindicator.bitrate') }
</Text>
<BaseIndicator
icon = { IconArrowDownLarge }
@@ -150,7 +192,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View>
<View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.packetloss')}` }
{ t('connectionindicator.packetloss') }
</Text>
<BaseIndicator
icon = { IconArrowDownLarge }
@@ -171,7 +213,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View>
<View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.resolution')} ` }
{ t('connectionindicator.resolution') }
</Text>
<Text style = { styles.statsInfoText }>
{ this.state.resolutionString }
@@ -179,7 +221,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View>
<View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.codecs')}` }
{ t('connectionindicator.codecs') }
</Text>
<Text style = { styles.statsInfoText }>
{ this.state.codecString }
@@ -206,7 +248,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @inheritdoc
* returns {void}
*/
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps: IProps) {
if (prevProps.participantID !== this.props.participantID) {
statsEmitter.unsubscribeToClientStats(
prevProps.participantID, this._onStatsUpdated);
@@ -215,8 +257,6 @@ class ConnectionStatusComponent extends Component<Props, State> {
}
}
_onStatsUpdated: Object => void;
/**
* Callback invoked when new connection stats associated with the passed in
* user ID are available. Will update the component's display of current
@@ -239,9 +279,8 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {State}
*/
_buildState(stats) {
_buildState(stats: any) {
const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {};
const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {};
return {
@@ -254,7 +293,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
? this.state.packetLostUploadString : `${uploadPacketLost}%`,
serverRegionString: this._extractServer(stats) ?? this.state.serverRegionString,
codecString: this._extractCodecs(stats) ?? this.state.codecString,
connectionString: this._extractConnection(stats) ?? this.state.connectionString
connectionString: this._extractConnection(stats)
};
}
@@ -265,7 +304,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {string}
*/
_extractResolutionString(stats) {
_extractResolutionString(stats: any) {
const { framerate, resolution } = stats;
const resolutionString = Object.keys(resolution || {})
@@ -290,7 +329,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {{ download, upload }}
*/
_extractBitrate(stats) {
_extractBitrate(stats: any) {
return stats.bitrate;
}
@@ -301,7 +340,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {{ download, upload }}
*/
_extractPacketLost(stats) {
_extractPacketLost(stats: any) {
return stats.packetLoss;
}
@@ -312,7 +351,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {string}
*/
_extractServer(stats) {
_extractServer(stats: any) {
return stats.serverRegion;
}
@@ -323,17 +362,17 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {string}
*/
_extractCodecs(stats) {
_extractCodecs(stats: any) {
const { codec } = stats;
let codecString;
if (codec) {
const audioCodecs = Object.values(codec)
.map(c => c.audio)
.map((c: any) => c.audio)
.filter(Boolean);
const videoCodecs = Object.values(codec)
.map(c => c.video)
.map((c: any) => c.video)
.filter(Boolean);
if (audioCodecs.length || videoCodecs.length) {
@@ -352,14 +391,39 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {string}
*/
_extractConnection(stats) {
_extractConnection(stats: any) {
const { connectionQuality } = stats;
const {
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
} = this.props;
if (connectionQuality) {
const signalLevel = Math.floor(connectionQuality / 33.4);
return CONNECTION_QUALITY[signalLevel];
if (_isConnectionStatusInactive) {
return 'connectionindicator.quality.inactive';
} else if (_isConnectionStatusInterrupted) {
return 'connectionindicator.quality.lost';
} else if (typeof connectionQuality === 'undefined') {
return 'connectionindicator.quality.good';
}
const qualityConfig = this._getQualityConfig(connectionQuality);
return qualityConfig.msg;
}
/**
* Get the quality configuration from CONNECTION_QUALITY which has a percentage
* that matches or exceeds the passed in percentage. The implementation
* assumes CONNECTION_QUALITY is already sorted by highest to lowest
* percentage.
*
* @param {number} percent - The connection percentage, out of 100, to find
* the closest matching configuration for.
* @private
* @returns {Object}
*/
_getQualityConfig(percent: number): any {
return CONNECTION_QUALITY.find(x => percent >= x.percent) || {};
}
/**
@@ -406,12 +470,19 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const { participantID } = ownProps;
const tracks = state['features/base/tracks'];
const _videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
const _isConnectionStatusInactive = isTrackStreamingStatusInactive(_videoTrack);
const _isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
return {
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_participantDisplayName: getParticipantDisplayName(state, participantID)
};
}
// @ts-ignore
export default translate(connect(_mapStateToProps)(withTheme(ConnectionStatusComponent)));