diff --git a/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java b/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java index 7bcf009..5b34477 100644 --- a/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java +++ b/android/src/main/java/in/juspay/hypersdkreact/HyperFragmentViewManager.java @@ -21,6 +21,7 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; import org.json.JSONObject; @@ -35,6 +36,13 @@ public class HyperFragmentViewManager extends ViewGroupManager { private static final int COMMAND_PROCESS = 175; private final ReactApplicationContext reactContext; + // Track props for each view + private String currentNamespace = null; + private String currentPayload = null; + private FrameLayout currentView = null; + + // Architecture detection + private final Boolean newArchEnabled = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; public HyperFragmentViewManager(ReactApplicationContext reactContext) { this.reactContext = reactContext; @@ -58,6 +66,67 @@ protected FrameLayout createViewInstance(@NonNull ThemedReactContext context) { public Map getCommandsMap() { return MapBuilder.of("process", COMMAND_PROCESS); } + // Fabric-compatible props + @ReactProp(name = "ns") + public void setNs(FrameLayout view, @Nullable String ns) { + currentNamespace = ns; + currentView = view; + tryProcessProps(); + } + + @ReactProp(name = "payload") + public void setPayload(FrameLayout view, @Nullable String payload) { + currentPayload = payload; + currentView = view; + tryProcessProps(); + } + + @Override + public void onDropViewInstance(@NonNull FrameLayout view) { + super.onDropViewInstance(view); + currentNamespace = null; + currentPayload = null; + currentView = null; + } + + private void tryProcessProps() { + if (currentNamespace != null && currentPayload != null && currentView != null && newArchEnabled) { + currentView.post(() -> { + processWithProps(currentView, currentNamespace, currentPayload); + }); + } + } + + private void processWithProps(FrameLayout view, String namespace, String payload) { + try { + setupLayout(view); + + JSONObject fragments = new JSONObject(); + fragments.put(namespace, view); + + JSONObject payloadObj = new JSONObject(payload); + payloadObj.getJSONObject("payload").put("fragmentViewGroups", fragments); + + FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity(); + HyperServices hyperServices = HyperSdkReactModule.getHyperServices(); + + if (activity == null || hyperServices == null) { + return; + } + + hyperServices.process(activity, payloadObj); + + } catch (Exception e) { + SdkTracker.trackAndLogBootException( + NAME, + LogConstants.CATEGORY_LIFECYCLE, + LogConstants.SUBCATEGORY_HYPER_SDK, + LogConstants.SDK_TRACKER_LABEL, + "Exception in processWithProps", + e + ); + } + } @Override public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) { @@ -122,7 +191,11 @@ private void setupLayout(View view) { @Override public void doFrame(long frameTimeNanos) { try { - manuallyLayoutChildren(view); + if (newArchEnabled) { + manuallyLayoutChildrenForNewArch(view); + } else { + manuallyLayoutChildren(view); + } view.getViewTreeObserver().dispatchOnGlobalLayout(); Choreographer.getInstance().postFrameCallback(this); } catch (Exception e) { @@ -152,4 +225,18 @@ private void manuallyLayoutChildren(@NonNull View view) { view.layout(0, 0, width, height); } + + private void manuallyLayoutChildrenForNewArch(@NonNull View view) { + // Get the final size AND position calculated by React Native's Yoga engine + int width = view.getWidth(); + int height = view.getHeight(); + int left = view.getLeft(); + int top = view.getTop(); + + view.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) + ); + view.layout(left, top, left + width, top + height); + } } diff --git a/hyper-sdk-react.podspec b/hyper-sdk-react.podspec index 5ecb88a..c14c29e 100644 --- a/hyper-sdk-react.podspec +++ b/hyper-sdk-react.podspec @@ -1,41 +1,110 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +rn_minor_version = 0 + [ + '../react-native/package.json', + '../../react-native/package.json', + '../../../react-native/package.json', + ].each do |relative_path| + if rn_minor_version == 0 + path = File.join(__dir__, relative_path) + if File.exist?(path) + begin + package1 = JSON.parse(File.read(path)) + version = package1 ['version'] + if version == '*' || version.include?('*') + rn_minor_version = 80 + else + rn_minor_version = version.split('.')[1].to_i + end + break + rescue => e + end + end + end + end + +#Fallback - search in common locations +if rn_minor_version == 0 + common_paths = [ + File.expand_path('node_modules/react-native/package.json', Dir.pwd), + File.expand_path('../../node_modules/react-native/package.json', __dir__), + ] + common_paths.each do |path| + if File.exist?(path) + begin + package1 = JSON.parse(File.read(path)) + version = package1 ['version'] + if version == '*' || version.include?('*') + rn_minor_version = 80 + else + rn_minor_version = version.split('.')[1].to_i + end + break + rescue => e + end + end + end +end + +# Fallback if still not found +if rn_minor_version == 0 + rn_minor_version = 77 +end +puts ("Found react native minor version as #{rn_minor_version}").yellow + folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' hyper_sdk_version = "2.2.2.8" begin - package_json_path = File.expand_path(File.join(__dir__, "../../package.json")) - apps_package = JSON.parse(File.read(package_json_path)) - if apps_package["hyperSdkIOSVersion"] - override_version = apps_package["hyperSdkIOSVersion"] - hyper_sdk_version = Gem::Version.new(override_version) > Gem::Version.new(hyper_sdk_version) ? override_version : hyper_sdk_version - if hyper_sdk_version != override_version - puts ("Ignoring the overriden SDK version present in package.json (#{override_version}) as there is a newer version present in the SDK (#{hyper_sdk_version}).").yellow - end - end + package_json_path = File.expand_path(File.join(__dir__, "../../package.json")) + apps_package = JSON.parse(File.read(package_json_path)) + if apps_package["hyperSdkIOSVersion"] + override_version = apps_package["hyperSdkIOSVersion"] + hyper_sdk_version = Gem::Version.new(override_version) > Gem::Version.new(hyper_sdk_version) ? override_version : hyper_sdk_version + if hyper_sdk_version != override_version + puts ("Ignoring the overriden SDK version present in package.json (#{override_version}) as there is a newer version present in the SDK (#{hyper_sdk_version}).").yellow + end + end rescue => e - puts ("An error occurred while overrding the IOS SDK Version. #{e.message}").red + puts ("An error occurred while overrding the IOS SDK Version. #{e.message}").red end puts ("HyperSDK Version: #{hyper_sdk_version}") +# Prepare source files based on RN version +source_files_array = ["ios/**/*.{h,m,mm,swift}"] +exclude_files = [] + +if rn_minor_version >= 78 + source_files_array << "ios/latest/**/*.{h,m,mm,swift}" + exclude_files << "ios/rn77/**/*" +else + source_files_array << "ios/rn77/**/*.{h,m,mm,swift}" + exclude_files << "ios/latest/**/*" +end + Pod::Spec.new do |s| - s.name = "hyper-sdk-react" - s.version = package["version"] - s.summary = package["description"] - s.homepage = package["homepage"] - s.license = package["license"] - s.authors = package["author"] - - s.platforms = { :ios => "12.0" } - s.source = { :git => "https://github.com/juspay/hyper-sdk-react.git", :tag => "v#{s.version}" } - - s.static_framework = true - s.source_files = "ios/**/*.{h,m,mm,swift}" - - s.dependency "React-Core" - s.dependency "React-RCTAppDelegate" - s.dependency "HyperSDK", hyper_sdk_version + s.name = "hyper-sdk-react" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "12.0" } + s.source = { :git => "https://github.com/juspay/hyper-sdk-react.git", :tag => "v#{s.version}" } + + s.static_framework = true + s.source_files = source_files_array + + # Set exclude files + s.exclude_files = exclude_files + + s.dependency "React-Core" + s.dependency "React-RCTAppDelegate" + s.dependency "HyperSDK", hyper_sdk_version end diff --git a/ios/HyperMerchantView.h b/ios/HyperMerchantView.h new file mode 100644 index 0000000..c12d3c5 --- /dev/null +++ b/ios/HyperMerchantView.h @@ -0,0 +1,21 @@ +// +// HyperMerchantView.h +// Pods +// +// Created by Yaswanth Polisetti on 16/10/25. +// + + +// HyperMerchantView.h +#ifndef HyperMerchantView_h +#define HyperMerchantView_h + +#import + +@interface HyperMerchantView : UIView + ++ (UIView *)createReactNativeViewWithModuleName:(NSString *)moduleName; + +@end + +#endif diff --git a/ios/HyperSdkReact.mm b/ios/HyperSdkReact.mm index ca3ad01..d280835 100644 --- a/ios/HyperSdkReact.mm +++ b/ios/HyperSdkReact.mm @@ -17,13 +17,7 @@ #import #import -#if __has_include("RCTAppDelegate.h") && __has_include("RCTRootViewFactory.h") -#import "RCTAppDelegate.h" -#import "RCTRootViewFactory.h" -#define HAS_NEW_ARCH_SUPPORT 1 -#else -#define HAS_NEW_ARCH_SUPPORT 0 -#endif +#import "HyperMerchantView.h" __weak static HyperServices *_hyperServicesReference; @@ -184,39 +178,29 @@ - (UIView * _Nullable)merchantViewForViewType:(NSString * _Nonnull)viewType { return rrv; }; + UIView *rrv = [HyperMerchantView createReactNativeViewWithModuleName:moduleName]; - #if HAS_NEW_ARCH_SUPPORT - - bool rootFactoryAvailable = false; - id appDelegate = RCTSharedApplication().delegate; - rootFactoryAvailable = [appDelegate respondsToSelector:@selector(rootViewFactory)]; - if (!rootFactoryAvailable) { - return oldArchCall(); - } - - RCTRootViewFactory *factory = ((RCTAppDelegate *)appDelegate).rootViewFactory; - MerchantViewRoot *wrapper = [[MerchantViewRoot alloc] init]; + if (rrv == nil) { + return oldArchCall(); + } + MerchantViewRoot *wrapper = [[MerchantViewRoot alloc] init]; + [wrapper addSubview:rrv]; - UIView *rrv = [factory viewWithModuleName:moduleName initialProperties:nil]; - [wrapper addSubview:rrv]; - - // Remove background colour. Default colour white is getting applied to the merchant view - wrapper.backgroundColor = UIColor.clearColor ; - - // Remove height 0, width 0 constraints added by default. - wrapper.translatesAutoresizingMaskIntoConstraints = false; - - rrv.translatesAutoresizingMaskIntoConstraints = false; + + // Remove background colour. Default colour white is getting applied to the merchant view + wrapper.backgroundColor = UIColor.clearColor ; + + // Remove height 0, width 0 constraints added by default. + wrapper.translatesAutoresizingMaskIntoConstraints = false; + + rrv.translatesAutoresizingMaskIntoConstraints = false; - [self.rootHolder setObject:wrapper forKey:moduleName]; - addHeightConstraint(wrapper); + [self.rootHolder setObject:wrapper forKey:moduleName]; + addHeightConstraint(wrapper); - // This is sent to hypersdk. Hyper sdk adds the view to it's heirarchy and set's superview's top and bottom to match rrv's top and bottom - return wrapper; - #else - return oldArchCall(); - #endif + // This is sent to hypersdk. Hyper sdk adds the view to it's heirarchy and set's superview's top and bottom to match rrv's top and bottom + return wrapper; } - (void) onWebViewReady:(WKWebView *)webView { @@ -439,7 +423,13 @@ + (NSString*)dictionaryToString:(id)dict{ @end @implementation HyperFragmentViewManagerIOS -RCT_EXPORT_MODULE() + + +RCT_EXPORT_MODULE(HyperFragmentViewManagerIOS) + +NSString *_currentNamespace; +NSString *_currentPayload; +UIView *_currentView; - (dispatch_queue_t)methodQueue{ return dispatch_get_main_queue(); @@ -454,7 +444,81 @@ - (UIView *)view return [[UIView alloc] init]; } -RCT_EXPORT_METHOD(process:(nonnull NSNumber *)viewTag nameSpace:(NSString *)nameSpace payload:(NSString *)payload) + +RCT_CUSTOM_VIEW_PROPERTY(ns, NSString, UIView) +{ + [self setNs:json forView:view]; +} + +RCT_CUSTOM_VIEW_PROPERTY(payload, NSString, UIView) +{ + [self setPayload:json forView:view]; +} + + +- (void) setHeight:(NSString*)ns forView:(UIView*)view { + +} + +- (void) setWidth:(NSString*)ns forView:(UIView*)view { + +} +- (void)setNs:(NSString *)ns forView:(UIView *)view +{ + _currentNamespace = ns; + _currentView = view; + [self tryProcessProps]; +} + + +- (void)setPayload:(NSString *)payload forView:(UIView *)view +{ + _currentPayload = payload; + _currentView = view; + [self tryProcessProps]; +} + +- (void)tryProcessProps +{ + if (_currentNamespace && _currentPayload && _currentView) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self processWithPropsForView:_currentView ns:_currentNamespace payload:_currentPayload]; + }); + } +} + +- (void)processWithPropsForView:(UIView *)view ns:(NSString *)ns payload:(NSString *)payload +{ + HyperServices *hyperServicesInstance = _hyperServicesReference; + if (payload && payload.length > 0) { + @try { + NSDictionary *jsonData = [HyperSdkReact stringToDictionary:payload]; + if (jsonData && [jsonData isKindOfClass:[NSDictionary class]] && jsonData.allKeys.count > 0) { + if (hyperServicesInstance.baseViewController == nil || hyperServicesInstance.baseViewController.view.window == nil) { + id baseViewController = RCTPresentedViewController(); + if ([baseViewController isMemberOfClass:RCTModalHostViewController.class] && [baseViewController presentingViewController]) { + [hyperServicesInstance setBaseViewController:[baseViewController presentingViewController]]; + } else { + [hyperServicesInstance setBaseViewController:baseViewController]; + } + } + + [self manuallyLayoutChildren:view]; + + NSMutableDictionary *nestedPayload = [jsonData[@"payload"] mutableCopy]; + NSDictionary *fragmentViewGroup = @{ns: view}; + nestedPayload[@"fragmentViewGroups"] = fragmentViewGroup; + NSMutableDictionary *updatedJsonData = [jsonData mutableCopy]; + updatedJsonData[@"payload"] = nestedPayload; + [hyperServicesInstance process:[updatedJsonData copy]]; + } + } @catch (NSException *exception) { + // Handle exception silently + } + } +} + +RCT_EXPORT_METHOD(process:(nonnull NSNumber *)viewTag ns:(NSString *)ns payload:(NSString *)payload) { HyperServices *hyperServicesInstance = _hyperServicesReference; if (payload && payload.length>0) { @@ -477,7 +541,7 @@ - (UIView *)view return; } NSMutableDictionary *nestedPayload = [jsonData[@"payload"] mutableCopy]; - NSDictionary *fragmentViewGroup = @{nameSpace: view}; + NSDictionary *fragmentViewGroup = @{ns: view}; nestedPayload[@"fragmentViewGroups"] = fragmentViewGroup; NSMutableDictionary *updatedJsonData = [jsonData mutableCopy]; updatedJsonData[@"payload"] = nestedPayload; @@ -496,4 +560,3 @@ - (void)manuallyLayoutChildren:(UIView *)view { } @end - diff --git a/ios/latest/HyperMerchantView.mm b/ios/latest/HyperMerchantView.mm new file mode 100644 index 0000000..7940c52 --- /dev/null +++ b/ios/latest/HyperMerchantView.mm @@ -0,0 +1,54 @@ +// +// HyperMerchantView.mm +// Pods +// +// Created by Yaswanth Polisetti on 16/10/25. +// +// HyperMerchantView.mm +#import "HyperMerchantView.h" +#import +#if __has_include("RCTRootViewFactory.h") +#import "RCTRootViewFactory.h" +#define HAS_NEW_ARCH_SUPPORT 1 +#else +#define HAS_NEW_ARCH_SUPPORT 0 +#endif + +@implementation HyperMerchantView ++ (UIView *)createReactNativeViewWithModuleName:(NSString *)moduleName { + + #if HAS_NEW_ARCH_SUPPORT + id appDelegate = RCTSharedApplication().delegate; + unsigned int ivarCount = 0; + Ivar *ivars = class_copyIvarList([appDelegate class], &ivarCount); + id factory = nil; + for (unsigned int i = 0; i < ivarCount; i++) { + const char *ivarName = ivar_getName(ivars[i]); + // Swift mangles property names - look for reactNativeFactory or _reactNativeFactory + if (strcmp(ivarName, "_reactNativeFactory") == 0 || strcmp(ivarName, "reactNativeFactory") == 0) { + factory = object_getIvar(appDelegate, ivars[i]); + break; + } + } + free(ivars); + if (!factory) { + return nil; + } + // Now use the factory + if (![factory respondsToSelector:@selector(rootViewFactory)]) { + return nil; + } + id rootViewFactory = [factory performSelector:@selector(rootViewFactory)]; + if (![rootViewFactory respondsToSelector:@selector(viewWithModuleName:initialProperties:)]) { + return nil; + } + UIView *rrv = [rootViewFactory performSelector:@selector(viewWithModuleName:initialProperties:) + withObject:moduleName + withObject:nil]; + + return rrv; + #else + return nil; + #endif +} +@end diff --git a/ios/rn77/HyperMerchantView.mm b/ios/rn77/HyperMerchantView.mm new file mode 100644 index 0000000..8aff7c2 --- /dev/null +++ b/ios/rn77/HyperMerchantView.mm @@ -0,0 +1,37 @@ +// +// HyperMerchantView.mm +// Pods +// +// Created by Yaswanth Polisetti on 16/10/25. +// +// HyperMerchantView.mm +#import "HyperMerchantView.h" +#import + +#if __has_include("RCTAppDelegate.h") && __has_include("RCTRootViewFactory.h") +#import "RCTAppDelegate.h" +#import "RCTRootViewFactory.h" +#define HAS_NEW_ARCH_SUPPORT 1 +#else +#define HAS_NEW_ARCH_SUPPORT 0 +#endif + +@implementation HyperMerchantView ++ (UIView *)createReactNativeViewWithModuleName:(NSString *)moduleName { + + #if HAS_NEW_ARCH_SUPPORT + + bool rootFactoryAvailable = false; + id appDelegate = RCTSharedApplication().delegate; + rootFactoryAvailable = [appDelegate respondsToSelector:@selector(rootViewFactory)]; + if (!rootFactoryAvailable) { + return nil; + } + RCTRootViewFactory *factory = ((RCTAppDelegate *)appDelegate).rootViewFactory; + UIView *rrv = [factory viewWithModuleName:moduleName initialProperties:nil]; + return rrv; + #else + return nil; + #endif +} +@end diff --git a/src/HyperFragmentView.tsx b/src/HyperFragmentView.tsx index 7861b57..0dbd509 100644 --- a/src/HyperFragmentView.tsx +++ b/src/HyperFragmentView.tsx @@ -30,23 +30,27 @@ if (Platform.OS === 'android') { ); } +const newArchEnabled = (global as any)?.nativeFabricUIManager ? true : false; + const createFragment = (viewId: number, namespace: string, payload: string) => { - if (Platform.OS === 'android') { - UIManager.dispatchViewManagerCommand( - viewId, - //@ts-ignore - UIManager.HyperFragmentViewManager.Commands.process.toString(), - [viewId, namespace, payload] - ); - } else { - const commandId = UIManager.getViewManagerConfig( - 'HyperFragmentViewManagerIOS' - ).Commands.process; - if (typeof commandId !== 'undefined') { - UIManager.dispatchViewManagerCommand(viewId, commandId, [ - namespace, - payload, - ]); + if (!newArchEnabled) { + if (Platform.OS === 'android') { + UIManager.dispatchViewManagerCommand( + viewId, + //@ts-ignore + UIManager.HyperFragmentViewManager.Commands.process.toString(), + [viewId, namespace, payload] + ); + } else { + const commandId = UIManager.getViewManagerConfig( + 'HyperFragmentViewManagerIOS' + ).Commands.process; + if (typeof commandId !== 'undefined') { + UIManager.dispatchViewManagerCommand(viewId, commandId, [ + namespace, + payload, + ]); + } } } }; @@ -71,7 +75,16 @@ const HyperFragmentView: React.FC = ({ return ( - + {newArchEnabled ? ( + + ) : ( + + )} ); };