From bb11b56bd654589eebfc260bedecc6138cc4d69a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 23 Oct 2024 11:54:47 -0700 Subject: [PATCH 1/4] Add Inbox route and associated icon: define Inbox route in constants and update navigation types --- example/src/components/App/App.constants.ts | 1 + example/src/constants/routes.ts | 1 + example/src/types/navigation.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/example/src/components/App/App.constants.ts b/example/src/components/App/App.constants.ts index ede7fad58..14de65c8c 100644 --- a/example/src/components/App/App.constants.ts +++ b/example/src/components/App/App.constants.ts @@ -1,5 +1,6 @@ import { Route } from '../../constants'; export const routeIcon = { + [Route.Inbox]: 'mail-outline', [Route.User]: 'person-outline', }; diff --git a/example/src/constants/routes.ts b/example/src/constants/routes.ts index 69f78aa21..3ea4d61ff 100644 --- a/example/src/constants/routes.ts +++ b/example/src/constants/routes.ts @@ -1,4 +1,5 @@ export enum Route { + Inbox = 'Inbox', Login = 'Login', Main = 'Main', User = 'User', diff --git a/example/src/types/navigation.ts b/example/src/types/navigation.ts index 8b2e04933..ea2c85e2d 100644 --- a/example/src/types/navigation.ts +++ b/example/src/types/navigation.ts @@ -8,6 +8,7 @@ import type { StackScreenProps } from '@react-navigation/stack'; import { Route } from '../constants/routes'; export type MainScreenParamList = { + [Route.Inbox]: undefined; [Route.User]: undefined; }; From b0c5b92ad61692b06e1ce66c1ea46507f809c4d1 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 23 Oct 2024 12:00:39 -0700 Subject: [PATCH 2/4] Add Inbox tab state management: introduce isInboxTab and returnToInboxTrigger in IterableAppProps, update context provider, and initialize state --- example/src/hooks/useIterableApp.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 7c3aa12ac..237c8c65f 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -34,6 +34,8 @@ interface IterableAppProps { initialize: (navigation: Navigation) => void; /** Whether the SDK has been initialized */ isInitialized?: boolean; + /** Is the tab in focus the `Inbox` tab? */ + isInboxTab: boolean; /** Whether the user is logged in */ isLoggedIn?: boolean; /** @@ -45,10 +47,16 @@ interface IterableAppProps { loginInProgress?: boolean; /** Logs the user out */ logout: () => void; + /** TODO: Ask @evantk91 or @Ayyanchira what this is for */ + returnToInboxTrigger: boolean; /** Sets the API key for the user */ setApiKey: (value: string) => void; + /** Sets whether the tab in focus is the `Inbox` tab */ + setIsInboxTab: (value: boolean) => void; /** Sets whether the login is in progress */ setLoginInProgress: (value: boolean) => void; + /** TODO: Ask @evantk91 or @Ayyanchira what this is for */ + setReturnToInboxTrigger: (value: boolean) => void; /** Sets the user ID for the user */ setUserId: (value: string) => void; /** The user ID for the user */ @@ -59,13 +67,17 @@ const IterableAppContext = createContext({ apiKey: undefined, config: null, initialize: () => undefined, + isInboxTab: false, isInitialized: false, isLoggedIn: false, login: () => undefined, loginInProgress: false, logout: () => undefined, + returnToInboxTrigger: false, setApiKey: () => undefined, + setIsInboxTab: () => undefined, setLoginInProgress: () => undefined, + setReturnToInboxTrigger: () => undefined, setUserId: () => undefined, userId: undefined, }); @@ -75,6 +87,9 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; export const IterableAppProvider: FunctionComponent< React.PropsWithChildren > = ({ children }) => { + const [returnToInboxTrigger, setReturnToInboxTrigger] = + useState(false); + const [isInboxTab, setIsInboxTab] = useState(false); const [itblConfig, setItblConfig] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); const [isInitialized, setIsInitialized] = useState(false); @@ -191,13 +206,17 @@ export const IterableAppProvider: FunctionComponent< apiKey, config: itblConfig, initialize, + isInboxTab, isInitialized, isLoggedIn, login, loginInProgress, logout, + returnToInboxTrigger, setApiKey, + setIsInboxTab, setLoginInProgress, + setReturnToInboxTrigger, setUserId, userId, }} From fd85e408df267624d2d43f9a83403e213c4e0e08 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 23 Oct 2024 12:07:56 -0700 Subject: [PATCH 3/4] Add Inbox component and constants: implement Inbox component with customizations and create Inbox.constants for styling Fixed main iterable app so that constants are property being exported. --- .../src/components/Inbox/Inbox.constants.ts | 68 +++++++++++++++++++ example/src/components/Inbox/Inbox.tsx | 21 ++++++ example/src/components/Inbox/index.ts | 2 + src/IterableInbox.tsx | 6 +- src/index.tsx | 7 +- 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 example/src/components/Inbox/Inbox.constants.ts create mode 100644 example/src/components/Inbox/Inbox.tsx create mode 100644 example/src/components/Inbox/index.ts diff --git a/example/src/components/Inbox/Inbox.constants.ts b/example/src/components/Inbox/Inbox.constants.ts new file mode 100644 index 000000000..35f48c783 --- /dev/null +++ b/example/src/components/Inbox/Inbox.constants.ts @@ -0,0 +1,68 @@ +export const iterableInboxCustomization = { + navTitle: 'Iterable', + noMessagesTitle: 'No messages today', + noMessagesBody: 'Come back later', + + unreadIndicatorContainer: { + flexDirection: 'column', + justifyContent: 'flex-start', + }, + + unreadIndicator: { + width: 15, + height: 15, + borderRadius: 15 / 2, + backgroundColor: 'orange', + marginLeft: 10, + marginRight: 10, + marginTop: 10, + }, + + unreadMessageIconContainer: { + paddingLeft: 0, + flexDirection: 'column', + justifyContent: 'center', + }, + + readMessageIconContainer: { + paddingLeft: 35, + flexDirection: 'column', + justifyContent: 'center', + }, + + messageContainer: { + paddingLeft: 10, + width: '65%', + flexDirection: 'column', + justifyContent: 'center', + }, + + title: { + fontSize: 22, + paddingBottom: 10, + }, + + body: { + fontSize: 15, + color: 'lightgray', + width: '65%', + flexWrap: 'wrap', + paddingBottom: 10, + }, + + createdAt: { + fontSize: 12, + color: 'lightgray', + }, + + messageRow: { + flexDirection: 'row', + backgroundColor: 'white', + paddingTop: 10, + paddingBottom: 10, + height: 200, + borderStyle: 'solid', + borderColor: 'red', + borderTopWidth: 1, + }, +}; diff --git a/example/src/components/Inbox/Inbox.tsx b/example/src/components/Inbox/Inbox.tsx new file mode 100644 index 000000000..7fa5c25c4 --- /dev/null +++ b/example/src/components/Inbox/Inbox.tsx @@ -0,0 +1,21 @@ +import { + IterableInbox, + type IterableInboxProps, +} from '@iterable/react-native-sdk'; + +import { useIterableApp } from '../../hooks'; +import { iterableInboxCustomization } from './Inbox.constants'; + +export const Inbox = (props: IterableInboxProps) => { + const { returnToInboxTrigger } = useIterableApp(); + + return ( + + ); +}; + +export default Inbox; diff --git a/example/src/components/Inbox/index.ts b/example/src/components/Inbox/index.ts new file mode 100644 index 000000000..cd08fd6f0 --- /dev/null +++ b/example/src/components/Inbox/index.ts @@ -0,0 +1,2 @@ +export * from './Inbox'; +export { default } from './Inbox'; diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index 58e6912a0..08f4c6836 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -30,7 +30,7 @@ const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); // TODO: Comment -type InboxProps = { +export interface IterableInboxProps { returnToInboxTrigger?: boolean; messageListItemLayout?: Function; customizations?: IterableInboxCustomizations; @@ -38,7 +38,7 @@ type InboxProps = { tabBarPadding?: number; safeAreaMode?: boolean; showNavTitle?: boolean; -}; +} // TODO: Comment export const IterableInbox = ({ @@ -49,7 +49,7 @@ export const IterableInbox = ({ tabBarPadding = 20, safeAreaMode = true, showNavTitle = true, -}: InboxProps) => { +}: IterableInboxProps) => { const defaultInboxTitle = 'Inbox'; const inboxDataModel = new IterableInboxDataModel(); diff --git a/src/index.tsx b/src/index.tsx index 3490edcd7..63217e0e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,11 @@ */ export type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; export type { InboxRowViewModel } from './InboxRowViewModel'; -export { Iterable, IterableCommerceItem } from './Iterable'; +export { + Iterable, + IterableAttributionInfo, + IterableCommerceItem, +} from './Iterable'; export { IterableAction, IterableActionContext, @@ -27,6 +31,7 @@ export { } from './IterableInAppClasses'; export { IterableInAppManager } from './IterableInAppManager'; export { IterableInAppMessage } from './IterableInAppMessage'; +export { IterableInbox, type IterableInboxProps } from './IterableInbox'; export type { IterableInboxCustomizations } from './IterableInboxCustomizations'; export { IterableInboxEmptyState } from './IterableInboxEmptyState'; export { IterableInboxMessageCell } from './IterableInboxMessageCell'; From 5478edc24698bb15474b6c3f2d306bf1c4fee82e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 23 Oct 2024 12:30:37 -0700 Subject: [PATCH 4/4] Add Inbox tab and unread message count: implement Inbox tab with badge for unread messages, update navigation routes, and enhance state management in useIterableApp --- example/src/components/App/Main.tsx | 51 +++++++++++++++++++++++++++- example/src/hooks/useIterableApp.tsx | 2 +- src/IterableInboxMessageDisplay.tsx | 2 +- src/IterableInboxMessageList.tsx | 4 +-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/example/src/components/App/Main.tsx b/example/src/components/App/Main.tsx index a02ae5f2b..c4b946315 100644 --- a/example/src/components/App/Main.tsx +++ b/example/src/components/App/Main.tsx @@ -1,14 +1,42 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { useState, useEffect } from 'react'; + +import { Iterable } from '@iterable/react-native-sdk'; import { colors, Route } from '../../constants'; import type { MainScreenParamList } from '../../types'; import { routeIcon } from './App.constants'; import { getIcon } from './App.utils'; import { User } from '../User'; +import Inbox from '../Inbox'; +import { useIterableApp } from '../../hooks'; const Tab = createBottomTabNavigator(); export const Main = () => { + const { + isInboxTab, + isLoggedIn, + loginInProgress, + returnToInboxTrigger, + setIsInboxTab, + setReturnToInboxTrigger, + userId, + } = useIterableApp(); + const [unreadMessageCount, setUnreadMessageCount] = useState(0); + + useEffect(() => { + if (loginInProgress) return; + if (isLoggedIn) { + Iterable.inAppManager.getMessages().then((messages) => { + setUnreadMessageCount(messages.length); + }); + } else { + // Reset unread message count when user logs out + setUnreadMessageCount(0); + } + }, [isLoggedIn, loginInProgress, userId]); + return ( <> { }; }} > - + ({ + tabPress: () => { + if (isInboxTab) { + setReturnToInboxTrigger(!returnToInboxTrigger); + } + setIsInboxTab(true); + }, + })} + /> + ({ + tabPress: () => setIsInboxTab(false), + })} + /> ); diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 237c8c65f..41a400b4a 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -125,7 +125,7 @@ export const IterableAppProvider: FunctionComponent< config.inAppDisplayInterval = 1.0; // Min gap between in-apps. No need to set this in production. config.urlHandler = (url: string) => { - const routeNames = [Route.User]; + const routeNames = [Route.Inbox, Route.User]; for (const route of routeNames) { if (url.includes(route.toLowerCase())) { // TODO: Figure out typing for this diff --git a/src/IterableInboxMessageDisplay.tsx b/src/IterableInboxMessageDisplay.tsx index 65bd4f504..9e2f9849c 100644 --- a/src/IterableInboxMessageDisplay.tsx +++ b/src/IterableInboxMessageDisplay.tsx @@ -213,7 +213,7 @@ export const IterableInboxMessageDisplay = ({ }} > - + Inbox diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index 5352be2b2..5902b8b9e 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -52,9 +52,7 @@ export const IterableInboxMessageList = ({ swipingCheck={setSwiping} messageListItemLayout={messageListItemLayout} deleteRow={(messageId: string) => deleteRow(messageId)} - handleMessageSelect={(messageId: string, i: number) => - handleMessageSelect(messageId, i) - } + handleMessageSelect={handleMessageSelect} contentWidth={contentWidth} isPortrait={isPortrait} />