This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,11 @@
<manifest package="org.unimodules.adapters.react"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data
android:name="org.unimodules.core.AppLoader#react-native-headless"
android:value="org.unimodules.adapters.react.apploader.RNHeadlessAppLoader"/>
</application>
</manifest>

View File

@ -0,0 +1,48 @@
package org.unimodules.adapters.react;
import com.facebook.react.bridge.Dynamic;
import org.unimodules.core.arguments.MapArguments;
import org.unimodules.core.arguments.ReadableArguments;
public class ArgumentsHelper {
public static Object getNativeArgumentForExpectedClass(Dynamic argument, Class<?> expectedArgumentClass) {
switch (argument.getType()) {
case String:
return argument.asString();
case Map:
if (expectedArgumentClass.isAssignableFrom(ReadableArguments.class)) {
return new MapArguments(argument.asMap().toHashMap());
}
return argument.asMap().toHashMap();
case Array:
return argument.asArray().toArrayList();
case Number:
// Argument of type .Number is remembered as Double by default.
Double doubleArgument = argument.asDouble();
// We have to provide ExportedModule with proper Number value
if (expectedArgumentClass == byte.class || expectedArgumentClass == Byte.class) {
return doubleArgument.byteValue();
} else if (expectedArgumentClass == short.class || expectedArgumentClass == Short.class) {
return doubleArgument.shortValue();
} else if (expectedArgumentClass == int.class || expectedArgumentClass == Integer.class) {
return doubleArgument.intValue();
} else if (expectedArgumentClass == float.class || expectedArgumentClass == Float.class) {
return doubleArgument.floatValue();
} else if (expectedArgumentClass == long.class || expectedArgumentClass == Long.class) {
return doubleArgument.longValue();
} else {
return doubleArgument;
}
case Boolean:
return argument.asBoolean();
case Null:
return null;
default:
// JS argument is not null, however we can't recognize the type.
throw new RuntimeException(
"Don't know how to convert React Native argument of type " + argument.getType() + " to native."
);
}
}
}

View File

@ -0,0 +1,73 @@
package org.unimodules.adapters.react;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import org.unimodules.adapters.react.views.SimpleViewManagerAdapter;
import org.unimodules.adapters.react.views.ViewGroupManagerAdapter;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.interfaces.InternalModule;
import java.util.ArrayList;
import java.util.List;
/**
* An adapter over {@link ModuleRegistry}, compatible with React (implementing {@link ReactPackage}).
* Provides React Native with native modules and view managers,
* which in turn are created by packages provided by {@link ReactModuleRegistryProvider}.
*/
public class ModuleRegistryAdapter implements ReactPackage {
protected ReactModuleRegistryProvider mModuleRegistryProvider;
protected ReactAdapterPackage mReactAdapterPackage = new ReactAdapterPackage();
public ModuleRegistryAdapter(ReactModuleRegistryProvider moduleRegistryProvider) {
mModuleRegistryProvider = moduleRegistryProvider;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
ModuleRegistry moduleRegistry = mModuleRegistryProvider.get(reactContext);
for (InternalModule internalModule : mReactAdapterPackage.createInternalModules(reactContext)) {
moduleRegistry.registerInternalModule(internalModule);
}
return getNativeModulesFromModuleRegistry(reactContext, moduleRegistry);
}
protected List<NativeModule> getNativeModulesFromModuleRegistry(ReactApplicationContext reactContext, ModuleRegistry moduleRegistry) {
List<NativeModule> nativeModulesList = new ArrayList<>(2);
nativeModulesList.add(new NativeModulesProxy(reactContext, moduleRegistry));
// Add listener that will notify org.unimodules.core.ModuleRegistry when all modules are ready
nativeModulesList.add(new ModuleRegistryReadyNotifier(moduleRegistry));
ReactPackagesProvider reactPackagesProvider = moduleRegistry.getModule(ReactPackagesProvider.class);
for (ReactPackage reactPackage : reactPackagesProvider.getReactPackages()) {
nativeModulesList.addAll(reactPackage.createNativeModules(reactContext));
}
return nativeModulesList;
}
@Override
@SuppressWarnings("unchecked")
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagerList = new ArrayList<>(mModuleRegistryProvider.getReactViewManagers(reactContext));
for (org.unimodules.core.ViewManager viewManager : mModuleRegistryProvider.getViewManagers(reactContext)) {
switch (viewManager.getViewManagerType()) {
case GROUP:
viewManagerList.add(new ViewGroupManagerAdapter(viewManager));
break;
case SIMPLE:
viewManagerList.add(new SimpleViewManagerAdapter(viewManager));
break;
}
}
return viewManagerList;
}
}

View File

