From eba280b58fa24e103858a0a40f38875f2dc43d59 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 9 Oct 2023 20:41:48 +0530 Subject: [PATCH 1/2] chore: [Android Only] React Native Widget --- .../in/juspay/hypersdkreact/CustomView.java | 20 +++ .../hypersdkreact/HyperSdkReactPackage.java | 4 +- .../in/juspay/hypersdkreact/MyFragment.java | 49 +++++++ .../juspay/hypersdkreact/MyViewManager.java | 127 ++++++++++++++++++ example/android/build.gradle | 1 + example/src/HomeScreen.tsx | 6 +- package.json | 5 +- src/MyView.tsx | 50 +++++++ src/index.tsx | 3 + 9 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 android/src/main/java/in/juspay/hypersdkreact/CustomView.java create mode 100644 android/src/main/java/in/juspay/hypersdkreact/MyFragment.java create mode 100644 android/src/main/java/in/juspay/hypersdkreact/MyViewManager.java create mode 100644 src/MyView.tsx diff --git a/android/src/main/java/in/juspay/hypersdkreact/CustomView.java b/android/src/main/java/in/juspay/hypersdkreact/CustomView.java new file mode 100644 index 0000000..2754edb --- /dev/null +++ b/android/src/main/java/in/juspay/hypersdkreact/CustomView.java @@ -0,0 +1,20 @@ +package in.juspay.hypersdkreact; +import android.content.Context; +import android.graphics.Color; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +public class CustomView extends FrameLayout { + public CustomView(@NonNull Context context) { + super(context); + this.setPadding(16,16,16,16); + this.setBackgroundColor(Color.parseColor("#5FD3F3")); + + TextView text = new TextView(context); + text.setText("Welcome to Android Fragments with React Native."); + this.addView(text); + } +} diff --git a/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactPackage.java b/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactPackage.java index 93ea88d..d25456b 100644 --- a/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactPackage.java +++ b/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactPackage.java @@ -28,6 +28,8 @@ public List createNativeModules(@NonNull ReactApplicationContext r @Override @SuppressWarnings("rawtypes") public List createViewManagers(@NonNull ReactApplicationContext reactContext) { - return Collections.emptyList(); + return Collections.singletonList( + new MyViewManager(reactContext) + ); } } diff --git a/android/src/main/java/in/juspay/hypersdkreact/MyFragment.java b/android/src/main/java/in/juspay/hypersdkreact/MyFragment.java new file mode 100644 index 0000000..b250ff1 --- /dev/null +++ b/android/src/main/java/in/juspay/hypersdkreact/MyFragment.java @@ -0,0 +1,49 @@ +package in.juspay.hypersdkreact; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + + +public class MyFragment extends Fragment { + CustomView customView; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + super.onCreateView(inflater, parent, savedInstanceState); + customView = new CustomView(this.requireContext()); + return customView; // this CustomView could be any view that you want to render + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // do any logic that should happen in an `onCreate` method, e.g: + // customView.onCreate(savedInstanceState); + } + + @Override + public void onPause() { + super.onPause(); + // do any logic that should happen in an `onPause` method + // e.g.: customView.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + // do any logic that should happen in an `onResume` method + // e.g.: customView.onResume(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // do any logic that should happen in an `onDestroy` method + // e.g.: customView.onDestroy(); + } +} diff --git a/android/src/main/java/in/juspay/hypersdkreact/MyViewManager.java b/android/src/main/java/in/juspay/hypersdkreact/MyViewManager.java new file mode 100644 index 0000000..dc0392e --- /dev/null +++ b/android/src/main/java/in/juspay/hypersdkreact/MyViewManager.java @@ -0,0 +1,127 @@ +package in.juspay.hypersdkreact; + +import android.graphics.Color; +import android.util.Log; +import android.view.Choreographer; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.annotations.ReactPropGroup; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.ThemedReactContext; + +import java.util.Map; +import android.view.ViewGroup; + +public class MyViewManager extends ViewGroupManager { + + public static final String REACT_CLASS = "MyViewManager"; + public final int COMMAND_CREATE = 1; + private int propWidth; + private int propHeight; + + ReactApplicationContext reactContext; + + public MyViewManager(ReactApplicationContext reactContext) { + this.reactContext = reactContext; + } + + @Override + public String getName() { + return REACT_CLASS; + } + + /** + * Return a FrameLayout which will later hold the Fragment + */ + @Override + public FrameLayout createViewInstance(ThemedReactContext reactContext) { + return new FrameLayout(reactContext); + } + + /** + * Map the "create" command to an integer + */ + @Nullable + @Override + public Map getCommandsMap() { + return MapBuilder.of("create", COMMAND_CREATE); + } + + /** + * Handle "create" command (called from JS) and call createFragment method + */ + @Override + public void receiveCommand( + @NonNull FrameLayout root, + String commandId, + @Nullable ReadableArray args + ) { + super.receiveCommand(root, commandId, args); + assert args != null; + int reactNativeViewId = args.getInt(0); + int commandIdInt = Integer.parseInt(commandId); + + if (commandIdInt == COMMAND_CREATE) { + createFragment(root, reactNativeViewId); + } + } + + @ReactPropGroup(names = {"width", "height"}, customType = "Style") + public void setStyle(FrameLayout view, int index, Integer value) { + if (index == 0) { + propWidth = value; + } + + if (index == 1) { + propHeight = value; + } + } + + /** + * Replace your React Native view with a custom fragment + */ + public void createFragment(FrameLayout root, int reactNativeViewId) { + ViewGroup parentView = root.findViewById(reactNativeViewId); + setupLayout(parentView); + + final MyFragment myFragment = new MyFragment(); + FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity(); + activity.getSupportFragmentManager() + .beginTransaction() + .replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId)) + .commit(); + } + + public void setupLayout(View view) { + Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + manuallyLayoutChildren(view); + view.getViewTreeObserver().dispatchOnGlobalLayout(); + Choreographer.getInstance().postFrameCallback(this); + } + }); + } + + /** + * Layout all children properly + */ + public void manuallyLayoutChildren(View view) { + int width = propWidth>0 ? propWidth: View.MeasureSpec.getSize(ViewGroup.LayoutParams.WRAP_CONTENT); + int height = propHeight>0 ? propHeight: View.MeasureSpec.getSize(ViewGroup.LayoutParams.WRAP_CONTENT); + + view.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + + view.layout(0, 0, width, height); + } +} diff --git a/example/android/build.gradle b/example/android/build.gradle index 3e5f8ca..efca58a 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,5 +26,6 @@ buildscript { allprojects { repositories { maven { url "https://maven.juspay.in/jp-build-packages/hyper-sdk/" } + maven { url "https://sdk.getsimpl.com/" } } } diff --git a/example/src/HomeScreen.tsx b/example/src/HomeScreen.tsx index 93539fb..a936f94 100644 --- a/example/src/HomeScreen.tsx +++ b/example/src/HomeScreen.tsx @@ -21,7 +21,7 @@ import { ScrollView, } from 'react-native'; import HyperAPIUtils from './API'; -import HyperSdkReact from 'hyper-sdk-react'; +import HyperSdkReact, { MyView } from 'hyper-sdk-react'; import HyperUtils from './Utils'; import merchantConfig from './merchant_config.json'; import customerConfig from './customer_config.json'; @@ -389,6 +389,10 @@ class HomeScreen extends React.Component { + + Amount: Rs.10 + + ); } diff --git a/package.json b/package.json index 774e35f..1a0d045 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,8 @@ }, "eslintIgnore": [ "node_modules/", - "lib/" + "lib/", + "src/MyView.tsx" ], "prettier": { "quoteProps": "consistent", @@ -163,4 +164,4 @@ ] ] } -} +} \ No newline at end of file diff --git a/src/MyView.tsx b/src/MyView.tsx new file mode 100644 index 0000000..a4997c7 --- /dev/null +++ b/src/MyView.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { + View, + PixelRatio, + UIManager, + findNodeHandle, + requireNativeComponent +} from 'react-native'; + +export interface MyViewProps { + height?: number; + width?: number; +} + +export const MyViewManager = + requireNativeComponent('MyViewManager'); + +const createFragment = (viewId: number) => { + UIManager.dispatchViewManagerCommand( + viewId, + //@ts-ignore + UIManager.MyViewManager.Commands.create.toString(), + [viewId], + ); +} + +const MyView: React.FC = ({ height, width }) => { + const ref = React.useRef(null); + React.useEffect(() => { + const viewId = findNodeHandle(ref.current); + if (viewId) { + createFragment(viewId); + } + }, [height, width]); + + return ( + + + + ) +} + +export default MyView; diff --git a/src/index.tsx b/src/index.tsx index 5f62e6f..d5e2b97 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,9 @@ import { NativeModules, Platform } from 'react-native'; +// @ts-ignore +export { default as MyView } from './MyView'; + const LINKING_ERROR = `The package 'hyper-sdk-react' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + From d17321e3dc15b495ad15bc4c96950f40f6583ef6 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 11 Oct 2023 15:51:51 +0530 Subject: [PATCH 2/2] chore: [IOS Only] React Native Widge --- ios/MyViewManagerManager.h | 4 ++++ ios/MyViewManagerManager.m | 38 ++++++++++++++++++++++++++++++++++++++ src/MyView.tsx | 11 +++++++---- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 ios/MyViewManagerManager.h create mode 100644 ios/MyViewManagerManager.m diff --git a/ios/MyViewManagerManager.h b/ios/MyViewManagerManager.h new file mode 100644 index 0000000..f1abff1 --- /dev/null +++ b/ios/MyViewManagerManager.h @@ -0,0 +1,4 @@ + +#import +@interface MyViewManagerManager : NSObject +@end diff --git a/ios/MyViewManagerManager.m b/ios/MyViewManagerManager.m new file mode 100644 index 0000000..b504d60 --- /dev/null +++ b/ios/MyViewManagerManager.m @@ -0,0 +1,38 @@ +#import +#import +#import +#import + +@interface MyViewManagerManager : RCTViewManager + +@end + +@implementation MyViewManagerManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + UIView *myView = [[UIView alloc] init]; + UILabel *label = [[UILabel alloc] init]; + label.text = @"Welcome to IOS UIView with React Native."; + label.textAlignment = NSTextAlignmentLeft; + label.backgroundColor = UIColor.blueColor; + label.textColor = UIColor.whiteColor; + [label sizeToFit]; + [myView addSubview:label]; + + return myView; +} + +RCT_CUSTOM_VIEW_PROPERTY(width, float, UIView) +{ + view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, [RCTConvert CGFloat:json], view.frame.size.height); +} + +RCT_CUSTOM_VIEW_PROPERTY(height, float, UIView) +{ + view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, [RCTConvert CGFloat:json]); +} + +@end diff --git a/src/MyView.tsx b/src/MyView.tsx index a4997c7..787cae1 100644 --- a/src/MyView.tsx +++ b/src/MyView.tsx @@ -4,7 +4,8 @@ import { PixelRatio, UIManager, findNodeHandle, - requireNativeComponent + requireNativeComponent, + Platform } from 'react-native'; export interface MyViewProps { @@ -27,9 +28,11 @@ const createFragment = (viewId: number) => { const MyView: React.FC = ({ height, width }) => { const ref = React.useRef(null); React.useEffect(() => { - const viewId = findNodeHandle(ref.current); - if (viewId) { - createFragment(viewId); + if (Platform.OS == 'android') { + const viewId = findNodeHandle(ref.current); + if (viewId) { + createFragment(viewId); + } } }, [height, width]);