Skip to content

Commit

Permalink
feat: secure entry react component for android
Browse files Browse the repository at this point in the history
  • Loading branch information
TakaGoto committed Jul 22, 2024
1 parent c526063 commit e195c86
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 1 deletion.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,21 @@ import { TicketsSdkEmbedded } from 'react-native-ticketmaster-ignite';
</View>
```

#### SecureEntryView (android only)

Example:

```typescript

import { SecureEntryAndroid } from 'react-native-ticketmaster-ignite';

<View>
<SecureEntryAndroid token="token_here" />
</View>
```



#### RetailSDK

Module responsible for the purchase and prepurchase flows in the Retail SDK.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.ticketmasterignite
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.ticketmasterignite.tickets.SecureEntryViewFragmentManager
import com.ticketmasterignite.tickets.TicketsViewManager

public class TicketmasterIgnitePackage : ReactPackage {
override fun createViewManagers(
reactContext: ReactApplicationContext
) = listOf(TicketsViewManager(reactContext))
) = listOf(TicketsViewManager(reactContext), SecureEntryViewFragmentManager(reactContext))

override fun createNativeModules(
reactContext: ReactApplicationContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.ticketmasterignite.tickets

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.ticketmaster.presence.SecureEntryView
import com.ticketmasterignite.R

class SecureEntryFragment : Fragment() {
private var secureEntryView: SecureEntryView? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_secure_entry_view, container, false)
secureEntryView = view.findViewById(R.id.secure_entry_view)
return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val token = arguments?.getString("token") ?: ""
secureEntryView?.setToken(token)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.ticketmasterignite.tickets

import android.os.Bundle
import android.view.Choreographer
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.uimanager.annotations.ReactPropGroup

class SecureEntryViewFragmentManager (
private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {
private var propWidth: Int? = null
private var propHeight: Int? = null
private var secureEntryToken: String? = null
private var customFragment: SecureEntryFragment? = null

override fun getName() = REACT_CLASS

/**
* Return a FrameLayout which will later hold the Fragment
*/
override fun createViewInstance(reactContext: ThemedReactContext) =
FrameLayout(reactContext)

/**
* Map the "create" command to an integer
*/
override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)

/**
* Handle "create" command (called from JS) and call createFragment method
*/
override fun receiveCommand(
root: FrameLayout,
commandId: String,
args: ReadableArray?
) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)

when (commandId.toInt()) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
}
}

@ReactPropGroup(names = ["width", "height"], customType = "Style")
fun setStyle(view: FrameLayout, index: Int, value: Int) {
if (index == 0) propWidth = value
if (index == 1) propHeight = value
}

@ReactProp(name = "token")
fun setToken(view: View, token: ReadableMap) {
token.getString("token")?.let { secureEntryToken = it }
}


/**
* Replace your React Native view with a custom fragment
*/
private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)

customFragment = SecureEntryFragment()
if (!secureEntryToken.isNullOrEmpty()) {
val args = Bundle().apply {
putString("token", secureEntryToken)
}
customFragment?.apply {
arguments = args
}
}

val activity = reactContext.currentActivity as FragmentActivity
activity.supportFragmentManager
.beginTransaction()
.replace(reactNativeViewId, customFragment!!, reactNativeViewId.toString())
.commit()
}

fun setupLayout(view: View) {
Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}

/**
* Layout all children properly
*/
private fun manuallyLayoutChildren(view: View) {
// propWidth and propHeight coming from react-native props
val width = requireNotNull(propWidth)
val height = requireNotNull(propHeight)

view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))

view.layout(0, 80, width, height)
}

companion object {
private const val REACT_CLASS = "SecureEntryViewManager"
private const val COMMAND_CREATE = 1
}
}
10 changes: 10 additions & 0 deletions android/src/main/res/layout/fragment_secure_entry_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- res/layout/fragment_my_custom_view.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.ticketmaster.presence.SecureEntryView
android:id="@+id/secure_entry_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
12 changes: 12 additions & 0 deletions example/src/navigators/BottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MyEvents from '../screens/MyEvents';
import HomeIcon from '../assets/svg/HomeIcon';
import MyEventsIcon from '../assets/svg/MyEventsIcon';
import Config from 'react-native-config';
import SecureEntryView from '../screens/SecureEntryView.android';

const Tab = createBottomTabNavigator();

Expand Down Expand Up @@ -56,6 +57,17 @@ const BottomTabs = () => {
),
}}
/>
<Tab.Screen
name="Secure Entry"
component={SecureEntryView}
options={{
tabBarLabel: 'Secure Entry',
unmountOnBlur: true,
tabBarIcon: ({ focused }) => (

Check warning on line 66 in example/src/navigators/BottomTabs.tsx

View workflow job for this annotation

GitHub Actions / lint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “BottomTabs” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<MyEventsIcon fill={focused ? Config.PRIMARY_COLOR : 'grey'} />
),
}}
/>
</Tab.Navigator>
);
};
Expand Down
6 changes: 6 additions & 0 deletions example/src/screens/SecureEntryView.android.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { SecureEntryAndroid } from 'react-native-ticketmaster-ignite';

const SecureEntryView = () => <SecureEntryAndroid token="token_here" />;

export default SecureEntryView;
3 changes: 3 additions & 0 deletions src/SecureEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { requireNativeComponent } from 'react-native';

export const SecureEntry = requireNativeComponent('SecureEntryViewManager');
44 changes: 44 additions & 0 deletions src/SecureEntryAndroid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { requireNativeComponent, useWindowDimensions } from 'react-native';
import React, { useEffect, useRef } from 'react';
import { PixelRatio, UIManager, findNodeHandle } from 'react-native';

interface SecureEntryProps {
token: string;
}

const createFragment = (viewId: number | null) =>
UIManager.dispatchViewManagerCommand(
viewId,
// we are calling the 'create' command
(UIManager as any).SecureEntryViewManager.Commands.create.toString(),
[viewId]
);

const SecureEntryViewManager = requireNativeComponent<SecureEntryProps>(
'SecureEntryViewManager'
);

export const SecureEntryAndroid: React.FC<SecureEntryProps> = (props) => {
const ref = useRef(null);
const height = useWindowDimensions().height;
const width = useWindowDimensions().width;
const token = props.token.toString();

useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId);
}, []);

return (
<SecureEntryViewManager
token={{ token }}

Check failure on line 34 in src/SecureEntryAndroid.tsx

View workflow job for this annotation

GitHub Actions / lint

Type '{ token: string; }' is not assignable to type 'string'.
style={{
// converts dpi to px, provide desired height
height: PixelRatio.getPixelSizeForLayoutSize(height),
// converts dpi to px, provide desired width
width: PixelRatio.getPixelSizeForLayoutSize(width),
}}
ref={ref}
/>
);
};
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AccountsSDK } from './AccountsSdk';
import { TicketsSdk } from './TicketsSdk';
import { TicketsSdkEmbeddedIos } from './TicketsSdkEmbeddedIos';
import { TicketsSdkEmbeddedAndroid } from './TicketsSdkEmbeddedAndroid';
import { SecureEntryAndroid } from './SecureEntryAndroid';
import { RetailSDK } from './RetailSdk';
import { IgniteProvider } from './IgniteProvider';
import { useIgnite } from './useIgnite';
Expand All @@ -11,6 +12,7 @@ export {
TicketsSdk, // Tickets SDK modal is only available for iOS
TicketsSdkEmbeddedIos,
TicketsSdkEmbeddedAndroid,
SecureEntryAndroid,
RetailSDK,
IgniteProvider,
useIgnite,
Expand Down

0 comments on commit e195c86

Please sign in to comment.