@ -0,0 +1,30 @@
package org.unimodules.adapters.react;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.NativeModule;
import org.unimodules.core.ModuleRegistry;
/**
* {@link ModuleRegistryReadyNotifier} is exported as a native module
* to React Native and when {@link com.facebook.react.ReactInstanceManager}
* notifies {@link com.facebook.react.bridge.NativeModule} of being ready
* ({@link NativeModule#initialize()}) it delegates the call to {@link ModuleRegistry}.
*/
public class ModuleRegistryReadyNotifier extends BaseJavaModule {
private ModuleRegistry mModuleRegistry;
public ModuleRegistryReadyNotifier(ModuleRegistry moduleRegistry) {
mModuleRegistry = moduleRegistry;
}
@Override
public String getName() {
return null;
}
@Override
public void initialize() {
mModuleRegistry.ensureIsInitialized();
}
}

View File

@ -0,0 +1,204 @@
package org.unimodules.adapters.react;
import android.util.SparseArray;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableType;
import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.ViewManager;
import org.unimodules.core.interfaces.ExpoMethod;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* A wrapper/proxy for all {@link ExportedModule}s, gets exposed as {@link com.facebook.react.bridge.NativeModule},
* so that JS code can call methods of the internal modules.
*/
public class NativeModulesProxy extends ReactContextBaseJavaModule {
private final static String NAME = "NativeUnimoduleProxy";
private final static String VIEW_MANAGERS_NAMES_KEY = "viewManagersNames";
private final static String MODULES_CONSTANTS_KEY = "modulesConstants";
private final static String EXPORTED_METHODS_KEY = "exportedMethods";
private final static String METHOD_INFO_KEY = "key";
private final static String METHOD_INFO_NAME = "name";
private final static String METHOD_INFO_ARGUMENTS_COUNT = "argumentsCount";
private final static String UNEXPECTED_ERROR = "E_UNEXPECTED_ERROR";
private final static String UNDEFINED_METHOD_ERROR = "E_UNDEFINED_METHOD";
private final static String ARGS_TYPES_MISMATCH_ERROR = "E_ARGS_TYPES_MISMATCH";
private ModuleRegistry mModuleRegistry;
private Map<String, Map<String, Integer>> mExportedMethodsKeys;
private Map<String, SparseArray<String>> mExportedMethodsReverseKeys;
public NativeModulesProxy(ReactApplicationContext context, ModuleRegistry moduleRegistry) {
super(context);
mModuleRegistry = moduleRegistry;
mExportedMethodsKeys = new HashMap<>();
mExportedMethodsReverseKeys = new HashMap<>();
}
@Override
public String getName() {
return NAME;
}
@Nullable
@Override
public Map<String, Object> getConstants() {
mModuleRegistry.ensureIsInitialized();
Collection<ExportedModule> exportedModules = mModuleRegistry.getAllExportedModules();
Collection<ViewManager> viewManagers = mModuleRegistry.getAllViewManagers();
Map<String, Object> modulesConstants = new HashMap<>(exportedModules.size());
Map<String, Object> exportedMethodsMap = new HashMap<>(exportedModules.size());
List<String> viewManagersNames = new ArrayList<>(viewManagers.size());
for (ExportedModule exportedModule : exportedModules) {
String moduleName = exportedModule.getName();
modulesConstants.put(moduleName, exportedModule.getConstants());
List<Map<String, Object>> exportedMethods = transformExportedMethodsMap(exportedModule.getExportedMethods());
assignExportedMethodsKeys(moduleName, exportedMethods);
exportedMethodsMap.put(moduleName, exportedMethods);
}
for (ViewManager viewManager : viewManagers) {
viewManagersNames.add(viewManager.getName());
}
Map<String, Object> constants = new HashMap<>(2);
constants.put(MODULES_CONSTANTS_KEY, modulesConstants);
constants.put(EXPORTED_METHODS_KEY, exportedMethodsMap);
constants.put(VIEW_MANAGERS_NAMES_KEY, viewManagersNames);
return constants;
}
/**
* The only exported {@link ReactMethod}.
* JavaScript can call native modules' exported methods ({@link ExpoMethod}) using this method as a proxy.
* For native {@link ExpoMethod} `void put(String key, int value)` in `NativeDictionary` module
* JavaScript could call `NativeModulesProxy.callMethod("NativeDictionary", "put", ["key", 42])`
* or `NativeModulesProxy.callMethod("NativeDictionary", 2, ["key", 42])`, where the second argument
* is a method's constant key.
*/
@ReactMethod
public void callMethod(String moduleName, Dynamic methodKeyOrName, ReadableArray arguments, final Promise promise) {
String methodName;
if (methodKeyOrName.getType() == ReadableType.String) {
methodName = methodKeyOrName.asString();
} else if (methodKeyOrName.getType() == ReadableType.Number) {
methodName = mExportedMethodsReverseKeys.get(moduleName).get(methodKeyOrName.asInt());
} else {
promise.reject(UNEXPECTED_ERROR, "Method key is neither a String nor an Integer -- don't know how to map it to method name.");
return;
}
try {
List<Object> nativeArguments = getNativeArgumentsForMethod(arguments, mModuleRegistry.getExportedModule(moduleName).getExportedMethodInfos().get(methodName));
nativeArguments.add(new PromiseWrapper(promise));
mModuleRegistry.getExportedModule(moduleName).invokeExportedMethod(methodName, nativeArguments);
} catch (IllegalArgumentException e) {
promise.reject(ARGS_TYPES_MISMATCH_ERROR, e.getMessage(), e);
} catch (RuntimeException e) {
promise.reject(UNEXPECTED_ERROR, "Encountered an exception while calling native method: " + e.getMessage(), e);
} catch (NoSuchMethodException e) {
promise.reject(
UNDEFINED_METHOD_ERROR,
"Method " + methodName + " of Java module " + moduleName + " is undefined.",
e
);
}
}
/**
* Converts {@link ReadableArray} of arguments into a list of Java Objects.
* Throws {@link RuntimeException} if it can't convert some {@link ReadableType} to Object.
* Method is used when converting Double to proper argument.
*/
private static List<Object> getNativeArgumentsForMethod(ReadableArray arguments, ExportedModule.MethodInfo methodInfo) {
List<Object> nativeArguments = new ArrayList<>();
for (int i = 0; i < arguments.size(); i++) {
nativeArguments.add(ArgumentsHelper.getNativeArgumentForExpectedClass(arguments.getDynamic(i), methodInfo.getParameterTypes()[i]));
}
return nativeArguments;
}
/**
* Transforms exportedMethodsMap to a map of methodInfos
*/
private List<Map<String, Object>> transformExportedMethodsMap(Map<String, Method> exportedMethods) {
List<Map<String, Object>> methods = new ArrayList<>(exportedMethods.size());
for (Map.Entry<String, Method> entry : exportedMethods.entrySet()) {
methods.add(getMethodInfo(entry.getKey(), entry.getValue()));
}
return methods;
}
/**
* Returns methodInfo Map (a Map containing a value for key argumentsCount).
*/
private Map<String, Object> getMethodInfo(String name, Method method) {
Map<String, Object> info = new HashMap<>(1);
info.put(METHOD_INFO_NAME, name);
info.put(METHOD_INFO_ARGUMENTS_COUNT, method.getParameterTypes().length - 1); // - 1 is for the Promise
return info;
}
/**
* Assigns keys to exported method infos and updates {@link #mExportedMethodsKeys} and {@link #mExportedMethodsReverseKeys}.
* Mutates maps in provided list.
*/
private void assignExportedMethodsKeys(String moduleName, List<Map<String, Object>> exportedMethodsInfos) {
if (mExportedMethodsKeys.get(moduleName) == null) {
mExportedMethodsKeys.put(moduleName, new HashMap<String, Integer>());
}
if (mExportedMethodsReverseKeys.get(moduleName) == null) {
mExportedMethodsReverseKeys.put(moduleName, new SparseArray<String>());
}
for (int i = 0; i < exportedMethodsInfos.size(); i++) {
Map<String, Object> methodInfo = exportedMethodsInfos.get(i);
if (methodInfo.get(METHOD_INFO_NAME) == null || !(methodInfo.get(METHOD_INFO_NAME) instanceof String)) {
throw new RuntimeException("No method name in MethodInfo - " + methodInfo.toString());
}
String methodName = (String) methodInfo.get(METHOD_INFO_NAME);
Integer maybePreviousIndex = mExportedMethodsKeys.get(moduleName).get(methodName);
if (maybePreviousIndex == null) {
int key = mExportedMethodsKeys.get(moduleName).values().size();
methodInfo.put(METHOD_INFO_KEY, key);
mExportedMethodsKeys.get(moduleName).put(methodName, key);
mExportedMethodsReverseKeys.get(moduleName).put(key, methodName);
} else {
int key = maybePreviousIndex;
methodInfo.put(METHOD_INFO_KEY, key);
}
}
}
@Override
public void onCatalystInstanceDestroy() {
mModuleRegistry.onDestroy();
}
}

