Compare commits
24 Commits
jitihouse/
...
watchos
Author | SHA1 | Date |
---|---|---|
Saúl Ibarra Corretgé | dcedb6334e | |
paweldomas | da5f1081f2 | |
paweldomas | 2ee44578c8 | |
Saúl Ibarra Corretgé | 53697f9d7e | |
Saúl Ibarra Corretgé | 0ffeef3ef4 | |
paweldomas | 2cb2de917e | |
paweldomas | f048a89a13 | |
Saúl Ibarra Corretgé | c0e00dab4c | |
Saúl Ibarra Corretgé | aef73cc85f | |
Saúl Ibarra Corretgé | d183d41fdf | |
Saúl Ibarra Corretgé | b4f236b433 | |
Saúl Ibarra Corretgé | dcb32d2792 | |
paweldomas | ea4cd420fa | |
Saúl Ibarra Corretgé | 0ef4be6e9e | |
paweldomas | 72d1144f47 | |
paweldomas | 2d1f2ed8bd | |
Saúl Ibarra Corretgé | 94a26a33c6 | |
Saúl Ibarra Corretgé | 93aa8c5883 | |
Saúl Ibarra Corretgé | 367bc693f7 | |
Saúl Ibarra Corretgé | f2df6fb657 | |
Saúl Ibarra Corretgé | c6c7f08fb2 | |
Saúl Ibarra Corretgé | eee01c314c | |
Saúl Ibarra Corretgé | 836710924e | |
Saúl Ibarra Corretgé | 205dc5f0c3 |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"presets": [
|
||||
"react-native",
|
||||
"react-native-stage-0"
|
||||
]
|
||||
}
|
|
@ -208,14 +208,6 @@ Helper method which should be called from the activity's `onResume` method.
|
|||
|
||||
This is a static method.
|
||||
|
||||
#### onNewIntent(intent)
|
||||
|
||||
Helper method for integrating the *deep linking* functionality. If your app's
|
||||
activity is launched in "singleTask" mode this method should be called from the
|
||||
activity's `onNewIntent` method.
|
||||
|
||||
This is a static method.
|
||||
|
||||
#### JitsiMeetViewListener
|
||||
|
||||
`JitsiMeetViewListener` provides an interface apps can implement to listen to
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class AppInfoModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* React Native module name.
|
||||
*/
|
||||
private static final String MODULE_NAME = "AppInfo";
|
||||
|
||||
public AppInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a mapping with the constants this module is exporting.
|
||||
*
|
||||
* @return a {@link Map} mapping the constants to be exported with their
|
||||
* values.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
Context context = getReactApplicationContext();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo appInfo;
|
||||
PackageInfo packageInfo;
|
||||
|
||||
try {
|
||||
appInfo = pm.getApplicationInfo(context.getPackageName(), 0);
|
||||
packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
constants.put("name", "");
|
||||
constants.put("version", "");
|
||||
return constants;
|
||||
}
|
||||
|
||||
constants.put("name", pm.getApplicationLabel(appInfo));
|
||||
constants.put("version", packageInfo.versionName);
|
||||
return constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
}
|
|
@ -200,7 +200,12 @@ public class JitsiMeetActivity
|
|||
*/
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null) {
|
||||
view.loadURLString(uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,8 +19,6 @@ package org.jitsi.meet.sdk;
|
|||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -61,6 +59,7 @@ public class JitsiMeetView extends FrameLayout {
|
|||
ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new ProximityModule(reactContext)
|
||||
|
@ -207,34 +206,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* <tt>Activity.onNewIntent</tt> so we can do the required internal
|
||||
* processing. Note that this is only needed if the activity's "launchMode"
|
||||
* was set to "singleTask". This is required for deep linking to work once
|
||||
* the application is already running.
|
||||
*
|
||||
* @param intent - <tt>Intent</tt> instance which was received.
|
||||
*/
|
||||
public static void onNewIntent(Intent intent) {
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
|
|
|
@ -25,6 +25,7 @@ target 'JitsiMeet' do
|
|||
:path => '../node_modules/react-native-keep-awake'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
|
||||
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
|
|
@ -91,34 +91,6 @@ Loads a specific URL which may identify a conference to join. If the specified
|
|||
URL is `nil` and the Welcome page is enabled, the Welcome page is displayed
|
||||
instead.
|
||||
|
||||
#### Universal / deep linking
|
||||
|
||||
In order to support Universal / deep linking, `JitsiMeetView` offers 2 class
|
||||
methods that you app's delegate should call in order for the app to follow those
|
||||
links.
|
||||
|
||||
```objc
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||
{
|
||||
return [JitsiMeetView application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation
|
||||
{
|
||||
return [JitsiMeetView application:application
|
||||
openURL:url
|
||||
sourceApplication:sourceApplication
|
||||
annotation:annotation];
|
||||
}
|
||||
```
|
||||
|
||||
### JitsiMeetViewDelegate
|
||||
|
||||
This delegate is optional, and can be set on the `JitsiMeetView` instance using
|
||||
|
|
|
@ -11,14 +11,41 @@
|
|||
0B26BE6F1EC5BC3C00EEFB41 /* JitsiMeet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
0B412F1F1EDEE6E800B1A0A6 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */; };
|
||||
0B412F211EDEE95300B1A0A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0B412F201EDEE95300B1A0A6 /* Main.storyboard */; };
|
||||
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */; };
|
||||
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B70016F1F7C51CC005944F4 /* InCallController.swift */; };
|
||||
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; };
|
||||
0BD6B4381EF82A6B00D1F4CD /* WebRTC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
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 */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
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 */; };
|
||||
/* 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;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
|
@ -32,6 +59,28 @@
|
|||
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;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -39,7 +88,19 @@
|
|||
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
|
||||
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
|
||||
0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
@ -51,6 +112,13 @@
|
|||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0BEA5C2E1F7B8F73000D0AB4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -72,6 +140,32 @@
|
|||
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 */,
|
||||
);
|
||||
name = "WatchKit extension";
|
||||
path = watchos/extension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -95,6 +189,8 @@
|
|||
0B26BE711EC5BC4D00EEFB41 /* Frameworks */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
13B07FAE1A68108700A75B9A /* src */,
|
||||
0BEA5C261F7B8F73000D0AB4 /* Watch app */,
|
||||
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
|
@ -104,6 +200,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
|
||||
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
|
||||
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -111,6 +209,40 @@
|
|||
/* 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 */,
|
||||
);
|
||||
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 /* jitsi-meet */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */;
|
||||
|
@ -122,10 +254,12 @@
|
|||
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
|
||||
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */,
|
||||
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
|
||||
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "jitsi-meet";
|
||||
productName = "Jitsi Meet";
|
||||
|
@ -138,9 +272,20 @@
|
|||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0900;
|
||||
LastUpgradeCheck = 0820;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
0BEA5C241F7B8F73000D0AB4 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
DevelopmentTeam = BQNXB4G3KQ;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
0BEA5C301F7B8F73000D0AB4 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
DevelopmentTeam = BQNXB4G3KQ;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = BQNXB4G3KQ;
|
||||
SystemCapabilities = {
|
||||
|
@ -165,11 +310,30 @@
|
|||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* jitsi-meet */,
|
||||
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
|
||||
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
|
||||
);
|
||||
};
|
||||
/* 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;
|
||||
|
@ -228,6 +392,18 @@
|
|||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
0BEA5C2D1F7B8F73000D0AB4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */,
|
||||
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.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;
|
||||
|
@ -240,7 +416,28 @@
|
|||
};
|
||||
/* 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 */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
0BEA5C281F7B8F73000D0AB4 /* Base */,
|
||||
);
|
||||
name = Interface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -252,6 +449,140 @@
|
|||
/* 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 = BQNXB4G3KQ;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
IBSC_MODULE = JitsiMeetCompanion_Extension;
|
||||
INFOPLIST_FILE = watchos/app/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios.watchkitapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.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 = BQNXB4G3KQ;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
IBSC_MODULE = JitsiMeetCompanion_Extension;
|
||||
INFOPLIST_FILE = watchos/app/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios.watchkitapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.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 = BQNXB4G3KQ;
|
||||
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.JitsiMeet.ios.watchkitapp.watchkitextension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.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 = BQNXB4G3KQ;
|
||||
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.JitsiMeet.ios.watchkitapp.watchkitextension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -409,6 +740,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 "jitsi-meet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -16,8 +16,22 @@
|
|||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#include <Availability.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
// Weakly load the Intents framework since it's not available on iOS 9.
|
||||
@import Intents;
|
||||
|
||||
// Constant describing iOS 10.0.0
|
||||
static const NSOperatingSystemVersion ios10 = {
|
||||
.majorVersion = 10,
|
||||
.minorVersion = 0,
|
||||
.patchVersion = 0
|
||||
};
|
||||
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
|
@ -28,22 +42,68 @@
|
|||
|
||||
#pragma mark Linking delegate methods
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
|
||||
return [JitsiMeetView application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
|
||||
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.window.rootViewController.view;
|
||||
if (!view) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
[view loadURL:userActivity.webpageURL];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Check for CallKit intents only on iOS >= 10
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10]) {
|
||||
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]
|
||||
|| [userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
|
||||
INInteraction *interaction = [userActivity interaction];
|
||||
INIntent *intent = interaction.intent;
|
||||
NSString *handle;
|
||||
BOOL isAudio = NO;
|
||||
|
||||
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
|
||||
INStartAudioCallIntent *startCallIntent
|
||||
= (INStartAudioCallIntent *)intent;
|
||||
handle = startCallIntent.contacts.firstObject.personHandle.value;
|
||||
isAudio = YES;
|
||||
} else {
|
||||
INStartVideoCallIntent *startCallIntent
|
||||
= (INStartVideoCallIntent *)intent;
|
||||
handle = startCallIntent.contacts.firstObject.personHandle.value;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Load the URL contained in the handle
|
||||
[view loadURLObject:@{
|
||||
@"url": handle,
|
||||
@"configOverwrite": @{
|
||||
@"startAudioOnly": @(isAudio)
|
||||
}
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation {
|
||||
return [JitsiMeetView application:application
|
||||
openURL:url
|
||||
sourceApplication:sourceApplication
|
||||
annotation:annotation];
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.window.rootViewController.view;
|
||||
[view loadURL:url];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"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-44@2x.png",
|
||||
"scale" : "2x",
|
||||
"role" : "longLook",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 6.5 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 11 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="13196" 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="13173"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="13051"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Meetings-->
|
||||
<scene sceneID="aou-V4-d1y">
|
||||
<objects>
|
||||
<controller title="Meetings" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="JitsiMeetCompanion" customModuleProvider="target">
|
||||
<items>
|
||||
<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="timeLabel" destination="V5K-sm-jEH" id="fWQ-kx-vE4"/>
|
||||
</connections>
|
||||
</tableRow>
|
||||
</items>
|
||||
</table>
|
||||
</items>
|
||||
<connections>
|
||||
<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>
|
|
@ -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>1.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>org.jitsi.JitsiMeet.ios</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"assets" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Circular.imageset",
|
||||
"role" : "circular"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Extra Large.imageset",
|
||||
"role" : "extra-large"
|
||||
},
|
||||
{
|
||||
"filename" : "jitsi.imageset"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Modular.imageset",
|
||||
"role" : "modular"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Utilitarian.imageset",
|
||||
"role" : "utilitarian"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
BIN
ios/app/watchos/extension/Assets.xcassets/Complication.complicationset/jitsi.imageset/jitsi@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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([.forward, .backward])
|
||||
handler([])
|
||||
}
|
||||
|
||||
func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
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
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
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) {
|
||||
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/jitsi")!)
|
||||
if complication.family == .circularSmall {
|
||||
let small = CLKComplicationTemplateCircularSmallRingImage()
|
||||
small.imageProvider = imageProvider
|
||||
// This method will be called once per supported complication, and the results will be cached
|
||||
handler(small)
|
||||
} else if complication.family == .utilitarianSmall {
|
||||
let utilitarian = CLKComplicationTemplateUtilitarianSmallRingImage()
|
||||
utilitarian.imageProvider = imageProvider
|
||||
handler(utilitarian)
|
||||
} else if complication.family == .modularSmall {
|
||||
let modular = CLKComplicationTemplateModularSmallRingImage()
|
||||
modular.imageProvider = imageProvider
|
||||
handler(modular)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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 {
|
||||
|
||||
func applicationDidFinishLaunching() {
|
||||
// Start Watch Connectivity
|
||||
if WCSession.isSupported() {
|
||||
let session = WCSession.default
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive() {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillResignActive() {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, etc.
|
||||
}
|
||||
|
||||
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
|
||||
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
|
||||
for task in backgroundTasks {
|
||||
// Use a switch statement to check the task type
|
||||
switch task {
|
||||
case let backgroundTask as WKApplicationRefreshBackgroundTask:
|
||||
// Be sure to complete the background task once you’re done.
|
||||
backgroundTask.setTaskCompletedWithSnapshot(false)
|
||||
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
|
||||
// Snapshot tasks have a unique completion call, make sure to set your expiration date
|
||||
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
|
||||
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
|
||||
// Be sure to complete the connectivity task once you’re done.
|
||||
connectivityTask.setTaskCompletedWithSnapshot(false)
|
||||
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
|
||||
// Be sure to complete the URL session task once you’re done.
|
||||
urlSessionTask.setTaskCompletedWithSnapshot(false)
|
||||
default:
|
||||
// make sure to complete unhandled task types
|
||||
task.setTaskCompletedWithSnapshot(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith
|
||||
activationState: WCSessionActivationState, error: Error?) {
|
||||
if let error = error {
|
||||
print("WC Session activation failed with error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
print("WC Session activated with state: \(activationState.rawValue)")
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
|
||||
|
||||
print("WC SESSION DID RECEIVE APP CONTEXT");
|
||||
|
||||
let conferenceURL = applicationContext["conferenceURL"] as? NSString ?? "NULL";
|
||||
print("CONFERENCE URL \(conferenceURL)");
|
||||
|
||||
let micMuted = applicationContext["micMuted"] as? NSNumber ?? 0;
|
||||
print("MIC MUTED \(micMuted)");
|
||||
|
||||
// Update recent URLs
|
||||
let recentURLs = applicationContext["recentURLs"];
|
||||
if let recentURLsArray = recentURLs as? NSArray {
|
||||
let controller = WKExtension.shared().rootInterfaceController as! InterfaceController
|
||||
controller.updateRecents(withRecents: recentURLsArray)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if conferenceURL != "NULL" {
|
||||
let room = conferenceURL.components(separatedBy: "/").last
|
||||
let context = ["room" : room, "roomUrl" : conferenceURL as String!, "skipJoin" : "true", "muted" : micMuted.boolValue.description ]
|
||||
DispatchQueue.main.async {
|
||||
currentController.pushController(withName: "InCallController", context: context)
|
||||
}
|
||||
}
|
||||
} else if let controller = WKExtension.shared().visibleInterfaceController as? InCallController {
|
||||
if conferenceURL == "NULL" {
|
||||
DispatchQueue.main.async {
|
||||
controller.popToRootController()
|
||||
}
|
||||
} else {
|
||||
// Update muted state
|
||||
controller.updateMutedButton(withMuted: micMuted.boolValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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!
|
||||
|
||||
var muted: Bool!
|
||||
|
||||
@IBAction func hangupClicked() {
|
||||
sendMessage(["command": "hangup"])
|
||||
popToRootController()
|
||||
}
|
||||
|
||||
@IBAction func muteClicked() {
|
||||
sendMessage(["command": "toggleMute"])
|
||||
updateMutedButton(withMuted: !muted)
|
||||
}
|
||||
|
||||
func sendMessage(_ message: [String : Any]) {
|
||||
if WCSession.isSupported() {
|
||||
let session = WCSession.default
|
||||
session.sendMessage(message, replyHandler: nil, errorHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func updateMutedButton(withMuted isMuted: Bool) {
|
||||
muted = isMuted
|
||||
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? [String : String] {
|
||||
if data["skipJoin"] != "yes" {
|
||||
sendMessage(["command": "joinConference", "data" : data["roomUrl"]!])
|
||||
}
|
||||
|
||||
roomLabel.setText(data["room"]!)
|
||||
updateMutedButton(withMuted: data["muted"] == "true")
|
||||
}
|
||||
}
|
||||
|
||||
override func willActivate() {
|
||||
// This method is called when watch view controller is about to be visible to user
|
||||
super.willActivate()
|
||||
}
|
||||
|
||||
override func didAppear() {
|
||||
super.didAppear()
|
||||
|
||||
timer.start()
|
||||
}
|
||||
|
||||
override func didDeactivate() {
|
||||
// This method is called when watch view controller is no longer visible
|
||||
super.didDeactivate()
|
||||
}
|
||||
|
||||
}
|
|
@ -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>1.9</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.JitsiMeet.ios.watchkitapp</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
<key>WKExtensionDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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 Foundation
|
||||
|
||||
|
||||
class InterfaceController: WKInterfaceController {
|
||||
|
||||
@IBOutlet var table: WKInterfaceTable!
|
||||
|
||||
func updateRecents(withRecents recents: NSArray) {
|
||||
table.setNumberOfRows(recents.count, withRowType: "MeetingRowType")
|
||||
for (index, entry) in recents.enumerated() {
|
||||
// FIXME possible runtime exception
|
||||
let entryDict = entry as! NSDictionary
|
||||
let roomURL = entryDict["roomURL"] as! NSString
|
||||
let timestamp = entryDict["timestamp"] 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)
|
||||
}
|
||||
}
|
||||
|
||||
override func awake(withContext context: Any?) {
|
||||
super.awake(withContext: context)
|
||||
}
|
||||
|
||||
override func willActivate() {
|
||||
// This method is called when watch view controller is about to be visible to user
|
||||
super.willActivate()
|
||||
}
|
||||
|
||||
override func didDeactivate() {
|
||||
// This method is called when watch view controller is no longer visible
|
||||
super.didDeactivate()
|
||||
}
|
||||
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String, in table: WKInterfaceTable, rowIndex: Int) -> Any? {
|
||||
let controller = table.rowController(at: rowIndex) as! MeetingRowController
|
||||
return ["room" : controller.room, "roomUrl" : controller.roomUrl, "muted" : "false"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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!
|
||||
|
||||
var room: String!
|
||||
var roomUrl: String!
|
||||
}
|
|
@ -14,6 +14,10 @@
|
|||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */; };
|
||||
0BB9AD771F5EC6CE001C08DB /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BB9AD761F5EC6CE001C08DB /* CallKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
0BB9AD791F5EC6D7001C08DB /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BB9AD781F5EC6D7001C08DB /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */; };
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BB9AD7C1F60356D001C08DB /* AppInfo.m */; };
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495C1EC4B6C600B793EE /* AudioMode.m */; };
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495D1EC4B6C600B793EE /* POSIX.m */; };
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495E1EC4B6C600B793EE /* Proximity.m */; };
|
||||
|
@ -31,6 +35,10 @@
|
|||
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
|
||||
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExternalAPI.m; sourceTree = "<group>"; };
|
||||
0BB9AD761F5EC6CE001C08DB /* CallKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CallKit.framework; path = System/Library/Frameworks/CallKit.framework; sourceTree = SDKROOT; };
|
||||
0BB9AD781F5EC6D7001C08DB /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallKit.m; sourceTree = "<group>"; };
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppInfo.m; sourceTree = "<group>"; };
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = "<group>"; };
|
||||
0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = "<group>"; };
|
||||
0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = "<group>"; };
|
||||
|
@ -48,6 +56,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BB9AD791F5EC6D7001C08DB /* Intents.framework in Frameworks */,
|
||||
0BB9AD771F5EC6CE001C08DB /* CallKit.framework in Frameworks */,
|
||||
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */,
|
||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */,
|
||||
);
|
||||
|
@ -87,6 +97,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
|
||||
|
@ -104,6 +116,8 @@
|
|||
9C3C6FA2341729836589B856 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
|
||||
0BB9AD761F5EC6CE001C08DB /* CallKit.framework */,
|
||||
0B93EF7A1EC608550030D24D /* CoreText.framework */,
|
||||
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
|
||||
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
|
||||
|
@ -222,13 +236,16 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-JitsiMeet-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C7BC10B338C94EEB98048E64 /* [CP] Copy Pods Resources */ = {
|
||||
|
@ -237,9 +254,21 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -253,6 +282,8 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright @ 2017-present 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 "RCTBridgeModule.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@interface AppInfo : NSObject<RCTBridgeModule>
|
||||
@end
|
||||
|
||||
@implementation AppInfo
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
- (NSDictionary *)constantsToExport {
|
||||
NSString *name = [[[NSBundle mainBundle]infoDictionary]objectForKey :@"CFBundleDisplayName"];
|
||||
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
||||
if (version == nil) {
|
||||
version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
|
||||
if (version == nil) {
|
||||
version = @"";
|
||||
}
|
||||
}
|
||||
|
||||
return @{ @"name" : name, @"version" : version };
|
||||
};
|
||||
|
||||
@end
|
|
@ -0,0 +1,334 @@
|
|||
//
|
||||
// Based on RNCallKit
|
||||
//
|
||||
// Original license:
|
||||
//
|
||||
// Copyright (c) 2016, Ian Yu-Hsun Lin
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
// Weakly load CallKit, because it's not available on iOS 9.
|
||||
@import CallKit;
|
||||
|
||||
|
||||
// Events we will emit.
|
||||
static NSString *const RNCallKitPerformAnswerCallAction = @"performAnswerCallAction";
|
||||
static NSString *const RNCallKitPerformEndCallAction = @"performEndCallAction";
|
||||
static NSString *const RNCallKitPerformSetMutedCallAction = @"performSetMutedCallAction";
|
||||
static NSString *const RNCallKitProviderDidReset = @"providerDidReset";
|
||||
|
||||
|
||||
@interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
|
||||
@end
|
||||
|
||||
@implementation RNCallKit
|
||||
{
|
||||
CXCallController *callKitCallController;
|
||||
CXProvider *callKitProvider;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
{
|
||||
return @[
|
||||
RNCallKitPerformAnswerCallAction,
|
||||
RNCallKitPerformEndCallAction,
|
||||
RNCallKitPerformSetMutedCallAction,
|
||||
RNCallKitProviderDidReset
|
||||
];
|
||||
}
|
||||
|
||||
// Configure CallKit
|
||||
RCT_EXPORT_METHOD(setup:(NSDictionary *)options)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][setup] options = %@", options);
|
||||
#endif
|
||||
callKitCallController = [[CXCallController alloc] init];
|
||||
if (callKitProvider) {
|
||||
[callKitProvider invalidate];
|
||||
}
|
||||
callKitProvider = [[CXProvider alloc] initWithConfiguration:[self getProviderConfiguration: options]];
|
||||
[callKitProvider setDelegate:self queue:nil];
|
||||
}
|
||||
|
||||
#pragma mark - CXCallController call actions
|
||||
|
||||
// Display the incoming call to the user
|
||||
RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
||||
handle:(NSString *)handle
|
||||
hasVideo:(BOOL)hasVideo
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][displayIncomingCall] uuidString = %@", uuidString);
|
||||
#endif
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
||||
callUpdate.remoteHandle
|
||||
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
||||
callUpdate.supportsDTMF = NO;
|
||||
callUpdate.supportsHolding = NO;
|
||||
callUpdate.supportsGrouping = NO;
|
||||
callUpdate.supportsUngrouping = NO;
|
||||
callUpdate.hasVideo = hasVideo;
|
||||
|
||||
[callKitProvider reportNewIncomingCallWithUUID:uuid
|
||||
update:callUpdate
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error == nil) {
|
||||
resolve(nil);
|
||||
} else {
|
||||
reject(nil, @"Error reporting new incoming call", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// End call
|
||||
RCT_EXPORT_METHOD(endCall:(NSString *)uuidString
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][endCall] uuidString = %@", uuidString);
|
||||
#endif
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
CXEndCallAction *action = [[CXEndCallAction alloc] initWithCallUUID:uuid];
|
||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
||||
}
|
||||
|
||||
// Mute / unmute (audio)
|
||||
RCT_EXPORT_METHOD(setMuted:(NSString *)uuidString
|
||||
muted:(BOOL) muted
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][setMuted] uuidString = %@", uuidString);
|
||||
#endif
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
CXSetMutedCallAction *action
|
||||
= [[CXSetMutedCallAction alloc] initWithCallUUID:uuid muted:muted];
|
||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
||||
}
|
||||
|
||||
// Start outgoing call
|
||||
RCT_EXPORT_METHOD(startCall:(NSString *)uuidString
|
||||
handle:(NSString *)handle
|
||||
video:(BOOL)video
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][startCall] uuidString = %@", uuidString);
|
||||
#endif
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
CXHandle *callHandle
|
||||
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
||||
CXStartCallAction *action
|
||||
= [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
|
||||
action.video = video;
|
||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
||||
}
|
||||
|
||||
// Indicate call failed
|
||||
RCT_EXPORT_METHOD(reportCallFailed:(NSString *)uuidString
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
[callKitProvider reportCallWithUUID:uuid
|
||||
endedAtDate:[NSDate date]
|
||||
reason:CXCallEndedReasonFailed];
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
// Indicate outgoing call connected
|
||||
RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)uuidString
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
[callKitProvider reportOutgoingCallWithUUID:uuid
|
||||
connectedAtDate:[NSDate date]];
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
// Update call in case we have a display name or video capability changes
|
||||
RCT_EXPORT_METHOD(updateCall:(NSString *)uuidString
|
||||
options:(NSDictionary *)options
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][updateCall] uuidString = %@ options = %@", uuidString, options);
|
||||
#endif
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
||||
CXCallUpdate *update = [[CXCallUpdate alloc] init];
|
||||
if (options[@"displayName"]) {
|
||||
update.localizedCallerName = options[@"displayName"];
|
||||
}
|
||||
if (options[@"hasVideo"]) {
|
||||
update.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
|
||||
}
|
||||
[callKitProvider reportCallWithUUID:uuid updated:update];
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
#pragma mark - Helper methods
|
||||
|
||||
- (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary* )settings
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][getProviderConfiguration]");
|
||||
#endif
|
||||
CXProviderConfiguration *providerConfiguration
|
||||
= [[CXProviderConfiguration alloc] initWithLocalizedName:settings[@"appName"]];
|
||||
providerConfiguration.supportsVideo = YES;
|
||||
providerConfiguration.maximumCallGroups = 1;
|
||||
providerConfiguration.maximumCallsPerCallGroup = 1;
|
||||
providerConfiguration.supportedHandleTypes
|
||||
= [NSSet setWithObjects:[NSNumber numberWithInteger:CXHandleTypeGeneric], nil];
|
||||
if (settings[@"imageName"]) {
|
||||
providerConfiguration.iconTemplateImageData
|
||||
= UIImagePNGRepresentation([UIImage imageNamed:settings[@"imageName"]]);
|
||||
}
|
||||
if (settings[@"ringtoneSound"]) {
|
||||
providerConfiguration.ringtoneSound = settings[@"ringtoneSound"];
|
||||
}
|
||||
return providerConfiguration;
|
||||
}
|
||||
|
||||
- (void)requestTransaction:(CXTransaction *)transaction
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
|
||||
#endif
|
||||
[callKitCallController requestTransaction:transaction completion:^(NSError * _Nullable error) {
|
||||
if (error == nil) {
|
||||
resolve(nil);
|
||||
} else {
|
||||
NSLog(@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error);
|
||||
reject(nil, @"Error processing CallKit transaction", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - CXProviderDelegate
|
||||
|
||||
// Called when the provider has been reset. We should terminate all calls.
|
||||
- (void)providerDidReset:(CXProvider *)provider {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:providerDidReset]");
|
||||
#endif
|
||||
[self sendEventWithName:RNCallKitProviderDidReset body:nil];
|
||||
}
|
||||
|
||||
// Answering incoming call
|
||||
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction]");
|
||||
#endif
|
||||
[self sendEventWithName:RNCallKitPerformAnswerCallAction
|
||||
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
||||
[action fulfill];
|
||||
}
|
||||
|
||||
// Call ended, user request
|
||||
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction]");
|
||||
#endif
|
||||
[self sendEventWithName:RNCallKitPerformEndCallAction
|
||||
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
||||
[action fulfill];
|
||||
}
|
||||
|
||||
// Handle audio mute from CallKit view
|
||||
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction]");
|
||||
#endif
|
||||
[self sendEventWithName:RNCallKitPerformSetMutedCallAction
|
||||
body:@{ @"callUUID": action.callUUID.UUIDString,
|
||||
@"muted": [NSNumber numberWithBool:action.muted]}];
|
||||
[action fulfill];
|
||||
}
|
||||
|
||||
// Starting outgoing call
|
||||
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction]");
|
||||
#endif
|
||||
[action fulfill];
|
||||
|
||||
// Update call info
|
||||
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
||||
callUpdate.remoteHandle = action.handle;
|
||||
callUpdate.supportsDTMF = NO;
|
||||
callUpdate.supportsHolding = NO;
|
||||
callUpdate.supportsGrouping = NO;
|
||||
callUpdate.supportsUngrouping = NO;
|
||||
callUpdate.hasVideo = action.isVideo;
|
||||
[callKitProvider reportCallWithUUID:action.callUUID updated:callUpdate];
|
||||
|
||||
// Notify the system about the outgoing call
|
||||
[callKitProvider reportOutgoingCallWithUUID:action.callUUID
|
||||
startedConnectingAtDate:[NSDate date]];
|
||||
}
|
||||
|
||||
// These just help with debugging
|
||||
|
||||
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession]");
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession]");
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction]");
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
|
@ -28,15 +28,6 @@
|
|||
+ (BOOL)application:(UIApplication *_Nonnull)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *_Nonnull)launchOptions;
|
||||
|
||||
+ (BOOL)application:(UIApplication * _Nonnull)application
|
||||
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
|
||||
restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler;
|
||||
|
||||
+ (BOOL)application:(UIApplication * _Nonnull)application
|
||||
openURL:(NSURL * _Nonnull)URL
|
||||
sourceApplication:(NSString * _Nullable)sourceApplication
|
||||
annotation:(id _Nullable)annotation;
|
||||
|
||||
- (void)loadURL:(NSURL * _Nullable)url;
|
||||
|
||||
- (void)loadURLObject:(NSDictionary * _Nullable)urlObject;
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include <mach/mach_time.h>
|
||||
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTLinkingManager.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
@ -133,48 +132,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark Linking delegate helpers
|
||||
// https://facebook.github.io/react-native/docs/linking.html
|
||||
|
||||
+ (BOOL)application:(UIApplication *)application
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||
{
|
||||
// XXX At least twice we received bug reports about malfunctioning loadURL
|
||||
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
|
||||
// expected in our testing. But that was to be expected because the app does
|
||||
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
||||
// channel Universal linking through loadURL.
|
||||
if ([userActivity.activityType
|
||||
isEqualToString:NSUserActivityTypeBrowsingWeb]
|
||||
&& [JitsiMeetView loadURLInViews:userActivity.webpageURL]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [RCTLinkingManager application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
|
||||
+ (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation {
|
||||
// XXX At least twice we received bug reports about malfunctioning loadURL
|
||||
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
|
||||
// expected in our testing. But that was to be expected because the app does
|
||||
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
||||
// channel Universal linking through loadURL.
|
||||
if ([JitsiMeetView loadURLInViews:url]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [RCTLinkingManager application:application
|
||||
openURL:url
|
||||
sourceApplication:sourceApplication
|
||||
annotation:annotation];
|
||||
}
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
- (instancetype)init {
|
||||
|
@ -281,32 +238,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
|
||||
#pragma mark Private methods
|
||||
|
||||
/**
|
||||
* Loads a specific {@link NSURL} in all existing {@code JitsiMeetView}s.
|
||||
*
|
||||
* @param url - The {@code NSURL} to load in all existing
|
||||
* {@code JitsiMeetView}s.
|
||||
* @return {@code YES} if the specified {@code url} was submitted for loading in
|
||||
* at least one {@code JitsiMeetView}; otherwise, {@code NO}.
|
||||
*/
|
||||
+ (BOOL)loadURLInViews:(NSURL *)url {
|
||||
BOOL handled = NO;
|
||||
|
||||
if (views) {
|
||||
for (NSString *externalAPIScope in views) {
|
||||
JitsiMeetView *view
|
||||
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
|
||||
if (view) {
|
||||
[view loadURL:url];
|
||||
handled = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
|
||||
return [views objectForKey:externalAPIScope];
|
||||
}
|
||||
|
|
|
@ -38,11 +38,11 @@
|
|||
"i18next": "8.4.3",
|
||||
"i18next-browser-languagedetector": "2.0.0",
|
||||
"i18next-xhr-backend": "1.4.2",
|
||||
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jitsi-meet-logger": "jitsi/jitsi-meet-logger",
|
||||
"jquery": "2.1.4",
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.0",
|
||||
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jquery-ui": "1.10.5",
|
||||
"jssha": "2.2.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
|
@ -64,6 +64,7 @@
|
|||
"react-native-locale-detector": "1.0.1",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-vector-icons": "4.3.0",
|
||||
"react-native-watch-connectivity": "saghul/react-native-watch-connectivity#podspec-module",
|
||||
"react-native-webrtc": "jitsi/react-native-webrtc",
|
||||
"react-redux": "5.0.6",
|
||||
"redux": "3.7.2",
|
||||
|
@ -73,6 +74,7 @@
|
|||
"strophejs-plugins": "0.0.7",
|
||||
"styled-components": "1.3.0",
|
||||
"url-polyfill": "github/url-polyfill",
|
||||
"uuid": "3.1.0",
|
||||
"xmldom": "0.1.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -82,6 +84,8 @@
|
|||
"babel-polyfill": "6.26.0",
|
||||
"babel-preset-es2015": "6.24.1",
|
||||
"babel-preset-react": "6.24.1",
|
||||
"babel-preset-react-native": "4.0.0",
|
||||
"babel-preset-react-native-stage-0": "1.0.1",
|
||||
"babel-preset-stage-1": "6.24.1",
|
||||
"clean-css": "3.4.25",
|
||||
"css-loader": "0.28.5",
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
/* global __DEV__ */
|
||||
|
||||
import React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { Platform } from '../../base/react';
|
||||
import '../../recent';
|
||||
import '../../mobile/audio-mode';
|
||||
import '../../mobile/background';
|
||||
import '../../mobile/callkit';
|
||||
import '../../mobile/external-api';
|
||||
import '../../mobile/full-screen';
|
||||
import '../../mobile/permissions';
|
||||
import '../../mobile/proximity';
|
||||
import '../../mobile/wake-lock';
|
||||
import '../../mobile/watchos';
|
||||
|
||||
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
|
||||
|
@ -47,9 +50,6 @@ export class App extends AbstractApp {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onLinkingURL = this._onLinkingURL.bind(this);
|
||||
|
||||
// In the Release configuration, React Native will (intentionally) throw
|
||||
// an unhandled JavascriptException for an unhandled JavaScript error.
|
||||
// This will effectively kill the application. In accord with the Web,
|
||||
|
@ -57,34 +57,6 @@ export class App extends AbstractApp {
|
|||
this._maybeDisableExceptionsManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to notifications about activating URLs registered to be handled
|
||||
* by this app.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
* @see https://facebook.github.io/react-native/docs/linking.html
|
||||
*/
|
||||
componentWillMount() {
|
||||
super.componentWillMount();
|
||||
|
||||
Linking.addEventListener('url', this._onLinkingURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from notifications about activating URLs registered to be
|
||||
* handled by this app.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
* @see https://facebook.github.io/react-native/docs/linking.html
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
Linking.removeEventListener('url', this._onLinkingURL);
|
||||
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
|
@ -122,20 +94,6 @@ export class App extends AbstractApp {
|
|||
global.ErrorUtils.setGlobalHandler(newHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notified by React's Linking API that a specific URL registered to be
|
||||
* handled by this App was activated.
|
||||
*
|
||||
* @param {Object} event - The details of the notification/event.
|
||||
* @param {string} event.url - The URL registered to be handled by this App
|
||||
* which was activated.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLinkingURL({ url }) {
|
||||
this._openURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* The type of Redux action which signals that {@link JitsiMeetJS.init()} was
|
||||
* invoked and completed successfully.
|
||||
*
|
||||
* {
|
||||
* type: LIB_LOAD_STORAGE_DONE
|
||||
* }
|
||||
*/
|
||||
export const LIB_LOAD_STORAGE_DONE = Symbol('LIB_LOAD_STORAGE_DONE');
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that {@link JitsiMeetJS} was disposed.
|
||||
*
|
||||
|
|
|
@ -6,7 +6,10 @@ import { PARTICIPANT_LEFT } from '../participants';
|
|||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import { disposeLib, initLib, setWebRTCReady } from './actions';
|
||||
import { LIB_DID_INIT, LIB_INIT_ERROR } from './actionTypes';
|
||||
import {
|
||||
LIB_DID_INIT, LIB_INIT_ERROR,
|
||||
LIB_LOAD_STORAGE_DONE
|
||||
} from './actionTypes';
|
||||
import { WEBRTC_NOT_READY, WEBRTC_NOT_SUPPORTED } from './constants';
|
||||
|
||||
/**
|
||||
|
@ -23,6 +26,15 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
switch (action.type) {
|
||||
case LIB_DID_INIT:
|
||||
store.dispatch(setWebRTCReady(true));
|
||||
if (window.localStorage.setLoadingCompleteCb) {
|
||||
window.localStorage.setLoadingCompleteCb(() => {
|
||||
store.dispatch({
|
||||
type: LIB_LOAD_STORAGE_DONE
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error('No window.localStorage.setLoadingCompleteCb');
|
||||
}
|
||||
break;
|
||||
|
||||
case LIB_INIT_ERROR:
|
||||
|
|
|
@ -33,6 +33,7 @@ export default class Storage {
|
|||
if (typeof this._keyPrefix !== 'undefined') {
|
||||
// Load all previously persisted data items from React Native's
|
||||
// AsyncStorage.
|
||||
console.info('LOAD STORAGE START');
|
||||
AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => {
|
||||
// XXX The keys argument of getAllKeys' callback may or may not
|
||||
// be preceded by an error argument.
|
||||
|
@ -61,11 +62,25 @@ export default class Storage {
|
|||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
console.info('LOAD STORAGE DONE');
|
||||
this.loadingComplete = true;
|
||||
if (typeof this._loadingCompleteCallback === 'function') {
|
||||
console.info('LOAD STORAGE DONE - CALLING CALLBACK');
|
||||
this._loadingCompleteCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoadingCompleteCb(callback) {
|
||||
this._loadingCompleteCallback = callback;
|
||||
if (this.loadingComplete) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys from this storage.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
import {
|
||||
NativeModules,
|
||||
NativeEventEmitter,
|
||||
Platform
|
||||
} from 'react-native';
|
||||
|
||||
const RNCallKit = NativeModules.RNCallKit;
|
||||
|
||||
/**
|
||||
* Thin wrapper around Apple's CallKit functionality.
|
||||
*
|
||||
* In CallKit requests are performed via actions (either user or system started)
|
||||
* and async events are reported via dedicated methods. This class exposes that
|
||||
* functionality in the form of methods and events. One important thing to note
|
||||
* is that even if an action is started by the system (because the user pressed
|
||||
* the "end call" button in the CallKit view, for example) the event will be
|
||||
* emitted in the same way as it would if the action originated from calling
|
||||
* the "endCall" method in this class, for example.
|
||||
*
|
||||
* Emitted events:
|
||||
* - performAnswerCallAction: The user pressed the answer button.
|
||||
* - performEndCallAction: The call should be ended.
|
||||
* - performSetMutedCallAction: The call muted state should change. The
|
||||
* ancillary `data` object contains a `muted` attribute.
|
||||
* - providerDidReset: The system has reset, all calls should be terminated.
|
||||
* This event gets no associated data.
|
||||
*
|
||||
* All events get a `data` object with a `callUUID` property, unless stated
|
||||
* otherwise.
|
||||
*/
|
||||
class CallKit extends NativeEventEmitter {
|
||||
/**
|
||||
* Initializes a new {@code CallKit} instance.
|
||||
*/
|
||||
constructor() {
|
||||
super(RNCallKit);
|
||||
this._setup = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if the current platform is supported, false otherwise. The
|
||||
* supported platforms are: iOS >= 10.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isSupported() {
|
||||
return Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if CallKit was setup, and throws an exception in that case.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_checkSetup() {
|
||||
if (!this._setup) {
|
||||
throw new Error('CallKit not initialized, call setup() first.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for the given event.
|
||||
*
|
||||
* @param {string} event - Name of the event we are interested in.
|
||||
* @param {Function} listener - Function which will be called when the
|
||||
* desired event is emitted.
|
||||
* @returns {void}
|
||||
*/
|
||||
addEventListener(event, listener) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addListener(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies CallKit about an incoming call. This will display the system
|
||||
* incoming call view.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @param {string} handle - Call handle in CallKit's terms. The room URL.
|
||||
* @param {boolean} hasVideo - True if it's a video call, false otherwise.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
displayIncomingCall(uuid, handle, hasVideo = true) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.displayIncomingCall(uuid, handle, hasVideo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request CallKit to end the call.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
endCall(uuid) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.endCall(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for the given event.
|
||||
*
|
||||
* @param {string} event - Name of the event we are no longer interested in.
|
||||
* @param {Function} listener - Function which used to be called when the
|
||||
* desired event was emitted.
|
||||
* @returns {void}
|
||||
*/
|
||||
removeEventListener(event, listener) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeListener(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate CallKit that the outgoing call with the given UUID is now
|
||||
* connected.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
reportConnectedOutgoingCall(uuid) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.reportConnectedOutgoingCall(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate CallKit that the call with the given UUID has failed.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
reportCallFailed(uuid) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.reportCallFailed(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell CallKit about the audio muted state.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @param {boolean} muted - True if audio is muted, false otherwise.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setMuted(uuid, muted) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.setMuted(uuid, muted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare / initialize CallKit. This method must be called before any
|
||||
* other.
|
||||
*
|
||||
* @param {Object} options - Initialization options.
|
||||
* @param {string} options.imageName - Image to be used in CallKit's
|
||||
* application button..
|
||||
* @param {string} options.ringtoneSound - Ringtone to be used for incoming
|
||||
* calls.
|
||||
* @returns {void}
|
||||
*/
|
||||
setup(options = {}) {
|
||||
if (CallKit.isSupported()) {
|
||||
options.appName = NativeModules.AppInfo.name;
|
||||
RNCallKit.setup(options);
|
||||
}
|
||||
|
||||
this._setup = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate CallKit about a new outgoing call.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @param {string} handle - Call handle in CallKit's terms. The room URL in
|
||||
* our case.
|
||||
* @param {boolean} hasVideo - True if it's a video call, false otherwise.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
startCall(uuid, handle, hasVideo = true) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.startCall(uuid, handle, hasVideo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an ongoing call's parameters.
|
||||
*
|
||||
* @param {string} uuid - Unique identifier for the call.
|
||||
* @param {Object} options - Object with properties which should be updated.
|
||||
* @param {string} options.displayName - Display name for the caller.
|
||||
* @param {boolean} options.hasVideo - True if the call has video, false
|
||||
* otherwise.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateCall(uuid, options) {
|
||||
this._checkSetup();
|
||||
if (!CallKit.isSupported()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return RNCallKit.updateCall(uuid, options);
|
||||
}
|
||||
}
|
||||
|
||||
export default new CallKit();
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* The type of redux action to set the CallKit event listeners.
|
||||
*
|
||||
* {
|
||||
* type: _SET_CALLKIT_LISTENERS,
|
||||
* listeners: Map|null
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_CALLKIT_LISTENERS = Symbol('_SET_CALLKIT_LISTENERS');
|
|
@ -0,0 +1,2 @@
|
|||
import './middleware';
|
||||
import './reducer';
|
|
@ -0,0 +1,194 @@
|
|||
/* @flow */
|
||||
|
||||
import uuid from 'uuid';
|
||||
|
||||
import {
|
||||
APP_WILL_MOUNT,
|
||||
APP_WILL_UNMOUNT,
|
||||
appNavigate
|
||||
} from '../../app';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_JOINED
|
||||
} from '../../base/conference';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_VIDEO_MUTED,
|
||||
isVideoMutedByAudioOnly,
|
||||
setAudioMuted
|
||||
} from '../../base/media';
|
||||
import { MiddlewareRegistry, toState } from '../../base/redux';
|
||||
import { _SET_CALLKIT_LISTENERS } from './actionTypes';
|
||||
import CallKit from './CallKit';
|
||||
|
||||
/**
|
||||
* Middleware that captures several system actions and hooks up CallKit.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case _SET_CALLKIT_LISTENERS: {
|
||||
const { listeners } = getState()['features/callkit'];
|
||||
|
||||
if (listeners) {
|
||||
for (const [ event, listener ] of listeners) {
|
||||
CallKit.removeEventListener(event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
if (action.listeners) {
|
||||
for (const [ event, listener ] of action.listeners) {
|
||||
CallKit.addEventListener(event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_MOUNT: {
|
||||
CallKit.setup(); // TODO: set app icon.
|
||||
const listeners = new Map();
|
||||
const callEndListener = data => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference && conference.callUUID === data.callUUID) {
|
||||
// We arrive here when a call is ended by the system, for
|
||||
// for example when another incoming call is received and the
|
||||
// user selects "End & Accept".
|
||||
delete conference.callUUID;
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
};
|
||||
|
||||
listeners.set('performEndCallAction', callEndListener);
|
||||
|
||||
// Set the same listener for providerDidReset. According to the docs,
|
||||
// when the system resets we should terminate all calls.
|
||||
listeners.set('providerDidReset', callEndListener);
|
||||
|
||||
const setMutedListener = data => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference && conference.callUUID === data.callUUID) {
|
||||
// Break the loop. Audio can be muted both from the CallKit
|
||||
// interface and from the Jitsi Meet interface. We must keep
|
||||
// them in sync, but at some point the loop needs to be broken.
|
||||
// We are doing it here, on the CallKit handler.
|
||||
const { muted } = getState()['features/base/media'].audio;
|
||||
|
||||
if (muted !== data.muted) {
|
||||
dispatch(setAudioMuted(Boolean(data.muted)));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
listeners.set('performSetMutedCallAction', setMutedListener);
|
||||
|
||||
dispatch({
|
||||
type: _SET_CALLKIT_LISTENERS,
|
||||
listeners
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
dispatch({
|
||||
type: _SET_CALLKIT_LISTENERS,
|
||||
listeners: null
|
||||
});
|
||||
break;
|
||||
|
||||
case CONFERENCE_FAILED: {
|
||||
const { callUUID } = action.conference;
|
||||
|
||||
if (callUUID) {
|
||||
CallKit.reportCallFailed(callUUID);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_LEFT: {
|
||||
const { callUUID } = action.conference;
|
||||
|
||||
if (callUUID) {
|
||||
CallKit.endCall(callUUID);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const { callUUID } = action.conference;
|
||||
|
||||
if (callUUID) {
|
||||
CallKit.reportConnectedOutgoingCall(callUUID);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_WILL_JOIN: {
|
||||
const conference = action.conference;
|
||||
const url = getInviteURL(getState);
|
||||
const hasVideo = !isVideoMutedByAudioOnly({ getState });
|
||||
|
||||
// When assigning the call UUID, do so in upper case, since iOS will
|
||||
// return it upper cased.
|
||||
conference.callUUID = uuid.v4().toUpperCase();
|
||||
CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
|
||||
.then(() => {
|
||||
const { room } = getState()['features/base/conference'];
|
||||
|
||||
CallKit.updateCall(conference.callUUID, { displayName: room });
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_AUDIO_MUTED: {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference && conference.callUUID) {
|
||||
CallKit.setMuted(conference.callUUID, action.muted);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_VIDEO_MUTED: {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference && conference.callUUID) {
|
||||
const hasVideo = !isVideoMutedByAudioOnly({ getState });
|
||||
|
||||
CallKit.updateCall(conference.callUUID, { hasVideo });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the currently active conference.
|
||||
*
|
||||
* @param {Function|Object} stateOrGetState - The redux state or redux's
|
||||
* {@code getState} function.
|
||||
* @returns {Conference|undefined}
|
||||
*/
|
||||
function getCurrentConference(stateOrGetState: Function | Object): ?Object {
|
||||
const state = toState(stateOrGetState);
|
||||
const { conference, joining } = state['features/base/conference'];
|
||||
|
||||
return conference || joining;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { ReducerRegistry } from '../../base/redux';
|
||||
|
||||
import {
|
||||
_SET_CALLKIT_LISTENERS
|
||||
} from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/callkit', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_CALLKIT_LISTENERS:
|
||||
return {
|
||||
...state,
|
||||
listeners: action.listeners
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* {
|
||||
* type: ADD_RECENT_URL,
|
||||
* recentURLs: Array
|
||||
* }
|
||||
*/
|
||||
export const SET_RECENT_URLS = Symbol('SET_RECENT_URLS');
|
||||
|
||||
/**
|
||||
* {
|
||||
* type: SET_MIC_MUTED,
|
||||
* micMuted: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_MIC_MUTED = Symbol('SET_MIC_MUTED');
|
||||
|
||||
/**
|
||||
* {
|
||||
* type: SET_CONFERENCE_URL,
|
||||
* conferenceURL: String?
|
||||
* }
|
||||
*/
|
||||
export const SET_CONFERENCE_URL = Symbol('SET_CONFERENCE_URL');
|
|
@ -0,0 +1,8 @@
|
|||
import { SET_CONFERENCE_URL } from './actionTypes';
|
||||
|
||||
export function setConferenceURL(conferenceURL) {
|
||||
return {
|
||||
type: SET_CONFERENCE_URL,
|
||||
conferenceURL
|
||||
};
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import './middleware';
|
||||
import './reducer';
|
|
@ -0,0 +1,202 @@
|
|||
/* @flow */
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
import * as watch from 'react-native-watch-connectivity';
|
||||
|
||||
import { setConferenceURL } from './actions';
|
||||
import {
|
||||
SET_CONFERENCE_URL,
|
||||
SET_MIC_MUTED,
|
||||
SET_RECENT_URLS
|
||||
} from './actionTypes';
|
||||
import { ADD_RECENT_URL, LOADED_RECENT_URLS } from '../../recent';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT, appNavigate } from '../../app';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
} from '../../base/conference';
|
||||
import { SET_AUDIO_MUTED, toggleAudioMuted } from '../../base/media';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware that captures conference actions and sets the correct audio mode
|
||||
* based on the type of conference. Audio-only conferences don't use the speaker
|
||||
* by default, and video conferences do.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
if (Platform.OS !== 'ios') {
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
watch.subscribeToWatchState((err, watchState) => {
|
||||
if (!err) {
|
||||
console.log('watchState', watchState);
|
||||
|
||||
// FIXME that does not seem to help with the initial sync up
|
||||
// if (watchState === 'Activated') {
|
||||
// _updateApplicationContext(getState);
|
||||
// }
|
||||
} else {
|
||||
console.log('ERROR getting watchState');
|
||||
}
|
||||
});
|
||||
|
||||
watch.subscribeToMessages((err, message) => {
|
||||
if (err) {
|
||||
console.log('ERROR getting watch message');
|
||||
} else {
|
||||
switch (message.command) {
|
||||
case 'joinConference': {
|
||||
const newConferenceURL = message.data;
|
||||
const oldConferenceURL
|
||||
= _getConferenceUrlFromBaseConf(getState);
|
||||
|
||||
console.info(`WATCH - JOIN URL: ${newConferenceURL}`);
|
||||
if (oldConferenceURL === newConferenceURL) {
|
||||
console.info('No need to navigate');
|
||||
} else {
|
||||
// Set conference URL early to avoid NULL being sent as
|
||||
// part of other updates.
|
||||
// FIXME check if we'd go back to NULL on join failure.
|
||||
dispatch(setConferenceURL(newConferenceURL));
|
||||
dispatch(appNavigate(newConferenceURL));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'toggleMute':
|
||||
console.info('WATCH - TOGGLE MUTED');
|
||||
dispatch(toggleAudioMuted());
|
||||
break;
|
||||
case 'hangup':
|
||||
console.info('WATCH - HANG UP');
|
||||
dispatch(appNavigate(undefined));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ADD_RECENT_URL:
|
||||
case LOADED_RECENT_URLS: {
|
||||
dispatch({
|
||||
type: SET_RECENT_URLS,
|
||||
recentURLs: getState()['features/recent'].entries
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_MUTED: {
|
||||
const { audio } = getState()['features/base/media'];
|
||||
|
||||
dispatch({
|
||||
type: SET_MIC_MUTED,
|
||||
micMuted: Boolean(audio.muted)
|
||||
});
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_WILL_LEAVE: {
|
||||
const conferenceURL = _getConferenceUrlFromBaseConf(getState);
|
||||
const watchConferenceURL = _getWatchConferenceURL(getState);
|
||||
|
||||
// This may not be a real failure
|
||||
if (action.type === CONFERENCE_FAILED) {
|
||||
const conference = getState()['features/base/conference'];
|
||||
|
||||
if (conference.authRequired || conference.passwordRequired) {
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME I have bad feelings about this logic, but it aims to fix
|
||||
// problem with setting NULL temporarily when selecting new conference
|
||||
// on the watch while still in the previous room. It will first emit
|
||||
// CONFERENCE_WILL_LEVE, before joining the new room and we don't want
|
||||
// to send NULL.
|
||||
if (watchConferenceURL !== 'NULL'
|
||||
&& watchConferenceURL !== conferenceURL) {
|
||||
console.info(
|
||||
'Ignored action',
|
||||
action.type,
|
||||
`possibly for the previous conference ?: ${conferenceURL}`);
|
||||
} else if (action.type === CONFERENCE_WILL_LEAVE
|
||||
&& conferenceURL === watchConferenceURL) {
|
||||
dispatch(setConferenceURL('NULL'));
|
||||
} else if (conferenceURL !== watchConferenceURL) {
|
||||
dispatch(setConferenceURL(conferenceURL));
|
||||
} else {
|
||||
console.info(
|
||||
'Did nothing on',
|
||||
action.type,
|
||||
conferenceURL,
|
||||
watchConferenceURL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
case CONFERENCE_JOINED: {
|
||||
// NOTE for some reason 'null' does not update context - must be string
|
||||
const conferenceURL = _getConferenceUrlFromBaseConf(getState);
|
||||
const oldConferenceURL = _getWatchConferenceURL(getState);
|
||||
|
||||
// NOTE Those updates are expensive!
|
||||
if (conferenceURL !== oldConferenceURL) {
|
||||
dispatch(setConferenceURL(conferenceURL));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Here list all actions that affect the watch OS application context.
|
||||
// The reducer should form all those actions into our context structure.
|
||||
case SET_CONFERENCE_URL:
|
||||
case SET_MIC_MUTED:
|
||||
case SET_RECENT_URLS: {
|
||||
_updateApplicationContext(getState, action);
|
||||
break;
|
||||
}
|
||||
case APP_WILL_UNMOUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
function _getWatchConferenceURL(getState) {
|
||||
const { conferenceURL } = getState()['features/mobile/watchos'];
|
||||
|
||||
return conferenceURL;
|
||||
}
|
||||
|
||||
function _getConferenceUrlFromBaseConf(getState) {
|
||||
|
||||
// FIXME probably authRequired and paswordRequired should be included
|
||||
// as well...
|
||||
const { conference, joining, leaving }
|
||||
= getState()['features/base/conference'];
|
||||
const theConference = conference || joining || leaving;
|
||||
const conferenceURLObj
|
||||
= theConference && theConference[JITSI_CONFERENCE_URL_KEY];
|
||||
|
||||
// NOTE for some reason 'null' does not update context - must be string
|
||||
return conferenceURLObj ? getInviteURL(conferenceURLObj) : 'NULL';
|
||||
}
|
||||
|
||||
function _updateApplicationContext(getState, action) {
|
||||
const context = getState()['features/mobile/watchos'];
|
||||
|
||||
console.info('UPDATING WATCH CONTEXT', context, action.type);
|
||||
watch.updateApplicationContext(context);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { ReducerRegistry, set } from '../../base/redux';
|
||||
import {
|
||||
SET_CONFERENCE_URL, SET_MIC_MUTED,
|
||||
SET_RECENT_URLS
|
||||
} from './actionTypes';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
// NOTE for some reason 'null' does not update context
|
||||
conferenceURL: 'NULL',
|
||||
micMuted: false,
|
||||
recentURLs: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature features/recording.
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/mobile/watchos', (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_CONFERENCE_URL: {
|
||||
return set(state, 'conferenceURL', action.conferenceURL);
|
||||
}
|
||||
case SET_MIC_MUTED: {
|
||||
return set(state, 'micMuted', action.micMuted);
|
||||
}
|
||||
case SET_RECENT_URLS: {
|
||||
return set(state, 'recentURLs', action.recentURLs);
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* {
|
||||
* type: ADD_RECENT_URL,
|
||||
* roomURL: string,
|
||||
* timestamp: Number
|
||||
* }
|
||||
*/
|
||||
export const ADD_RECENT_URL = Symbol('ADD_RECENT_URL');
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* {
|
||||
* type: LOADED_RECENT_URLS,
|
||||
* entries: []
|
||||
* }
|
||||
*/
|
||||
export const LOADED_RECENT_URLS = Symbol('LOADED_RECENT_URLS');
|
|
@ -0,0 +1,4 @@
|
|||
export * from './actionTypes';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
|
@ -0,0 +1,54 @@
|
|||
/* @flow */
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { CONFERENCE_WILL_JOIN } from '../base/conference';
|
||||
import { LIB_LOAD_STORAGE_DONE } from '../base/lib-jitsi-meet';
|
||||
|
||||
import {ADD_RECENT_URL, LOADED_RECENT_URLS} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware that captures conference actions and sets the correct audio mode
|
||||
* based on the type of conference. Audio-only conferences don't use the speaker
|
||||
* by default, and video conferences do.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case LIB_LOAD_STORAGE_DONE: {
|
||||
// FIXME this appears to be called when a conference is left
|
||||
let entries = [];
|
||||
const recentURLs = window.localStorage.getItem('recentURLs');
|
||||
|
||||
if (recentURLs) {
|
||||
console.info('FOUND STORED URLs', recentURLs);
|
||||
try {
|
||||
entries = JSON.parse(recentURLs);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse recent URLS', error);
|
||||
}
|
||||
} else {
|
||||
console.info('NO STORED URLs found');
|
||||
}
|
||||
dispatch({
|
||||
type: LOADED_RECENT_URLS,
|
||||
entries
|
||||
});
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_WILL_JOIN: {
|
||||
dispatch({
|
||||
type: ADD_RECENT_URL,
|
||||
roomURL: getInviteURL(getState),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { ReducerRegistry, set } from '../base/redux';
|
||||
import { ADD_RECENT_URL, LOADED_RECENT_URLS } from './actionTypes';
|
||||
|
||||
const MAX_LENGTH = 25;
|
||||
|
||||
const INITIAL_STATE = {
|
||||
entries: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature features/recording.
|
||||
*/
|
||||
ReducerRegistry.register('features/recent', (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case LOADED_RECENT_URLS: {
|
||||
// FIXME if that happens too late it may overwrite any recent URLs
|
||||
const newState = set(state, 'entries', action.entries);
|
||||
|
||||
console.info('RECENT STATE ON LOAD: ', newState);
|
||||
|
||||
return newState;
|
||||
}
|
||||
case ADD_RECENT_URL: {
|
||||
return _addRecentUrl(state, action);
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* FIXME.
|
||||
* @param state
|
||||
* @param action
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function _addRecentUrl(state, action) {
|
||||
const { roomURL, timestamp } = action;
|
||||
let existingIdx = -1;
|
||||
const entries = _.cloneDeep(state.entries);
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
if (entries[i].roomURL === roomURL) {
|
||||
existingIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIdx !== -1) {
|
||||
console.info('DELETED ALREADY EXISTING', roomURL);
|
||||
entries.splice(existingIdx, 1);
|
||||
}
|
||||
|
||||
entries.unshift({
|
||||
roomURL,
|
||||
timestamp
|
||||
});
|
||||
|
||||
if (entries.length > MAX_LENGTH) {
|
||||
const removed = entries.pop();
|
||||
|
||||
console.info('SIZE LIMIT exceeded, removed:', removed);
|
||||
}
|
||||
|
||||
const newState = set(state, 'entries', entries);
|
||||
|
||||
console.info('RECENT URLs', newState);
|
||||
|
||||
window.localStorage.setItem('recentURLs', JSON.stringify(entries));
|
||||
|
||||
return newState;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/* @flow */
|
||||
|
||||
import { Share } from 'react-native';
|
||||
import { NativeModules, Share } from 'react-native';
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
|
@ -37,7 +37,7 @@ function _shareRoom(roomURL: string, dispatch: Function) {
|
|||
// review before i18n was introduces in react/. However, I reviewed it
|
||||
// afterwards. Translate the display/human-readable strings.
|
||||
const message = `Click the following link to join the meeting: ${roomURL}`;
|
||||
const title = 'Jitsi Meet Conference';
|
||||
const title = `${NativeModules.AppInfo.name} Conference`;
|
||||
const onFulfilled
|
||||
= (shared: boolean) => dispatch(endShareRoom(roomURL, shared));
|
||||
|
||||
|
|