[RN] Add an example how to consume the public SDK invite API

This commit is contained in:
Lyubo Marinov 2018-05-06 22:39:20 -05:00
parent 85612b9ae1
commit 7ffdaf59c7
8 changed files with 253 additions and 58 deletions

View File

@ -23,11 +23,15 @@ import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetView; import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener; import org.jitsi.meet.sdk.JitsiMeetViewListener;
import org.jitsi.meet.sdk.invite.AddPeopleController; import org.jitsi.meet.sdk.invite.AddPeopleController;
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
import org.jitsi.meet.sdk.invite.InviteController;
import org.jitsi.meet.sdk.invite.InviteControllerListener; import org.jitsi.meet.sdk.invite.InviteControllerListener;
import com.calendarevents.CalendarEventsPackage; import com.calendarevents.CalendarEventsPackage;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -43,6 +47,14 @@ import java.util.Map;
* {@code react-native run-android}. * {@code react-native run-android}.
*/ */
public class MainActivity extends JitsiMeetActivity { public class MainActivity extends JitsiMeetActivity {
/**
* The query to perform through {@link AddPeopleController} when the
* {@code InviteButton} is tapped in order to exercise the public API of the
* feature invite. If {@code null}, the {@code InviteButton} will not be
* rendered.
*/
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
@Override @Override
protected JitsiMeetView initializeView() { protected JitsiMeetView initializeView() {
JitsiMeetView view = super.initializeView(); JitsiMeetView view = super.initializeView();
@ -93,23 +105,68 @@ public class MainActivity extends JitsiMeetActivity {
} }
}); });
view.getInviteController().setListener( // inviteController
new InviteControllerListener() { final InviteController inviteController
public void beginAddPeople( = view.getInviteController();
AddPeopleController addPeopleController) {
// Log with the tag "ReactNative" in order to have the inviteController.setListener(new InviteControllerListener() {
// log visible in react-native log-android as well. public void beginAddPeople(
Log.d( AddPeopleController addPeopleController) {
"ReactNative", onInviteControllerBeginAddPeople(
InviteControllerListener.class.getSimpleName() inviteController,
+ ".beginAddPeople"); addPeopleController);
} }
}); });
inviteController.setAddPeopleEnabled(
ADD_PEOPLE_CONTROLLER_QUERY != null);
inviteController.setDialOutEnabled(
inviteController.isAddPeopleEnabled());
} }
return view; return view;
} }
private void onAddPeopleControllerInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
// Technically, endAddPeople will automatically be invoked if there are
// no failedInviteees i.e. the invite succeeeded for all specified
// invitees.
addPeopleController.endAddPeople();
}
private void onAddPeopleControllerReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
int size = results.size();
if (size > 0) {
// Exercise AddPeopleController's inviteById implementation.
List<String> ids = new ArrayList<>(size);
for (Map<String, Object> result : results) {
Object id = result.get("id");
if (id != null) {
ids.add(id.toString());
}
}
addPeopleController.inviteById(ids);
return;
}
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
addPeopleController.endAddPeople();
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do // As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
@ -122,9 +179,61 @@ public class MainActivity extends JitsiMeetActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
private void onInviteControllerBeginAddPeople(
InviteController inviteController,
AddPeopleController addPeopleController) {
// Log with the tag "ReactNative" in order to have the log visible in
// react-native log-android as well.
Log.d(
"ReactNative",
InviteControllerListener.class.getSimpleName() + ".beginAddPeople");
String query = ADD_PEOPLE_CONTROLLER_QUERY;
if (query != null
&& (inviteController.isAddPeopleEnabled()
|| inviteController.isDialOutEnabled())) {
addPeopleController.setListener(new AddPeopleControllerListener() {
public void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
onAddPeopleControllerInviteSettled(
addPeopleController,
failedInvitees);
}
public void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
onAddPeopleControllerReceivedResults(
addPeopleController,
results, query);
}
});
addPeopleController.performQuery(query);
} else {
// XXX Explicitly invoke endAddPeople on addPeopleController;
// otherwise, it is going to be memory-leaked in the associated
// InviteController and no subsequent InviteButton clicks/taps will
// be delivered.
addPeopleController.endAddPeople();
}
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(
CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults); int requestCode,
super.onRequestPermissionsResult(requestCode, permissions, grantResults); String[] permissions,
} int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(
requestCode,
permissions,
grantResults);
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults);
}
} }

View File

@ -122,6 +122,11 @@ public class JitsiMeetActivity extends AppCompatActivity {
JitsiMeetView view = initializeView(); JitsiMeetView view = initializeView();
if (view != null) { if (view != null) {
// XXX Allow extenders who override initializeView() to configure
// the view before the first loadURL(). Probably works around a
// problem related to ReactRootView#setAppProperties().
view.loadURL(null);
this.view = view; this.view = view;
setContentView(this.view); setContentView(this.view);
} }
@ -144,8 +149,6 @@ public class JitsiMeetActivity extends AppCompatActivity {
} }
view.setWelcomePageEnabled(welcomePageEnabled); view.setWelcomePageEnabled(welcomePageEnabled);
view.loadURL(null);
return view; return view;
} }