View File

@ -0,0 +1,38 @@
package org.unimodules.adapters.react;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
import org.unimodules.core.Promise;
import java.util.List;
import javax.annotation.Nullable;
/**
* Decorator for {@link com.facebook.react.bridge.Promise},
* so we don't have to implement these inline in {@link NativeModulesProxy}.
*/
/* package */ class PromiseWrapper implements Promise {
private com.facebook.react.bridge.Promise mPromise;
/* package */ PromiseWrapper(com.facebook.react.bridge.Promise promise) {
super();
mPromise = promise;
}
public void resolve(@Nullable Object value) {
if (value instanceof Bundle) {
mPromise.resolve(Arguments.fromBundle((Bundle) value));
} else if (value instanceof List) {
mPromise.resolve(Arguments.fromList((List) value));
} else {
mPromise.resolve(value);
}
}
public void reject(String code, String message, Throwable e) {
mPromise.reject(code, message, e);
}
}

View File

@ -0,0 +1,39 @@
package org.unimodules.adapters.react;
import android.content.Context;
import com.facebook.react.bridge.ReactContext;
import java.util.Arrays;
import java.util.List;
import org.unimodules.adapters.react.apploader.RNHeadlessAppLoader;
import org.unimodules.adapters.react.services.CookieManagerModule;
import org.unimodules.adapters.react.services.EventEmitterModule;
import org.unimodules.adapters.react.services.FontManagerModule;
import org.unimodules.adapters.react.services.RuntimeEnvironmentModule;
import org.unimodules.adapters.react.services.UIManagerModuleWrapper;
import org.unimodules.core.BasePackage;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.core.interfaces.Package;
import org.unimodules.apploader.AppLoaderProvider;
/**
* A {@link Package} creating modules provided with the @unimodules/react-native-adapter package.
*/
public class ReactAdapterPackage extends BasePackage {
@Override
public List<InternalModule> createInternalModules(Context context) {
// We can force-cast here, because this package will only be used in React Native context.
ReactContext reactContext = (ReactContext) context;
return Arrays.asList(
new CookieManagerModule(reactContext),
new UIManagerModuleWrapper(reactContext),
new EventEmitterModule(reactContext),
new FontManagerModule(),
new RuntimeEnvironmentModule()
);
}
}

