Skip to content

Commit

Permalink
CU/8253-Dylan-NotificationRQMigration (#8254)
Browse files Browse the repository at this point in the history
Co-authored-by: Theo Bentum <[email protected]>
Co-authored-by: Therese <[email protected]>
  • Loading branch information
3 people authored Oct 4, 2024
1 parent b3babd5 commit de30b5a
Show file tree
Hide file tree
Showing 21 changed files with 568 additions and 388 deletions.
94 changes: 94 additions & 0 deletions VAMobile/patches/react-native-notifications+5.1.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
diff --git a/node_modules/react-native-notifications/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java b/node_modules/react-native-notifications/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
index f9c858b..94ea188 100644
--- a/node_modules/react-native-notifications/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
+++ b/node_modules/react-native-notifications/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
@@ -2,8 +2,8 @@
package com.wix.reactnativenotifications;

import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationManagerCompat;
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationManagerCompat;

public abstract class NotificationManagerCompatFacade {
public static NotificationManagerCompat from(@NonNull Context context) {
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
index 90969b2..4c00e69 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
@@ -63,7 +63,7 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
@Override
public void onNewIntent(Intent intent) {
if (NotificationIntentAdapter.canHandleIntent(intent)) {
- Bundle notificationData = intent.getExtras();
+ Bundle notificationData = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
final IPushNotification notification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationData);
if (notification != null) {
notification.onOpened();
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java
index 5b7f15f..7b3ee7e 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java
@@ -15,6 +15,7 @@ import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.AppLifecycleFacadeHolder;
import com.wix.reactnativenotifications.core.InitialNotificationHolder;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
+import com.wix.reactnativenotifications.core.ReactAppLifecycleFacade;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.wix.reactnativenotifications.core.notification.PushNotification;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
@@ -66,7 +67,12 @@ public class RNNotificationsPackage implements ReactPackage, AppLifecycleFacade.

@Override
public void onActivityStarted(Activity activity) {
- if (InitialNotificationHolder.getInstance().get() == null) {
+ boolean isReactInitialized = false;
+ if (AppLifecycleFacadeHolder.get() instanceof ReactAppLifecycleFacade) {
+ isReactInitialized = AppLifecycleFacadeHolder.get().isReactInitialized();
+ }
+
+ if (InitialNotificationHolder.getInstance().get() == null && !isReactInitialized) {
callOnOpenedIfNeed(activity);
}
}
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java
index 1e7e871..62e5cb8 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java
@@ -14,17 +14,9 @@ public class NotificationIntentAdapter {

@SuppressLint("UnspecifiedImmutableFlag")
public static PendingIntent createPendingNotificationIntent(Context appContext, PushNotificationProps notification) {
- if (canHandleTrampolineActivity(appContext)) {
- Intent intent = new Intent(appContext, ProxyService.class);
- intent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
- return PendingIntent.getService(appContext, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT);
- } else {
- Intent mainActivityIntent = appContext.getPackageManager().getLaunchIntentForPackage(appContext.getPackageName());
- mainActivityIntent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
- TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(appContext);
- taskStackBuilder.addNextIntentWithParentStack(mainActivityIntent);
- return taskStackBuilder.getPendingIntent((int) System.currentTimeMillis(), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
- }
+ Intent intent = appContext.getPackageManager().getLaunchIntentForPackage(appContext.getPackageName());
+ intent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
+ return PendingIntent.getActivity(appContext, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
}

public static boolean canHandleTrampolineActivity(Context appContext) {
diff --git a/node_modules/react-native-notifications/lib/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java b/node_modules/react-native-notifications/lib/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
index f9c858b..94ea188 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/reactNative59/java/com/wix/reactnativenotifications/NotificationManagerCompatFacade.java
@@ -2,8 +2,8 @@
package com.wix.reactnativenotifications;

import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationManagerCompat;
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationManagerCompat;

public abstract class NotificationManagerCompatFacade {
public static NotificationManagerCompat from(@NonNull Context context) {
32 changes: 18 additions & 14 deletions VAMobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { profileAddressType } from 'screens/HomeScreen/ProfileScreen/ContactInfo
import EditAddressScreen from 'screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen'
import store, { RootState } from 'store'
import { injectStore } from 'store/api/api'
import { AnalyticsState, AuthState, NotificationsState, handleTokenCallbackUrl, initializeAuth } from 'store/slices'
import { AnalyticsState, AuthState, handleTokenCallbackUrl, initializeAuth } from 'store/slices'
import { SettingsState } from 'store/slices'
import {
AccessibilityState,
Expand All @@ -64,7 +64,7 @@ import { useHeaderStyles, useTopPaddingAsHeaderStyles } from 'utils/hooks/header
import i18n from 'utils/i18n'
import { isIOS } from 'utils/platform'

import NotificationManager from './components/NotificationManager'
import NotificationManager, { useNotificationContext } from './components/NotificationManager'
import VeteransCrisisLineScreen from './screens/HomeScreen/VeteransCrisisLineScreen/VeteransCrisisLineScreen'
import OnboardingCarousel from './screens/OnboardingCarousel'
import EditDirectDepositScreen from './screens/PaymentsScreen/DirectDepositScreen/EditDirectDepositScreen'
Expand Down Expand Up @@ -189,6 +189,7 @@ export function AuthGuard() {
const dispatch = useAppDispatch()
const { initializing, loggedIn, syncing, firstTimeLogin, canStoreWithBiometric, displayBiometricsPreferenceScreen } =
useSelector<RootState, AuthState>((state) => state.auth)
const { tappedForegroundNotification, setTappedForegroundNotification } = useNotificationContext()
const { loadingRemoteConfig, remoteConfigActivated } = useSelector<RootState, SettingsState>(
(state) => state.settings,
)
Expand Down Expand Up @@ -268,18 +269,22 @@ export function AuthGuard() {

useEffect(() => {
console.debug('AuthGuard: initializing')
dispatch(initializeAuth())

const listener = (event: { url: string }): void => {
if (event.url?.startsWith('vamobile://login-success?')) {
dispatch(handleTokenCallbackUrl(event.url))
if (loggedIn && tappedForegroundNotification) {
console.debug('User tapped foreground notification. Skipping initializeAuth.')
setTappedForegroundNotification(false)
} else if (!loggedIn) {
dispatch(initializeAuth())
const listener = (event: { url: string }): void => {
if (event.url?.startsWith('vamobile://login-success?')) {
dispatch(handleTokenCallbackUrl(event.url))
}
}
const sub = Linking.addEventListener('url', listener)
return (): void => {
sub?.remove()
}
}
const sub = Linking.addEventListener('url', listener)
return (): void => {
sub?.remove()
}
}, [dispatch])
}, [dispatch, loggedIn, tappedForegroundNotification, setTappedForegroundNotification])

useEffect(() => {
// Log campaign analytics if the app is launched by a campaign link
Expand Down Expand Up @@ -392,8 +397,7 @@ export function AppTabs() {

export function AuthedApp() {
const headerStyles = useHeaderStyles()
const { initialUrl } = useSelector<RootState, NotificationsState>((state) => state.notifications)

const { initialUrl } = useNotificationContext()
const homeScreens = getHomeScreens()
const benefitsScreens = getBenefitsScreens()
const healthScreens = getHealthScreens()
Expand Down
41 changes: 41 additions & 0 deletions VAMobile/src/api/notifications/getPushPreferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import { useQuery } from '@tanstack/react-query'

import { GetPushPrefsResponse, LoadPushPreferencesData } from 'api/types'
import store from 'store'
import { get } from 'store/api'

import { notificationKeys } from './queryKeys'
import { DEVICE_ENDPOINT_SID } from './registerDevice'

/**
* Fetch user push preferences
*/
const getPushPreferences = async (): Promise<LoadPushPreferencesData | undefined> => {
const endpoint_sid = await AsyncStorage.getItem(DEVICE_ENDPOINT_SID)
const demoMode = store.getState().demo.demoMode
let response
if (endpoint_sid) {
response = await get<GetPushPrefsResponse>(`/v0/push/prefs/${endpoint_sid}`)
} else if (demoMode) {
response = await get<GetPushPrefsResponse>(`/v0/push/prefs/`)
}
return {
preferences: response?.data.attributes.preferences || [],
}
}

/**
* Returns a query for user push preferences
*/
export const usePushPreferences = (options?: { enabled?: boolean }) => {
return useQuery({
...options,
queryKey: notificationKeys.pushPreferences,
queryFn: () => getPushPreferences(),
meta: {
errorName: 'getPushPreferences: Service error',
},
})
}
30 changes: 30 additions & 0 deletions VAMobile/src/api/notifications/getSystemNotificationSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useQuery } from '@tanstack/react-query'

import { LoadSystemNotificationsData } from 'api/types'
import { notificationsEnabled } from 'utils/notifications'

import { notificationKeys } from './queryKeys'

/**
* Fetch user system notification settings
*/
const getSystemNotificationsSettings = async (): Promise<LoadSystemNotificationsData | undefined> => {
const systemNotificationsOn = await notificationsEnabled()
return {
systemNotificationsOn,
}
}

/**
* Returns a query for user system notification settings
*/
export const useSystemNotificationsSettings = (options?: { enabled?: boolean }) => {
return useQuery({
...options,
queryKey: notificationKeys.systemSettings,
queryFn: () => getSystemNotificationsSettings(),
meta: {
errorName: 'getSystemNotificationsSettings: Failed to retrieve system notification setting',
},
})
}
5 changes: 5 additions & 0 deletions VAMobile/src/api/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './getPushPreferences'
export * from './getSystemNotificationSettings'
export * from './queryKeys'
export * from './registerDevice'
export * from './updatePushPreferences'
4 changes: 4 additions & 0 deletions VAMobile/src/api/notifications/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const notificationKeys = {
pushPreferences: ['pushPreferences'] as const,
systemSettings: ['systemSettings'] as const,
}
65 changes: 65 additions & 0 deletions VAMobile/src/api/notifications/registerDevice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import { useMutation } from '@tanstack/react-query'

import { PUSH_APP_NAME, PushOsName, PushRegistration, PushRegistrationResponse, RegisterDeviceParams } from 'api/types'
import { UserAnalytics } from 'constants/analytics'
import { put } from 'store/api'
import { logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics'
import { isErrorObject } from 'utils/common'
import { getDeviceName } from 'utils/deviceData'
import { isIOS } from 'utils/platform'

export const DEVICE_TOKEN_KEY = '@store_device_token'
export const DEVICE_ENDPOINT_SID = '@store_device_endpoint_sid'
export const USER_ID = '@store_user_id'
/**
* Registers device for push notifications
*/
const registerDevice = async (
registerDeviceParams: RegisterDeviceParams,
): Promise<PushRegistrationResponse | undefined> => {
if (registerDeviceParams.deviceToken) {
const savedToken = await AsyncStorage.getItem(DEVICE_TOKEN_KEY)
const savedSid = await AsyncStorage.getItem(DEVICE_ENDPOINT_SID)
const savedUserID = await AsyncStorage.getItem(USER_ID)
const isNewUser = !savedUserID || (registerDeviceParams.userID && savedUserID !== registerDeviceParams.userID)
const deviceName = await getDeviceName()
if (!savedToken || savedToken !== registerDeviceParams.deviceToken || !savedSid || isNewUser) {
const params: PushRegistration = {
deviceName: deviceName,
deviceToken: registerDeviceParams.deviceToken,
appName: PUSH_APP_NAME,
osName: isIOS() ? PushOsName.ios : PushOsName.android,
debug: false,
}
return put<PushRegistrationResponse>('/v0/push/register', params)
}
} else {
await AsyncStorage.removeItem(DEVICE_TOKEN_KEY)
}
}

/**
* Returns a mutation for registering users device for push notifications
*/
export const useRegisterDevice = () => {
return useMutation({
mutationFn: registerDevice,
onSettled: async (data, error, variables) => {
setAnalyticsUserProperty(UserAnalytics.vama_uses_notifications(variables.deviceToken ? true : false))
},
onSuccess: async (response, variables) => {
if (response) {
await AsyncStorage.setItem(DEVICE_ENDPOINT_SID, response?.data.attributes.endpointSid)
variables.deviceToken && (await AsyncStorage.setItem(DEVICE_TOKEN_KEY, variables.deviceToken))
variables.userID && (await AsyncStorage.setItem(USER_ID, variables.userID))
}
},
onError: (error) => {
if (isErrorObject(error)) {
logNonFatalErrorToFirebase(error, 'registerDevice: Service error')
}
},
})
}
47 changes: 47 additions & 0 deletions VAMobile/src/api/notifications/updatePushPreferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import { useMutation, useQueryClient } from '@tanstack/react-query'

import { LoadPushPreferencesData, PushPreference } from 'api/types'
import { put } from 'store/api'
import { logNonFatalErrorToFirebase } from 'utils/analytics'
import { isErrorObject } from 'utils/common'

import { notificationKeys } from './queryKeys'
import { DEVICE_ENDPOINT_SID } from './registerDevice'

/**
* Updates a user's push preference
*/
const updatePushPreferences = async (preference: PushPreference) => {
const endpoint_sid = await AsyncStorage.getItem(DEVICE_ENDPOINT_SID)
const params = { preference: preference.preferenceId, enabled: !preference.value }
return put(`/v0/push/prefs/${endpoint_sid}`, params)
}

/**
* Returns a mutation for updating users push preference
*/
export const useUpdatePushPreferences = () => {
const queryClient = useQueryClient()

return useMutation({
mutationFn: updatePushPreferences,
onSuccess: (data: unknown, preference: PushPreference) => {
const pushPreferences = queryClient.getQueryData(notificationKeys.pushPreferences) as LoadPushPreferencesData
const index = pushPreferences.preferences.findIndex((p) => p.preferenceId === preference.preferenceId)
const newPrefSetting: PushPreference = {
preferenceId: preference.preferenceId,
preferenceName: preference.preferenceName,
value: !preference.value,
}
pushPreferences.preferences.splice(index, 1, newPrefSetting)
queryClient.setQueryData(notificationKeys.pushPreferences, pushPreferences)
},
onError: (error) => {
if (isErrorObject(error)) {
logNonFatalErrorToFirebase(error, 'setPushPref: Service error')
}
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ export type GetPushPrefsResponse = {
}
}
}

export type LoadPushPreferencesData = {
preferences: PushPreference[]
}

export type LoadSystemNotificationsData = {
systemNotificationsOn: boolean
}

export type RegisterDeviceParams = {
deviceToken?: string
userID?: string
}
1 change: 1 addition & 0 deletions VAMobile/src/api/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './DisabilityRatingData'
export * from './EmailData'
export * from './FacilityData'
export * from './LettersData'
export * from './Notifications'
export * from './PaymentData'
export * from './PhoneData'
export * from './PersonalInformationData'
Expand Down
Loading

0 comments on commit de30b5a

Please sign in to comment.