Selaa lähdekoodia

[RN] add support for inviting participants during a call on mobile (2)

j8
Lyubo Marinov 7 vuotta sitten
vanhempi
commit
effd3728b6
51 muutettua tiedostoa jossa 2139 lisäystä ja 1455 poistoa
  1. 15
    6
      android/app/src/main/java/org/jitsi/meet/MainActivity.java
  2. 0
    184
      android/sdk/src/main/java/org/jitsi/meet/sdk/InviteSearchController.java
  3. 0
    126
      android/sdk/src/main/java/org/jitsi/meet/sdk/InviteSearchModule.java
  4. 1
    2
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java
  5. 56
    165
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java
  6. 0
    4
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewAdapter.java
  7. 0
    10
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewListener.java
  8. 43
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/ReactContextUtils.java
  9. 125
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java
  10. 204
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/invite/AddPeopleController.java
  11. 49
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/invite/AddPeopleControllerListener.java
  12. 265
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteController.java
  13. 29
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteControllerListener.java
  14. 119
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java
  15. 1
    1
      ios/app/src/ViewController.h
  16. 25
    1
      ios/app/src/ViewController.m
  17. 42
    8
      ios/sdk/sdk.xcodeproj/project.pbxproj
  18. 0
    49
      ios/sdk/src/InviteSearch.h
  19. 0
    215
      ios/sdk/src/InviteSearch.m
  20. 7
    1
      ios/sdk/src/JitsiMeet.h
  21. 2
    3
      ios/sdk/src/JitsiMeetView.h
  22. 12
    6
      ios/sdk/src/JitsiMeetView.m
  23. 0
    11
      ios/sdk/src/JitsiMeetViewDelegate.h
  24. 33
    0
      ios/sdk/src/invite/AddPeopleController+Private.h
  25. 31
    0
      ios/sdk/src/invite/AddPeopleController.h
  26. 79
    0
      ios/sdk/src/invite/AddPeopleController.m
  27. 38
    0
      ios/sdk/src/invite/AddPeopleControllerDelegate.h
  28. 30
    0
      ios/sdk/src/invite/Invite+Private.h
  29. 90
    0
      ios/sdk/src/invite/Invite.m
  30. 50
    0
      ios/sdk/src/invite/InviteController+Private.h
  31. 32
    0
      ios/sdk/src/invite/InviteController.h
  32. 118
    0
      ios/sdk/src/invite/InviteController.m
  33. 29
    0
      ios/sdk/src/invite/InviteControllerDelegate.h
  34. 24
    0
      react/features/invite/actionTypes.js
  35. 16
    0
      react/features/invite/actions.js
  36. 0
    3
      react/features/invite/components/AddPeopleDialog.native.js
  37. 18
    14
      react/features/invite/components/AddPeopleDialog.web.js
  38. 92
    22
      react/features/invite/components/InviteButton.native.js
  39. 174
    153
      react/features/invite/functions.js
  40. 5
    3
      react/features/invite/middleware.any.js
  41. 208
    0
      react/features/invite/middleware.native.js
  42. 50
    0
      react/features/invite/middleware.web.js
  43. 8
    1
      react/features/invite/reducer.js
  44. 3
    3
      react/features/mobile/external-api/middleware.js
  45. 0
    46
      react/features/mobile/invite-search/actionTypes.js
  46. 0
    50
      react/features/mobile/invite-search/actions.js
  47. 0
    5
      react/features/mobile/invite-search/index.js
  48. 0
    233
      react/features/mobile/invite-search/middleware.js
  49. 0
    14
      react/features/mobile/invite-search/reducer.js
  50. 5
    73
      react/features/toolbox/components/Toolbox.native.js
  51. 11
    43
      react/features/toolbox/components/Toolbox.web.js

+ 15
- 6
android/app/src/main/java/org/jitsi/meet/MainActivity.java Näytä tiedosto

@@ -19,10 +19,11 @@ package org.jitsi.meet;
19 19
 import android.os.Bundle;
20 20
 import android.util.Log;
21 21
 
22
-import org.jitsi.meet.sdk.InviteSearchController;
23 22
 import org.jitsi.meet.sdk.JitsiMeetActivity;
24 23
 import org.jitsi.meet.sdk.JitsiMeetView;
25 24
 import org.jitsi.meet.sdk.JitsiMeetViewListener;
25
+import org.jitsi.meet.sdk.invite.AddPeopleController;
26
+import org.jitsi.meet.sdk.invite.InviteControllerListener;
26 27
 
27 28
 import com.calendarevents.CalendarEventsPackage;
28 29
 
@@ -86,16 +87,24 @@ public class MainActivity extends JitsiMeetActivity {
86 87
                     on("CONFERENCE_WILL_LEAVE", data);
87 88
                 }
88 89
 
89
-                @Override
90
-                public void launchNativeInvite(InviteSearchController inviteSearchController) {
91
-                    on("LAUNCH_NATIVE_INVITE", new HashMap<String, Object>());
92
-                }
93
-
94 90
                 @Override
95 91
                 public void onLoadConfigError(Map<String, Object> data) {
96 92
                     on("LOAD_CONFIG_ERROR", data);
97 93
                 }
98 94
             });
95
+
96
+            view.getInviteController().setListener(
97
+                new InviteControllerListener() {
98
+                    public void beginAddPeople(
99
+                            AddPeopleController addPeopleController) {
100
+                        // Log with the tag "ReactNative" in order to have the
101
+                        // log visible in react-native log-android as well.
102
+                        Log.d(
103
+                            "ReactNative",
104
+                            InviteControllerListener.class.getSimpleName()
105
+                                + ".beginAddPeople");
106
+                    }
107
+                });
99 108
         }
100 109
 
101 110
         return view;

+ 0
- 184
android/sdk/src/main/java/org/jitsi/meet/sdk/InviteSearchController.java Näytä tiedosto

@@ -1,184 +0,0 @@
1
-package org.jitsi.meet.sdk;
2
-
3
-import android.util.Log;
4
-
5
-import com.facebook.react.bridge.ReadableArray;
6
-import com.facebook.react.bridge.ReadableMap;
7
-import com.facebook.react.bridge.WritableArray;
8
-import com.facebook.react.bridge.WritableNativeArray;
9
-import com.facebook.react.bridge.WritableNativeMap;
10
-
11
-import java.lang.ref.WeakReference;
12
-import java.util.ArrayList;
13
-import java.util.HashMap;
14
-import java.util.List;
15
-import java.util.Map;
16
-import java.util.UUID;
17
-
18
-/**
19
- * Controller object used by native code to query and submit user selections for the user invitation flow.
20
- */
21
-public class InviteSearchController {
22
-
23
-    /**
24
-     * The InviteSearchControllerDelegate for this controller, used to pass query
25
-     * results back to the native code that initiated the query.
26
-     */
27
-    private InviteSearchControllerDelegate searchControllerDelegate;
28
-
29
-    /**
30
-     * Local cache of search query results.  Used to re-hydrate the list
31
-     * of selected items based on their ids passed to submitSelectedItemIds
32
-     * in order to pass the full item maps back to the JitsiMeetView during submission.
33
-     */
34
-    private Map<String, ReadableMap> items = new HashMap<>();
35
-
36
-    /**
37
-     * Randomly generated UUID, used for identification in the InviteSearchModule
38
-     */
39
-    private String uuid = UUID.randomUUID().toString();
40
-
41
-    private WeakReference<InviteSearchModule> parentModuleRef;
42
-
43
-    public InviteSearchController(InviteSearchModule module) {
44
-        parentModuleRef = new WeakReference<>(module);
45
-    }
46
-
47
-    /**
48
-     * Start a search for entities to invite with the given query.
49
-     * Results will be returned through the associated InviteSearchControllerDelegate's
50
-     * onReceiveResults method.
51
-     *
52
-     * @param query
53
-     */
54
-    public void performQuery(String query) {
55
-        JitsiMeetView.onInviteQuery(query, uuid);
56
-    }
57
-
58
-    /**
59
-     * Send invites to selected users based on their item ids
60
-     *
61
-     * @param ids
62
-     */
63
-    public void submitSelectedItemIds(List<String> ids) {
64
-        WritableArray selectedItems = new WritableNativeArray();
65
-        for(int i=0; i<ids.size(); i++) {
66
-            if(items.containsKey(ids.get(i))) {
67
-                WritableNativeMap map = new WritableNativeMap();
68
-                map.merge(items.get(ids.get(i)));
69
-                selectedItems.pushMap(map);
70
-            } else {
71
-                // if the id doesn't exist in the map, we can't do anything, so just skip it
72
-            }
73
-        }
74
-
75
-        JitsiMeetView.submitSelectedItems(selectedItems, uuid);
76
-    }
77
-
78
-    /**
79
-     * Caches results received by the search into a local map for use
80
-     * later when the items are submitted.  Submission requires the full
81
-     * map of information, but only the IDs are returned back to the delegate.
82
-     * Using this map means we don't have to send the whole map back to the delegate.
83
-     *
84
-     * @param results
85
-     * @param query
86
-     */
87
-    void receivedResultsForQuery(ReadableArray results, String query) {
88
-
89
-        List<Map<String, Object>> jvmResults = new ArrayList<>();
90
-        // cache results for use in submission later
91
-        // convert to jvm array
92
-        for(int i=0; i<results.size(); i++) {
93
-            ReadableMap map = results.getMap(i);
94
-            if(map.hasKey("id")) {
95
-                items.put(map.getString("id"), map);
96
-            } else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
97
-                items.put(map.getString("number"), map);
98
-            } else {
99
-                Log.w("InviteSearchController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
100
-            }
101
-
102
-            jvmResults.add(map.toHashMap());
103
-        }
104
-
105
-
106
-        searchControllerDelegate.onReceiveResults(this, jvmResults, query);
107
-    }
108
-
109
-    /**
110
-     *
111
-     * @return the InviteSearchControllerDelegate for this controller, used to pass query
112
-     * results back to the native code that initiated the query.
113
-     */
114
-    public InviteSearchControllerDelegate getSearchControllerDelegate() {
115
-        return searchControllerDelegate;
116
-    }
117
-
118
-    /**
119
-     * Sets the InviteSearchControllerDelegate for this controller, used to pass query results
120
-     * back to the native code that initiated the query.
121
-     *
122
-     * @param searchControllerDelegate
123
-     */
124
-    public void setSearchControllerDelegate(InviteSearchControllerDelegate searchControllerDelegate) {
125
-        this.searchControllerDelegate = searchControllerDelegate;
126
-    }
127
-
128
-    /**
129
-     * Cancel the invitation flow and free memory allocated to the InviteSearchController.  After
130
-     * calling this method, this object is invalid - a new InviteSearchController will be passed
131
-     * to the caller through launchNativeInvite.
132
-     */
133
-    public void cancelSearch() {
134
-        InviteSearchModule parentModule = parentModuleRef.get();
135
-        if(parentModule != null) {
136
-            parentModule.removeSearchController(uuid);
137
-        }
138
-    }
139
-
140
-    /**
141
-     * @return the unique identifier for this InviteSearchController
142
-     */
143
-    public String getUuid() {
144
-        return uuid;
145
-    }
146
-
147
-    public interface InviteSearchControllerDelegate {
148
-        /**
149
-         * Called when results are received for a query called through InviteSearchController.query()
150
-         *
151
-         * @param searchController
152
-         * @param results a List of Map<String, Object> objects that represent items returned by the query.
153
-         *                The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
154
-         *                "user" types have properties at "id", "name", and "avatar"
155
-         *                "videosipgw" types have properties at "id" and "name"
156
-         *                "phone" types have properties at "number", "title", "and "subtitle"
157
-         * @param query the query that generated the given results
158
-         */
159
-        void onReceiveResults(InviteSearchController searchController, List<Map<String, Object>> results, String query);
160
-
161
-        /**
162
-         * Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes successfully
163
-         * and invitations are sent to all given IDs.
164
-         *
165
-         * @param searchController the active {@link InviteSearchController} for this invite flow.  This object will be
166
-         *                         cleaned up after the call to inviteSucceeded completes.
167
-         */
168
-        void inviteSucceeded(InviteSearchController searchController);
169
-
170
-        /**
171
-         * Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes, but the
172
-         * invitation fails for one or more of the selected items.
173
-         *
174
-         * @param searchController the active {@link InviteSearchController} for this invite flow.  This object
175
-         *                         should be cleaned up by calling {@link InviteSearchController#cancelSearch()} if
176
-         *                         the user exits the invite flow.  Otherwise, it can stay active if the user
177
-         *                         will attempt to invite
178
-         * @param failedInviteItems a {@code List} of {@code Map<String, Object>} dictionaries that represent the
179
-         *                          invitations that failed.  The data type of the objects is identical to the results
180
-         *                          returned in onReceiveResuls.
181
-         */
182
-        void inviteFailed(InviteSearchController searchController, List<Map<String, Object>> failedInviteItems);
183
-    }
184
-}

+ 0
- 126
android/sdk/src/main/java/org/jitsi/meet/sdk/InviteSearchModule.java Näytä tiedosto

@@ -1,126 +0,0 @@
1
-package org.jitsi.meet.sdk;
2
-
3
-import android.util.Log;
4
-
5
-import com.facebook.react.bridge.ReactApplicationContext;
6
-import com.facebook.react.bridge.ReactContextBaseJavaModule;
7
-import com.facebook.react.bridge.ReactMethod;
8
-import com.facebook.react.bridge.ReadableArray;
9
-import com.facebook.react.bridge.ReadableMap;
10
-
11
-import java.util.ArrayList;
12
-import java.util.HashMap;
13
-import java.util.Map;
14
-
15
-/**
16
- * Native module for Invite Search
17
- */
18
-class InviteSearchModule extends ReactContextBaseJavaModule {
19
-
20
-    /**
21
-     * Map of InviteSearchController objects passed to connected JitsiMeetView.
22
-     * A call to launchNativeInvite will create a new InviteSearchController and pass
23
-     * it back to the caller.  On a successful invitation, the controller will be removed automatically.
24
-     * On a failed invitation, the caller has the option of calling InviteSearchController#cancelSearch()
25
-     * to remove the controller from this map.  The controller should also be removed if the user cancels
26
-     * the invitation flow.
27
-     */
28
-    private Map<String, InviteSearchController> searchControllers = new HashMap<>();
29
-
30
-    public InviteSearchModule(ReactApplicationContext reactContext) {
31
-        super(reactContext);
32
-    }
33
-
34
-    /**
35
-     * Launch the native user invite flow
36
-     *
37
-     * @param externalAPIScope a string that represents a connection to a specific JitsiMeetView
38
-     */
39
-    @ReactMethod
40
-    public void launchNativeInvite(String externalAPIScope) {
41
-        JitsiMeetView viewToLaunchInvite = JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
42
-
43
-        if(viewToLaunchInvite == null) {
44
-            return;
45
-        }
46
-
47
-        if(viewToLaunchInvite.getListener() == null) {
48
-            return;
49
-        }
50
-
51
-        InviteSearchController controller = createSearchController();
52
-        viewToLaunchInvite.getListener().launchNativeInvite(controller);
53
-    }
54
-
55
-    /**
56
-     * Callback for results received from the JavaScript invite search call
57
-     *
58
-     * @param results the results in a ReadableArray of ReadableMap objects
59
-     * @param query the query associated with the search
60
-     * @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
61
-     */
62
-    @ReactMethod
63
-    public void receivedResults(ReadableArray results, String query, String inviteSearchControllerScope) {
64
-        InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
65
-
66
-        if(controller == null) {
67
-            Log.w("InviteSearchModule", "Received results, but unable to find active controller to send results back");
68
-            return;
69
-        }
70
-
71
-        controller.receivedResultsForQuery(results, query);
72
-
73
-    }
74
-
75
-    /**
76
-     * Callback for invitation failures
77
-     *
78
-     * @param items the items for which the invitation failed
79
-     * @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
80
-     */
81
-    @ReactMethod
82
-    public void inviteFailedForItems(ReadableArray items, String inviteSearchControllerScope) {
83
-        InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
84
-
85
-        if(controller == null) {
86
-            Log.w("InviteSearchModule", "Invite failed, but unable to find active controller to notify");
87
-            return;
88
-        }
89
-
90
-        ArrayList<Map<String, Object>> jvmItems = new ArrayList<>();
91
-        for(int i=0; i<items.size(); i++) {
92
-            ReadableMap item = items.getMap(i);
93
-            jvmItems.add(item.toHashMap());
94
-        }
95
-
96
-        controller.getSearchControllerDelegate().inviteFailed(controller, jvmItems);
97
-    }
98
-
99
-    @ReactMethod
100
-    public void inviteSucceeded(String inviteSearchControllerScope) {
101
-        InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
102
-
103
-        if(controller == null) {
104
-            Log.w("InviteSearchModule", "Invite succeeded, but unable to find active controller to notify");
105
-            return;
106
-        }
107
-
108
-        controller.getSearchControllerDelegate().inviteSucceeded(controller);
109
-        searchControllers.remove(inviteSearchControllerScope);
110
-    }
111
-
112
-    void removeSearchController(String inviteSearchControllerUuid) {
113
-        searchControllers.remove(inviteSearchControllerUuid);
114
-    }
115
-
116
-    @Override
117
-    public String getName() {
118
-        return "InviteSearch";
119
-    }
120
-
121
-    private InviteSearchController createSearchController() {
122
-        InviteSearchController searchController = new InviteSearchController(this);
123
-        searchControllers.put(searchController.getUuid(), searchController);
124
-        return searchController;
125
-    }
126
-}

+ 1
- 2
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java Näytä tiedosto

@@ -17,7 +17,6 @@
17 17
 package org.jitsi.meet.sdk;
18 18
 
19 19
 import android.content.Intent;
20
-import android.content.res.Configuration;
21 20
 import android.net.Uri;
22 21
 import android.os.Build;
23 22
 import android.os.Bundle;
@@ -229,7 +228,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
229 228
         if (!super.onKeyUp(keyCode, event)
230 229
                 && BuildConfig.DEBUG
231 230
                 && (reactInstanceManager
232
-                        = JitsiMeetView.getReactInstanceManager())
231
+                        = ReactInstanceManagerHolder.getReactInstanceManager())
233 232
                     != null