View File

@ -0,0 +1,101 @@
package org.unimodules.adapters.react;
import android.content.Context;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReactApplicationContext;
import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.ModuleRegistryProvider;
import org.unimodules.core.ViewManager;
import org.unimodules.core.interfaces.Function;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* Since React Native v0.55, {@link com.facebook.react.ReactPackage#createViewManagers(ReactApplicationContext)}
* gets called only once per lifetime of {@link com.facebook.react.ReactInstanceManager}.
* <p>
* To make @unimodules/react-native-adapter compatible with this change we have to remember view managers collection
* which is returned in {@link ModuleRegistryAdapter#createViewManagers(ReactApplicationContext)}
* only once (and managers returned this one time will persist "forever").
*/
public class ReactModuleRegistryProvider extends ModuleRegistryProvider {
private Collection<ViewManager> mViewManagers;
private Collection<com.facebook.react.uimanager.ViewManager> mReactViewManagers;
private Collection<SingletonModule> mSingletonModules;
public ReactModuleRegistryProvider(List<Package> initialPackages) {
this(initialPackages, null);
}
public ReactModuleRegistryProvider(List<Package> initialPackages, List<SingletonModule> singletonModules) {
super(initialPackages);
mSingletonModules = singletonModules;
}
@Override
public ModuleRegistry get(Context context) {
Collection<InternalModule> internalModules = new ArrayList<>();
Collection<ExportedModule> exportedModules = new ArrayList<>();
ReactPackagesProvider reactPackagesProvider = new ReactPackagesProvider();
for (Package pkg : getPackages()) {
internalModules.addAll(pkg.createInternalModules(context));
exportedModules.addAll(pkg.createExportedModules(context));
if (pkg instanceof ReactPackage) {
reactPackagesProvider.addPackage((ReactPackage) pkg);
}
}
internalModules.add(reactPackagesProvider);
return new ModuleRegistry(internalModules, exportedModules, getViewManagers(context), getSingletonModules(context));
}
private Collection<SingletonModule> getSingletonModules(Context context) {
// If singleton modules were provided to registry provider, then just pass them to module registry.
if (mSingletonModules != null) {
return mSingletonModules;
}
Collection<SingletonModule> singletonModules = new ArrayList<>();
for (Package pkg : getPackages()) {
singletonModules.addAll(pkg.createSingletonModules(context));
}
return singletonModules;
}
/* package */ Collection<ViewManager> getViewManagers(Context context) {
if (mViewManagers != null) {
return mViewManagers;
}
mViewManagers = new HashSet<>();
mViewManagers.addAll(createViewManagers(context));
return mViewManagers;
}
/* package */ Collection<com.facebook.react.uimanager.ViewManager> getReactViewManagers(ReactApplicationContext context) {
if (mReactViewManagers != null) {
return mReactViewManagers;
}
mReactViewManagers = new HashSet<>();
for (Package pkg : getPackages()) {
if (pkg instanceof ReactPackage) {
mReactViewManagers.addAll(((ReactPackage) pkg).createViewManagers(context));
}
}
return mReactViewManagers;
}
}

View File

@ -0,0 +1,39 @@
package org.unimodules.adapters.react;
import com.facebook.react.ReactPackage;
import org.unimodules.core.interfaces.InternalModule;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Holder for ReactPackages -- visible only to the adapter.
* <p>
* We want to be able to create platform-specific unimodules.
* Thus, we need a way to pass in ReactPackages via unimodules infrastructure.
* This internal module is populated with ReactPackages by ReactModuleRegistryProvider
* and is used by ModuleRegistryAdapter when it creates native modules list.
*/
public class ReactPackagesProvider implements InternalModule {
private Collection<ReactPackage> mReactPackages;
@Override
public List<? extends Class> getExportedInterfaces() {
return Collections.singletonList(ReactPackagesProvider.class);
}
public ReactPackagesProvider() {
mReactPackages = new ArrayList<>();
}
public void addPackage(ReactPackage reactPackage) {
mReactPackages.add(reactPackage);
}
public Collection<ReactPackage> getReactPackages() {
return mReactPackages;
}
}

View File

@ -0,0 +1,33 @@
package org.unimodules.adapters.react.apploader
import java.lang.ref.WeakReference
interface HeadlessAppLoaderListener {
fun appLoaded(appId: String)
fun appDestroyed(appId: String)
}
object HeadlessAppLoaderNotifier {
val listeners: MutableSet<WeakReference<HeadlessAppLoaderListener>> = mutableSetOf()
fun registerListener(listener: HeadlessAppLoaderListener) {
listeners.add(WeakReference(listener))
}
fun notifyAppLoaded(appId: String?) {
if (appId != null) {
listeners.forEach { it.get()?.appLoaded(appId) }
}
}
fun notifyAppDestroyed(appId: String?) {
if (appId != null) {
listeners.forEach { it.get()?.appDestroyed(appId) }
}
}
}

View File

@ -0,0 +1,60 @@
package org.unimodules.adapters.react.apploader
import android.content.Context
import com.facebook.react.ReactApplication
import com.facebook.react.ReactInstanceManager
import org.unimodules.apploader.HeadlessAppLoader
import org.unimodules.core.interfaces.Consumer
import org.unimodules.core.interfaces.DoNotStrip
private val appRecords: MutableMap<String, ReactInstanceManager> = mutableMapOf()
class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context) : HeadlessAppLoader {
//region HeadlessAppLoader
override fun loadApp(context: Context, params: HeadlessAppLoader.Params?, alreadyRunning: Runnable?, callback: Consumer<Boolean>?) {
if (params == null || params.appId == null) {
throw IllegalArgumentException("Params must be set with appId!")
}
if (context.applicationContext is ReactApplication) {
val reactInstanceManager = (context.applicationContext as ReactApplication).reactNativeHost.reactInstanceManager
if (!appRecords.containsKey(params.appId)) {
reactInstanceManager.addReactInstanceEventListener {
HeadlessAppLoaderNotifier.notifyAppLoaded(params.appId)
callback?.apply(true)
}
appRecords[params.appId] = reactInstanceManager
if (reactInstanceManager.hasStartedCreatingInitialContext()) {
reactInstanceManager.recreateReactContextInBackground()
} else {
reactInstanceManager.createReactContextInBackground()
}
} else {
alreadyRunning?.run()
}
} else {
throw IllegalStateException("Your application must implement ReactApplication")
}
}
override fun invalidateApp(appId: String?): Boolean {
return if (appRecords.containsKey(appId) && appRecords[appId] != null) {
val appRecord: ReactInstanceManager = appRecords[appId]!!
android.os.Handler(context.mainLooper).post {
appRecord.destroy()
HeadlessAppLoaderNotifier.notifyAppDestroyed(appId)
appRecords.remove(appId)
}
true
} else {
false
}
}
override fun isRunning(appId: String?): Boolean =
appRecords.contains(appId) && appRecords[appId]!!.hasStartedCreatingInitialContext()
//endregion HeadlessAppLoader
}

View File

@ -0,0 +1,44 @@
package org.unimodules.adapters.react.services;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.ForwardingCookieHandler;
import java.net.CookieHandler;
import java.util.Collections;
import java.util.List;
import org.unimodules.core.interfaces.InternalModule;
public class CookieManagerModule extends ForwardingCookieHandler implements InternalModule, NativeModule {
private static final String TAG = "CookieManagerModule";
public CookieManagerModule(ReactContext context) {
super(context);
}
@Override
public String getName() {
return null;
}
@Override
public void initialize() {
// do nothing
}
@Override
public List<Class> getExportedInterfaces() {
return Collections.singletonList((Class) CookieHandler.class);
}
@Override
public boolean canOverrideExistingModule() {
return false;
}
@Override
public void onCatalystInstanceDestroy() {
// do nothing
}
}

View File

@ -0,0 +1,87 @@
package org.unimodules.adapters.react.services;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import java.util.Collections;
import java.util.List;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.core.interfaces.services.EventEmitter;
public class EventEmitterModule implements EventEmitter, InternalModule {
private ReactContext mReactContext;
public EventEmitterModule(ReactContext reactContext) {
mReactContext = reactContext;
}
@Override
public void emit(String eventName, Bundle eventBody) {
mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, Arguments.fromBundle(eventBody));
}
@Override
public void emit(final int viewId, final Event event) {
mReactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(getReactEventFromEvent(viewId, event));
}
@Override
public void emit(final int viewId, final String eventName, final Bundle eventBody) {
mReactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(new com.facebook.react.uimanager.events.Event(viewId) {
@Override
public String getEventName() {
return eventName;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(viewId, eventName, eventBody != null ? Arguments.fromBundle(eventBody) : null);
}
@Override
public boolean canCoalesce() {
return false;
}
@Override
public short getCoalescingKey() {
return 0;
}
});
}
@Override
public List<Class> getExportedInterfaces() {
return Collections.singletonList((Class) EventEmitter.class);
}
private static com.facebook.react.uimanager.events.Event getReactEventFromEvent(final int viewId, final Event event) {
return new com.facebook.react.uimanager.events.Event(viewId) {
@Override
public String getEventName() {
return event.getEventName();
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(viewId, event.getEventName(), Arguments.fromBundle(event.getEventBody()));
}
@Override
public boolean canCoalesce() {
return event.canCoalesce();
}
@Override
public short getCoalescingKey() {
return event.getCoalescingKey();
}
};
}
}

