From 1d8ee9d32f649f75d58fc0441f3c37c4cb44a8c8 Mon Sep 17 00:00:00 2001 From: Lyubo Marinov Date: Wed, 6 Sep 2017 13:00:34 -0500 Subject: [PATCH] [RN] Reduce maintenance JitsiMeetViewListener currently has methods of one and the same pattern so adding new methods i.e. events i.e. redux action types is a question of repetition in the Java source code. Speed up the support of new events by trying to deal with them in a generic way. The same goes for JitsiMeetViewDelegate. --- .../org/jitsi/meet/sdk/ExternalAPIModule.java | 83 ++++++++++++++----- ios/sdk/src/ExternalAPI.m | 43 ++++++---- 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java index 138745c6b..0982ff246 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java @@ -25,7 +25,12 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator; import org.jitsi.meet.sdk.JitsiMeetView; import org.jitsi.meet.sdk.JitsiMeetViewListener; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; /** * Module implementing a simple API to enable a proximity sensor-controlled @@ -35,10 +40,60 @@ import java.util.HashMap; */ class ExternalAPIModule extends ReactContextBaseJavaModule { /** - * React Native module name. + * The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e. + * redux action types. + */ + private static final Map JITSI_MEET_VIEW_LISTENER_METHODS + = new HashMap<>(); + + /** + * The name of this module to be used in the React Native bridge. */ private static final String MODULE_NAME = "ExternalAPI"; + static { + // Figure out the mapping between the JitsiMeetViewListener methods + // and the events i.e. redux action types. + Pattern onPattern = Pattern.compile("^on[A-Z]+"); + Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)"); + + for (Method method : JitsiMeetViewListener.class.getDeclaredMethods()) { + // * The method must be public (because it is declared by an + // interface). + // * The method must be/return void. + if (!Modifier.isPublic(method.getModifiers()) + || !Void.TYPE.equals(method.getReturnType())) { + continue; + } + + // * The method name must start with "on" followed by a + // capital/uppercase letter (in agreement with the camelcase + // coding style customary to Java in general and the projects of + // the Jitsi community in particular). + String name = method.getName(); + + if (!onPattern.matcher(name).find()) { + continue; + } + + // * The method must accept/have exactly 1 parameter of a type + // assignable from HashMap. + Class[] parameterTypes = method.getParameterTypes(); + + if (parameterTypes.length != 1 + || !parameterTypes[0].isAssignableFrom(HashMap.class)) { + continue; + } + + // Convert the method name to an event name. + name + = camelcasePattern.matcher(name.substring(2)) + .replaceAll("$1_$2") + .toUpperCase(Locale.ROOT); + JITSI_MEET_VIEW_LISTENER_METHODS.put(name, method); + } + } + /** * Initializes a new module instance. There shall be a single instance of * this module throughout the lifetime of the application. @@ -85,26 +140,14 @@ class ExternalAPIModule extends ReactContextBaseJavaModule { return; } - switch (name) { - case "CONFERENCE_FAILED": - listener.onConferenceFailed(toHashMap(data)); - break; + Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name); - case "CONFERENCE_JOINED": - listener.onConferenceJoined(toHashMap(data)); - break; - - case "CONFERENCE_LEFT": - listener.onConferenceLeft(toHashMap(data)); - break; - - case "CONFERENCE_WILL_JOIN": - listener.onConferenceWillJoin(toHashMap(data)); - break; - - case "CONFERENCE_WILL_LEAVE": - listener.onConferenceWillLeave(toHashMap(data)); - break; + if (method != null) { + try { + method.invoke(listener, toHashMap(data)); + } catch (ReflectiveOperationException roe) { + throw new RuntimeException(roe); + } } } diff --git a/ios/sdk/src/ExternalAPI.m b/ios/sdk/src/ExternalAPI.m index 647574734..5e7a2f4af 100644 --- a/ios/sdk/src/ExternalAPI.m +++ b/ios/sdk/src/ExternalAPI.m @@ -52,26 +52,33 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name return; } - if ([name isEqualToString:@"CONFERENCE_FAILED"] - && [delegate respondsToSelector:@selector(conferenceFailed:)]) { - [delegate conferenceFailed:data]; + SEL sel = NSSelectorFromString([self methodNameFromEventName:name]); - } else if ([name isEqualToString:@"CONFERENCE_JOINED"] - && [delegate respondsToSelector:@selector(conferenceJoined:)]) { - [delegate conferenceJoined:data]; - - } else if ([name isEqualToString:@"CONFERENCE_LEFT"] - && [delegate respondsToSelector:@selector(conferenceLeft:)]) { - [delegate conferenceLeft:data]; - - } else if ([name isEqualToString:@"CONFERENCE_WILL_JOIN"] - && [delegate respondsToSelector:@selector(conferenceWillJoin:)]) { - [delegate conferenceWillJoin:data]; - - } else if ([name isEqualToString:@"CONFERENCE_WILL_LEAVE"] - && [delegate respondsToSelector:@selector(conferenceWillLeave:)]) { - [delegate conferenceWillLeave:data]; + if (sel && [delegate respondsToSelector:sel]) { + [delegate performSelector:sel withObject:data]; } } +/** + * Converts a specific event name i.e. redux action type description to a + * method name. + * + * @param eventName The event name to convert to a method name. + * @return A method name constructed from the specified {@code eventName}. + */ +- (NSString *)methodNameFromEventName:(NSString *)eventName { + NSMutableString *methodName + = [NSMutableString stringWithCapacity:eventName.length]; + + for (NSString *c in [eventName componentsSeparatedByString:@"_"]) { + if (c.length) { + [methodName appendString: + methodName.length ? c.capitalizedString : c.lowercaseString]; + } + } + [methodName appendString:@":"]; + + return methodName; +} + @end