234 233
                 && keyCode == KeyEvent.KEYCODE_MENU) {
235 234
             reactInstanceManager.showDevOptionsDialog();

+ 56
- 165
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java Näytä tiedosto

@@ -17,7 +17,6 @@
17 17
 package org.jitsi.meet.sdk;
18 18
 
19 19
 import android.app.Activity;
20
-import android.app.Application;
21 20
 import android.content.Context;
22 21
 import android.content.Intent;
23 22
 import android.net.Uri;
@@ -29,22 +28,13 @@ import android.widget.FrameLayout;
29 28
 
30 29
 import com.facebook.react.ReactInstanceManager;
31 30
 import com.facebook.react.ReactRootView;
32
-import com.facebook.react.bridge.NativeModule;
33
-import com.facebook.react.bridge.ReactApplicationContext;
34
-import com.facebook.react.bridge.ReactContext;
35
-import com.facebook.react.bridge.WritableArray;
36
-import com.facebook.react.bridge.WritableMap;
37
-import com.facebook.react.bridge.WritableNativeMap;
38
-import com.facebook.react.common.LifecycleState;
39 31
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
40
-import com.facebook.react.modules.core.DeviceEventManagerModule;
41 32
 import com.rnimmersive.RNImmersiveModule;
42 33
 
34
+import org.jitsi.meet.sdk.invite.InviteController;
35
+
43 36
 import java.net.URL;
44
-import java.util.Arrays;
45 37
 import java.util.Collections;
46
-import java.util.List;
47
-import java.util.Map;
48 38
 import java.util.Set;
49 39
 import java.util.UUID;
50 40
 import java.util.WeakHashMap;
@@ -62,30 +52,9 @@ public class JitsiMeetView extends FrameLayout {
62 52
      */
63 53
     private final static String TAG = JitsiMeetView.class.getSimpleName();
64 54
 
65
-    /**
66
-     * React Native bridge. The instance manager allows embedding applications
67
-     * to create multiple root views off the same JavaScript bundle.
68
-     */
69
-    private static ReactInstanceManager reactInstanceManager;
70
-
71 55
     private static final Set<JitsiMeetView> views
72 56
         = Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
73 57
 
74
-    private static List<NativeModule> createNativeModules(
75
-            ReactApplicationContext reactContext) {
76
-        return Arrays.<NativeModule>asList(
77
-            new AndroidSettingsModule(reactContext),
78
-            new AppInfoModule(reactContext),
79
-            new AudioModeModule(reactContext),
80
-            new ExternalAPIModule(reactContext),
81
-            new InviteSearchModule(reactContext),
82
-            new PictureInPictureModule(reactContext),
83
-            new ProximityModule(reactContext),
84
-            new WiFiStatsModule(reactContext),
85
-            new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
86
-        );
87
-    }
88
-
89 58
     public static JitsiMeetView findViewByExternalAPIScope(
90 59
             String externalAPIScope) {
91 60
         synchronized (views) {
@@ -99,47 +68,6 @@ public class JitsiMeetView extends FrameLayout {
99 68
         return null;
100 69
     }
101 70
 
102
-    // XXX Strictly internal use only (at the time of this writing)!
103
-    static ReactInstanceManager getReactInstanceManager() {
104
-        return reactInstanceManager;
105
-    }
106
-
107
-    /**
108
-     * Internal method to initialize the React Native instance manager. We
109
-     * create a single instance in order to load the JavaScript bundle a single
110
-     * time. All {@code ReactRootView} instances will be tied to the one and
111
-     * only {@code ReactInstanceManager}.
112
-     *
113
-     * @param application {@code Application} instance which is running.
114
-     */
115
-    private static void initReactInstanceManager(Application application) {
116
-        reactInstanceManager
117
-            = ReactInstanceManager.builder()
118
-                .setApplication(application)
119
-                .setBundleAssetName("index.android.bundle")
120
-                .setJSMainModulePath("index.android")
121
-                .addPackage(new com.calendarevents.CalendarEventsPackage())
122
-                .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
123
-                .addPackage(new com.facebook.react.shell.MainReactPackage())
124
-                .addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
125
-                .addPackage(new com.oblador.vectoricons.VectorIconsPackage())
126
-                .addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
127
-                .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
128
-                .addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
129
-                .addPackage(new com.rnimmersive.RNImmersivePackage())
130
-                .addPackage(new com.zmxv.RNSound.RNSoundPackage())
131
-                .addPackage(new ReactPackageAdapter() {
132
-                    @Override
133
-                    public List<NativeModule> createNativeModules(
134
-                            ReactApplicationContext reactContext) {
135
-                        return JitsiMeetView.createNativeModules(reactContext);
136
-                    }
137
-                })
138
-                .setUseDeveloperSupport(BuildConfig.DEBUG)
139
-                .setInitialLifecycleState(LifecycleState.RESUMED)
140
-                .build();
141
-    }
142
-
143 71
     /**
144 72
      * Loads a specific URL {@code String} in all existing
145 73
      * {@code JitsiMeetView}s.
@@ -174,6 +102,9 @@ public class JitsiMeetView extends FrameLayout {
174 102
      * implementation.
175 103
      */
176 104
     public static boolean onBackPressed() {
105
+        ReactInstanceManager reactInstanceManager
106
+            = ReactInstanceManagerHolder.getReactInstanceManager();
107
+
177 108
         if (reactInstanceManager == null) {
178 109
             return false;
179 110
         } else {
@@ -190,6 +121,9 @@ public class JitsiMeetView extends FrameLayout {
190 121
      * @param activity {@code Activity} being destroyed.
191 122
      */
192 123
     public static void onHostDestroy(Activity activity) {
124
+        ReactInstanceManager reactInstanceManager
125
+            = ReactInstanceManagerHolder.getReactInstanceManager();
126
+
193 127
         if (reactInstanceManager != null) {
194 128
             reactInstanceManager.onHostDestroy(activity);
195 129
         }
@@ -202,6 +136,9 @@ public class JitsiMeetView extends FrameLayout {
202 136
      * @param activity {@code Activity} being paused.
203 137
      */
204 138
     public static void onHostPause(Activity activity) {
139
+        ReactInstanceManager reactInstanceManager
140
+            = ReactInstanceManagerHolder.getReactInstanceManager();
141
+
205 142
         if (reactInstanceManager != null) {
206 143
             reactInstanceManager.onHostPause(activity);
207 144
         }
@@ -228,6 +165,9 @@ public class JitsiMeetView extends FrameLayout {
228 165
     public static void onHostResume(
229 166
             Activity activity,
230 167
             DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
168
+        ReactInstanceManager reactInstanceManager
169
+            = ReactInstanceManagerHolder.getReactInstanceManager();
170
+        
231 171
         if (reactInstanceManager != null) {
232 172
             reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
233 173
         }
@@ -256,6 +196,9 @@ public class JitsiMeetView extends FrameLayout {
256 196
             return;
257 197
         }
258 198
 
199
+        ReactInstanceManager reactInstanceManager
200
+            = ReactInstanceManagerHolder.getReactInstanceManager();
201
+
259 202
         if (reactInstanceManager != null) {
260 203
             reactInstanceManager.onNewIntent(intent);
261 204
         }
@@ -269,63 +212,9 @@ public class JitsiMeetView extends FrameLayout {
269 212
      * This is currently not mandatory.
270 213
      */
271 214
     public static void onUserLeaveHint() {
272
-        sendEvent("onUserLeaveHint", null);
273
-    }
274
-
275
-    /**
276
-     * Starts a query for users to invite to the conference.  Results will be
277
-     * returned through the {@link InviteSearchController.InviteSearchControllerDelegate#onReceiveResults(InviteSearchController, List, String)}
278
-     * method.
279
-     *
280
-     * @param query {@code String} to use for the query
281
-     */
282
-    public static void onInviteQuery(String query, String inviteSearchControllerScope) {
283
-        WritableNativeMap params = new WritableNativeMap();
284
-        params.putString("query", query);
285
-        params.putString("inviteScope", inviteSearchControllerScope);
286
-        sendEvent("performQueryAction", params);
287
-    }
288
-
289
-    /**
290
-     * Sends JavaScript event to submit invitations to the given item ids
291
-     *
292
-     * @param selectedItems a WritableArray of WritableNativeMaps representing selected items.
293
-     *                  Each map representing a selected item should match the data passed
294
-     *                  back in the return from a query.
295
-     */
296
-    public static void submitSelectedItems(WritableArray selectedItems, String inviteSearchControllerScope) {
297
-        WritableNativeMap params = new WritableNativeMap();
298
-        params.putArray("selectedItems", selectedItems);
299
-        params.putString("inviteScope", inviteSearchControllerScope);
300
-        sendEvent("performSubmitInviteAction", params);
301
-    }
302
-
303
-    /**
304
-     * Helper function to send an event to JavaScript.
305
-     *
306
-     * @param eventName {@code String} containing the event name.
307
-     * @param data {@code Object} optional ancillary data for the event.
308
-     */
309
-    private static void sendEvent(
310
-            String eventName,
311
-            @Nullable Object data) {
312
-        if (reactInstanceManager != null) {
313
-            ReactContext reactContext
314
-                = reactInstanceManager.getCurrentReactContext();
315
-            if (reactContext != null) {
316
-                reactContext
317
-                    .getJSModule(
318
-                        DeviceEventManagerModule.RCTDeviceEventEmitter.class)
319
-                    .emit(eventName, data);
320
-            }
321
-        }
215
+        ReactInstanceManagerHolder.emitEvent("onUserLeaveHint", null);
322 216
     }
323 217
 
324
-    /**
325
-     * Whether user invitation is enabled.
326
-     */
327
-    private boolean addPeopleEnabled;
328
-
329 218
     /**
330 219
      * The default base {@code URL} used to join a conference when a partial URL
331 220
      * (e.g. a room name only) is specified to {@link #loadURLString(String)} or
@@ -333,11 +222,6 @@ public class JitsiMeetView extends FrameLayout {
333 222
      */
334 223
     private URL defaultURL;
335 224
 
336
-    /**
337
-     * Whether the ability to add users by phone number is enabled.
338
-     */
339
-    private boolean dialOutEnabled;
340
-
341 225
     /**
342 226
      * The unique identifier of this {@code JitsiMeetView} within the process
343 227
      * for the purposes of {@link ExternalAPI}. The name scope was inspired by
@@ -346,6 +230,12 @@ public class JitsiMeetView extends FrameLayout {
346 230
      */
347 231
     private final String externalAPIScope;
348 232
 
233
+    /**
234
+     * The entry point into the invite feature of Jitsi Meet. The Java
235
+     * counterpart of the JavaScript {@code InviteButton}.
236
+     */
237
+    private final InviteController inviteController;
238
+
349 239
     /**
350 240
      * {@link JitsiMeetViewListener} instance for reporting events occurring in
351 241
      * Jitsi Meet.
@@ -374,15 +264,18 @@ public class JitsiMeetView extends FrameLayout {
374 264
 
375 265
         setBackgroundColor(BACKGROUND_COLOR);
376 266
 
377
-        if (reactInstanceManager == null) {
378
-            initReactInstanceManager(((Activity) context).getApplication());
379
-        }
267
+        ReactInstanceManagerHolder.initReactInstanceManager(
268
+            ((Activity) context).getApplication());
380 269
 
381 270
         // Hook this JitsiMeetView into ExternalAPI.
382 271
         externalAPIScope = UUID.randomUUID().toString();
383 272
         synchronized (views) {
384 273
             views.add(this);
385 274
         }
275
+
276
+        // The entry point into the invite feature of Jitsi Meet. The Java
277
+        // counterpart of the JavaScript InviteButton.
278
+        inviteController = new InviteController(externalAPIScope);
386 279
     }
387 280
 
388 281
     /**
@@ -413,6 +306,19 @@ public class JitsiMeetView extends FrameLayout {
413 306
         return defaultURL;
414 307
     }
415 308
 
309
+    /**
310
+     * Gets the {@link InviteController} which represents the entry point into
311
+     * the invite feature of Jitsi Meet and is the Java counterpart of the
312
+     * JavaScript {@code InviteButton}.
313
+     *
314
+     * @return the {@link InviteController} which represents the entry point
315
+     * into the invite feature of Jitsi Meet and is the Java counterpart of the
316
+     * JavaScript {@code InviteButton}
317
+     */
318
+    public InviteController getInviteController() {
319
+        return inviteController;
320
+    }
321
+
416 322
     /**
417 323
      * Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
418 324
      *
@@ -483,6 +389,18 @@ public class JitsiMeetView extends FrameLayout {
483 389
         // externalAPIScope
484 390
         props.putString("externalAPIScope", externalAPIScope);
485 391
 
392
+        // inviteController
393
+        InviteController inviteController = getInviteController();
394
+
395
+        if (inviteController != null) {
396
+            props.putBoolean(
397
+                "addPeopleEnabled",
398
+                inviteController.isAddPeopleEnabled());
399
+            props.putBoolean(
400
+                "dialOutEnabled",
401
+                inviteController.isDialOutEnabled());
402
+        }
403
+
486 404
         // pictureInPictureEnabled
487 405
         props.putBoolean(
488 406
             "pictureInPictureEnabled",
@@ -496,9 +414,6 @@ public class JitsiMeetView extends FrameLayout {
496 414
         // welcomePageEnabled
497 415
         props.putBoolean("welcomePageEnabled", welcomePageEnabled);
498 416
 
499
-        props.putBoolean("addPeopleEnabled", addPeopleEnabled);
500
-        props.putBoolean("dialOutEnabled", dialOutEnabled);
501
-
502 417
         // XXX The method loadURLObject: is supposed to be imperative i.e.
503 418
         // a second invocation with one and the same URL is expected to join
504 419
         // the respective conference again if the first invocation was followed
@@ -513,7 +428,7 @@ public class JitsiMeetView extends FrameLayout {
513 428
         if (reactRootView == null) {
514 429
             reactRootView = new ReactRootView(getContext());
515 430
             reactRootView.startReactApplication(
516
-                reactInstanceManager,
431
+                ReactInstanceManagerHolder.getReactInstanceManager(),
517 432
                 "App",
518 433
                 props);
519 434
             reactRootView.setBackgroundColor(BACKGROUND_COLOR);
@@ -580,18 +495,6 @@ public class JitsiMeetView extends FrameLayout {
580 495
         }
581 496
     }
582 497
 
583
-    /**
584
-     * Sets whether the ability to add users to the call is enabled.
585
-     * If this is enabled, an add user button will appear on the {@link JitsiMeetView}.
586
-     * If enabled, and the user taps the add user button,
587
-     * {@link JitsiMeetViewListener#launchNativeInvite(Map)} will be called.
588
-     *
589
-     * @param addPeopleEnabled {@code true} to enable the add people button; otherwise, {@code false}
590
-     */
591
-    public void setAddPeopleEnabled(boolean addPeopleEnabled) {
592
-        this.addPeopleEnabled = addPeopleEnabled;
593
-    }
594
-
595 498
     /**
596 499
      * Sets the default base {@code URL} used to join a conference when a
597 500
      * partial URL (e.g. a room name only) is specified to
@@ -605,18 +508,6 @@ public class JitsiMeetView extends FrameLayout {
605 508
         this.defaultURL = defaultURL;
606 509
     }
607 510
 
608
-    /**
609
-     * Sets whether the ability to add phone numbers to the call is enabled.
610
-     * Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to
611
-     * be effective.
612
-     *
613
-     * @param dialOutEnabled {@code true} to enable the ability to add
614
-     *                       phone numbers to the call; otherwise, {@code false}
615
-     */
616
-    public void setDialOutEnabled(boolean dialOutEnabled) {
617
-        this.dialOutEnabled = dialOutEnabled;
618
-    }
619
-
620 511
     /**
621 512
      * Sets a specific {@link JitsiMeetViewListener} on this
622 513
      * {@code JitsiMeetView}.

+ 0
- 4
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewAdapter.java Näytä tiedosto

@@ -46,8 +46,4 @@ public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
46 46
     @Override
47 47
     public void onLoadConfigError(Map<String, Object> data) {
48 48
     }
49
-
50
-    @Override
51
-    public void launchNativeInvite(InviteSearchController inviteSearchController) {
52
-    }
53 49
 }

+ 0
- 10
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetViewListener.java Näytä tiedosto

@@ -59,16 +59,6 @@ public interface JitsiMeetViewListener {
59 59
      */
60 60
     void onConferenceWillLeave(Map<String, Object> data);
61 61
 
62
-    /**
63
-     * Called when the add user button is tapped.
64
-     *
65
-     * @param inviteSearchController {@code InviteSearchController} scoped
66
-     * for this user invite flow. The {@code InviteSearchController} is used
67
-     * to start user queries and accepts an {@code InviteSearchControllerDelegate}
68
-     * for receiving user query responses.
69
-     */
70
-    void launchNativeInvite(InviteSearchController inviteSearchController);
71
-
72 62
     /**
73 63
      * Called when loading the main configuration file from the Jitsi Meet
74 64
      * deployment fails.

+ 43
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/ReactContextUtils.java Näytä tiedosto

@@ -0,0 +1,43 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.support.annotation.Nullable;
20
+
21
+import com.facebook.react.bridge.ReactContext;
22
+import com.facebook.react.modules.core.DeviceEventManagerModule;
23
+
24
+public class ReactContextUtils {
25
+    public static boolean emitEvent(
26
+            ReactContext reactContext,
27
+            String eventName,
28
+            @Nullable Object data) {
29
+        if (reactContext == null) {
30
+            // XXX If no ReactContext is specified, emit through the
31
+            // ReactContext of ReactInstanceManager. ReactInstanceManager
32
+            // cooperates with ReactContextUtils i.e. ReactInstanceManager will
33
+            // not invoke ReactContextUtils without a ReactContext.
34
+            return ReactInstanceManagerHolder.emitEvent(eventName, data);
35
+        }
36
+
37
+        reactContext
38
+            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
39
+            .emit(eventName, data);
40
+
41
+        return true;
42
+    }
43
+}

+ 125
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java Näytä tiedosto

@@ -0,0 +1,125 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.app.Application;
20
+import android.support.annotation.Nullable;
21
+
22
+import com.facebook.react.ReactInstanceManager;
23
+import com.facebook.react.bridge.NativeModule;
24
+import com.facebook.react.bridge.ReactContext;
25
+import com.facebook.react.bridge.ReactApplicationContext;
26
+import com.facebook.react.common.LifecycleState;
27
+
28
+import java.util.Arrays;
29
+import java.util.List;
30
+
31
+public class ReactInstanceManagerHolder {
32
+    /**
33
+     * React Native bridge. The instance manager allows embedding applications
34
+     * to create multiple root views off the same JavaScript bundle.
35
+     */
36
+    private static ReactInstanceManager reactInstanceManager;
37
+
38
+    private static List<NativeModule> createNativeModules(
39
+            ReactApplicationContext reactContext) {
40
+        return Arrays.<NativeModule>asList(
41
+            new AndroidSettingsModule(reactContext),
42
+            new AppInfoModule(reactContext),
43
+            new AudioModeModule(reactContext),
44
+            new ExternalAPIModule(reactContext),
45
+            new PictureInPictureModule(reactContext),
46
+            new ProximityModule(reactContext),
47
+            new WiFiStatsModule(reactContext),
48
+            new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
49
+            new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
50
+        );
51
+    }
52
+
53
+    /**
54
+     * Helper function to send an event to JavaScript.
55
+     *
56
+     * @param eventName {@code String} containing the event name.
57
+     * @param data {@code Object} optional ancillary data for the event.
58
+     */
59
+    public static boolean emitEvent(
60
+            String eventName,
61
+            @Nullable Object data) {
62
+        ReactInstanceManager reactInstanceManager
63
+            = ReactInstanceManagerHolder.getReactInstanceManager();
64
+        
65
+        if (reactInstanceManager != null) {
66
+            ReactContext reactContext
67
+                = reactInstanceManager.getCurrentReactContext();
68
+
69
+            return
70
+                reactContext != null
71
+                    && ReactContextUtils.emitEvent(
72
+                        reactContext,
73
+                        eventName,
74
+                        data);
75
+        }
76
+
77
+        return false;
78
+    }
79
+
80
+    static ReactInstanceManager getReactInstanceManager() {
81
+        return reactInstanceManager;
82
+    }
83
+
84
+    /**
85
+     * Internal method to initialize the React Native instance manager. We
86
+     * create a single instance in order to load the JavaScript bundle a single
87
+     * time. All {@code ReactRootView} instances will be tied to the one and
88
+     * only {@code ReactInstanceManager}.
89
+     *
90
+     * @param application {@code Application} instance which is running.
91
+     */
92
+    static void initReactInstanceManager(Application application) {
93
+        if (reactInstanceManager != null) {
94
+            return;
95
+        }
96
+
97
+        reactInstanceManager
98
+            = ReactInstanceManager.builder()
99
+                .setApplication(application)
100
+                .setBundleAssetName("index.android.bundle")
101
+                .setJSMainModulePath("index.android")
102
+                .addPackage(new com.calendarevents.CalendarEventsPackage())
103
+                .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
104
+                .addPackage(new com.facebook.react.shell.MainReactPackage())
105
+                .addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
106
+                .addPackage(new com.oblador.vectoricons.VectorIconsPackage())
107
+                .addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
108
+                .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
109
+                .addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
110
+                .addPackage(new com.rnimmersive.RNImmersivePackage())
111
+                .addPackage(new com.zmxv.RNSound.RNSoundPackage())
112
+                .addPackage(new ReactPackageAdapter() {
113
+                    @Override
114
+                    public List<NativeModule> createNativeModules(
115
+                            ReactApplicationContext reactContext) {
116
+                        return
117
+                            ReactInstanceManagerHolder.createNativeModules(
118
+                                reactContext);
119
+                    }
120
+                })
121
+                .setUseDeveloperSupport(BuildConfig.DEBUG)
122
+                .setInitialLifecycleState(LifecycleState.RESUMED)
123
+                .build();
124
+    }
125
+}

+ 204
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/invite/AddPeopleController.java Näytä tiedosto

@@ -0,0 +1,204 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.invite;
18
+
19
+import android.util.Log;
20
+
21
+import com.facebook.react.bridge.ReactApplicationContext;
22
+import com.facebook.react.bridge.ReadableArray;
23
+import com.facebook.react.bridge.ReadableMap;
24
+import com.facebook.react.bridge.WritableArray;
25
+import com.facebook.react.bridge.WritableNativeArray;
26
+import com.facebook.react.bridge.WritableNativeMap;
27
+
28
+import java.lang.ref.WeakReference;
29
+import java.util.ArrayList;
30
+import java.util.HashMap;
31
+import java.util.List;
32
+import java.util.Map;
33
+import java.util.UUID;
34
+
35
+/**
36
+ * Controller object used by native code to query and submit user selections for the user invitation flow.
37
+ */
38
+public class AddPeopleController {
39
+
40
+    /**
41
+     * The AddPeopleControllerListener for this controller, used to pass query
42
+     * results back to the native code that initiated the query.
43
+     */
44
+    private AddPeopleControllerListener listener;
45
+
46
+    /**
47
+     * Local cache of search query results.  Used to re-hydrate the list
48
+     * of selected items based on their ids passed to inviteById
49
+     * in order to pass the full item maps back to the JitsiMeetView during submission.
50
+     */
51
+    private final Map<String, ReadableMap> items = new HashMap<>();
52
+
53
+    private final WeakReference<InviteController> owner;
54
+
55
+    private final WeakReference<ReactApplicationContext> reactContext;
56
+    /**
57
+     * Randomly generated UUID, used for identification in the InviteModule
58
+     */
59
+    private final String uuid = UUID.randomUUID().toString();
60
+
61
+    public AddPeopleController(
62
+            InviteController owner,
63
+            ReactApplicationContext reactContext) {
64
+        this.owner = new WeakReference<>(owner);
65
+        this.reactContext = new WeakReference<>(reactContext);
66
+    }
67
+
68
+    /**
69
+     * Cancel the invitation flow and free memory allocated to the
70
+     * AddPeopleController. After calling this method, this object is invalid -
71
+     * a new AddPeopleController will be passed to the caller through
72
+     * beginAddPeople.
73
+     */
74
+    public void endAddPeople() {
75
+        InviteController owner = this.owner.get();
76
+
77
+        if (owner != null) {
78
+            owner.endAddPeople(this);
79
+        }
80
+    }
81
+
82
+    /**
83
+     *
84
+     * @return the AddPeopleControllerListener for this controller, used to pass
85
+     * query results back to the native code that initiated the query.
86
+     */
87
+    public AddPeopleControllerListener getListener() {
88
+        return listener;
89
+    }
90
+
91
+    final ReactApplicationContext getReactApplicationContext() {
92
+        return reactContext.get();
93
+    }
94
+
95
+    /**
96
+     *
97
+     * @return the unique identifier for this AddPeopleController
98
+     */
99
+    public String getUuid() {
100
+        return uuid;
101
+    }
102
+
103
+    /**
104
+     * Send invites to selected users based on their item ids
105
+     *
106
+     * @param ids
107
+     */
108
+    public void inviteById(List<String> ids) {
109
+        InviteController owner = this.owner.get();
110
+
111
+        if (owner != null) {
112
+            WritableArray invitees = new WritableNativeArray();
113
+    
114
+            for(int i = 0, size = ids.size(); i < size; i++) {
115
+                String id = ids.get(i);
116
+
117
+                if(items.containsKey(id)) {
118
+                    WritableNativeMap map = new WritableNativeMap();
119
+                    map.merge(items.get(ids));
120
+                    invitees.pushMap(map);
121
+                } else {
122
+                    // If the id doesn't exist in the map, we can't do anything,
123
+                    // so just skip it.
124
+                }
125
+            }
126
+    
127
+            owner.invite(this, invitees);
128
+        }
129
+    }
130
+
131
+    void inviteSettled(ReadableArray failedInvitees) {
132
+        AddPeopleControllerListener listener = getListener();
133
+
134
+        if (listener != null) {
135
+            ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
136
+
137
+            for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
138
+                jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
139
+            }
140
+
141
+            listener.inviteSettled(this, jFailedInvitees);
142
+        }
143
+    }
144
+
145
+    /**
146
+     * Start a search for entities to invite with the given query. Results will
147
+     * be returned through the associated AddPeopleControllerListener's
148
+     * onReceiveResults method.
149
+     *
150
+     * @param query
151
+     */
152
+    public void performQuery(String query) {
153
+        InviteController owner = this.owner.get();
154
+
155
+        if (owner != null) {
156
+            owner.performQuery(this, query);
157
+        }
158
+    }
159
+
160
+    /**
161
+     * Caches results received by the search into a local map for use
162
+     * later when the items are submitted.  Submission requires the full
163
+     * map of information, but only the IDs are returned back to the delegate.
164
+     * Using this map means we don't have to send the whole map back to the delegate.
165
+     *
166
+     * @param results
167
+     * @param query
168
+     */
169
+    void receivedResultsForQuery(ReadableArray results, String query) {
170
+        AddPeopleControllerListener listener = getListener();
171
+
172
+        if (listener != null) {
173
+            List<Map<String, Object>> jvmResults = new ArrayList<>();
174
+
175
+            // cache results for use in submission later
176
+            // convert to jvm array
177
+            for(int i = 0; i < results.size(); i++) {
178
+                ReadableMap map = results.getMap(i);
179
+
180
+                if(map.hasKey("id")) {
181
+                    items.put(map.getString("id"), map);
182
+                } else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
183
+                    items.put(map.getString("number"), map);
184
+                } else {
185
+                    Log.w("AddPeopleController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
186
+                }
187
+
188
+                jvmResults.add(map.toHashMap());
189
+            }
190
+
191
+            listener.onReceiveResults(this, jvmResults, query);
192
+        }
193
+    }
194
+
195
+    /**
196
+     * Sets the AddPeopleControllerListener for this controller, used to pass
197
+     * query results back to the native code that initiated the query.
198
+     *
199
+     * @param listener
200
+     */
201
+    public void setListener(AddPeopleControllerListener listener) {
202
+        this.listener = listener;
203
+    }
204
+}

+ 49
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/invite/AddPeopleControllerListener.java Näytä tiedosto

@@ -0,0 +1,49 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.invite;
18
+
19
+import java.util.List;
20
+import java.util.Map;
21
+
22
+public interface AddPeopleControllerListener {
23
+    /**
24
+     * Called when results are received for a query called through AddPeopleController.query()
25
+     *
26
+     * @param addPeopleController
27
+     * @param results a List of Map<String, Object> objects that represent items returned by the query.
28
+     *                The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
29
+     *                "user" types have properties at "id", "name", and "avatar"
30
+     *                "videosipgw" types have properties at "id" and "name"
31
+     *                "phone" types have properties at "number", "title", "and "subtitle"
32
+     * @param query the query that generated the given results
33
+     */
34
+    void onReceiveResults(AddPeopleController addPeopleController, List<Map<String, Object>> results, String query);
35
+
36
+    /**
37
+     * Called when the call to {@link AddPeopleController#inviteById(List)} completes, but the
38
+     * invitation fails for one or more of the selected items.
39
+     *
40
+     * @param addPeopleController the active {@link AddPeopleController} for this invite flow.  This object
41
+     *                         should be cleaned up by calling {@link AddPeopleController#endAddPeople()} if
42
+     *                         the user exits the invite flow.  Otherwise, it can stay active if the user
43
+     *                         will attempt to invite
44
+     * @param failedInvitees a {@code List} of {@code Map<String, Object>} dictionaries that represent the
45
+     *                          invitations that failed.  The data type of the objects is identical to the results
46
+     *                          returned in onReceiveResuls.
47
+     */
48
+    void inviteSettled(AddPeopleController addPeopleController, List<Map<String, Object>> failedInvitees);
49
+}

+ 265
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteController.java Näytä tiedosto

@@ -0,0 +1,265 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.invite;
18
+
19
+import com.facebook.react.bridge.Arguments;
20
+import com.facebook.react.bridge.ReactApplicationContext;
21
+import com.facebook.react.bridge.ReactContext;
22
+import com.facebook.react.bridge.ReadableArray;
23
+import com.facebook.react.bridge.WritableArray;
24
+import com.facebook.react.bridge.WritableNativeMap;
25
+
26
+import org.jitsi.meet.sdk.ReactContextUtils;
27
+
28
+import java.util.Collections;
29
+import java.util.HashMap;
30
+import java.util.List;
31
+import java.util.Map;
32
+import java.util.UUID;
33
+import java.util.concurrent.Callable;
34
+import java.util.concurrent.Future;
35
+import java.util.concurrent.FutureTask;
36
+
37
+/**
38
+ * Represents the entry point into the invite feature of Jitsi Meet and is the
39
+ * Java counterpart of the JavaScript {@code InviteButton}.
40
+ */
41
+public class InviteController {
42
+    private AddPeopleController addPeopleController;
43
+
44
+    /**
45
+     * Whether adding/inviting people by name (as opposed to phone number) is
46
+     * enabled.
47
+     */
48
+    private Boolean addPeopleEnabled;
49
+
50
+    /**
51
+     * Whether adding/inviting people by phone number (as opposed to name) is
52
+     * enabled.
53
+     */
54
+    private Boolean dialOutEnabled;
55
+
56
+    private final String externalAPIScope;
57
+
58
+    private InviteControllerListener listener;
59
+
60
+    public InviteController(String externalAPIScope) {
61
+        this.externalAPIScope = externalAPIScope;
62
+    }
63
+
64
+    public InviteControllerListener getListener() {
65
+        return listener;
66
+    }
67
+
68
+    void beginAddPeople(ReactApplicationContext reactContext) {
69
+        InviteControllerListener listener = getListener();
70
+
71
+        if (listener != null) {
72
+            // XXX For the sake of simplicity and in order to reduce the risk of
73
+            // memory leaks, allow a single AddPeopleController at a time.
74
+            AddPeopleController addPeopleController = this.addPeopleController;
75
+
76
+            if (addPeopleController != null) {
77
+                return;
78
+            }
79
+
80
+            // Initialize a new AddPeopleController to represent the click/tap
81
+            // on the InviteButton and notify the InviteControllerListener
82
+            // about the event.
83
+            addPeopleController = new AddPeopleController(this, reactContext);
84
+
85
+            boolean success = false;
86
+
87
+            this.addPeopleController = addPeopleController;
88
+            try {
89
+                listener.beginAddPeople(addPeopleController);
90
+                success = true;
91
+            } finally {
92
+                if (!success) {
93
+                    endAddPeople(addPeopleController);
94
+                }
95
+            }
96
+        }
97
+    }
98
+
99
+    void endAddPeople(AddPeopleController addPeopleController) {
100
+        if (this.addPeopleController == addPeopleController) {
101
+            this.addPeopleController = null;
102
+        }
103
+    }
104
+
105
+    /**
106
+     * Sends JavaScript event to submit invitations to the given item ids
107
+     *
108
+     * @param invitees a WritableArray of WritableNativeMaps representing
109
+     * selected items. Each map representing a selected item should match the
110
+     * data passed back in the return from a query.
111
+     */
112
+    boolean invite(
113
+            AddPeopleController addPeopleController,
114
+            WritableArray invitees) {
115
+        return
116
+            invite(
117
+                addPeopleController.getUuid(),
118
+                addPeopleController.getReactApplicationContext(),
119
+                invitees);
120
+    }
121
+
122
+    public Future<List<Map<String, Object>>> invite(
123
+            final List<Map<String, Object>> invitees) {
124
+        final boolean inviteBegan
125
+            = invite(
126
+                UUID.randomUUID().toString(),
127
+                /* reactContext */ null,
128
+                Arguments.makeNativeArray(invitees));
129
+        FutureTask futureTask
130
+            = new FutureTask(new Callable() {
131
+                @Override
132
+                public List<Map<String, Object>> call() {
133
+                    if (inviteBegan) {
134
+                        // TODO Complete the returned Future when the invite
135
+                        // settles.
136
+                        return Collections.emptyList();
137
+                    } else {
138
+                        // The invite failed to even begin so report that all
139
+                        // invitees failed.
140
+                        return invitees;
141
+                    }
142
+                }
143
+            });
144
+
145
+        // If the invite failed to even begin, complete the returned Future
146
+        // already and the Future implementation will report that all invitees
147
+        // failed.
148
+        if (!inviteBegan) {
149
+            futureTask.run();
150
+        }
151
+
152
+        return futureTask;
153
+    }
154
+
155
+    private boolean invite(
156
+            String addPeopleControllerScope,
157
+            ReactContext reactContext,
158
+            WritableArray invitees) {
159
+        WritableNativeMap data = new WritableNativeMap();
160
+
161
+        data.putString("addPeopleControllerScope", addPeopleControllerScope);
162
+        data.putString("externalAPIScope", externalAPIScope);
163
+        data.putArray("invitees", invitees);
164
+
165
+        return
166
+            ReactContextUtils.emitEvent(
167
+                reactContext,
168
+                "org.jitsi.meet:features/invite#invite",
169
+                data);
170
+    }
171
+
172
+    void inviteSettled(
173
+            String addPeopleControllerScope,
174
+            ReadableArray failedInvitees) {
175
+        AddPeopleController addPeopleController = this.addPeopleController;
176
+
177
+        if (addPeopleController != null
178
+                && addPeopleController.getUuid().equals(
179
+                    addPeopleControllerScope)) {
180
+            try {
181
+                addPeopleController.inviteSettled(failedInvitees);
182
+            } finally {
183
+                if (failedInvitees.size() == 0) {
184
+                    endAddPeople(addPeopleController);
185
+                }
186
+            }
187
+        }
188
+    }
189
+
190
+    public boolean isAddPeopleEnabled() {
191
+        Boolean b = this.addPeopleEnabled;
192
+
193
+        return
194
+            (b == null || b.booleanValue()) ? (getListener() != null) : false;
195
+    }
196
+
197
+    public boolean isDialOutEnabled() {
198
+        Boolean b = this.dialOutEnabled;
199
+
200
+        return
201
+            (b == null || b.booleanValue()) ? (getListener() != null) : false;
202
+    }
203
+
204
+    /**
205
+     * Starts a query for users to invite to the conference.  Results will be
206
+     * returned through the {@link AddPeopleControllerListener#onReceiveResults(AddPeopleController, List, String)}
207
+     * method.
208
+     *
209
+     * @param query {@code String} to use for the query
210
+     */
211
+    void performQuery(AddPeopleController addPeopleController, String query) {
212
+        WritableNativeMap params = new WritableNativeMap();
213
+
214
+        params.putString("externalAPIScope", externalAPIScope);
215
+        params.putString("addPeopleControllerScope", addPeopleController.getUuid());
216
+        params.putString("query", query);
217
+        ReactContextUtils.emitEvent(
218
+            addPeopleController.getReactApplicationContext(),
219
+            "org.jitsi.meet:features/invite#performQuery",
220
+            params);
221
+    }
222
+
223
+    void receivedResultsForQuery(
224
+            String addPeopleControllerScope,
225
+            String query,
226
+            ReadableArray results) {
227
+        AddPeopleController addPeopleController = this.addPeopleController;
228
+
229
+        if (addPeopleController != null
230
+                && addPeopleController.getUuid().equals(
231
+                    addPeopleControllerScope)) {
232
+            addPeopleController.receivedResultsForQuery(results, query);
233
+        }
234
+    }
235
+
236
+    /**
237
+     * Sets whether the ability to add users to the call is enabled. If this is
238
+     * enabled, an add user button will appear on the {@link JitsiMeetView}. If
239
+     * enabled, and the user taps the add user button,
240
+     * {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
241
+     * will be called.
242
+     *
243
+     * @param addPeopleEnabled {@code true} to enable the add people button;
244
+     * otherwise, {@code false}
245
+     */
246
+    public void setAddPeopleEnabled(boolean addPeopleEnabled) {
247
+        this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
248
+    }
249
+
250
+    /**
251
+     * Sets whether the ability to add phone numbers to the call is enabled.
252
+     * Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
253
+     * effective.
254
+     *
255
+     * @param dialOutEnabled {@code true} to enable the ability to add phone
256
+     * numbers to the call; otherwise, {@code false}
257
+     */
258
+    public void setDialOutEnabled(boolean dialOutEnabled) {
259
+        this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
260
+    }
261
+
262
+    public void setListener(InviteControllerListener listener) {
263
+        this.listener = listener;
264
+    }
265
+}

+ 29
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteControllerListener.java Näytä tiedosto

@@ -0,0 +1,29 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.invite;
18
+
19
+public interface InviteControllerListener {
20
+    /**
21
+     * Called when the add user button is tapped.
22
+     *
23
+     * @param addPeopleController {@code AddPeopleController} scoped
24
+     * for this user invite flow. The {@code AddPeopleController} is used
25
+     * to start user queries and accepts an {@code AddPeopleControllerListener}
26
+     * for receiving user query responses.
27
+     */
28
+    void beginAddPeople(AddPeopleController addPeopleController);
29
+}

+ 119
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java Näytä tiedosto

@@ -0,0 +1,119 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk.invite;
18
+
19
+import android.util.Log;
20
+
21
+import com.facebook.react.bridge.ReactApplicationContext;
22
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
23
+import com.facebook.react.bridge.ReactMethod;
24
+import com.facebook.react.bridge.ReadableArray;
25
+
26
+import org.jitsi.meet.sdk.JitsiMeetView;
27
+
28
+/**
29
+ * Implements the react-native module of the feature invite.
30
+ */
31
+public class InviteModule extends ReactContextBaseJavaModule {
32
+    public InviteModule(ReactApplicationContext reactContext) {
33
+        super(reactContext);
34
+    }
35
+
36
+    /**
37
+     * Signals that a click/tap has been performed on {@code InviteButton} and
38
+     * that the execution flow for adding/inviting people to the current
39
+     * conference/meeting is to begin
40
+     *
41
+     * @param externalAPIScope the unique identifier of the
42
+     * {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
43
+     */
44
+    @ReactMethod
45
+    public void beginAddPeople(String externalAPIScope) {
46
+        InviteController inviteController
47
+            = findInviteControllerByExternalAPIScope(externalAPIScope);
48
+
49
+        if (inviteController != null) {
50
+            inviteController.beginAddPeople(getReactApplicationContext());
51
+        }
52
+    }
53
+
54
+    private InviteController findInviteControllerByExternalAPIScope(
55
+            String externalAPIScope) {
56
+        JitsiMeetView view
57
+            = JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
58
+
59
+        return view == null ? null : view.getInviteController();
60
+    }
61
+
62
+    @Override
63
+    public String getName() {
64
+        return "Invite";
65
+    }
66
+
67
+    /**
68
+     * Callback for invitation failures
69
+     *
70
+     * @param failedInvitees the items for which the invitation failed
71
+     * @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
72
+     */
73
+    @ReactMethod
74
+    public void inviteSettled(
75
+            String externalAPIScope,
76
+            String addPeopleControllerScope,
77
+            ReadableArray failedInvitees) {
78
+        InviteController inviteController
79
+            = findInviteControllerByExternalAPIScope(externalAPIScope);
80
+
81
+        if (inviteController == null) {
82
+            Log.w(
83
+                "InviteModule",
84
+                "Invite settled, but failed to find active controller to notify");
85
+        } else {
86
+            inviteController.inviteSettled(
87
+                addPeopleControllerScope,
88
+                failedInvitees);
89
+        }
90
+    }
91
+
92
+    /**
93
+     * Callback for results received from the JavaScript invite search call
94
+     *
95
+     * @param results the results in a ReadableArray of ReadableMap objects
96
+     * @param query the query associated with the search
97
+     * @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
98
+     */
99
+    @ReactMethod
100
+    public void receivedResults(
101
+            String externalAPIScope,
102
+            String addPeopleControllerScope,
103
+            String query,
104
+            ReadableArray results) {
105
+        InviteController inviteController
106
+            = findInviteControllerByExternalAPIScope(externalAPIScope);
107
+
108
+        if (inviteController == null) {
109
+            Log.w(
110
+                "InviteModule",
111
+                "Received results, but failed to find active controller to send results back");
112
+        } else {
113
+            inviteController.receivedResultsForQuery(
114
+                addPeopleControllerScope,
115
+                query,
116
+                results);
117
+        }
118
+    }
119
+}

+ 1
- 1
ios/app/src/ViewController.h Näytä tiedosto

@@ -18,6 +18,6 @@
18 18
 
19 19
 #import <JitsiMeet/JitsiMeet.h>
20 20
 
21
-@interface ViewController : UIViewController<JitsiMeetViewDelegate>
21
+@interface ViewController : UIViewController<JitsiMeetViewDelegate, InviteControllerDelegate>
22 22
 
23 23
 @end

+ 25
- 1
ios/app/src/ViewController.m Näytä tiedosto

@@ -27,12 +27,25 @@
27 27
 
28 28
     JitsiMeetView *view = (JitsiMeetView *) self.view;
29 29
 
30
+#ifdef DEBUG
31
+
30 32
     view.delegate = self;
33
+
34
+    // inviteController
35
+    InviteController *inviteController = view.inviteController;
36
+
37
+    //inviteController.addPeopleEnabled = TRUE;
38
+    //inviteController.dialOutEnabled = TRUE;
39
+    inviteController.delegate = self;
40
+
41
+#endif // #ifdef DEBUG
42
+
31 43
     // As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do want
32 44
     // the Welcome page to be enabled. It defaults to disabled in the SDK at the
33 45
     // time of this writing but it is clearer to be explicit about what we want
34 46
     // anyway.
35 47
     view.welcomePageEnabled = YES;
48
+
36 49
     [view loadURL:nil];
37 50
 }
38 51
 
@@ -68,6 +81,17 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
68 81
     _onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data);
69 82
 }
70 83
 
71
-#endif
84
+- (void)beginAddPeople:(AddPeopleController *)addPeopleController {
85
+    NSLog(
86
+        @"[%s:%d] InviteControllerDelegate %s",
87
+        __FILE__, __LINE__, __FUNCTION__);
88
+
89
+    // XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
90
+    // is going to be memory-leaked in the associated InviteController and no
91
+    // subsequent InviteButton clicks/taps will be delivered.
92
+    [addPeopleController endAddPeople];
93
+}
94
+
95
+#endif // #ifdef DEBUG
72 96
 
73 97
 @end

+ 42
- 8
ios/sdk/sdk.xcodeproj/project.pbxproj Näytä tiedosto

@@ -29,8 +29,13 @@
29 29
 		0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
30 30
 		75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
31 31
 		75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
32
-		412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 412BF89C206AA66F0053B9E5 /* InviteSearch.m */; };
33
-		412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 412BF89E206AA82F0053B9E5 /* InviteSearch.h */; settings = {ATTRIBUTES = (Public, ); }; };
32
+		B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
33
+		B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
34
+		B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
35
+		B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85320981A74000DEF7A /* AddPeopleController.h */; settings = {ATTRIBUTES = (Public, ); }; };
36
+		B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85420981A74000DEF7A /* InviteControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
37
+		B386B85C20981A75000DEF7A /* InviteController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85520981A75000DEF7A /* InviteController.h */; settings = {ATTRIBUTES = (Public, ); }; };
38
+		B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
34 39
 		C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
35 40
 		C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
36 41
 		C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
@@ -44,6 +49,9 @@
44 49
 		0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
45 50
 		0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
46 51
 		0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
52
+		0B6F414F20987DE600FF6789 /* Invite+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Invite+Private.h"; sourceTree = "<group>"; };
53
+		0B6F41502098840600FF6789 /* InviteController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InviteController+Private.h"; sourceTree = "<group>"; };
54
+		0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AddPeopleController+Private.h"; sourceTree = "<group>"; };
47 55
 		0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
48 56
 		0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
49 57
 		0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
@@ -64,10 +72,15 @@
64 72
 		0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
65 73
 		75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
66 74
 		75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
67
-		412BF89C206AA66F0053B9E5 /* InviteSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InviteSearch.m; sourceTree = "<group>"; };
68
-		412BF89E206AA82F0053B9E5 /* InviteSearch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InviteSearch.h; sourceTree = "<group>"; };
69 75
 		98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
70 76
 		9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
77
+		B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
78
+		B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
79
+		B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
80
+		B386B85320981A74000DEF7A /* AddPeopleController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleController.h; sourceTree = "<group>"; };
81
+		B386B85420981A74000DEF7A /* InviteControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteControllerDelegate.h; sourceTree = "<group>"; };
82
+		B386B85520981A75000DEF7A /* InviteController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteController.h; sourceTree = "<group>"; };
83
+		B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
71 84
 		C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
72 85
 		C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
73 86
 		C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
@@ -125,12 +138,11 @@
125 138
 		0BD906E71EC0C00300C8C18E /* src */ = {
126 139
 			isa = PBXGroup;
127 140
 			children = (
141
+				B386B84F20981A11000DEF7A /* invite */,
128 142
 				C6A3426B204F127900E062DD /* picture-in-picture */,
129 143
 				0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
130 144
 				0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
131 145
 				0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
132
-				412BF89E206AA82F0053B9E5 /* InviteSearch.h */,
133
-				412BF89C206AA66F0053B9E5 /* InviteSearch.m */,
134 146
 				0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
135 147
 				0BD906E91EC0C00300C8C18E /* Info.plist */,
136 148
 				0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
@@ -160,6 +172,23 @@
160 172
 			name = Frameworks;
161 173
 			sourceTree = "<group>";
162 174
 		};
175
+		B386B84F20981A11000DEF7A /* invite */ = {
176
+			isa = PBXGroup;
177
+			children = (
178
+				B386B85320981A74000DEF7A /* AddPeopleController.h */,
179
+				B386B85120981A74000DEF7A /* AddPeopleController.m */,
180
+				0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */,
181
+				B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */,
182
+				B386B85620981A75000DEF7A /* Invite.m */,
183
+				0B6F414F20987DE600FF6789 /* Invite+Private.h */,
184
+				B386B85520981A75000DEF7A /* InviteController.h */,
185
+				B386B85020981A74000DEF7A /* InviteController.m */,
186
+				0B6F41502098840600FF6789 /* InviteController+Private.h */,
187
+				B386B85420981A74000DEF7A /* InviteControllerDelegate.h */,
188
+			);
189
+			path = invite;
190
+			sourceTree = "<group>";
191
+		};
163 192
 		C5E72ADFC30ED96F9B35F076 /* Pods */ = {
164 193
 			isa = PBXGroup;
165 194
 			children = (
@@ -185,11 +214,14 @@
185 214
 			isa = PBXHeadersBuildPhase;
186 215
 			buildActionMask = 2147483647;
187 216
 			files = (
217
+				B386B85C20981A75000DEF7A /* InviteController.h in Headers */,
218
+				B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */,
188 219
 				C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
189
-				412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */,
190 220
 				0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
221
+				B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */,
191 222
 				0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
192 223
 				0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
224
+				B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */,
193 225
 				0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
194 226
 			);
195 227
 			runOnlyForDeploymentPostprocessing = 0;
@@ -350,11 +382,13 @@
350 382
 				0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
351 383
 				0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
352 384
 				0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
385
+				B386B85D20981A75000DEF7A /* Invite.m in Sources */,
353 386
 				0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
354 387
 				C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
388
+				B386B85720981A75000DEF7A /* InviteController.m in Sources */,
389
+				B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */,
355 390
 				0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
356 391
 				0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
357
-				412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */,
358 392
 				0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
359 393
 				C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
360 394
 				0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,

+ 0
- 49
ios/sdk/src/InviteSearch.h Näytä tiedosto

@@ -1,49 +0,0 @@
1
-/*
2
- * Copyright @ 2018-present Atlassian Pty Ltd
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- *     http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
-@class InviteSearchController;
18
-
19
-@protocol InviteSearchControllerDelegate
20
-
21
-/**
22
- * Called when an InviteSearchController has results for a query that was previously provided.
23
- */
24
-- (void)inviteSearchController:(InviteSearchController * _Nonnull)controller
25
-             didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
26
-                      forQuery:(NSString * _Nonnull)query;
27
-
28
-/**
29
- * Called when all invitations were sent successfully.
30
- */
31
-- (void)inviteDidSucceedForSearchController:(InviteSearchController * _Nonnull)searchController;
32
-
33
-/**
34
- * Called when one or more invitations fails to send successfully.
35
- */
36
-- (void)inviteDidFailForItems:(NSArray<NSDictionary *> * _Nonnull)items
37
-         fromSearchController:(InviteSearchController * _Nonnull)searchController;
38
-
39
-@end
40
-
41
-@interface InviteSearchController: NSObject
42
-
43
-@property (nonatomic, nullable, weak) id<InviteSearchControllerDelegate> delegate;
44
-
45
-- (void)performQuery:(NSString * _Nonnull)query;
46
-- (void)cancelSearch;
47
-- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids;
48
-
49
-@end

+ 0
- 215
ios/sdk/src/InviteSearch.m Näytä tiedosto

@@ -1,215 +0,0 @@
1
-/*
2
- * Copyright @ 2018-present Atlassian Pty Ltd
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- *     http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
-#import <React/RCTBridge.h>
18
-#import <React/RCTEventEmitter.h>
19
-#import <React/RCTUtils.h>
20
-
21
-#import "JitsiMeetView+Private.h"
22
-
23
-#import "InviteSearch.h"
24
-
25
-// The events emitted/supported by InviteSearch:
26
-static NSString * const InviteSearchPerformQueryAction = @"performQueryAction";
27
-static NSString * const InviteSearchPerformSubmitInviteAction = @"performSubmitInviteAction";
28
-
29
-
30
-@interface InviteSearch : RCTEventEmitter
31
-
32
-@end
33
-
34
-
35
-@interface InviteSearchController ()
36
-
37
-@property (nonatomic, readonly) NSString* _Nonnull identifier;
38
-@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
39
-@property (nonatomic, nullable, weak) InviteSearch* module;
40
-
41
-- (instancetype)initWithSearchModule:(InviteSearch *)module;
42
-
43
-- (void)didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
44
-                 forQuery:(NSString * _Nonnull)query;
45
-
46
-- (void)inviteDidSucceed;
47
-
48
-- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items;
49
-
50
-@end
51
-
52
-
53
-@implementation InviteSearch
54
-
55
-static NSMutableDictionary* searchControllers;
56
-
57
-RCT_EXTERN void RCTRegisterModule(Class);
58
-
59
-+ (void)load {
60
-    RCTRegisterModule(self);
61
-
62
-    searchControllers = [[NSMutableDictionary alloc] init];
63
-}
64
-
65
-+ (NSString *)moduleName {
66
-    return @"InviteSearch";
67
-}
68
-
69
-- (NSArray<NSString *> *)supportedEvents {
70
-    return @[
71
-        InviteSearchPerformQueryAction,
72
-        InviteSearchPerformSubmitInviteAction
73
-    ];
74
-}
75
-
76
-/**
77
- * Calls the corresponding JitsiMeetView's delegate to request that the native
78
- * invite search be presented.
79
- *
80
- * @param scope
81
- */
82
-RCT_EXPORT_METHOD(launchNativeInvite:(NSString *)scope) {
83
-    // The JavaScript App needs to provide uniquely identifying information to
84
-    // the native module so that the latter may match the former to the native
85
-    // JitsiMeetView which hosts it.
86
-    JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
87
-
88
-    if (!view) {
89
-        return;
90
-    }
91
-
92
-    id<JitsiMeetViewDelegate> delegate = view.delegate;
93
-
94
-    if (!delegate) {
95
-        return;
96
-    }
97
-
98
-    if ([delegate respondsToSelector:@selector(launchNativeInviteForSearchController:)]) {
99
-        InviteSearchController* searchController = [searchControllers objectForKey:scope];
100
-        if (!searchController) {
101
-            searchController = [self makeInviteSearchController];
102
-        }
103
-
104
-        [delegate launchNativeInviteForSearchController:searchController];
105
-    }
106
-}
107
-
108
-RCT_EXPORT_METHOD(inviteSucceeded:(NSString *)inviteScope) {
109
-    InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
110
-
111
-    [searchController inviteDidSucceed];
112
-
113
-    [searchControllers removeObjectForKey:inviteScope];
114
-}
115
-
116
-RCT_EXPORT_METHOD(inviteFailedForItems:(NSArray<NSDictionary *> *)items inviteScope:(NSString *)inviteScope) {
117
-    InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
118
-
119
-    [searchController inviteDidFailForItems:items];
120
-}
121
-
122
-RCT_EXPORT_METHOD(receivedResults:(NSArray *)results forQuery:(NSString *)query inviteScope:(NSString *)inviteScope) {
123
-
124
-    InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
125
-
126
-    [searchController didReceiveResults:results forQuery:query];
127
-}
128
-
129
-- (InviteSearchController *)makeInviteSearchController {
130
-    InviteSearchController* searchController = [[InviteSearchController alloc] initWithSearchModule:self];
131
-
132
-    [searchControllers setObject:searchController forKey:searchController.identifier];
133
-
134
-    return searchController;
135
-}
136
-
137
-- (void)performQuery:(NSString * _Nonnull)query inviteScope:(NSString * _Nonnull)inviteScope  {
138
-    [self sendEventWithName:InviteSearchPerformQueryAction body:@{ @"query": query, @"inviteScope": inviteScope }];
139
-}
140
-
141
-- (void)cancelSearchForInviteScope:(NSString * _Nonnull)inviteScope {
142
-    [searchControllers removeObjectForKey:inviteScope];
143
-}
144
-
145
-- (void)submitSelectedItems:(NSArray<NSDictionary *> * _Nonnull)items inviteScope:(NSString * _Nonnull)inviteScope {
146
-    [self sendEventWithName:InviteSearchPerformSubmitInviteAction body:@{ @"selectedItems": items, @"inviteScope": inviteScope }];
147
-}
148
-
149
-@end
150
-
151
-
152
-@implementation InviteSearchController
153
-
154
-- (instancetype)initWithSearchModule:(InviteSearch *)module {
155
-    self = [super init];
156
-    if (self) {
157
-        _identifier = [[NSUUID UUID] UUIDString];
158
-
159
-        self.items = [[NSMutableDictionary alloc] init];
160
-        self.module = module;
161
-    }
162
-    return self;
163
-}
164
-
165
-- (void)performQuery:(NSString *)query {
166
-    [self.module performQuery:query inviteScope:self.identifier];
167
-}
168
-
169
-- (void)cancelSearch {
170
-    [self.module cancelSearchForInviteScope:self.identifier];
171
-}
172
-
173
-- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids {
174
-    NSMutableArray* items = [[NSMutableArray alloc] init];
175
-
176
-    for (NSString* itemId in ids) {
177
-        id item = [self.items objectForKey:itemId];
178
-
179
-        if (item) {
180
-            [items addObject:item];
181
-        }
182
-    }
183
-
184
-    [self.module submitSelectedItems:items inviteScope:self.identifier];
185
-}
186
-
187
-- (void)didReceiveResults:(NSArray<NSDictionary *> *)results forQuery:(NSString *)query {
188
-    for (NSDictionary* item in results) {
189
-        NSString* itemId = item[@"id"];
190
-        NSString* itemType = item[@"type"];
191
-        if (itemId) {
192
-            [self.items setObject:item forKey:itemId];
193
-        } else if (itemType != nil && [itemType isEqualToString: @"phone"]) {
194
-            NSString* number = item[@"number"];
195
-            if (number) {
196
-                [self.items setObject:item forKey:number];
197
-            }
198
-        }
199
-    }
200
-
201
-    [self.delegate inviteSearchController:self didReceiveResults:results forQuery:query];
202
-}
203
-
204
-- (void)inviteDidSucceed {
205
-    [self.delegate inviteDidSucceedForSearchController:self];
206
-}
207
-
208
-- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items {
209
-    if (!items) {
210
-        items = @[];
211
-    }
212
-    [self.delegate inviteDidFailForItems:items fromSearchController:self];
213
-}
214
-
215
-@end

+ 7
- 1
ios/sdk/src/JitsiMeet.h Näytä tiedosto

@@ -14,6 +14,12 @@
14 14
  * limitations under the License.
15 15
  */
16 16
 
17
+// JitsiMeetView
17 18
 #import <JitsiMeet/JitsiMeetView.h>
18 19
 #import <JitsiMeet/JitsiMeetViewDelegate.h>
19
-#import <JitsiMeet/InviteSearch.h>
20
+
21
+// invite/
22
+#import <JitsiMeet/AddPeopleController.h>
23
+#import <JitsiMeet/AddPeopleControllerDelegate.h>
24
+#import <JitsiMeet/InviteController.h>
25
+#import <JitsiMeet/InviteControllerDelegate.h>

+ 2
- 3
ios/sdk/src/JitsiMeetView.h Näytä tiedosto

@@ -17,17 +17,16 @@
17 17
 #import <Foundation/Foundation.h>
18 18
 #import <UIKit/UIKit.h>
19 19
 
20
+#import "InviteController.h"
20 21
 #import "JitsiMeetViewDelegate.h"
21 22
 
22 23
 @interface JitsiMeetView : UIView
23 24
 
24
-@property (nonatomic) BOOL addPeopleEnabled;
25
-
26 25
 @property (copy, nonatomic, nullable) NSURL *defaultURL;
27 26
 
28 27
 @property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
29 28
 
30
-@property (nonatomic) BOOL dialOutEnabled;
29
+@property (nonatomic, readonly) InviteController *inviteController;
31 30
 
32 31
 @property (nonatomic) BOOL pictureInPictureEnabled;
33 32
 

+ 12
- 6
ios/sdk/src/JitsiMeetView.m Näytä tiedosto

@@ -23,6 +23,8 @@
23 23
 #import <React/RCTLinkingManager.h>
24 24
 #import <React/RCTRootView.h>
25 25
 
26
+#import "Invite+Private.h"
27
+#import "InviteController+Private.h"
26 28
 #import "JitsiMeetView+Private.h"
27 29
 #import "RCTBridgeWrapper.h"
28 30
 
@@ -268,12 +270,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
268 270
         props[@"defaultURL"] = [self.defaultURL absoluteString];
269 271
     }
270 272
 
271
-    props[@"addPeopleEnabled"] = @(self.addPeopleEnabled);
272
-    props[@"dialOutEnabled"] = @(self.dialOutEnabled);
273 273
     props[@"externalAPIScope"] = externalAPIScope;
274 274
     props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
275 275
     props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
276 276
 
277
+    props[@"addPeopleEnabled"] = @(_inviteController.addPeopleEnabled);
278
+    props[@"dialOutEnabled"] = @(_inviteController.dialOutEnabled);
279
+
277 280
     // XXX If urlObject is nil, then it must appear as undefined in the
278 281
     // JavaScript source code so that we check the launchOptions there.
279 282
     if (urlObject) {
@@ -405,10 +408,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
405 408
     });
406 409
 
407 410
     // Hook this JitsiMeetView into ExternalAPI.
408
-    if (!externalAPIScope) {
409
-        externalAPIScope = [NSUUID UUID].UUIDString;
410
-        [views setObject:self forKey:externalAPIScope];
411
-    }
411
+    externalAPIScope = [NSUUID UUID].UUIDString;
412
+    [views setObject:self forKey:externalAPIScope];
413
+
414
+    Invite *inviteModule = [bridgeWrapper.bridge moduleForName:@"Invite"];
415
+    _inviteController
416
+        = [[InviteController alloc] initWithExternalAPIScope:externalAPIScope
417
+                                                   andInviteModule:inviteModule];
412 418
 
413 419
     // Set a background color which is in accord with the JavaScript and Android
414 420
     // parts of the application and causes less perceived visual flicker than

+ 0
- 11
ios/sdk/src/JitsiMeetViewDelegate.h Näytä tiedosto

@@ -14,8 +14,6 @@
14 14
  * limitations under the License.
15 15
  */
16 16
 
17
-@class InviteSearchController;
18
-
19 17
 @protocol JitsiMeetViewDelegate <NSObject>
20 18
 
21 19
 @optional
@@ -57,15 +55,6 @@
57 55
  */
58 56
 - (void)conferenceWillLeave:(NSDictionary *)data;
59 57
 
60
-
61
-/**
62
- * Called when the invite button in the conference is tapped.
63
- *
64
- * The search controller provided can be used to query user search within the
65
- * conference.
66
- */
67
-- (void)launchNativeInviteForSearchController:(InviteSearchController *)searchController;
68
-
69 58
 /**
70 59
  * Called when entering Picture-in-Picture is requested by the user. The app
71 60
  * should now activate its Picture-in-Picture implementation (and resize the

+ 33
- 0
ios/sdk/src/invite/AddPeopleController+Private.h Näytä tiedosto

@@ -0,0 +1,33 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "AddPeopleController.h"
18
+#import "InviteController.h"
19
+
20
+@interface AddPeopleController ()
21
+
22
+@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
23
+@property (nonatomic, weak) InviteController *owner;
24
+@property (nonatomic, readonly) NSString* _Nonnull uuid;
25
+
26
+- (instancetype)initWithOwner:(InviteController *)owner;
27
+
28
+- (void)inviteSettled:(NSArray<NSDictionary *> *)failedInvitees;
29
+
30
+- (void)receivedResults:(NSArray<NSDictionary*> * _Nonnull)results
31
+               forQuery:(NSString * _Nonnull)query;
32
+
33
+@end

+ 31
- 0
ios/sdk/src/invite/AddPeopleController.h Näytä tiedosto

@@ -0,0 +1,31 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+#import "AddPeopleControllerDelegate.h"
20
+
21
+@interface AddPeopleController: NSObject
22
+
23
+@property (nonatomic, nullable, weak) id<AddPeopleControllerDelegate> delegate;
24
+
25
+- (void)endAddPeople;
26
+
27
+- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids;
28
+
29
+- (void)performQuery:(NSString * _Nonnull)query;
30
+
31
+@end

+ 79
- 0
ios/sdk/src/invite/AddPeopleController.m Näytä tiedosto

@@ -0,0 +1,79 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "AddPeopleController+Private.h"
18
+#import "InviteController+Private.h"
19
+
20
+@implementation AddPeopleController
21
+
22
+- (instancetype)initWithOwner:(InviteController *)owner {
23
+    self = [super init];
24
+    if (self) {
25
+        _uuid = [[NSUUID UUID] UUIDString];
26
+        _items = [[NSMutableDictionary alloc] init];
27
+        _owner = owner;
28
+    }
29
+    return self;
30
+}
31
+
32
+#pragma mark API
33
+
34
+- (void)endAddPeople {
35
+    [self.owner endAddPeopleForController:self];
36
+}
37
+
38
+- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids {
39
+    NSMutableArray* invitees = [[NSMutableArray alloc] init];
40
+
41
+    for (NSString* itemId in ids) {
42
+        id invitee = [self.items objectForKey:itemId];
43
+
44
+        if (invitee) {
45
+            [invitees addObject:invitee];
46
+        }
47
+    }
48
+
49
+    [self.owner invite:invitees forController:self];
50
+}
51
+
52
+- (void)performQuery:(NSString *)query {
53
+    [self.owner performQuery:query forController:self];
54
+}
55
+
56
+#pragma mark Internal API, used to call the delegate and report to the user
57
+
58
+- (void)receivedResults:(NSArray<NSDictionary *> *)results forQuery:(NSString *)query {
59
+    for (NSDictionary* item in results) {
60
+        NSString* itemId = item[@"id"];
61
+        NSString* itemType = item[@"type"];
62
+        if (itemId) {
63
+            [self.items setObject:item forKey:itemId];
64
+        } else if (itemType != nil && [itemType isEqualToString: @"phone"]) {
65
+            NSString* number = item[@"number"];
66
+            if (number) {
67
+                [self.items setObject:item forKey:number];
68
+            }
69
+        }
70
+    }
71
+
72
+    [self.delegate addPeopleController:self didReceiveResults:results forQuery:query];
73
+}
74
+
75
+- (void)inviteSettled:(NSArray<NSDictionary *> *)failedInvitees {
76
+    [self.delegate inviteSettled:failedInvitees fromSearchController:self];
77
+}
78
+
79
+@end

+ 38
- 0
ios/sdk/src/invite/AddPeopleControllerDelegate.h Näytä tiedosto

@@ -0,0 +1,38 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+#import "AddPeopleController.h"
20
+
21
+@class AddPeopleController;
22
+
23
+@protocol AddPeopleControllerDelegate
24
+
25
+/**
26
+ * Called when an AddPeopleController has results for a query that was previously provided.
27
+ */
28
+- (void)addPeopleController:(AddPeopleController * _Nonnull)controller
29
+          didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
30
+                   forQuery:(NSString * _Nonnull)query;
31
+
32
+/**
33
+ * TODO.
34
+ */
35
+- (void)inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
36
+ fromSearchController:(AddPeopleController * _Nonnull)addPeopleController;
37
+
38
+@end

+ 30
- 0
ios/sdk/src/invite/Invite+Private.h Näytä tiedosto

@@ -0,0 +1,30 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <React/RCTBridge.h>
18
+#import <React/RCTEventEmitter.h>
19
+
20
+@interface Invite : RCTEventEmitter <RCTBridgeModule>
21
+
22
+- (void)            invite:(NSArray<NSDictionary *> * _Nonnull)invitees
23
+          externalAPIScope:(NSString * _Nonnull)externalAPIScope
24
+  addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
25
+
26
+- (void)      performQuery:(NSString * _Nonnull)query
27
+          externalAPIScope:(NSString * _Nonnull)externalAPIScope
28
+  addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
29
+
30
+@end

+ 90
- 0
ios/sdk/src/invite/Invite.m Näytä tiedosto

@@ -0,0 +1,90 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "Invite+Private.h"
18
+#import "InviteController+Private.h"
19
+#import "JitsiMeetView+Private.h"
20
+
21
+// The events emitted/supported by the Invite react-native module:
22
+//
23
+// XXX The event names are ridiculous on purpose. Even though iOS makes it look
24
+// like it emits within the bounderies of a react-native module ony, it actually
25
+// also emits through DeviceEventEmitter. (Of course, Android emits only through
26
+// DeviceEventEmitter.)
27
+static NSString * const InvitePerformQueryAction
28
+    = @"org.jitsi.meet:features/invite#performQuery";
29
+static NSString * const InvitePerformSubmitInviteAction
30
+    = @"org.jitsi.meet:features/invite#invite";
31
+
32
+@implementation Invite
33
+
34
+RCT_EXPORT_MODULE();
35
+
36
+- (NSArray<NSString *> *)supportedEvents {
37
+    return @[
38
+        InvitePerformQueryAction,
39
+        InvitePerformSubmitInviteAction
40
+    ];
41
+}
42
+
43
+/**
44
+ * Calls the corresponding JitsiMeetView's delegate to request that the native
45
+ * invite search be presented.
46
+ *
47
+ * @param scope
48
+ */
49
+RCT_EXPORT_METHOD(beginAddPeople:(NSString *)externalAPIScope) {
50
+    JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
51
+    InviteController *controller = view.inviteController;
52
+    [controller beginAddPeople];
53
+}
54
+
55
+RCT_EXPORT_METHOD(inviteSettled:(NSString *)externalAPIScope
56
+       addPeopleControllerScope:(NSString *)addPeopleControllerScope
57
+                 failedInvitees:(NSArray *)failedInvitees) {
58
+    JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
59
+    InviteController *controller = view.inviteController;
60
+    [controller inviteSettled:addPeopleControllerScope failedInvitees:failedInvitees];
61
+}
62
+
63
+RCT_EXPORT_METHOD(receivedResults:(NSString *)externalAPIScope
64
+         addPeopleControllerScope:(NSString *)addPeopleControllerScope
65
+                            query:(NSString *)query
66
+                          results:(NSArray *)results) {
67
+    JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
68
+    InviteController *controller = view.inviteController;
69
+    [controller receivedResults:addPeopleControllerScope query:query results:results];
70
+}
71
+
72
+- (void)            invite:(NSArray<NSDictionary *> * _Nonnull)invitees
73
+          externalAPIScope:(NSString * _Nonnull)externalAPIScope
74
+  addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
75
+    [self sendEventWithName:InvitePerformSubmitInviteAction
76
+                       body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
77
+                               @"externalAPIScope": externalAPIScope,
78
+                               @"invitees": invitees }];
79
+}
80
+
81
+- (void)      performQuery:(NSString * _Nonnull)query
82
+          externalAPIScope:(NSString * _Nonnull)externalAPIScope
83
+  addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
84
+    [self sendEventWithName:InvitePerformQueryAction
85
+                       body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
86
+                               @"externalAPIScope": externalAPIScope,
87
+                               @"query": query }];
88
+}
89
+
90
+@end

+ 50
- 0
ios/sdk/src/invite/InviteController+Private.h Näytä tiedosto

@@ -0,0 +1,50 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "InviteController.h"
18
+
19
+#import "AddPeopleController.h"
20
+#import "Invite+Private.h"
21
+
22
+@interface InviteController ()
23
+
24
+@property (nonatomic, nullable) AddPeopleController *addPeopleController;
25
+
26
+@property (nonatomic) NSString *externalAPIScope;
27
+
28
+@property (nonatomic, nullable, weak) Invite *inviteModule;
29
+
30
+- (instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
31
+                         andInviteModule:(Invite * _Nonnull)inviteModule;
32
+
33
+- (void)beginAddPeople;
34
+
35
+- (void)endAddPeopleForController:(AddPeopleController *)controller;
36
+
37
+- (void) invite:(NSArray *)invitees
38
+  forController:(AddPeopleController * _Nonnull)controller;
39
+
40
+- (void)inviteSettled:(NSString * _Nonnull)addPeopleControllerScope
41
+       failedInvitees:(NSArray *)failedInvitees;
42
+
43
+- (void)performQuery:(NSString * _Nonnull)query
44
+       forController:(AddPeopleController * _Nonnull)controller;
45
+
46
+- (void)receivedResults:(NSString * _Nonnull)addPeopleControllerScope
47
+                  query:(NSString * _Nonnull)query
48
+                results:(NSArray *)results;
49
+
50
+@end

+ 32
- 0
ios/sdk/src/invite/InviteController.h Näytä tiedosto

@@ -0,0 +1,32 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Foundation/Foundation.h>
18
+
19
+#import "InviteControllerDelegate.h"
20
+
21
+@interface InviteController : NSObject
22
+
23
+@property (nonatomic) BOOL addPeopleEnabled;
24
+
25
+@property (nonatomic) BOOL dialOutEnabled;
26
+
27
+@property (nonatomic, nullable, weak) id<InviteControllerDelegate> delegate;
28
+
29
+- (void)  invite:(NSArray *)invitees
30
+  withCompletion:(void (^)(NSArray<NSDictionary *> *failedInvitees))completion;
31
+
32
+@end

+ 118
- 0
ios/sdk/src/invite/InviteController.m Näytä tiedosto

@@ -0,0 +1,118 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "InviteController+Private.h"
18
+#import "AddPeopleController+Private.h"
19
+
20
+@implementation InviteController
21
+
22
+-(instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
23
+                        andInviteModule:(Invite * _Nonnull)inviteModule {
24
+    self = [super init];
25
+    if (self) {
26
+        self.externalAPIScope = externalAPIScope;
27
+        self.inviteModule = inviteModule;
28
+    }
29
+
30
+    return self;
31
+}
32
+
33
+-(void)beginAddPeople {
34
+    if (_delegate == nil) {
35
+        return;
36
+    }
37
+
38
+    if (_addPeopleController != nil) {
39
+        return;
40
+    }
41
+
42
+    _addPeopleController = [[AddPeopleController alloc] initWithOwner:self];
43
+
44
+    @try {
45
+        if (self.delegate
46
+                && [self.delegate respondsToSelector:@selector(beginAddPeople:)]) {
47
+            [self.delegate beginAddPeople:_addPeopleController];
48
+        }
49
+    } @catch (NSException *e) {
50
+        [self endAddPeopleForController:_addPeopleController];
51
+    }
52
+}
53
+
54
+-(void)endAddPeopleForController:(AddPeopleController *)controller {
55
+    if (self.addPeopleController == controller) {
56
+        self.addPeopleController = nil;
57
+    }
58
+}
59
+
60
+#pragma mark Result handling
61
+
62
+- (void)inviteSettled:(NSString *)addPeopleControllerScope
63
+       failedInvitees:(NSArray *)failedInvitees {
64
+    AddPeopleController *controller = self.addPeopleController;
65
+
66
+    if (controller != nil
67
+            && [controller.uuid isEqualToString:addPeopleControllerScope]) {
68
+        @try {
69
+            [controller inviteSettled:failedInvitees];
70
+        } @finally {
71
+            if ([failedInvitees count] == 0) {
72
+                [self endAddPeopleForController:controller];
73
+            }
74
+        }
75
+    }
76
+}
77
+
78
+- (void)receivedResults:(NSString *)addPeopleControllerScope
79
+                  query:(NSString *)query
80
+                results:(NSArray *)results {
81
+    AddPeopleController *controller = self.addPeopleController;
82
+
83
+    if (controller != nil
84
+            && [controller.uuid isEqualToString:addPeopleControllerScope]) {
85
+        [controller receivedResults:results forQuery:query];
86
+    }
87
+}
88
+
89
+#pragma mark Use the Invite react-native module to emit the search / submission events
90
+
91
+- (void) invite:(NSArray *)invitees
92
+  forController:(AddPeopleController * _Nonnull)controller {
93
+    [self        invite:invitees
94
+     forControllerScope:controller.uuid];
95
+}
96
+
97
+- (void)      invite:(NSArray *)invitees
98
+  forControllerScope:(NSString * _Nonnull)controllerScope {
99
+    [self.inviteModule invite:invitees
100
+             externalAPIScope:self.externalAPIScope
101
+     addPeopleControllerScope:controllerScope];
102
+}
103
+
104
+- (void)  invite:(NSArray *)invitees
105
+  withCompletion:(void (^)(NSArray<NSDictionary *> *failedInvitees))completion {
106
+    // TODO Execute the specified completion block when the invite settles.
107
+    [self        invite:invitees
108
+     forControllerScope:[[NSUUID UUID] UUIDString]];
109
+}
110
+
111
+- (void)performQuery:(NSString * _Nonnull)query
112
+       forController:(AddPeopleController * _Nonnull)controller {
113
+    [self.inviteModule performQuery:query
114
+                   externalAPIScope:self.externalAPIScope
115
+           addPeopleControllerScope:controller.uuid];
116
+}
117
+
118
+@end

+ 29
- 0
ios/sdk/src/invite/InviteControllerDelegate.h Näytä tiedosto

@@ -0,0 +1,29 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import "AddPeopleController.h"
18
+
19
+@protocol InviteControllerDelegate <NSObject>
20
+
21
+/**
22
+ * Called when the invite button in the conference is tapped.
23
+ *
24
+ * The search controller provided can be used to query user search within the
25
+ * conference.
26
+ */
27
+- (void)beginAddPeople:(AddPeopleController *)addPeopleController;
28
+
29
+@end

+ 24
- 0
react/features/invite/actionTypes.js Näytä tiedosto

@@ -1,3 +1,27 @@
1
+/**
2
+ * The type of the (redux) action which signals that a click/tap has been
3
+ * performed on {@link InviteButton} and that the execution flow for
4
+ * adding/inviting people to the current conference/meeting is to begin.
5
+ *
6
+ * {
7
+ *     type: BEGIN_ADD_PEOPLE
8
+ * }
9
+ */
10
+export const BEGIN_ADD_PEOPLE = Symbol('BEGIN_ADD_PEOPLE');
11
+
12
+/**
13
+ * The type of redux action to set the {@code EventEmitter} subscriptions
14
+ * utilized by the feature invite.
15
+ *
16
+ * {
17
+ *     type: _SET_EMITTER_SUBSCRIPTIONS,
18
+ *     emitterSubscriptions: Array|undefined
19
+ * }
20
+ *
21
+ * @protected
22
+ */
23
+export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');
24
+
1 25
 /**
2 26
  * The type of the action which signals an error occurred while requesting dial-
3 27
  * in numbers.

+ 16
- 0
react/features/invite/actions.js Näytä tiedosto

@@ -1,11 +1,27 @@
1 1
 // @flow
2 2
 
3 3
 import {
4
+    BEGIN_ADD_PEOPLE,
4 5
     UPDATE_DIAL_IN_NUMBERS_FAILED,
5 6
     UPDATE_DIAL_IN_NUMBERS_SUCCESS
6 7
 } from './actionTypes';
7 8
 import { getDialInConferenceID, getDialInNumbers } from './functions';
8 9
 
10
+/**
11
+ * Creates a (redux) action to signal that a click/tap has been performed on
12
+ * {@link InviteButton} and that the execution flow for adding/inviting people
13
+ * to the current conference/meeting is to begin.
14
+ *
15
+ * @returns {{
16
+ *     type: BEGIN_ADD_PEOPLE
17
+ * }}
18
+ */
19
+export function beginAddPeople() {
20
+    return {
21
+        type: BEGIN_ADD_PEOPLE
22
+    };
23
+}
24
+
9 25
 /**
10 26
  * Sends AJAX requests for dial-in numbers and conference ID.
11 27
  *

+ 0
- 3
react/features/invite/components/AddPeopleDialog.native.js Näytä tiedosto

@@ -1,3 +0,0 @@
1
-/**
2
- * Created by ystamcheva on 8/6/17.
3
- */

+ 18
- 14
react/features/invite/components/AddPeopleDialog.web.js Näytä tiedosto

@@ -71,12 +71,12 @@ class AddPeopleDialog extends Component<*, *> {
71 71
         /**
72 72
          * Whether or not to show Add People functionality.
73 73
          */
74
-        enableAddPeople: PropTypes.bool,
74
+        addPeopleEnabled: PropTypes.bool,
75 75
 
76 76
         /**
77 77
          * Whether or not to show Dial Out functionality.
78 78
          */
79
-        enableDialOut: PropTypes.bool,
79
+        dialOutEnabled: PropTypes.bool,
80 80
 
81 81
         /**
82 82
          * The function closing the dialog.
@@ -187,21 +187,21 @@ class AddPeopleDialog extends Component<*, *> {
187 187
      * @returns {ReactElement}
188 188
      */
189 189
     render() {
190
-        const { enableAddPeople, enableDialOut, t } = this.props;
190
+        const { addPeopleEnabled, dialOutEnabled, t } = this.props;
191 191
         let isMultiSelectDisabled = this.state.addToCallInProgress || false;
192 192
         let placeholder;
193 193
         let loadingMessage;
194 194
         let noMatches;
195 195
 
196
-        if (enableAddPeople && enableDialOut) {
196
+        if (addPeopleEnabled && dialOutEnabled) {
197 197
             loadingMessage = 'addPeople.loading';
198 198
             noMatches = 'addPeople.noResults';
199 199
             placeholder = 'addPeople.searchPeopleAndNumbers';
200
-        } else if (enableAddPeople) {
200
+        } else if (addPeopleEnabled) {
201 201
             loadingMessage = 'addPeople.loadingPeople';
202 202
             noMatches = 'addPeople.noResults';
203 203
             placeholder = 'addPeople.searchPeople';
204
-        } else if (enableDialOut) {
204
+        } else if (dialOutEnabled) {
205 205
             loadingMessage = 'addPeople.loadingNumber';
206 206
             noMatches = 'addPeople.noValidNumbers';
207 207
             placeholder = 'addPeople.searchNumbers';
@@ -481,8 +481,8 @@ class AddPeopleDialog extends Component<*, *> {
481 481
      */
482 482
     _query(query = '') {
483 483
         const {
484
-            enableAddPeople,
485
-            enableDialOut,
484
+            addPeopleEnabled,
485
+            dialOutEnabled,
486 486
             _dialOutAuthUrl,
487 487
             _jwt,
488 488
             _peopleSearchQueryTypes,
@@ -491,8 +491,8 @@ class AddPeopleDialog extends Component<*, *> {
491 491
 
492 492
         const options = {
493 493
             dialOutAuthUrl: _dialOutAuthUrl,
494
-            enableAddPeople,
495
-            enableDialOut,
494
+            addPeopleEnabled,
495
+            dialOutEnabled,
496 496
             jwt: _jwt,
497 497
             peopleSearchQueryTypes: _peopleSearchQueryTypes,
498 498
             peopleSearchUrl: _peopleSearchUrl
@@ -609,7 +609,11 @@ function _mapStateToProps(state) {
609 609
     };
610 610
 }
611 611
 
612
-export default translate(connect(_mapStateToProps, {
613
-    hideDialog,
614
-    inviteVideoRooms })(
615
-    AddPeopleDialog));
612
+export default translate(
613
+    connect(
614
+            _mapStateToProps,
615
+            /* mapDispatchToProps */ {
616
+                hideDialog,
617
+                inviteVideoRooms
618
+            })(
619
+        AddPeopleDialog));

+ 92
- 22
react/features/invite/components/InviteButton.native.js Näytä tiedosto

@@ -3,9 +3,22 @@
3 3
 import React, { Component } from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 
6
-import { launchNativeInvite } from '../../mobile/invite-search';
6
+import { beginShareRoom } from '../../share-room';
7 7
 import { ToolbarButton } from '../../toolbox';
8 8
 
9
+import { beginAddPeople } from '../actions';
10
+import { isAddPeopleEnabled, isDialOutEnabled } from '../functions';
11
+
12
+/**
13
+ * The indicator which determines (at bundle time) whether there should be a
14
+ * {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
15
+ * feature share-room in the user interface of the app.
16
+ *
17
+ * @private
18
+ * @type {boolean}
19
+ */
20
+const _SHARE_ROOM_TOOLBAR_BUTTON = true;
21
+
9 22
 /**
10 23
  * The type of {@link EnterPictureInPictureToobarButton}'s React
11 24
  * {@code Component} props.
@@ -13,21 +26,28 @@ import { ToolbarButton } from '../../toolbox';
13 26
 type Props = {
14 27
 
15 28
     /**
16
-     * Indicates if the "Add to call" feature is available.
29
+     * Whether or not the feature to directly invite people into the
30
+     * conference is available.
17 31
      */
18
-    enableAddPeople: boolean,
32
+    _addPeopleEnabled: boolean,
19 33
 
20 34
     /**
21
-     * Indicates if the "Dial out" feature is available.
35
+     * Whether or not the feature to dial out to number to join the
36
+     * conference is available.
22 37
      */
23
-    enableDialOut: boolean,
38
+    _dialOutEnabled: boolean,
24 39
 
25 40
     /**
26 41
      * Launches native invite dialog.
27 42
      *
28 43
      * @protected
29 44
      */
30
-    onLaunchNativeInvite: Function,
45
+    _onAddPeople: Function,
46
+
47
+    /**
48
+     * Begins the UI procedure to share the conference/room URL.
49
+     */
50
+    _onShareRoom: Function
31 51
 };
32 52
 
33 53
 /**
@@ -43,22 +63,32 @@ class InviteButton extends Component<Props> {
43 63
      */
44 64
     render() {
45 65
         const {
46
-            enableAddPeople,
47
-            enableDialOut,
48
-            onLaunchNativeInvite,
66
+            _addPeopleEnabled,
67
+            _dialOutEnabled,
68
+            _onAddPeople,
69
+            _onShareRoom,
49 70
             ...props
50 71
         } = this.props;
51 72
 
52
-        if (!enableAddPeople && !enableDialOut) {
53
-            return null;
73
+        if (_SHARE_ROOM_TOOLBAR_BUTTON) {
74
+            return (
75
+                <ToolbarButton
76
+                    iconName = 'link'
77
+                    onClick = { _onShareRoom }
78
+                    { ...props } />
79
+            );
54 80
         }
55 81
 
56
-        return (
57
-            <ToolbarButton
58
-                iconName = { 'add' }
59
-                onClick = { onLaunchNativeInvite }
60
-                { ...props } />
61
-        );
82
+        if (_addPeopleEnabled || _dialOutEnabled) {
83
+            return (
84
+                <ToolbarButton
85
+                    iconName = { 'link' }
86
+                    onClick = { _onAddPeople }
87
+                    { ...props } />
88
+            );
89
+        }
90
+
91
+        return null;
62 92
     }
63 93
 }
64 94
 
@@ -68,13 +98,13 @@ class InviteButton extends Component<Props> {
68 98
  *
69 99
  * @param {Function} dispatch - The redux action {@code dispatch} function.
70 100
  * @returns {{
71
-*      onLaunchNativeInvite
101
+ *     _onAddPeople,
102
+ *     _onShareRoom
72 103
  * }}
73 104
  * @private
74 105
  */
75 106
 function _mapDispatchToProps(dispatch) {
76 107
     return {
77
-
78 108
         /**
79 109
          * Launches native invite dialog.
80 110
          *
@@ -82,10 +112,50 @@ function _mapDispatchToProps(dispatch) {
82 112
          * @returns {void}
83 113
          * @type {Function}
84 114
          */
85
-        onLaunchNativeInvite() {
86
-            dispatch(launchNativeInvite());
115
+        _onAddPeople() {
116
+            dispatch(beginAddPeople());
117
+        },
118
+
119
+        /**
120
+         * Begins the UI procedure to share the conference/room URL.
121
+         *
122
+         * @private
123
+         * @returns {void}
124
+         * @type {Function}
125
+         */
126
+        _onShareRoom() {
127
+            dispatch(beginShareRoom());
87 128
         }
88 129
     };
89 130
 }
90 131
 
91
-export default connect(undefined, _mapDispatchToProps)(InviteButton);
132
+/**
133
+ * Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
134
+ * props.
135
+ *
136
+ * @param {Object} state - The redux store/state.
137
+ * @private
138
+ * @returns {{
139
+ * }}
140
+ */
141
+function _mapStateToProps(state) {
142
+    return {
143
+        /**
144
+         * Whether or not the feature to directly invite people into the
145
+         * conference is available.
146
+         *
147
+         * @type {boolean}
148
+         */
149
+        _addPeopleEnabled: isAddPeopleEnabled(state),
150
+
151
+        /**
152
+         * Whether or not the feature to dial out to number to join the
153
+         * conference is available.
154
+         *
155
+         * @type {boolean}
156
+         */
157
+        _dialOutEnabled: isDialOutEnabled(state)
158
+    };
159
+}
160
+
161
+export default connect(_mapStateToProps, _mapDispatchToProps)(InviteButton);

+ 174
- 153
react/features/invite/functions.js Näytä tiedosto

@@ -8,6 +8,37 @@ declare var interfaceConfig: Object;
8 8
 
9 9
 const logger = require('jitsi-meet-logger').getLogger(__filename);
10 10
 
11
+/**
12
+ * Sends an ajax request to check if the phone number can be called.
13
+ *
14
+ * @param {string} dialNumber - The dial number to check for validity.
15
+ * @param {string} dialOutAuthUrl - The endpoint to use for checking validity.
16
+ * @returns {Promise} - The promise created by the request.
17
+ */
18
+export function checkDialNumber(
19
+        dialNumber: string,
20
+        dialOutAuthUrl: string
21
+): Promise<Object> {
22
+
23
+    if (!dialOutAuthUrl) {
24
+        // no auth url, let's say it is valid
25
+        const response = {
26
+            allow: true,
27
+            phone: `+${dialNumber}`
28
+        };
29
+
30
+        return Promise.resolve(response);
31
+    }
32
+
33
+    const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
34
+
35
+    return new Promise((resolve, reject) => {
36
+        $.getJSON(fullUrl)
37
+            .then(resolve)
38
+            .catch(reject);
39
+    });
40
+}
41
+
11 42
 /**
12 43
  * Sends a GET request to obtain the conference ID necessary for identifying
13 44
  * which conference to join after diaing the dial-in service.
@@ -22,7 +53,9 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
22 53
 export function getDialInConferenceID(
23 54
         baseUrl: string,
24 55
         roomName: string,
25
-        mucURL: string): Promise<Object> {
56
+        mucURL: string
57
+): Promise<Object> {
58
+
26 59
     const conferenceIDURL = `${baseUrl}?conference=${roomName}@${mucURL}`;
27 60
 
28 61
     return doGetJSON(conferenceIDURL);
@@ -40,121 +73,6 @@ export function getDialInNumbers(url: string): Promise<*> {
40 73
     return doGetJSON(url);
41 74
 }
42 75
 
43
-/**
44
- * Sends a post request to an invite service.
45
- *
46
- * @param {string} inviteServiceUrl - The invite service that generates the
47
- * invitation.
48
- * @param {string} inviteUrl - The url to the conference.
49
- * @param {string} jwt - The jwt token to pass to the search service.
50
- * @param {Immutable.List} inviteItems - The list of the "user" or "room"
51
- * type items to invite.
52
- * @returns {Promise} - The promise created by the request.
53
- */
54
-function invitePeopleAndChatRooms( // eslint-disable-line max-params
55
-        inviteServiceUrl: string,
56
-        inviteUrl: string,
57
-        jwt: string,
58
-        inviteItems: Array<Object>): Promise<void> {
59
-    if (!inviteItems || inviteItems.length === 0) {
60
-        return Promise.resolve();
61
-    }
62
-
63
-    return new Promise((resolve, reject) => {
64
-        $.post(
65
-                `${inviteServiceUrl}?token=${jwt}`,
66
-                JSON.stringify({
67
-                    'invited': inviteItems,
68
-                    'url': inviteUrl
69
-                }),
70
-                resolve,
71
-                'json')
72
-            .fail((jqxhr, textStatus, error) => reject(error));
73
-    });
74
-}
75
-
76
-/**
77
- * Sends an ajax request to a directory service.
78
- *
79
- * @param {string} serviceUrl - The service to query.
80
- * @param {string} jwt - The jwt token to pass to the search service.
81
- * @param {string} text - Text to search.
82
- * @param {Array<string>} queryTypes - Array with the query types that will be
83
- * executed - "conferenceRooms" | "user" | "room".
84
- * @returns {Promise} - The promise created by the request.
85
- */
86
-export function searchDirectory( // eslint-disable-line max-params
87
-        serviceUrl: string,
88
-        jwt: string,
89
-        text: string,
90
-        queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
91
-): Promise<Array<Object>> {
92
-    const query = encodeURIComponent(text);
93
-    const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
94
-
95
-    return fetch(`${serviceUrl}?query=${query}&queryTypes=${
96
-        queryTypesString}&jwt=${jwt}`)
97
-            .then(response => {
98
-                const jsonify = response.json();
99
-
100
-                if (response.ok) {
101
-                    return jsonify;
102
-                }
103
-
104
-                return jsonify
105
-                    .then(result => Promise.reject(result));
106
-            })
107
-            .catch(error => {
108
-                logger.error(
109
-                    'Error searching directory:', error);
110
-
111
-                return Promise.reject(error);
112
-            });
113
-}
114
-
115
-/**
116
- * RegExp to use to determine if some text might be a phone number.
117
- *
118
- * @returns {RegExp}
119
- */
120
-function isPhoneNumberRegex(): RegExp {
121
-    let regexString = '^[0-9+()-\\s]*$';
122
-
123
-    if (typeof interfaceConfig !== 'undefined') {
124
-        regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
125
-    }
126
-
127
-    return new RegExp(regexString);
128
-}
129
-
130
-/**
131
- * Sends an ajax request to check if the phone number can be called.
132
- *
133
- * @param {string} dialNumber - The dial number to check for validity.
134
- * @param {string} dialOutAuthUrl - The endpoint to use for checking validity.
135
- * @returns {Promise} - The promise created by the request.
136
- */
137
-export function checkDialNumber(
138
-        dialNumber: string, dialOutAuthUrl: string): Promise<Object> {
139
-    if (!dialOutAuthUrl) {
140
-        // no auth url, let's say it is valid
141
-        const response = {
142
-            allow: true,
143
-            phone: `+${dialNumber}`
144
-        };
145
-
146
-        return Promise.resolve(response);
147
-    }
148
-
149
-    const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
150
-
151
-    return new Promise((resolve, reject) => {
152
-        $.getJSON(fullUrl)
153
-            .then(resolve)
154
-            .catch(reject);
155
-    });
156
-}
157
-
158 76
 /**
159 77
  * Removes all non-numeric characters from a string.
160 78
  *
@@ -180,12 +98,12 @@ export type GetInviteResultsOptions = {
180 98
     /**
181 99
      * Whether or not to search for people.
182 100
      */
183
-    enableAddPeople: boolean,
101
+    addPeopleEnabled: boolean,
184 102
 
185 103
     /**
186 104
      * Whether or not to check phone numbers.
187 105
      */
188
-    enableDialOut: boolean,
106
+    dialOutEnabled: boolean,
189 107
 
190 108
     /**
191 109
      * Array with the query types that will be executed -
@@ -214,13 +132,15 @@ export type GetInviteResultsOptions = {
214 132
  */
215 133
 export function getInviteResultsForQuery(
216 134
         query: string,
217
-        options: GetInviteResultsOptions): Promise<*> {
135
+        options: GetInviteResultsOptions
136
+): Promise<*> {
137
+
218 138
     const text = query.trim();
219 139
 
220 140
     const {
221 141
         dialOutAuthUrl,
222
-        enableAddPeople,
223
-        enableDialOut,
142
+        addPeopleEnabled,
143
+        dialOutEnabled,
224 144
         peopleSearchQueryTypes,
225 145
         peopleSearchUrl,
226 146
         jwt
@@ -228,7 +148,7 @@ export function getInviteResultsForQuery(
228 148
 
229 149
     let peopleSearchPromise;
230 150
 
231
-    if (enableAddPeople && text) {
151
+    if (addPeopleEnabled && text) {
232 152
         peopleSearchPromise = searchDirectory(
233 153
             peopleSearchUrl,
234 154
             jwt,
@@ -242,7 +162,7 @@ export function getInviteResultsForQuery(
242 162
     const hasCountryCode = text.startsWith('+');
243 163
     let phoneNumberPromise;
244 164
 
245
-    if (enableDialOut && isMaybeAPhoneNumber(text)) {
165
+    if (dialOutEnabled && isMaybeAPhoneNumber(text)) {
246 166
         let numberToVerify = text;
247 167
 
248 168
         // When the number to verify does not start with a +, we assume no
@@ -296,6 +216,82 @@ export function getInviteResultsForQuery(
296 216
         });
297 217
 }
298 218
 
219
+/**
220
+ * Sends a post request to an invite service.
221
+ *
222
+ * @param {string} inviteServiceUrl - The invite service that generates the
223
+ * invitation.
224
+ * @param {string} inviteUrl - The url to the conference.
225
+ * @param {string} jwt - The jwt token to pass to the search service.
226
+ * @param {Immutable.List} inviteItems - The list of the "user" or "room"
227
+ * type items to invite.
228
+ * @returns {Promise} - The promise created by the request.
229
+ */
230
+function invitePeopleAndChatRooms( // eslint-disable-line max-params
231
+        inviteServiceUrl: string,
232
+        inviteUrl: string,
233
+        jwt: string,
234
+        inviteItems: Array<Object>
235
+): Promise<void> {
236
+
237
+    if (!inviteItems || inviteItems.length === 0) {
238
+        return Promise.resolve();
239
+    }
240
+
241
+    return new Promise((resolve, reject) => {
242
+        $.post(
243
+                `${inviteServiceUrl}?token=${jwt}`,
244
+                JSON.stringify({
245
+                    'invited': inviteItems,
246
+                    'url': inviteUrl
247
+                }),
248
+                resolve,
249
+                'json')
250
+            .fail((jqxhr, textStatus, error) => reject(error));
251
+    });
252
+}
253
+
254
+/**
255
+ * Determines if adding people is currently enabled.
256
+ *
257
+ * @param {boolean} state - Current state.
258
+ * @returns {boolean} Indication of whether adding people is currently enabled.
259
+ */
260
+export function isAddPeopleEnabled(state: Object): boolean {
261
+    const { isGuest } = state['features/base/jwt'];
262
+
263
+    if (!isGuest) {
264
+        // XXX The mobile/react-native app is capable of disabling the
265
+        // adding/inviting of people in the current conference. Anyway, the
266
+        // Web/React app does not have that capability so default appropriately.
267
+        const { app } = state['features/app'];
268
+        const addPeopleEnabled = app && app.props.addPeopleEnabled;
269
+
270
+        return (
271
+            (typeof addPeopleEnabled === 'undefined')
272
+                || Boolean(addPeopleEnabled));
273
+    }
274
+
275
+    return false;
276
+}
277
+
278
+/**
279
+ * Determines if dial out is currently enabled or not.
280
+ *
281
+ * @param {boolean} state - Current state.
282
+ * @returns {boolean} Indication of whether dial out is currently enabled.
283
+ */
284
+export function isDialOutEnabled(state: Object): boolean {
285
+    const { conference } = state['features/base/conference'];
286
+    const { isGuest } = state['features/base/jwt'];
287
+    const { enableUserRolesBasedOnToken } = state['features/base/config'];
288
+    const participant = getLocalParticipant(state);
289
+
290
+    return participant && participant.role === PARTICIPANT_ROLE.MODERATOR
291
+                && conference && conference.isSIPCallingSupported()
292
+                && (!enableUserRolesBasedOnToken || !isGuest);
293
+}
294
+
299 295
 /**
300 296
  * Checks whether a string looks like it could be for a phone number.
301 297
  *
@@ -315,6 +311,61 @@ function isMaybeAPhoneNumber(text: string): boolean {
315 311
     return Boolean(digits.length);
316 312
 }
317 313
 
314
+/**
315
+ * RegExp to use to determine if some text might be a phone number.
316
+ *
317
+ * @returns {RegExp}
318
+ */
319
+function isPhoneNumberRegex(): RegExp {
320
+    let regexString = '^[0-9+()-\\s]*$';
321
+
322
+    if (typeof interfaceConfig !== 'undefined') {
323
+        regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
324
+    }
325
+
326
+    return new RegExp(regexString);
327
+}
328
+
329
+/**
330
+ * Sends an ajax request to a directory service.
331
+ *
332
+ * @param {string} serviceUrl - The service to query.
333
+ * @param {string} jwt - The jwt token to pass to the search service.
334
+ * @param {string} text - Text to search.
335
+ * @param {Array<string>} queryTypes - Array with the query types that will be
336
+ * executed - "conferenceRooms" | "user" | "room".
337
+ * @returns {Promise} - The promise created by the request.
338
+ */
339
+export function searchDirectory( // eslint-disable-line max-params
340
+        serviceUrl: string,
341
+        jwt: string,
342
+        text: string,
343
+        queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
344
+): Promise<Array<Object>> {
345
+
346
+    const query = encodeURIComponent(text);
347
+    const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
348
+
349
+    return fetch(`${serviceUrl}?query=${query}&queryTypes=${
350
+        queryTypesString}&jwt=${jwt}`)
351
+            .then(response => {
352
+                const jsonify = response.json();
353
+
354
+                if (response.ok) {
355
+                    return jsonify;
356
+                }
357
+
358
+                return jsonify
359
+                    .then(result => Promise.reject(result));
360
+            })
361
+            .catch(error => {
362
+                logger.error(
363
+                    'Error searching directory:', error);
364
+
365
+                return Promise.reject(error);
366
+            });
367
+}
368
+
318 369
 /**
319 370
  * Type of the options to use when sending invites.
320 371
  */
@@ -436,33 +487,3 @@ export function sendInvitesForItems(
436 487
     return Promise.all(allInvitePromises)
437 488
         .then(() => invitesLeftToSend);
438 489
 }
439
-
440
-/**
441
- * Determines if adding people is currently enabled.
442
- *
443
- * @param {boolean} state - Current state.
444
- * @returns {boolean} Indication of whether adding people is currently enabled.
445
- */
446
-export function isAddPeopleEnabled(state: Object): boolean {
447
-    const { app } = state['features/app'];
448
-    const { isGuest } = state['features/base/jwt'];
449
-
450
-    return !isGuest && Boolean(app && app.props.addPeopleEnabled);
451
-}
452
-
453
-/**
454
- * Determines if dial out is currently enabled or not.
455
- *
456
- * @param {boolean} state - Current state.
457
- * @returns {boolean} Indication of whether dial out is currently enabled.
458
- */
459
-export function isDialOutEnabled(state: Object): boolean {
460
-    const { conference } = state['features/base/conference'];
461
-    const { isGuest } = state['features/base/jwt'];
462
-    const { enableUserRolesBasedOnToken } = state['features/base/config'];
463
-    const participant = getLocalParticipant(state);
464
-
465
-    return participant && participant.role === PARTICIPANT_ROLE.MODERATOR
466
-                && conference && conference.isSIPCallingSupported()
467
-                && (!enableUserRolesBasedOnToken || !isGuest);
468
-}

react/features/invite/middleware.js → react/features/invite/middleware.any.js Näytä tiedosto

@@ -1,3 +1,5 @@
1
+// @flow
2
+
1 3
 import { MiddlewareRegistry } from '../base/redux';
2 4
 
3 5
 import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
@@ -5,9 +7,10 @@ import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
5 7
 const logger = require('jitsi-meet-logger').getLogger(__filename);
6 8
 
7 9
 /**
8
- * Middleware that catches actions fetching dial-in numbers.
10
+ * The middleware of the feature invite common to mobile/react-native and
11
+ * Web/React.
9 12
  *
10
- * @param {Store} store - Redux store.
13
+ * @param {Store} store - The redux store.
11 14
  * @returns {Function}
12 15
  */
13 16
 // eslint-disable-next-line no-unused-vars
@@ -15,7 +18,6 @@ MiddlewareRegistry.register(store => next => action => {
15 18
     const result = next(action);
16 19
 
17 20
     switch (action.type) {
18
-
19 21
     case UPDATE_DIAL_IN_NUMBERS_FAILED:
20 22
         logger.error(
21 23
             'Error encountered while fetching dial-in numbers:',

+ 208
- 0
react/features/invite/middleware.native.js Näytä tiedosto

@@ -0,0 +1,208 @@
1
+// @flow
2
+
3
+import i18next from 'i18next';
4
+import { NativeEventEmitter, NativeModules } from 'react-native';
5
+
6
+import { MiddlewareRegistry } from '../base/redux';
7
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
8
+import { getInviteURL } from '../base/connection';
9
+import { inviteVideoRooms } from '../videosipgw';
10
+
11
+import {
12
+    BEGIN_ADD_PEOPLE,
13
+    _SET_EMITTER_SUBSCRIPTIONS
14
+} from './actionTypes';
15
+import {
16
+    getInviteResultsForQuery,
17
+    isAddPeopleEnabled,
18
+    isDialOutEnabled,
19
+    sendInvitesForItems
20
+} from './functions';
21
+import './middleware.any';
22
+
23
+/**
24
+ * The react-native module of the feature invite.
25
+ */
26
+const { Invite } = NativeModules;
27
+
28
+/**
29
+ * The middleware of the feature invite specific to mobile/react-native.
30
+ *
31
+ * @param {Store} store - The redux store.
32
+ * @returns {Function}
33
+ */
34
+Invite && MiddlewareRegistry.register(store => next => action => {
35
+    switch (action.type) {
36
+    case APP_WILL_MOUNT:
37
+        return _appWillMount(store, next, action);
38
+
39
+    case APP_WILL_UNMOUNT: {
40
+        const result = next(action);
41
+
42
+        store.dispatch({
43
+            type: _SET_EMITTER_SUBSCRIPTIONS,
44
+            emitterSubscriptions: undefined
45
+        });
46
+
47
+        return result;
48
+    }
49
+
50
+    case BEGIN_ADD_PEOPLE:
51
+        return _beginAddPeople(store, next, action);
52
+    }
53
+
54
+    return next(action);
55
+});
56
+
57
+/**
58
+ * Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
59
+ * dispatched within a specific redux {@code store}.
60
+ *
61
+ * @param {Store} store - The redux store in which the specified {@code action}
62
+ * is being dispatched.
63
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
64
+ * specified {@code action} to the specified {@code store}.
65
+ * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
66
+ * being dispatched in the specified {@code store}.
67
+ * @private
68
+ * @returns {*} The value returned by {@code next(action)}.
69
+ */
70
+function _appWillMount({ dispatch, getState }, next, action) {
71
+    const result = next(action);
72
+
73
+    const emitter = new NativeEventEmitter(Invite);
74
+    const context = {
75
+        dispatch,
76
+        getState
77
+    };
78
+
79
+    dispatch({
80
+        type: _SET_EMITTER_SUBSCRIPTIONS,
81
+        emitterSubscriptions: [
82
+            emitter.addListener(
83
+                'org.jitsi.meet:features/invite#performQuery',
84
+                _onPerformQuery,
85
+                context),
86
+            emitter.addListener(
87
+                'org.jitsi.meet:features/invite#invite',
88
+                _onInvite,
89
+                context)
90
+        ]
91
+    });
92
+
93
+    return result;
94
+}
95
+
96
+/**
97
+ * Notifies the feature invite that the action {@link BEGIN_ADD_PEOPLE} is being
98
+ * dispatched within a specific redux {@code store}.
99
+ *
100
+ * @param {Store} store - The redux store in which the specified {@code action}
101
+ * is being dispatched.
102
+ * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
103
+ * specified {@code action} to the specified {@code store}.
104
+ * @param {Action} action - The redux action {@code BEGIN_ADD_PEOPLE} which is
105
+ * being dispatched in the specified {@code store}.
106
+ * @private
107
+ * @returns {*} The value returned by {@code next(action)}.
108
+ */
109
+function _beginAddPeople({ getState }, next, action) {
110
+    const result = next(action);
111
+
112
+    // The JavaScript App needs to provide uniquely identifying information to
113
+    // the native Invite module so that the latter may match the former to the
114
+    // native JitsiMeetView which hosts it.
115
+    const { app } = getState()['features/app'];
116
+
117
+    if (app) {
118
+        const { externalAPIScope } = app.props;
119
+
120
+        if (externalAPIScope) {
121
+            Invite.beginAddPeople(externalAPIScope);
122
+        }
123
+    }
124
+
125
+    return result;
126
+}
127
+
128
+/**
129
+ * Handles the {@code invite} event of the feature invite i.e. invites specific
130
+ * invitees to the current, ongoing conference.
131
+ *
132
+ * @param {Object} event - The details of the event.
133
+ * @returns {void}
134
+ */
135
+function _onInvite(
136
+        { addPeopleControllerScope, externalAPIScope, invitees }) {
137
+    const { getState } = this; // eslint-disable-line no-invalid-this
138
+    const state = getState();
139
+    const { conference } = state['features/base/conference'];
140
+    const { inviteServiceUrl } = state['features/base/config'];
141
+    const options = {
142
+        conference,
143
+        inviteServiceUrl,
144
+        inviteUrl: getInviteURL(state),
145
+        inviteVideoRooms,
146
+        jwt: state['features/base/jwt'].jwt
147
+    };
148
+
149
+    sendInvitesForItems(invitees, options)
150
+        .then(failedInvitees =>
151
+            Invite.inviteSettled(
152
+                externalAPIScope,
153
+                addPeopleControllerScope,
154
+                failedInvitees));
155
+}
156
+
157
+/**
158
+ * Handles the {@code performQuery} event of the feature invite i.e. queries for
159
+ * invitees who may subsequently be invited to the current, ongoing conference.
160
+ *
161
+ * @param {Object} event - The details of the event.
162
+ * @returns {void}
163
+ */
164
+function _onPerformQuery(
165
+        { addPeopleControllerScope, externalAPIScope, query }) {
166
+    const { getState } = this; // eslint-disable-line no-invalid-this
167
+
168
+    const state = getState();
169
+    const {
170
+        dialOutAuthUrl,
171
+        peopleSearchQueryTypes,
172
+        peopleSearchUrl
173
+    } = state['features/base/config'];
174
+    const options = {
175
+        dialOutAuthUrl,
176
+        addPeopleEnabled: isAddPeopleEnabled(state),
177
+        dialOutEnabled: isDialOutEnabled(state),
178
+        jwt: state['features/base/jwt'].jwt,
179
+        peopleSearchQueryTypes,
180
+        peopleSearchUrl
181
+    };
182
+
183
+    getInviteResultsForQuery(query, options)
184
+        .catch(() => [])
185
+        .then(results => {
186
+            const translatedResults = results.map(result => {
187
+                if (result.type === 'phone') {
188
+                    result.title = i18next.t('addPeople.telephone', {
189
+                        number: result.number
190
+                    });
191
+
192
+                    if (result.showCountryCodeReminder) {
193
+                        result.subtitle = i18next.t(
194
+                            'addPeople.countryReminder'
195
+                        );
196
+                    }
197
+                }
198
+
199
+                return result;
200
+            }).filter(result => result.type !== 'phone' || result.allowed);
201
+
202
+            Invite.receivedResults(
203
+                externalAPIScope,
204
+                addPeopleControllerScope,
205
+                query,
206
+                translatedResults);
207
+        });
208
+}

+ 50
- 0
react/features/invite/middleware.web.js Näytä tiedosto

@@ -0,0 +1,50 @@
1
+// @flow
2
+
3
+import { openDialog } from '../base/dialog';
4
+import { MiddlewareRegistry } from '../base/redux';
5
+
6
+import { BEGIN_ADD_PEOPLE } from './actionTypes';
7
+import { AddPeopleDialog } from './components';
8
+import { isAddPeopleEnabled, isDialOutEnabled } from './functions';
9
+import './middleware.any';
10
+
11
+/**
12
+ * The middleware of the feature invite specific to Web/React.
13
+ *
14
+ * @param {Store} store - The redux store.
15
+ * @returns {Function}
16
+ */
17
+MiddlewareRegistry.register(store => next => action => {
18
+    switch (action.type) {
19
+    case BEGIN_ADD_PEOPLE:
20
+        return _beginAddPeople(store, next, action);
21
+    }
22
+
23
+    return next(action);
24
+});
25
+
26
+/**
27
+ * Notifies the feature invite that the action {@link BEGIN_ADD_PEOPLE} is being
28
+ * dispatched within a specific redux {@code store}.
29
+ *
30
+ * @param {Store} store - The redux store in which the specified {@code action}
31
+ * is being dispatched.
32
+ * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
33
+ * specified {@code action} to the specified {@code store}.
34
+ * @param {Action} action - The redux action {@code BEGIN_ADD_PEOPLE} which is
35
+ * being dispatched in the specified {@code store}.
36
+ * @private
37
+ * @returns {*} The value returned by {@code next(action)}.
38
+ */
39
+function _beginAddPeople({ dispatch, getState }, next, action) {
40
+    const result = next(action);
41
+
42
+    const state = getState();
43
+
44
+    dispatch(openDialog(AddPeopleDialog, {
45
+        addPeopleEnabled: isAddPeopleEnabled(state),
46
+        dialOutEnabled: isDialOutEnabled(state)
47
+    }));
48
+
49
+    return result;
50
+}

+ 8
- 1
react/features/invite/reducer.js Näytä tiedosto

@@ -1,6 +1,9 @@
1
-import { ReducerRegistry } from '../base/redux';
1
+// @flow
2
+
3
+import { assign, ReducerRegistry } from '../base/redux';
2 4
 
3 5
 import {
6
+    _SET_EMITTER_SUBSCRIPTIONS,
4 7
     UPDATE_DIAL_IN_NUMBERS_FAILED,
5 8
     UPDATE_DIAL_IN_NUMBERS_SUCCESS
6 9
 } from './actionTypes';
@@ -11,6 +14,10 @@ const DEFAULT_STATE = {
11 14
 
12 15
 ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
13 16
     switch (action.type) {
17
+    case _SET_EMITTER_SUBSCRIPTIONS:
18
+        return (
19
+            assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
20
+
14 21
     case UPDATE_DIAL_IN_NUMBERS_FAILED:
15 22
         return {
16 23
             ...state,

+ 3
- 3
react/features/mobile/external-api/middleware.js Näytä tiedosto

@@ -159,9 +159,9 @@ function _sendEvent(
159 159
         { getState }: { getState: Function },
160 160
         name: string,
161 161
         data: Object) {
162
-    // The JavaScript App needs to provide uniquely identifying information
163
-    // to the native ExternalAPI module so that the latter may match the former
164
-    // to the native JitsiMeetView which hosts it.
162
+    // The JavaScript App needs to provide uniquely identifying information to
163
+    // the native ExternalAPI module so that the latter may match the former to
164
+    // the native JitsiMeetView which hosts it.
165 165
     const { app } = getState()['features/app'];
166 166
 
167 167
     if (app) {

+ 0
- 46
react/features/mobile/invite-search/actionTypes.js Näytä tiedosto

@@ -1,46 +0,0 @@
1
-/**
2
- * The type of redux action to set InviteSearch's event subscriptions.
3
- *
4
- * {
5
- *     type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
6
- *     subscriptions: Array|undefined
7
- * }
8
- *
9
- * @protected
10
- */
11
-export const _SET_INVITE_SEARCH_SUBSCRIPTIONS
12
-  = Symbol('_SET_INVITE_SEARCH_SUBSCRIPTIONS');
13
-
14
-
15
-/**
16
- * The type of the action which signals a request to launch the native invite
17
- * dialog.
18
- *
19
- * {
20
- *     type: LAUNCH_NATIVE_INVITE
21
- * }
22
- */
23
-export const LAUNCH_NATIVE_INVITE = Symbol('LAUNCH_NATIVE_INVITE');
24
-
25
-/**
26
- * The type of the action which signals that native invites were sent
27
- * successfully.
28
- *
29
- * {
30
- *     type: SEND_INVITE_SUCCESS,
31
- *     inviteScope: string
32
- * }
33
- */
34
-export const SEND_INVITE_SUCCESS = Symbol('SEND_INVITE_SUCCESS');
35
-
36
-/**
37
- * The type of the action which signals that native invites failed to send
38
- * successfully.
39
- *
40
- * {
41
- *     type: SEND_INVITE_FAILURE,
42
- *     items: Array<*>,
43
- *     inviteScope: string
44
- * }
45
- */
46
-export const SEND_INVITE_FAILURE = Symbol('SEND_INVITE_FAILURE');

+ 0
- 50
react/features/mobile/invite-search/actions.js Näytä tiedosto

@@ -1,50 +0,0 @@
1
-// @flow
2
-
3
-import {
4
-    LAUNCH_NATIVE_INVITE,
5
-    SEND_INVITE_SUCCESS,
6
-    SEND_INVITE_FAILURE
7
-} from './actionTypes';
8
-
9
-/**
10
- * Launches the native invite dialog.
11
- *
12
- * @returns {{
13
- *     type: LAUNCH_NATIVE_INVITE
14
- * }}
15
- */
16
-export function launchNativeInvite() {
17
-    return {
18
-        type: LAUNCH_NATIVE_INVITE
19
-    };
20
-}
21
-
22
-/**
23
- * Indicates that all native invites were sent successfully.
24
- *
25
- * @param  {string} inviteScope - Scope identifier for the invite success. This
26
- * is used to look up relevant information on the native side.
27
- * @returns {void}
28
- */
29
-export function sendInviteSuccess(inviteScope: string) {
30
-    return {
31
-        type: SEND_INVITE_SUCCESS,
32
-        inviteScope
33
-    };
34
-}
35
-
36
-/**
37
- * Indicates that some native invites failed to send successfully.
38
- *
39
- * @param  {Array<*>} items - Invite items that failed to send.
40
- * @param  {string} inviteScope - Scope identifier for the invite failure. This
41
- * is used to look up relevant information on the native side.
42
- * @returns {void}
43
- */
44
-export function sendInviteFailure(items: Array<*>, inviteScope: string) {
45
-    return {
46
-        type: SEND_INVITE_FAILURE,
47
-        items,
48
-        inviteScope
49
-    };
50
-}

+ 0
- 5
react/features/mobile/invite-search/index.js Näytä tiedosto

@@ -1,5 +0,0 @@
1
-export * from './actions';
2
-export * from './actionTypes';
3
-
4
-import './reducer';
5
-import './middleware';

+ 0
- 233
react/features/mobile/invite-search/middleware.js Näytä tiedosto

@@ -1,233 +0,0 @@
1
-/* @flow */
2
-
3
-import i18next from 'i18next';
4
-import { NativeModules, NativeEventEmitter } from 'react-native';
5
-
6
-import { MiddlewareRegistry } from '../../base/redux';
7
-import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
8
-import { getInviteURL } from '../../base/connection';
9
-import {
10
-    getInviteResultsForQuery,
11
-    isAddPeopleEnabled,
12
-    isDialOutEnabled,
13
-    sendInvitesForItems
14
-} from '../../invite';
15
-import { inviteVideoRooms } from '../../videosipgw';
16
-
17
-import { sendInviteSuccess, sendInviteFailure } from './actions';
18
-import {
19
-    _SET_INVITE_SEARCH_SUBSCRIPTIONS,
20
-    LAUNCH_NATIVE_INVITE,
21
-    SEND_INVITE_SUCCESS,
22
-    SEND_INVITE_FAILURE
23
-} from './actionTypes';
24
-
25
-/**
26
- * Middleware that captures Redux actions and uses the InviteSearch module to
27
- * turn them into native events so the application knows about them.
28
- *
29
- * @param {Store} store - Redux store.
30
- * @returns {Function}
31
- */
32
-MiddlewareRegistry.register(store => next => action => {
33
-    const result = next(action);
34
-
35
-    switch (action.type) {
36
-
37
-    case APP_WILL_MOUNT:
38
-        return _appWillMount(store, next, action);
39
-
40
-    case APP_WILL_UNMOUNT:
41
-        store.dispatch({
42
-            type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
43
-            subscriptions: undefined
44
-        });
45
-        break;
46
-
47
-    case LAUNCH_NATIVE_INVITE:
48
-        launchNativeInvite(store);
49
-        break;
50
-
51
-    case SEND_INVITE_SUCCESS:
52
-        onSendInviteSuccess(action);
53
-        break;
54
-
55
-    case SEND_INVITE_FAILURE:
56
-        onSendInviteFailure(action);
57
-        break;
58
-    }
59
-
60
-    return result;
61
-});
62
-
63
-/**
64
- * Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
65
- * dispatched within a specific redux {@code store}.
66
- *
67
- * @param {Store} store - The redux store in which the specified {@code action}
68
- * is being dispatched.
69
- * @param {Dispatch} next - The redux dispatch function to dispatch the
70
- * specified {@code action} to the specified {@code store}.
71
- * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
72
- * being dispatched in the specified {@code store}.
73
- * @private
74
- * @returns {*}
75
- */
76
-function _appWillMount({ dispatch, getState }, next, action) {
77
-    const result = next(action);
78
-
79
-    const emitter = new NativeEventEmitter(NativeModules.InviteSearch);
80
-
81
-    const context = {
82
-        dispatch,
83
-        getState
84
-    };
85
-    const subscriptions = [
86
-        emitter.addListener(
87
-            'performQueryAction',
88
-            _onPerformQueryAction,
89
-            context),
90
-        emitter.addListener(
91
-            'performSubmitInviteAction',
92
-            _onPerformSubmitInviteAction,
93
-            context)
94
-    ];
95
-
96
-    dispatch({
97
-        type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
98
-        subscriptions
99
-    });
100
-
101
-    return result;
102
-}
103
-
104
-/**
105
- * Sends a request to the native counterpart of InviteSearch to launch a native.
106
- * invite search.
107
- *
108
- * @param {Object} store - The redux store.
109
- * @private
110
- * @returns {void}
111
- */
112
-function launchNativeInvite(store: { getState: Function }) {
113
-    // The JavaScript App needs to provide uniquely identifying information
114
-    // to the native module so that the latter may match the former
115
-    // to the native JitsiMeetView which hosts it.
116
-    const { app } = store.getState()['features/app'];
117
-
118
-    if (app) {
119
-        const { externalAPIScope } = app.props;
120
-
121
-        if (externalAPIScope) {
122
-            NativeModules.InviteSearch.launchNativeInvite(externalAPIScope);
123
-        }
124
-    }
125
-}
126
-
127
-/**
128
- * Sends a notification to the native counterpart of InviteSearch that all
129
- * invites were sent successfully.
130
- *
131
- * @param  {Object} action - The redux action {@code SEND_INVITE_SUCCESS} which
132
- * is being dispatched.
133
- * @returns {void}
134
- */
135
-function onSendInviteSuccess({ inviteScope }) {
136
-    NativeModules.InviteSearch.inviteSucceeded(inviteScope);
137
-}
138
-
139
-/**
140
- * Sends a notification to the native counterpart of InviteSearch that some
141
- * invite items failed to send successfully.
142
- *
143
- * @param  {Object} action - The redux action {@code SEND_INVITE_FAILURE} which
144
- * is being dispatched.
145
- * @returns {void}
146
- */
147
-function onSendInviteFailure({ items, inviteScope }) {
148
-    NativeModules.InviteSearch.inviteFailedForItems(items, inviteScope);
149
-}
150
-
151
-/**
152
- * Handles InviteSearch's event {@code performQueryAction}.
153
- *
154
- * @param {Object} event - The details of the InviteSearch event
155
- * {@code performQueryAction}.
156
- * @returns {void}
157
- */
158
-function _onPerformQueryAction({ query, inviteScope }) {
159
-    const { getState } = this; // eslint-disable-line no-invalid-this
160
-
161
-    const state = getState();
162
-
163
-    const {
164
-        dialOutAuthUrl,
165
-        peopleSearchQueryTypes,
166
-        peopleSearchUrl
167
-    } = state['features/base/config'];
168
-
169
-    const options = {
170
-        dialOutAuthUrl,
171
-        enableAddPeople: isAddPeopleEnabled(state),
172
-        enableDialOut: isDialOutEnabled(state),
173
-        jwt: state['features/base/jwt'].jwt,
174
-        peopleSearchQueryTypes,
175
-        peopleSearchUrl
176
-    };
177
-
178
-    getInviteResultsForQuery(query, options)
179
-        .catch(() => [])
180
-        .then(results => {
181
-            const translatedResults = results.map(result => {
182
-                if (result.type === 'phone') {
183
-                    result.title = i18next.t('addPeople.telephone', {
184
-                        number: result.number
185
-                    });
186
-
187
-                    if (result.showCountryCodeReminder) {
188
-                        result.subtitle = i18next.t(
189
-                            'addPeople.countryReminder'
190
-                        );
191
-                    }
192
-                }
193
-
194
-                return result;
195
-            }).filter(result => result.type !== 'phone' || result.allowed);
196
-
197
-            NativeModules.InviteSearch.receivedResults(
198
-                translatedResults,
199
-                query,
200
-                inviteScope);
201
-        });
202
-}
203
-
204
-/**
205
- * Handles InviteSearch's event {@code performSubmitInviteAction}.
206
- *
207
- * @param {Object} event - The details of the InviteSearch event.
208
- * @returns {void}
209
- */
210
-function _onPerformSubmitInviteAction({ selectedItems, inviteScope }) {
211
-    const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
212
-    const state = getState();
213
-    const { conference } = state['features/base/conference'];
214
-    const {
215
-        inviteServiceUrl
216
-    } = state['features/base/config'];
217
-    const options = {
218
-        conference,
219
-        inviteServiceUrl,
220
-        inviteUrl: getInviteURL(state),
221
-        inviteVideoRooms,
222
-        jwt: state['features/base/jwt'].jwt
223
-    };
224
-
225
-    sendInvitesForItems(selectedItems, options)
226
-        .then(invitesLeftToSend => {
227
-            if (invitesLeftToSend.length) {
228
-                dispatch(sendInviteFailure(invitesLeftToSend, inviteScope));
229
-            } else {
230
-                dispatch(sendInviteSuccess(inviteScope));
231
-            }
232
-        });
233
-}

+ 0
- 14
react/features/mobile/invite-search/reducer.js Näytä tiedosto

@@ -1,14 +0,0 @@
1
-import { assign, ReducerRegistry } from '../../base/redux';
2
-
3
-import { _SET_INVITE_SEARCH_SUBSCRIPTIONS } from './actionTypes';
4
-
5
-ReducerRegistry.register(
6
-    'features/invite-search',
7
-    (state = {}, action) => {
8
-        switch (action.type) {
9
-        case _SET_INVITE_SEARCH_SUBSCRIPTIONS:
10
-            return assign(state, 'subscriptions', action.subscriptions);
11
-        }
12
-
13
-        return state;
14
-    });

+ 5
- 73
react/features/toolbox/components/Toolbox.native.js Näytä tiedosto

@@ -14,16 +14,11 @@ import {
14 14
     isNarrowAspectRatio,
15 15
     makeAspectRatioAware
16 16
 } from '../../base/responsive-ui';
17
-import {
18
-    InviteButton,
19
-    isAddPeopleEnabled,
20
-    isDialOutEnabled
21
-} from '../../invite';
17
+import { InviteButton } from '../../invite';
22 18
 import {
23 19
     EnterPictureInPictureToolbarButton
24 20
 } from '../../mobile/picture-in-picture';
25 21
 import { beginRoomLockRequest } from '../../room-lock';
26
-import { beginShareRoom } from '../../share-room';
27 22
 
28 23
 import {
29 24
     abstractMapDispatchToProps,
@@ -51,18 +46,6 @@ type Props = {
51 46
      */
52 47
     _audioOnly: boolean,
53 48
 
54
-    /**
55
-     * Whether or not the feature to directly invite people into the
56
-     * conference is available.
57
-     */
58
-    _enableAddPeople: boolean,
59
-
60
-    /**
61
-     * Whether or not the feature to dial out to number to join the
62
-     * conference is available.
63
-     */
64
-    _enableDialOut: boolean,
65
-
66 49
     /**
67 50
      * The indicator which determines whether the toolbox is enabled.
68 51
      */
@@ -83,11 +66,6 @@ type Props = {
83 66
      */
84 67
     _onRoomLock: Function,
85 68
 
86
-    /**
87
-     * Begins the UI procedure to share the conference/room URL.
88
-     */
89
-    _onShareRoom: Function,
90
-
91 69
     /**
92 70
      * Toggles the audio-only flag of the conference.
93 71
      */
@@ -112,7 +90,6 @@ type Props = {
112 90
     dispatch: Function
113 91
 };
114 92
 
115
-
116 93
 /**
117 94
  * Implements the conference toolbox on React Native.
118 95
  */
@@ -219,13 +196,9 @@ class Toolbox extends Component<Props> {
219 196
         const underlayColor = 'transparent';
220 197
         const {
221 198
             _audioOnly: audioOnly,
222
-            _enableAddPeople: enableAddPeople,
223
-            _enableDialOut: enableDialOut,
224 199
             _videoMuted: videoMuted
225 200
         } = this.props;
226 201
 
227
-        const showInviteButton = enableAddPeople || enableDialOut;
228
-
229 202
         /* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
230 203
 
231 204
         return (
@@ -262,24 +235,10 @@ class Toolbox extends Component<Props> {
262 235
                     onClick = { this.props._onRoomLock }
263 236
                     style = { style }
264 237
                     underlayColor = { underlayColor } />
265
-                {
266
-                    !showInviteButton
267
-                        && <ToolbarButton
268
-                            iconName = 'link'
269
-                            iconStyle = { iconStyle }
270
-                            onClick = { this.props._onShareRoom }
271
-                            style = { style }
272
-                            underlayColor = { underlayColor } />
273
-                }
274
-                {
275
-                    showInviteButton
276
-                        && <InviteButton
277
-                            enableAddPeople = { enableAddPeople }
278
-                            enableDialOut = { enableDialOut }
279
-                            iconStyle = { iconStyle }
280
-                            style = { style }
281
-                            underlayColor = { underlayColor } />
282
-                }
238
+                <InviteButton
239
+                    iconStyle = { iconStyle }
240
+                    style = { style }
241
+                    underlayColor = { underlayColor } />
283 242
                 <EnterPictureInPictureToolbarButton
284 243
                     iconStyle = { iconStyle }
285 244
                     style = { style }
@@ -344,17 +303,6 @@ function _mapDispatchToProps(dispatch) {
344 303
             dispatch(beginRoomLockRequest());
345 304
         },
346 305
 
347
-        /**
348
-         * Begins the UI procedure to share the conference/room URL.
349
-         *
350
-         * @private
351
-         * @returns {void}
352
-         * @type {Function}
353
-         */
354
-        _onShareRoom() {
355
-            dispatch(beginShareRoom());
356
-        },
357
-
358 306
         /**
359 307
          * Toggles the audio-only flag of the conference.
360 308
          *
@@ -408,22 +356,6 @@ function _mapStateToProps(state) {
408 356
          */
409 357
         _audioOnly: Boolean(conference.audioOnly),
410 358
 
411
-        /**
412
-         * Whether or not the feature to directly invite people into the
413
-         * conference is available.
414
-         *
415
-         * @type {boolean}
416
-         */
417
-        _enableAddPeople: isAddPeopleEnabled(state),
418
-
419
-        /**
420
-         * Whether or not the feature to dial out to number to join the
421
-         * conference is available.
422
-         *
423
-         * @type {boolean}
424
-         */
425
-        _enableDialOut: isDialOutEnabled(state),
426
-
427 359
         /**
428 360
          * The indicator which determines whether the toolbox is enabled.
429 361
          *

+ 11
- 43
react/features/toolbox/components/Toolbox.web.js Näytä tiedosto

@@ -21,7 +21,12 @@ import { ChatCounter } from '../../chat';
21 21
 import { openDeviceSelectionDialog } from '../../device-selection';
22 22
 import { toggleDocument } from '../../etherpad';
23 23
 import { openFeedbackDialog } from '../../feedback';
24
-import { AddPeopleDialog, InfoDialogButton } from '../../invite';
24
+import {
25
+    beginAddPeople,
26
+    InfoDialogButton,
27
+    isAddPeopleEnabled,
28
+    isDialOutEnabled
29
+} from '../../invite';
25 30
 import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts';
26 31
 import { RECORDING_TYPES, toggleRecording } from '../../recording';
27 32
 import { toggleSharedVideo } from '../../shared-video';
@@ -43,12 +48,6 @@ import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
43 48
 
44 49
 type Props = {
45 50
 
46
-    /**
47
-     * Whether or not the feature for adding people directly into the call
48
-     * is enabled.
49
-     */
50
-    _addPeopleAvailable: boolean,
51
-
52 51
     /**
53 52
      * Whether or not the chat feature is currently displayed.
54 53
      */
@@ -69,12 +68,6 @@ type Props = {
69 68
      */
70 69
     _desktopSharingEnabled: boolean,
71 70
 
72
-    /**
73
-     * Whether or not the feature for telephony to dial out to a number is
74
-     * enabled.
75
-     */
76
-    _dialOutAvailable: boolean,
77
-
78 71
     /**
79 72
      * Whether or not a dialog is displayed.
80 73
      */
@@ -399,23 +392,6 @@ class Toolbox extends Component<Props, State> {
399 392
         this.props.dispatch(openFeedbackDialog(_conference));
400 393
     }
401 394
 
402
-    /**
403
-     * Opens the dialog for inviting people directly into the conference.
404
-     *
405
-     * @private
406
-     * @returns {void}
407
-     */
408
-    _doOpenInvite() {
409
-        const { _addPeopleAvailable, _dialOutAvailable, dispatch } = this.props;
410
-
411
-        if (_addPeopleAvailable || _dialOutAvailable) {
412
-            dispatch(openDialog(AddPeopleDialog, {
413
-                enableAddPeople: _addPeopleAvailable,
414
-                enableDialOut: _dialOutAvailable
415
-            }));
416
-        }
417
-    }
418
-
419 395
     /**
420 396
      * Dispatches an action to display {@code KeyboardShortcuts}.
421 397
      *
@@ -692,8 +668,7 @@ class Toolbox extends Component<Props, State> {
692 668
      */
693 669
     _onToolbarOpenInvite() {
694 670
         sendAnalytics(createToolbarEvent('invite'));
695
-
696
-        this._doOpenInvite();
671
+        this.props.dispatch(beginAddPeople());
697 672
     }
698 673
 
699 674
     _onToolbarOpenKeyboardShortcuts: () => void;
@@ -1118,10 +1093,8 @@ function _mapStateToProps(state) {
1118 1093
         callStatsID,
1119 1094
         disableDesktopSharing,
1120 1095
         enableRecording,
1121
-        enableUserRolesBasedOnToken,
1122 1096
         iAmRecorder
1123 1097
     } = state['features/base/config'];
1124
-    const { isGuest } = state['features/base/jwt'];
1125 1098
     const { isRecording, recordingType } = state['features/recording'];
1126 1099
     const sharedVideoStatus = state['features/shared-video'].status;
1127 1100
     const { current } = state['features/side-panel'];
@@ -1134,25 +1107,20 @@ function _mapStateToProps(state) {
1134 1107
     const localParticipant = getLocalParticipant(state);
1135 1108
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1136 1109
     const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
1137
-    const isAddPeopleAvailable = !isGuest;
1138
-    const isDialOutAvailable
1139
-        = isModerator
1140
-            && conference && conference.isSIPCallingSupported()
1141
-            && (!enableUserRolesBasedOnToken || !isGuest);
1110
+    const addPeopleEnabled = isAddPeopleEnabled(state);
1111
+    const dialOutEnabled = isDialOutEnabled(state);
1142 1112
 
1143 1113
     return {
1144
-        _addPeopleAvailable: isAddPeopleAvailable,
1145 1114
         _chatOpen: current === 'chat_container',
1146 1115
         _conference: conference,
1147 1116
         _desktopSharingEnabled: desktopSharingEnabled,
1148 1117
         _desktopSharingDisabledByConfig: disableDesktopSharing,
1149
-        _dialOutAvailable: isDialOutAvailable,
1150 1118
         _dialog: Boolean(state['features/base/dialog'].component),
1151 1119
         _editingDocument: Boolean(state['features/etherpad'].editing),
1152 1120
         _etherpadInitialized: Boolean(state['features/etherpad'].initialized),
1153 1121
         _feedbackConfigured: Boolean(callStatsID),
1154
-        _hideInviteButton: iAmRecorder
1155
-            || (!isAddPeopleAvailable && !isDialOutAvailable),
1122
+        _hideInviteButton:
1123
+            iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
1156 1124
         _isRecording: isRecording,
1157 1125
         _fullScreen: fullScreen,
1158 1126
         _localParticipantID: localParticipant.id,

Loading…
Peruuta
Tallenna