View File

@ -0,0 +1,23 @@
package org.unimodules.adapters.react.services;
import android.graphics.Typeface;
import com.facebook.react.views.text.ReactFontManager;
import java.util.Collections;
import java.util.List;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.interfaces.font.FontManager;
public class FontManagerModule implements FontManager, InternalModule {
@Override
public List<Class> getExportedInterfaces() {
return Collections.<Class>singletonList(FontManager.class);
}
@Override
public void setTypeface(String fontFamilyName, int style, Typeface typeface) {
ReactFontManager.getInstance().setTypeface(fontFamilyName, style, typeface);
}
}

View File

@ -0,0 +1,50 @@
package org.unimodules.adapters.react.services;
import com.facebook.react.modules.systeminfo.ReactNativeVersion;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.core.interfaces.RuntimeEnvironmentInterface;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class RuntimeEnvironmentModule implements InternalModule, RuntimeEnvironmentInterface {
@Override
public List<? extends Class> getExportedInterfaces() {
return Collections.singletonList(RuntimeEnvironmentInterface.class);
}
@Override
public String platformName() {
return "React Native";
}
@Override
public RuntimeEnvironmentInterface.PlatformVersion platformVersion() {
final Map<String, Object> version = ReactNativeVersion.VERSION;
return new RuntimeEnvironmentInterface.PlatformVersion() {
@Override
public int major() {
return (int) version.get("major");
}
@Override
public int minor() {
return (int) version.get("minor");
}
@Override
public int patch() {
return (int) version.get("patch");
}
@Override
public String prerelease() {
return (String) version.get("prerelease");
}
};
}
}