View File

@ -116,7 +116,7 @@ public class AddPeopleController {
if(items.containsKey(id)) { if(items.containsKey(id)) {
WritableNativeMap map = new WritableNativeMap(); WritableNativeMap map = new WritableNativeMap();
map.merge(items.get(ids)); map.merge(items.get(id));
invitees.pushMap(map); invitees.pushMap(map);
} else { } else {
// If the id doesn't exist in the map, we can't do anything, // If the id doesn't exist in the map, we can't do anything,
@ -138,14 +138,14 @@ public class AddPeopleController {
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap()); jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
} }
listener.inviteSettled(this, jFailedInvitees); listener.onInviteSettled(this, jFailedInvitees);
} }
} }
/** /**
* Start a search for entities to invite with the given query. Results will * Start a search for entities to invite with the given query. Results will
* be returned through the associated AddPeopleControllerListener's * be returned through the associated AddPeopleControllerListener's
* onReceiveResults method. * onReceivedResults method.
* *
* @param query * @param query
*/ */
@ -188,7 +188,7 @@ public class AddPeopleController {
jvmResults.add(map.toHashMap()); jvmResults.add(map.toHashMap());
} }
listener.onReceiveResults(this, jvmResults, query); listener.onReceivedResults(this, jvmResults, query);
} }
} }

View File

@ -21,29 +21,36 @@ import java.util.Map;
public interface AddPeopleControllerListener { public interface AddPeopleControllerListener {
/** /**
* Called when results are received for a query called through AddPeopleController.query() * Called when the call to {@link AddPeopleController#inviteById(List)}
* completes.
* *
* @param addPeopleController * @param addPeopleController the active {@link AddPeopleController} for
* @param results a List of Map<String, Object> objects that represent items returned by the query. * this invite flow. This object should be cleaned up by calling
* The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone". * {@link AddPeopleController#endAddPeople()} if the user exits the invite
* "user" types have properties at "id", "name", and "avatar" * flow. Otherwise, it can stay active if the user will attempt to invite
* "videosipgw" types have properties at "id" and "name" * @param failedInvitees a {@code List} of {@code Map<String, Object>}
* "phone" types have properties at "number", "title", "and "subtitle" * dictionaries that represent the invitations that failed. The data type of
* @param query the query that generated the given results * the objects is identical to the results returned in onReceivedResuls.
*/ */
void onReceiveResults(AddPeopleController addPeopleController, List<Map<String, Object>> results, String query); void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees);
/** /**
* Called when the call to {@link AddPeopleController#inviteById(List)} completes, but the * Called when results are received for a query called through
* invitation fails for one or more of the selected items. * AddPeopleController.query().
* *
* @param addPeopleController the active {@link AddPeopleController} for this invite flow. This object * @param addPeopleController
* should be cleaned up by calling {@link AddPeopleController#endAddPeople()} if * @param results a List of Map<String, Object> objects that represent items
* the user exits the invite flow. Otherwise, it can stay active if the user * returned by the query. The object at key "type" describes the type of
* will attempt to invite * item: "user", "videosipgw" (conference room), or "phone". "user" types
* @param failedInvitees a {@code List} of {@code Map<String, Object>} dictionaries that represent the * have properties at "id", "name", and "avatar". "videosipgw" types have
* invitations that failed. The data type of the objects is identical to the results * properties at "id" and "name". "phone" types have properties at "number",
* returned in onReceiveResuls. * "title", "and "subtitle"
* @param query the query that generated the given results
*/ */
void inviteSettled(AddPeopleController addPeopleController, List<Map<String, Object>> failedInvitees); void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query);
} }

View File

