diff --git a/android/README.md b/android/README.md index 1f1f42b43..c2d2c4809 100644 --- a/android/README.md +++ b/android/README.md @@ -93,11 +93,20 @@ See JitsiMeetView.loadURL. The `JitsiMeetView` class is the core of Jitsi Meet SDK. It's designed to display a Jitsi Meet conference view (or a welcome page). +#### getListener() + +Returns the `JitsiMeetView.Listener` instance attached to the view. + #### loadURL(url) Loads the given URL and joins the conference which is being pointed to. If null, it will load the welcome page. +#### setListener(listener) + +Sets the given listener (class implementing the `JitsiMeetView.Listener` +interface) on the view. + #### onBackPressed() Helper method which should be called from the activity's `onBackPressed` method. @@ -132,3 +141,41 @@ application's activity is launched in "singleTask" mode this method should be called from the activity's `onNewIntent` method. This is a static method. + +#### Listener + +JitsiMeetView.Listener provides an interface applications can implement in order +to get notified about the state of the Jitsi Meet conference. + +##### onConferenceFailed + +Called when a joining a conference was unsuccessful or when there was an error +while in a conference. + +The `data` HashMap contains an "error" key describing the error and a "url" +key with the conference URL. + +#### onConferenceJoined + +Called when a conference was joined. + +The `data` HashMap contains a "url" key with the conference URL. + +#### onConferenceLeft + +Called when a conference was left. + +The `data` HashMap contains a "url" key with the conference URL. + +#### onConferenceWillJoin + +Called before a conference is joined. + +The `data` HashMap contains a "url" key with the conference URL. + +#### onConferenceWillLeave + +Called before a conference is left. + +The `data` HashMap contains a "url" key with the conference URL. + diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetBaseActivity.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetBaseActivity.java index 92e0154a2..de5074645 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetBaseActivity.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetBaseActivity.java @@ -38,7 +38,15 @@ import java.net.URL; * the JKConferenceView static methods. */ public class JitsiMeetBaseActivity extends AppCompatActivity { + /** + * Instance of the {@JitsiMeetView} which this activity will display. + */ private JitsiMeetView jitsiMeetView; + + /** + * Code for identifying the permission to draw on top of other apps. The number is chosen + * arbitrarily and used to correlate the intent with its result. + */ public static final int OVERLAY_PERMISSION_REQ_CODE = 4242; /** diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java index 3bce82c60..e7cd956f2 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java @@ -30,13 +30,27 @@ import com.facebook.react.ReactRootView; import com.facebook.react.common.LifecycleState; import java.net.URL; +import java.util.HashMap; public class JitsiMeetView extends FrameLayout { /** - * React Native root view. + * Background color used by this view and the React Native root view. */ - private ReactRootView mReactRootView; + private static final int BACKGROUND_COLOR = 0xFF111111; + + /** + * {@JitsiMeetView.Listener} instance for reporting events occurring in Jitsi Meet. + */ + private JitsiMeetView.Listener listener; + + /** + * Reference to the single instance of this class we currently allow. It's currently used for + * fetching the instance from the listener's callbacks. + * + * TODO: lift this limitation. + */ + private static JitsiMeetView mInstance; /** * React Native bridge. The instance manager allows embedding applications to create multiple @@ -45,13 +59,25 @@ public class JitsiMeetView extends FrameLayout { private static ReactInstanceManager mReactInstanceManager; /** - * Background color used by this view and the React Native root view. + * React Native root view. */ - private static final int BACKGROUND_COLOR = 0xFF111111; + private ReactRootView mReactRootView; public JitsiMeetView(@NonNull Context context) { super(context); + if (mInstance != null) { + throw new RuntimeException("Only a single instance is currently allowed"); + } + + /* + * TODO: Only allow a single instance for now. All React Native modules are + * kinda singletons so global state would be broken since we have a single + * bridge. Once we have that sorted out multiple instances of JitsiMeetView + * will be allowed. + */ + mInstance = this; + setBackgroundColor(BACKGROUND_COLOR); if (mReactInstanceManager == null) { @@ -59,6 +85,50 @@ public class JitsiMeetView extends FrameLayout { } } + /** + * Returns the only instance of this class we currently allow creating. + * + * @returns The {@JitsiMeetView} instance. + */ + public static JitsiMeetView getInstance() { + return mInstance; + } + + /** + * Getter for the {@JitsiMeetView.Listener} set on this view. + * + * @returns The {@JitsiMeetView.Listener} instance. + */ + public Listener getListener() { + return listener; + } + + /** + * Internal method to initialize the React Native instance manager. We create a single instance + * in order to load the JavaScript bundle a single time. All ReactRootView instances + * will be tied to the one and only ReactInstanceManager. + * + * @param application - Application instance which is running. + */ + private static void initReactInstanceManager(Application application) { + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(application) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new com.corbt.keepawake.KCKeepAwakePackage()) + .addPackage(new com.facebook.react.shell.MainReactPackage()) + .addPackage(new com.oblador.vectoricons.VectorIconsPackage()) + .addPackage(new com.ocetnik.timer.BackgroundTimerPackage()) + .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage()) + .addPackage(new com.rnimmersive.RNImmersivePackage()) + .addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage()) + .addPackage(new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage()) + .addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + } + /** * Loads the given URL and displays the conference. If the specified URL is null, the welcome * page is displayed instead. @@ -84,28 +154,12 @@ public class JitsiMeetView extends FrameLayout { } /** - * Internal method to initialize the React Native instance manager. We create a single instance - * in order to load the JavaScript bundle a single time. All ReactRootView instances - * will be tied to the one and only ReactInstanceManager. + * Setter for the {@JitsiMeetView.Listener} set on this view. * - * @param application - Application instance which is running. + * @param listener - Listener for this view. */ - private static void initReactInstanceManager(Application application) { - mReactInstanceManager = ReactInstanceManager.builder() - .setApplication(application) - .setBundleAssetName("index.android.bundle") - .setJSMainModuleName("index.android") - .addPackage(new com.corbt.keepawake.KCKeepAwakePackage()) - .addPackage(new com.facebook.react.shell.MainReactPackage()) - .addPackage(new com.oblador.vectoricons.VectorIconsPackage()) - .addPackage(new com.ocetnik.timer.BackgroundTimerPackage()) - .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage()) - .addPackage(new com.rnimmersive.RNImmersivePackage()) - .addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage()) - .addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage()) - .setUseDeveloperSupport(BuildConfig.DEBUG) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); + public void setListener(Listener listener) { + this.listener = listener; } /** @@ -173,4 +227,41 @@ public class JitsiMeetView extends FrameLayout { mReactInstanceManager.onNewIntent(intent); } } + + /** + * Interface for listening to events coming from Jitsi Meet. + */ + public interface Listener { + /** + * Called when joining a conference fails or an ongoing conference is interrupted due to a + * failure. + * @param data - HashMap with an "error" key describing the problem, and a "url" key with + * the conference URL. + */ + void onConferenceFailed(HashMap data); + + /** + * Called when a conference was joined. + * @param data - HashMap with a "url" key with the conference URL. + */ + void onConferenceJoined(HashMap data); + + /** + * Called when the conference was left, typically after hanging up. + * @param data - HashMap with a "url" key with the conference URL. + */ + void onConferenceLeft(HashMap data); + + /** + * Called before the conference is joined. + * @param data - HashMap with a "url" key with the conference URL. + */ + void onConferenceWillJoin(HashMap data); + + /** + * Called before the conference is left. + * @param data - HashMap with a "url" key with the conference URL. + */ + void onConferenceWillLeave(HashMap data); + } } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIModule.java new file mode 100644 index 000000000..fa0063cf4 --- /dev/null +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIModule.java @@ -0,0 +1,108 @@ +/* + * 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. + */ + +package org.jitsi.meet.sdk.externalapi; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; + +import org.jitsi.meet.sdk.JitsiMeetView; + +import java.util.HashMap; + + +/** + * Module implementing a simple API to enable a proximity sensor-controlled + * wake lock. When the lock is held, if the proximity sensor detects a nearby + * object it will dim the screen and disable touch controls. The functionality + * is used with the conference audio-only mode. + */ +public class ExternalAPIModule extends ReactContextBaseJavaModule { + /** + * React Native module name. + */ + private static final String MODULE_NAME = "ExternalAPI"; + + /** + * Initializes a new module instance. There shall be a single instance of + * this module throughout the lifetime of the application. + * + * @param reactContext the {@link ReactApplicationContext} where this module + * is created. + */ + public ExternalAPIModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + /** + * Gets the name of this module to be used in the React Native bridge. + * + * @return The name of this module to be used in the React Native bridge. + */ + @Override + public String getName() { + return MODULE_NAME; + } + + /** + * Dispatches an event that occurred on JavaScript to the view's listener. + * @param name - Event name. + * @param data - Ancillary data for the event. + */ + @ReactMethod + public void sendEvent(final String name, ReadableMap data) { + JitsiMeetView view = JitsiMeetView.getInstance(); + JitsiMeetView.Listener listener = view != null ? view.getListener() : null; + + if (listener == null) { + return; + } + + // TODO: Sigh, converting a ReadableMap to a HashMap is not supported until React Native + // 0.46. + final HashMap dataMap = new HashMap<>(); + + try { + switch (name) { + case "CONFERENCE_FAILED": + dataMap.put("error", data.getString("error")); + dataMap.put("url", data.getString("url")); + listener.onConferenceFailed(dataMap); + break; + case "CONFERENCE_JOINED": + dataMap.put("url", data.getString("url")); + listener.onConferenceJoined(dataMap); + break; + case "CONFERENCE_LEFT": + dataMap.put("url", data.getString("url")); + listener.onConferenceLeft(dataMap); + break; + case "CONFERENCE_WILL_JOIN": + dataMap.put("url", data.getString("url")); + listener.onConferenceWillJoin(dataMap); + break; + case "CONFERENCE_WILL_LEAVE": + dataMap.put("url", data.getString("url")); + listener.onConferenceWillLeave(dataMap); + break; + } + } catch (UnsupportedOperationException e) { + // Allow partial interface implementations. + } + } +} diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIPackage.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIPackage.java new file mode 100644 index 000000000..7f86ae93c --- /dev/null +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/externalapi/ExternalAPIPackage.java @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package org.jitsi.meet.sdk.externalapi; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Implements {@link ReactPackage} for {@link ExternalAPIModule}. + */ +public class ExternalAPIPackage implements ReactPackage { + /** + * {@inheritDoc} + */ + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + * + * @return List of native modules to be exposed by React Native. + */ + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new ExternalAPIModule(reactContext)); + + return modules; + } + + /** + * {@inheritDoc} + */ + @Override + public List createViewManagers( + ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/ios/README.md b/ios/README.md index 3afce365f..4010c565b 100644 --- a/ios/README.md +++ b/ios/README.md @@ -69,4 +69,43 @@ continueUserActivity:(NSUserActivity *)userActivity ### JitsiMeetViewDelegate -TODO. +This delegate is optional, and can be set on the `JitsiMeetView` instance using +the `delegate` property. + +It provides information about the conference state: was it joined, left, did it +fail? + +All methods in this delegate are optional. + +##### conferenceFailed + +Called when a joining a conference was unsuccessful or when there was an error +while in a conference. + +The `data` dictionary contains an "error" key describing the error and a "url" +key with the conference URL. + +#### conferenceJoined + +Called when a conference was joined. + +The `data` dictionary contains a "url" key with the conference URL. + +#### conferenceLeft + +Called when a conference was left. + +The `data` dictionary contains a "url" key with the conference URL. + +#### conferenceWillJoin + +Called before a conference is joined. + +The `data` dictionary contains a "url" key with the conference URL. + +#### conferenceWillLeave + +Called before a conference is left. + +The `data` dictionary contains a "url" key with the conference URL. + diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index 5640fdf24..7cba8c32e 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; }; 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 */; }; 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 */; }; @@ -44,6 +45,7 @@ 0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = ""; }; 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = ""; }; + 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExternalAPI.m; sourceTree = ""; }; 0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = ""; }; 0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = ""; }; 0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = ""; }; @@ -100,6 +102,7 @@ isa = PBXGroup; children = ( 0BCA495C1EC4B6C600B793EE /* AudioMode.m */, + 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */, 0BD906E91EC0C00300C8C18E /* Info.plist */, 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */, 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */, @@ -267,6 +270,7 @@ buildActionMask = 2147483647; files = ( 0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */, + 0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */, 0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */, 0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */, 0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */, diff --git a/ios/sdk/src/ExternalAPI.m b/ios/sdk/src/ExternalAPI.m new file mode 100644 index 000000000..e3e8fa41b --- /dev/null +++ b/ios/sdk/src/ExternalAPI.m @@ -0,0 +1,66 @@ +/* + * 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 "JitsiMeetView.h" + + +@interface ExternalAPI : NSObject +@end + +@implementation ExternalAPI + +RCT_EXPORT_MODULE(); + +/** + * Dispatches an event that occurred on JavaScript to the view's delegate. + * + * - name: name of the event. + * - data: dictionary (JSON object in JS) with data associated with the event. + */ +RCT_EXPORT_METHOD(sendEvent:(NSString*)name data:(NSDictionary *) data) { + JitsiMeetView *view = [JitsiMeetView getInstance]; + id delegate = view != nil ? view.delegate : nil; + + if (delegate == nil) { + return; + } + + if ([name isEqualToString:@"CONFERENCE_FAILED"] && + [delegate respondsToSelector:@selector(conferenceFailed:)]) { + + [delegate conferenceFailed:data]; + } else if ([name isEqualToString:@"CONFERENCE_JOINED"] && + [delegate respondsToSelector:@selector(conferenceJoined:)]) { + + [delegate conferenceJoined:data]; + } else if ([name isEqualToString:@"CONFERENCE_LEFT"] && + [delegate respondsToSelector:@selector(conferenceLeft:)]) { + + [delegate conferenceLeft:data]; + } else if ([name isEqualToString:@"CONFERENCE_WILL_JOIN"] && + [delegate respondsToSelector:@selector(conferenceWillJoin:)]) { + + [delegate conferenceWillJoin:data]; + } else if ([name isEqualToString:@"CONFERENCE_WILL_LEAVE"] && + [delegate respondsToSelector:@selector(conferenceWillLeave:)]) { + + [delegate conferenceWillLeave:data]; + } +} + +@end diff --git a/ios/sdk/src/JitsiMeetView.h b/ios/sdk/src/JitsiMeetView.h index b7c5a35a0..fb3fe2d33 100644 --- a/ios/sdk/src/JitsiMeetView.h +++ b/ios/sdk/src/JitsiMeetView.h @@ -32,6 +32,8 @@ sourceApplication:(NSString *)sourceApplication annotation:(id)annotation; ++ (instancetype) getInstance; + - (void)loadURL:(nullable NSURL *)url; @end diff --git a/ios/sdk/src/JitsiMeetView.m b/ios/sdk/src/JitsiMeetView.m index 6129193f4..310834f75 100644 --- a/ios/sdk/src/JitsiMeetView.m +++ b/ios/sdk/src/JitsiMeetView.m @@ -55,6 +55,7 @@ RCTFatalHandler _RCTFatal = ^(NSError *error) { @implementation JitsiMeetView static RCTBridgeWrapper *bridgeWrapper; +static JitsiMeetView *instance; #pragma mark Linking delegate helpers // https://facebook.github.io/react-native/docs/linking.html @@ -125,6 +126,10 @@ static RCTBridgeWrapper *bridgeWrapper; #pragma mark private methods ++ (instancetype)getInstance { + return instance; +} + /* * Internal initialization: * @@ -136,6 +141,20 @@ static RCTBridgeWrapper *bridgeWrapper; - (void)initialize { static dispatch_once_t onceToken; + /* + * TODO: Only allow a single instance for now. All React Native modules are + * kinda singletons so global state would be broken since we have a single + * bridge. Once we have that sorted out multiple instances of JitsiMeetView + * will be allowed. + */ + if (instance != nil) { + @throw [NSException + exceptionWithName:@"RuntimeError" + reason:@"Only a single instance is currently allowed" + userInfo:nil]; + } + instance = self; + dispatch_once(&onceToken, ^{ // Set a background color which is in accord with the JavaScript and // Android parts of the application and causes less perceived visual diff --git a/ios/sdk/src/JitsiMeetViewDelegate.h b/ios/sdk/src/JitsiMeetViewDelegate.h index 90a460c70..c57a79dd6 100644 --- a/ios/sdk/src/JitsiMeetViewDelegate.h +++ b/ios/sdk/src/JitsiMeetViewDelegate.h @@ -16,6 +16,43 @@ @protocol JitsiMeetViewDelegate -// TODO: add delegate methods +@optional + +/* + * Called when a joining a conference was unsuccessful or when there was an error + * while in a conference. + * + * The `data` dictionary contains an "error" key describing the error and a "url" + * key with the conference URL. + */ +- (void) conferenceFailed:(NSDictionary *) data; + +/* + * Called when a conference was joined. + * + * The `data` dictionary contains a "url" key with the conference URL. + */ +- (void) conferenceJoined:(NSDictionary *) data; + +/* + * Called when a conference was left. + * + * The `data` dictionary contains a "url" key with the conference URL. + */ +- (void) conferenceLeft:(NSDictionary *) data; + +/* + * Called before a conference is joined. + * + * The `data` dictionary contains a "url" key with the conference URL. + */ +- (void) conferenceWillJoin:(NSDictionary *) data; + +/* + * Called before a conference is left. + * + * The `data` dictionary contains a "url" key with the conference URL. + */ +- (void) conferenceWillLeave:(NSDictionary *) data; @end diff --git a/react/features/app/components/App.native.js b/react/features/app/components/App.native.js index b5c6f6e39..da54edcf5 100644 --- a/react/features/app/components/App.native.js +++ b/react/features/app/components/App.native.js @@ -5,6 +5,7 @@ import { Linking } from 'react-native'; import { Platform } from '../../base/react'; import '../../mobile/audio-mode'; import '../../mobile/background'; +import '../../mobile/external-api'; import '../../mobile/full-screen'; import '../../mobile/proximity'; import '../../mobile/wake-lock'; @@ -22,7 +23,7 @@ export class App extends AbstractApp { * * @static */ - static propTypes = AbstractApp.propTypes + static propTypes = AbstractApp.propTypes; /** * Initializes a new App instance. diff --git a/react/features/mobile/external-api/index.js b/react/features/mobile/external-api/index.js new file mode 100644 index 000000000..d43689289 --- /dev/null +++ b/react/features/mobile/external-api/index.js @@ -0,0 +1 @@ +import './middleware'; diff --git a/react/features/mobile/external-api/middleware.js b/react/features/mobile/external-api/middleware.js new file mode 100644 index 000000000..5ed52496b --- /dev/null +++ b/react/features/mobile/external-api/middleware.js @@ -0,0 +1,75 @@ +/* @flow */ + +import { NativeModules } from 'react-native'; + +import { getInviteURL } from '../../base/connection'; +import { + CONFERENCE_FAILED, + CONFERENCE_JOINED, + CONFERENCE_LEFT, + CONFERENCE_WILL_JOIN, + CONFERENCE_WILL_LEAVE +} from '../../base/conference'; +import { MiddlewareRegistry } from '../../base/redux'; + + +/** + * Middleware that captures Redux actions and uses the ExternalAPI module to + * turn them into native events so the application knows about them. + * + * @param {Store} store - Redux store. + * @returns {Function} + */ +MiddlewareRegistry.register(store => next => action => { + const eventData = {}; + + switch (action.type) { + case CONFERENCE_FAILED: { + eventData.error = action.error; + eventData.url = getInviteURL(store.getState()); + _sendEvent('CONFERENCE_FAILED', eventData); + break; + } + + case CONFERENCE_JOINED: { + eventData.url = getInviteURL(store.getState()); + _sendEvent('CONFERENCE_JOINED', eventData); + break; + } + + case CONFERENCE_LEFT: { + eventData.url = getInviteURL(store.getState()); + _sendEvent('CONFERENCE_LEFT', eventData); + break; + } + + case CONFERENCE_WILL_JOIN: { + eventData.url = getInviteURL(store.getState()); + _sendEvent('CONFERENCE_WILL_JOIN', eventData); + break; + } + + case CONFERENCE_WILL_LEAVE: { + eventData.url = getInviteURL(store.getState()); + _sendEvent('CONFERENCE_WILL_LEAVE', eventData); + break; + } + + } + + return next(action); +}); + +/** + * Sends the given event to the native side of the application. Applications can + * then listen to the events using the mechanisms provided by the Jitsi Meet + * SDK. + * + * @param {string} name - Event name. + * @param {Object} data - Ancillary data for the event. + * @private + * @returns {void} + */ +function _sendEvent(name: string, data: Object) { + NativeModules.ExternalAPI.sendEvent(name, data); +}