View File

@ -0,0 +1,195 @@
package org.unimodules.adapters.react.services;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIManagerModule;
import org.unimodules.core.interfaces.ActivityEventListener;
import org.unimodules.core.interfaces.ActivityProvider;
import org.unimodules.core.interfaces.InternalModule;
import org.unimodules.core.interfaces.JavaScriptContextProvider;
import org.unimodules.core.interfaces.LifecycleEventListener;
import org.unimodules.core.interfaces.services.UIManager;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
public class UIManagerModuleWrapper implements
ActivityProvider,
InternalModule,
JavaScriptContextProvider,
UIManager {
private ReactContext mReactContext;
private Map<LifecycleEventListener, com.facebook.react.bridge.LifecycleEventListener> mLifecycleListenersMap = new WeakHashMap<>();
private Map<ActivityEventListener, com.facebook.react.bridge.ActivityEventListener> mActivityEventListenersMap = new WeakHashMap<>();
public UIManagerModuleWrapper(ReactContext reactContext) {
mReactContext = reactContext;
}
protected ReactContext getContext() {
return mReactContext;
}
@Override
public List<Class> getExportedInterfaces() {
return Arrays.<Class>asList(
ActivityProvider.class,
JavaScriptContextProvider.class,
UIManager.class
);
}
@Override
public <T> void addUIBlock(final int tag, final UIBlock<T> block, final Class<T> tClass) {
getContext().getNativeModule(UIManagerModule.class).addUIBlock(new com.facebook.react.uimanager.UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
View view = nativeViewHierarchyManager.resolveView(tag);
if (view == null) {
block.reject(new IllegalArgumentException("Expected view for this tag not to be null."));
} else {
try {
if (tClass.isInstance(view)) {
block.resolve(tClass.cast(view));
} else {
block.reject(new IllegalStateException(
"Expected view to be of " + tClass + "; found " + view.getClass() + " instead"));
}
} catch (Exception e) {
block.reject(e);
}
}
}
});
}
@Override
public void addUIBlock(final GroupUIBlock block) {
getContext().getNativeModule(UIManagerModule.class).addUIBlock(new com.facebook.react.uimanager.UIBlock() {
@Override
public void execute(final NativeViewHierarchyManager nativeViewHierarchyManager) {
block.execute(new ViewHolder() {
@Override
public View get(Object key) {
if (key instanceof Number) {
try {
return nativeViewHierarchyManager.resolveView(((Number) key).intValue());
} catch (IllegalViewOperationException e) {
return null;
}
} else {
Log.w("E_INVALID_TAG", "Provided tag is of class " + key.getClass() + " whereas React expects tags to be integers. Are you sure you're providing proper argument to addUIBlock?");
}
return null;
}
});
}
});
}
@Override
public void runOnUiQueueThread(Runnable runnable) {
if (getContext().isOnUiQueueThread()) {
runnable.run();
} else {
getContext().runOnUiQueueThread(runnable);
}
}
@Override
public void runOnClientCodeQueueThread(Runnable runnable) {
if (getContext().isOnJSQueueThread()) {
runnable.run();
} else {
getContext().runOnJSQueueThread(runnable);
}
}
@Override
public void registerLifecycleEventListener(final LifecycleEventListener listener) {
final WeakReference<LifecycleEventListener> weakListener = new WeakReference<>(listener);
mLifecycleListenersMap.put(listener, new com.facebook.react.bridge.LifecycleEventListener() {
@Override
public void onHostResume() {
LifecycleEventListener listener = weakListener.get();
if (listener != null) {
listener.onHostResume();
}
}
@Override
public void onHostPause() {
LifecycleEventListener listener = weakListener.get();
if (listener != null) {
listener.onHostPause();
}
}
@Override
public void onHostDestroy() {
LifecycleEventListener listener = weakListener.get();
if (listener != null) {
listener.onHostDestroy();
}
}
});
mReactContext.addLifecycleEventListener(mLifecycleListenersMap.get(listener));
}
@Override
public void unregisterLifecycleEventListener(LifecycleEventListener listener) {
getContext().removeLifecycleEventListener(mLifecycleListenersMap.get(listener));
mLifecycleListenersMap.remove(listener);
}
@Override
public void registerActivityEventListener(final ActivityEventListener activityEventListener) {
final WeakReference<ActivityEventListener> weakListener = new WeakReference<>(activityEventListener);
mActivityEventListenersMap.put(activityEventListener, new com.facebook.react.bridge.ActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
ActivityEventListener listener = weakListener.get();
if (listener != null) {
listener.onActivityResult(activity, requestCode, resultCode, data);
}
}
@Override
public void onNewIntent(Intent intent) {
ActivityEventListener listener = weakListener.get();
if (listener != null) {
listener.onNewIntent(intent);
}
}
});
mReactContext.addActivityEventListener(mActivityEventListenersMap.get(activityEventListener));
}
@Override
public void unregisterActivityEventListener(final ActivityEventListener activityEventListener) {
getContext().removeActivityEventListener(mActivityEventListenersMap.get(activityEventListener));
mActivityEventListenersMap.remove(activityEventListener);
}
public long getJavaScriptContextRef() {
return mReactContext.getJavaScriptContextHolder().get();
}
@Override
public Activity getCurrentActivity() {
return getContext().getCurrentActivity();
}
}