@ -61,10 +61,6 @@ public class InviteController {
this.externalAPIScope = externalAPIScope; this.externalAPIScope = externalAPIScope;
} }
public InviteControllerListener getListener() {
return listener;
}
void beginAddPeople(ReactApplicationContext reactContext) { void beginAddPeople(ReactApplicationContext reactContext) {
InviteControllerListener listener = getListener(); InviteControllerListener listener = getListener();
@ -102,6 +98,10 @@ public class InviteController {
} }
} }
public InviteControllerListener getListener() {
return listener;
}
/** /**
* Sends JavaScript event to submit invitations to the given item ids * Sends JavaScript event to submit invitations to the given item ids
* *
@ -203,7 +203,7 @@ public class InviteController {
/** /**
* Starts a query for users to invite to the conference. Results will be * Starts a query for users to invite to the conference. Results will be
* returned through the {@link AddPeopleControllerListener#onReceiveResults(AddPeopleController, List, String)} * returned through the {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}
* method. * method.
* *
* @param query {@code String} to use for the query * @param query {@code String} to use for the query
@ -211,8 +211,8 @@ public class InviteController {
void performQuery(AddPeopleController addPeopleController, String query) { void performQuery(AddPeopleController addPeopleController, String query) {
WritableNativeMap params = new WritableNativeMap(); WritableNativeMap params = new WritableNativeMap();
params.putString("externalAPIScope", externalAPIScope);
params.putString("addPeopleControllerScope", addPeopleController.getUuid()); params.putString("addPeopleControllerScope", addPeopleController.getUuid());
params.putString("externalAPIScope", externalAPIScope);
params.putString("query", query); params.putString("query", query);
ReactContextUtils.emitEvent( ReactContextUtils.emitEvent(
addPeopleController.getReactApplicationContext(), addPeopleController.getReactApplicationContext(),

View File

@ -18,6 +18,10 @@
#import <JitsiMeet/JitsiMeet.h> #import <JitsiMeet/JitsiMeet.h>
@interface ViewController : UIViewController<JitsiMeetViewDelegate, JMInviteControllerDelegate> @interface ViewController
: UIViewController<
JitsiMeetViewDelegate,
JMAddPeopleControllerDelegate,
JMInviteControllerDelegate>
@end @end

View File

@ -16,6 +16,13 @@
#import "ViewController.h" #import "ViewController.h"
/**
* The query to perform through JMAddPeopleController when the InviteButton is
* tapped in order to exercise the public API of the feature invite. If nil, the
* InviteButton will not be rendered.
*/
static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
@interface ViewController () @interface ViewController ()
@end @end
@ -31,7 +38,12 @@
view.delegate = self; view.delegate = self;
view.inviteController.delegate = self; // inviteController
JMInviteController *inviteController = view.inviteController;
inviteController.delegate = self;
inviteController.addPeopleEnabled
= inviteController.dialOutEnabled
= ADD_PEOPLE_CONTROLLER_QUERY != nil;
#endif // #ifdef DEBUG #endif // #ifdef DEBUG
@ -46,6 +58,8 @@
#if DEBUG #if DEBUG
// JitsiMeetViewDelegate
void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) { void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
NSLog( NSLog(
@"[%s:%d] JitsiMeetViewDelegate %@ %@", @"[%s:%d] JitsiMeetViewDelegate %@ %@",
@ -76,14 +90,72 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
_onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data); _onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data);
} }
// JMInviteControllerDelegate
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController { - (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
NSLog( NSLog(
@"[%s:%d] JMInviteControllerDelegate %s", @"[%s:%d] JMInviteControllerDelegate %s",
__FILE__, __LINE__, __FUNCTION__); __FILE__, __LINE__, __FUNCTION__);
NSString *query = ADD_PEOPLE_CONTROLLER_QUERY;
JitsiMeetView *view = (JitsiMeetView *) self.view;
JMInviteController *inviteController = view.inviteController;
if (query
&& (inviteController.addPeopleEnabled
|| inviteController.dialOutEnabled)) {
addPeopleController.delegate = self;
[addPeopleController performQuery:query];
} else {
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated JMInviteController
// and no subsequent InviteButton clicks/taps will be delivered.
[addPeopleController endAddPeople];
}
}
// JMAddPeopleControllerDelegate
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
forQuery:(NSString * _Nonnull)query {
NSUInteger count = results.count;
if (count) {
// Exercise JMAddPeopleController's inviteById: implementation.
NSMutableArray *ids = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; ++i) {
ids[i] = results[i][@"id"];
}
[controller inviteById:ids];
// Exercise JMInviteController's invite:withCompletion: implementation.
//
// XXX Technically, only at most one of the two exercises will result in
// an actual invitation eventually.
JitsiMeetView *view = (JitsiMeetView *) self.view;
JMInviteController *inviteController = view.inviteController;
[inviteController invite:results withCompletion:nil];
return;
}
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it // XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
// is going to be memory-leaked in the associated JMInviteController and no // is going to be memory-leaked in the associated JMInviteController and no
// subsequent InviteButton clicks/taps will be delivered. // subsequent InviteButton clicks/taps will be delivered.
[controller endAddPeople];
}
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController {
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
// is going to be memory-leaked in the associated JMInviteController and no
// subsequent InviteButton clicks/taps will be delivered. Technically,
// endAddPeople will automatically be invoked if there are no
// failedInviteees i.e. the invite succeeeded for all specified invitees.
[addPeopleController endAddPeople]; [addPeopleController endAddPeople];
} }

View File

@ -70,15 +70,6 @@ class InviteButton extends Component<Props> {
...props ...props
} = this.props; } = this.props;
if (_SHARE_ROOM_TOOLBAR_BUTTON) {
return (
<ToolbarButton
iconName = 'link'
onClick = { _onShareRoom }
{ ...props } />
);
}
if (_addPeopleEnabled || _dialOutEnabled) { if (_addPeopleEnabled || _dialOutEnabled) {
return ( return (
<ToolbarButton <ToolbarButton
@ -88,6 +79,15 @@ class InviteButton extends Component<Props> {
); );
} }
if (_SHARE_ROOM_TOOLBAR_BUTTON) {
return (
<ToolbarButton
iconName = 'link'
onClick = { _onShareRoom }
{ ...props } />
);
}
return null; return null;
} }
} }