View File

@ -0,0 +1,62 @@
package org.unimodules.adapters.react.views;
import android.view.View;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
import javax.annotation.Nullable;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.ViewManager;
import org.unimodules.core.interfaces.RegistryLifecycleListener;
public class SimpleViewManagerAdapter<M extends ViewManager<V>, V extends View> extends SimpleViewManager<V> implements RegistryLifecycleListener {
private M mViewManager;
public SimpleViewManagerAdapter(M viewManager) {
mViewManager = viewManager;
}
@Override
protected V createViewInstance(ThemedReactContext reactContext) {
return mViewManager.createViewInstance(reactContext);
}
@Override
public void onDropViewInstance(V view) {
mViewManager.onDropViewInstance(view);
super.onDropViewInstance(view);
}
@Nullable
@Override
public Map<String, Object> getConstants() {
return ViewManagerAdapterUtils.getConstants(mViewManager);
}
@Override
public String getName() {
return ViewManagerAdapterUtils.getViewManagerAdapterName(mViewManager);
}
@ReactProp(name = "proxiedProperties")
public void setProxiedProperties(V view, ReadableMap proxiedProperties) {
ViewManagerAdapterUtils.setProxiedProperties(getName(), mViewManager, view, proxiedProperties);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return ViewManagerAdapterUtils.getExportedCustomDirectEventTypeConstants(mViewManager);
}
@Override
public void onCreate(ModuleRegistry moduleRegistry) {
mViewManager.onCreate(moduleRegistry);
}
}

View File

@ -0,0 +1,62 @@
package org.unimodules.adapters.react.views;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
import javax.annotation.Nullable;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.ViewManager;
import org.unimodules.core.interfaces.RegistryLifecycleListener;
public class ViewGroupManagerAdapter<M extends ViewManager<V>, V extends ViewGroup> extends ViewGroupManager<V> implements RegistryLifecycleListener {
private M mViewManager;
public ViewGroupManagerAdapter(M viewManager) {
mViewManager = viewManager;
}
@Override
protected V createViewInstance(ThemedReactContext reactContext) {
return mViewManager.createViewInstance(reactContext);
}
@Override
public void onDropViewInstance(V view) {
mViewManager.onDropViewInstance(view);
super.onDropViewInstance(view);
}
@Nullable
@Override
public Map<String, Object> getConstants() {
return ViewManagerAdapterUtils.getConstants(mViewManager);
}
@Override
public String getName() {
return ViewManagerAdapterUtils.getViewManagerAdapterName(mViewManager);
}
@ReactProp(name = "proxiedProperties")
public void setProxiedProperties(V view, ReadableMap proxiedProperties) {
ViewManagerAdapterUtils.setProxiedProperties(getName(), mViewManager, view, proxiedProperties);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return ViewManagerAdapterUtils.getExportedCustomDirectEventTypeConstants(mViewManager);
}
@Override
public void onCreate(ModuleRegistry moduleRegistry) {
mViewManager.onCreate(moduleRegistry);
}
}

View File

@ -0,0 +1,58 @@
package org.unimodules.adapters.react.views;
import android.util.Log;
import android.view.View;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.common.MapBuilder;
import java.util.HashMap;
import java.util.Map;
import org.unimodules.adapters.react.ArgumentsHelper;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.ViewManager;
public class ViewManagerAdapterUtils {
/* package */ static String getViewManagerAdapterName(ViewManager viewManager) {
return "ViewManagerAdapter_" + viewManager.getName();
}
/* package */ static Map<String, Object> getConstants(ViewManager viewManager) {
Map<String, Object> constants = new HashMap<>();
constants.put("eventNames", viewManager.getExportedEventNames());
return constants;
}
/* package */ static Map<String, Object> getExportedCustomDirectEventTypeConstants(ViewManager viewManager) {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
// Somehow Java compiler thinks getExportedEventNames() returns list of Objects.
// ¯\_(ツ)_/¯
for (Object eventName : viewManager.getExportedEventNames()) {
if (eventName instanceof String) {
builder.put((String) eventName, MapBuilder.of("registrationName", eventName));
}
}
return builder.build();
}
/* package */ static <V extends View> void setProxiedProperties(String viewManagerAdapterName, ViewManager<V> viewManager, V view, ReadableMap proxiedProperties) {
ReadableMapKeySetIterator keyIterator = proxiedProperties.keySetIterator();
while (keyIterator.hasNextKey()) {
String key = keyIterator.nextKey();
try {
ViewManager.PropSetterInfo propSetterInfo = viewManager.getPropSetterInfos().get(key);
if (propSetterInfo == null) {
throw new IllegalArgumentException("No setter found for prop " + key + " in " + viewManagerAdapterName);
}
Dynamic dynamicPropertyValue = proxiedProperties.getDynamic(key);
Object castPropertyValue = ArgumentsHelper.getNativeArgumentForExpectedClass(dynamicPropertyValue, propSetterInfo.getExpectedValueClass());
viewManager.updateProp(view, key, castPropertyValue);
} catch (Exception e) {
Log.e(viewManagerAdapterName, "Error when setting prop " + key + ". " + e.getMessage());
}
}
}
}