diff --git a/App.tsx b/App.tsx
index 3960551f..4f03242e 100644
--- a/App.tsx
+++ b/App.tsx
@@ -23,7 +23,7 @@ import { toastConfigs, ToastContainer } from './RN-UI-LIB/src/components/toast';
import { APM_APP_NAME, APM_BASE_URL, ENV } from './src/constants/config';
import { COLORS } from './RN-UI-LIB/src/styles/colors';
-import { LocalStorageKeys } from './src/common/Constants';
+import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './src/common/Constants';
import Permissions from './src/screens/permissions/Permissions';
import { setJsErrorHandler } from './src/services/exception-handler.service';
import SuspenseLoader from './RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
@@ -37,8 +37,13 @@ import {
} from './src/components/utlis/PermissionUtils';
import usePolling from './src/hooks/usePolling';
import { MILLISECONDS_IN_A_SECOND } from './RN-UI-LIB/src/utlis/common';
+import { setItem } from './src/components/utlis/storageHelper';
+import { StorageKeys } from './src/types/storageKeys';
+import dayJs from 'dayjs';
import { GlobalImageMap, hydrateGlobalImageMap } from './src/common/CachedImage';
import analytics from '@react-native-firebase/analytics';
+import handleUpdatedConfigureValuesFromFirebase from './src/services/firebaseFetchAndUpdate.service';
+import { addClickstreamEvent } from './src/services/clickstreamEventService';
import ScreenshotBlocker from './src/components/utlis/ScreenshotBlocker';
initSentry();
@@ -104,6 +109,12 @@ function App() {
return route?.name || '';
};
+ async function setForegroundTimeStampAndClickstream() {
+ const now = dayJs().toString();
+ await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now });
+ }
+
usePolling(askForPermissions, PERMISSION_CHECK_POLL_INTERVAL);
initApm({
@@ -133,6 +144,9 @@ function App() {
setIsGlobalDocumentMapLoaded(true);
})();
checkCodePushAndSync();
+ handleUpdatedConfigureValuesFromFirebase();
+ setForegroundTimeStampAndClickstream();
+
return () => {
appStateChange.remove();
};
diff --git a/RN-UI-LIB b/RN-UI-LIB
index 4c7f4f68..8876fa8a 160000
--- a/RN-UI-LIB
+++ b/RN-UI-LIB
@@ -1 +1 @@
-Subproject commit 4c7f4f6880d96bffa856e04d7b2b4d383e42c5cf
+Subproject commit 8876fa8a05de03421e39f598a1365cf0ddfdb8ee
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 047ab2e5..649d58b3 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -131,8 +131,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
-def VERSION_CODE = 83
-def VERSION_NAME = "2.3.10"
+def VERSION_CODE = 88
+def VERSION_NAME = "2.4.4"
android {
ndkVersion rootProject.ext.ndkVersion
diff --git a/android/app/google-services.json b/android/app/google-services.json
index 9d2a90ba..1df006c6 100644
--- a/android/app/google-services.json
+++ b/android/app/google-services.json
@@ -1,39 +1,40 @@
{
- "project_info": {
- "project_number": "60755663443",
- "project_id": "address-verification-app",
- "storage_bucket": "address-verification-app.appspot.com"
- },
- "client": [
- {
- "client_info": {
- "mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6",
- "android_client_info": {
- "package_name": "com.avapp"
- }
- },
- "oauth_client": [
- {
- "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
- "client_type": 3
- }
- ],
- "api_key": [
- {
- "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c"
- }
- ],
- "services": {
- "appinvite_service": {
- "other_platform_oauth_client": [
- {
- "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
- "client_type": 3
- }
- ]
- }
+ "project_info": {
+ "project_number": "60755663443",
+ "project_id": "address-verification-app",
+ "storage_bucket": "address-verification-app.appspot.com",
+ "firebase_url": "https://address-verification-app-default-rtdb.firebaseio.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:60755663443:android:4a948ee9d0b4e3098584a6",
+ "android_client_info": {
+ "package_name": "com.avapp"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
}
}
- ],
- "configuration_version": "1"
- }
\ No newline at end of file
+ }
+ ],
+ "configuration_version": "1"
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4815e009..dbaef9b9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,7 +30,12 @@
->
-21 -> lollipop 28 ->
+
+
+
+
+
+
-
+
+
+
+
+
diff --git a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java
index 56c69465..879a05fe 100644
--- a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java
+++ b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java
@@ -1,32 +1,57 @@
package com.avapp;
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.location.LocationManager;
import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
import com.facebook.react.bridge.ActivityEventListener;
+import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import android.content.pm.PackageInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.Base64;
+
import org.json.JSONArray;
+import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
-public class DeviceUtilsModule extends ReactContextBaseJavaModule implements ActivityEventListener {
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+public class DeviceUtilsModule extends ReactContextBaseJavaModule {
private ReactApplicationContext RNContext;
+ private File imageFile;
+
+ private int WHATSAPP_SHARE_REQUEST_CODE = 12345;
+
public DeviceUtilsModule(@Nullable ReactApplicationContext reactContext){
super(reactContext);
RNContext = reactContext;
+ reactContext.addActivityEventListener(mActivityEventListener);
}
@Override
@@ -34,15 +59,22 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule implements Act
return "DeviceUtilsModule";
}
- @Override
- public void onActivityResult(Activity activity, int i, int i1, @Nullable Intent intent) {
+ private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
+ @Override
+ public void onActivityResult(Activity activity, int i, int i1, @Nullable Intent intent) {
+ if(i1 != RESULT_CANCELED) {
+ if (i == WHATSAPP_SHARE_REQUEST_CODE) {
+ new File(Uri.fromFile(imageFile).getPath()).delete();
+ }
+ }
+ }
- }
+ @Override
+ public void onNewIntent(Intent intent) {
- @Override
- public void onNewIntent(Intent intent) {
+ }
+ };
- }
@ReactMethod
public void isLocationEnabled (Promise promise) {
@@ -77,4 +109,74 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule implements Act
promise.reject( err);
}
}
+ private static File convertBase64ToFile(Context context,String base64Data) {
+ try {
+ byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
+ File outputDir = context.getCacheDir();
+ File file = File.createTempFile("temp_image", ".jpg", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(decodedBytes);
+ fos.flush();
+ fos.close();
+ return file;
+ } catch (IOException e) {
+ Log.e("ShareUtils", "Failed to convert Base64 to file: " + e.getMessage());
+ return null;
+ }
+ }
+
+ public boolean isWhatsAppInstalled() {
+ PackageManager packageManager = RNContext.getPackageManager();
+ List packages = packageManager.getInstalledPackages(PackageManager.GET_META_DATA);
+
+ for (PackageInfo packageInfo : packages) {
+ String packageName = packageInfo.packageName;
+ if(packageName.equals("com.whatsapp")){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @ReactMethod
+ public void sendFeedbackToWhatsapp(String message, String imageUrl, String mimeType, Promise promise) {
+ try{
+ if(!isWhatsAppInstalled()){
+ promise.reject("errorCode", "1");
+ return;
+ }
+
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, message);
+
+ if(imageUrl.equals("")) {
+ sendIntent.setType("text/plain");
+ sendIntent.setPackage("com.whatsapp");
+ getCurrentActivity().startActivity(sendIntent);
+ } else {
+ sendIntent.setType(mimeType);
+ sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ imageFile = convertBase64ToFile(getReactApplicationContext(), imageUrl);
+ Uri fileUri = FileProvider.getUriForFile(getReactApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", new File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
+ imageFile.getName()
+ )
+ );
+ sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
+ sendIntent.setPackage("com.whatsapp");
+ getCurrentActivity().startActivityForResult(sendIntent, WHATSAPP_SHARE_REQUEST_CODE);
+ }
+ promise.resolve(true);
+ return;
+
+ } catch (Error e){
+ promise.reject("errorCode","2");
+ }
+ return;
+ }
+
+
+
+
}
diff --git a/android/app/src/main/java/com/avapp/MainActivity.java b/android/app/src/main/java/com/avapp/MainActivity.java
index 9ac0a557..d688d4d8 100644
--- a/android/app/src/main/java/com/avapp/MainActivity.java
+++ b/android/app/src/main/java/com/avapp/MainActivity.java
@@ -3,6 +3,8 @@ package com.avapp;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
+
+import android.content.IntentFilter;
import android.os.Bundle;
public class MainActivity extends ReactActivity {
@@ -16,14 +18,10 @@ public class MainActivity extends ReactActivity {
return "AVAPP";
}
-
- // https://github.com/software-mansion/react-native-screens#android
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(null);
- }
-
-
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(null);
+ }
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
diff --git a/android/app/src/main/java/com/avapp/ScreenshotBlockerModule.java b/android/app/src/main/java/com/avapp/ScreenshotBlockerModule.java
index 37cd1b86..36739f1a 100644
--- a/android/app/src/main/java/com/avapp/ScreenshotBlockerModule.java
+++ b/android/app/src/main/java/com/avapp/ScreenshotBlockerModule.java
@@ -68,26 +68,29 @@ public class ScreenshotBlockerModule extends ReactContextBaseJavaModule {
public void startScreenshotTracking() {
if (!isTracking) {
isTracking = true;
- ContentResolver contentResolver = getCurrentActivity().getContentResolver();
- contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
- long lastEventTimestamp = 0;
+ Activity activity = getCurrentActivity();
+ if(activity != null) {
+ ContentResolver contentResolver = activity.getContentResolver();
+ contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ long lastEventTimestamp = 0;
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- long currentTimeMillis = System.currentTimeMillis();
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ long currentTimeMillis = System.currentTimeMillis();
- if (currentTimeMillis - lastEventTimestamp > 1000) {
- sendScreenshotEvent();
- lastEventTimestamp = currentTimeMillis;
+ if (currentTimeMillis - lastEventTimestamp > 1000) {
+ sendScreenshotEvent();
+ lastEventTimestamp = currentTimeMillis;
+ }
}
- }
- };
+ };
- contentResolver.registerContentObserver(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- true,
- contentObserver
- );
+ contentResolver.registerContentObserver(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ true,
+ contentObserver
+ );
+ }
}
}
diff --git a/android/app/src/main/java/com/avapp/ScreenshotBlockerModulePackage.java b/android/app/src/main/java/com/avapp/ScreenshotBlockerModulePackage.java
index f16bdd35..b62a1e78 100644
--- a/android/app/src/main/java/com/avapp/ScreenshotBlockerModulePackage.java
+++ b/android/app/src/main/java/com/avapp/ScreenshotBlockerModulePackage.java
@@ -1,6 +1,5 @@
package com.avapp;
-
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
diff --git a/babel.config.js b/babel.config.js
index 5bff64bf..ae7561b4 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -8,7 +8,24 @@ module.exports = {
cwd: 'babelrc',
extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js'],
alias: {
- '@cuteapp': './app',
+ '@root': './src',
+ '@components': './src/components',
+ '@hooks': './src/hooks',
+ '@actions': './src/action',
+ '@reducers': './src/reducer',
+ '@constants': './src/constants',
+ '@screens': './src/screens',
+ '@services': './src/services',
+ '@types': './src/types',
+ '@common': './src/common',
+ '@assets': './src/assets',
+ '@store': './src/store/store',
+ '@utils': './src/components/utlis',
+ '@rn-ui-lib/components': './RN-UI-LIB/src/components',
+ '@rn-ui-lib/icons': './RN-UI-LIB/src/Icons',
+ '@rn-ui-lib/styles': './RN-UI-LIB/src/styles/index',
+ '@rn-ui-lib/colors': './RN-UI-LIB/src/styles/colors',
+ '@rn-ui-lib/utils': './RN-UI-LIB/src/utlis',
},
},
],
diff --git a/package.json b/package.json
index dd0fcbad..1bc39ce4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "AV_APP",
- "version": "2.4.0",
+ "version": "2.4.4",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",
@@ -37,6 +37,7 @@
"@react-native-firebase/database": "16.4.6",
"@react-native-firebase/firestore": "16.5.0",
"@react-native-firebase/messaging": "17.4.0",
+ "@react-native-firebase/remote-config": "16.4.6",
"@react-native-google-signin/google-signin": "9.0.2",
"@react-navigation/bottom-tabs": "6.5.5",
"@react-navigation/native": "6.1.4",
@@ -81,7 +82,8 @@
"react-native-webview": "12.0.2",
"react-redux": "8.0.5",
"redux": "4.2.0",
- "redux-persist": "6.0.0"
+ "redux-persist": "6.0.0",
+ "rn-fetch-blob": "0.12.0"
},
"devDependencies": {
"@babel/core": "7.12.9",
diff --git a/src/assets/icons/ChevronDown.tsx b/src/assets/icons/ChevronDown.tsx
new file mode 100644
index 00000000..ce42c6ba
--- /dev/null
+++ b/src/assets/icons/ChevronDown.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import Svg, { Mask, Path, G } from 'react-native-svg';
+const ChevronDown = (props) => {
+ const { fillColor = '#969696', size = 24 } = props;
+ return (
+
+ );
+};
+export default ChevronDown;
diff --git a/src/assets/icons/ChevronUp.tsx b/src/assets/icons/ChevronUp.tsx
new file mode 100644
index 00000000..e4e5ba08
--- /dev/null
+++ b/src/assets/icons/ChevronUp.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import Svg, { Mask, Path, G } from 'react-native-svg';
+const ChevronUp = (props) => {
+ const { fillColor = '#969696', size = 24 } = props;
+ return (
+
+ );
+};
+export default ChevronUp;
diff --git a/src/assets/icons/WhatsAppFeedbackShareIcon.tsx b/src/assets/icons/WhatsAppFeedbackShareIcon.tsx
new file mode 100644
index 00000000..10067e6f
--- /dev/null
+++ b/src/assets/icons/WhatsAppFeedbackShareIcon.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import Svg, { Path, SvgProps } from 'react-native-svg';
+const SvgComponent = (props: SvgProps) => (
+
+);
+export default SvgComponent;
diff --git a/src/common/AgentActivityConfigurableConstants.ts b/src/common/AgentActivityConfigurableConstants.ts
new file mode 100644
index 00000000..015db2b6
--- /dev/null
+++ b/src/common/AgentActivityConfigurableConstants.ts
@@ -0,0 +1,19 @@
+let ACTIVITY_TIME_ON_APP: number = 5; //5 seconds
+let ACTIVITY_TIME_WINDOW_HIGH: number = 10; //10 minutes
+let ACTIVITY_TIME_WINDOW_MEDIUM: number = 30; //30 minutes
+
+export const getActivityTimeOnApp = () => ACTIVITY_TIME_ON_APP;
+export const getActivityTimeWindowHigh = () => ACTIVITY_TIME_WINDOW_HIGH;
+export const getActivityTimeWindowMedium = () => ACTIVITY_TIME_WINDOW_MEDIUM;
+
+export const setActivityTimeOnApp = (activityTimeOnApp: number) => {
+ ACTIVITY_TIME_ON_APP = activityTimeOnApp;
+};
+
+export const setActivityTimeWindowHigh = (activityTimeWindowHigh: number) => {
+ ACTIVITY_TIME_WINDOW_HIGH = activityTimeWindowHigh;
+};
+
+export const setActivityTimeWindowMedium = (activityTimeWindowMedium: number) => {
+ ACTIVITY_TIME_WINDOW_MEDIUM = activityTimeWindowMedium;
+};
diff --git a/src/common/Constants.ts b/src/common/Constants.ts
index 3896a70e..f9577e94 100644
--- a/src/common/Constants.ts
+++ b/src/common/Constants.ts
@@ -338,6 +338,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
description: 'FA_COLLECT_MONEY_NUMBER_CHANGED',
},
FA_COPY_LAN_CLICKED: { name: 'FA_COPY_LAN_CLICKED', description: 'FA_COPY_LAN_CLICKED' },
+ FA_COPY_EMPLOYER_NAME_CLICKED: {
+ name: 'FA_COPY_EMPLOYER_NAME_CLICKED',
+ description: 'FA_COPY_EMPLOYER_NAME_CLICKED',
+ },
FA_COPY_LINK_CLICKED: { name: 'FA_COPY_LINK_CLICKED', description: 'FA_COPY_LINK_CLICKED' },
FA_COPY_LINK_FAILED: { name: 'FA_COPY_LINK_FAILED', description: 'FA_COPY_LINK_FAILED' },
FA_PAST_FEEDBACKS_FEEDBACK_CLICKED: {
@@ -435,6 +439,15 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_UNIFIED_ENTITY_REQUEST_FAILED',
description: 'FA_UNIFIED_ENTITY_REQUEST_FAILED',
},
+ FA_SHARE_FEEDBACK_CLICKED: {
+ name: 'FA_SHARE_FEEDBACK_CLICKED',
+ description:
+ 'When user clicks on share feedback on case details screen for any of the filled feedback',
+ },
+ FA_SHARE_SUCCESSFUL: {
+ name: 'FA_SHARE_SUCCESSFUL',
+ description: 'When user is redirected to WhatsApp after clicking on share feedback',
+ },
// Notifications
FA_NOTIFICATION_ICON_CLICK: {
diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx
index 8517cd73..73fe23df 100644
--- a/src/common/TrackingComponent.tsx
+++ b/src/common/TrackingComponent.tsx
@@ -34,8 +34,17 @@ import { setLockData } from '../reducer/userSlice';
import { getConfigData } from '../action/configActions';
import { AppStates } from '../types/appStates';
import { StorageKeys } from '../types/storageKeys';
+import { AgentActivity } from '../types/agentActivity';
+import {
+ getActivityTimeOnApp,
+ getActivityTimeWindowMedium,
+ getActivityTimeWindowHigh,
+} from './AgentActivityConfigurableConstants';
import RNFS from 'react-native-fs';
import { GlobalImageMap } from './CachedImage';
+import { get } from 'react-hook-form';
+import { addClickstreamEvent } from '../services/clickstreamEventService';
+import { CLICKSTREAM_EVENT_NAMES } from './Constants';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
@@ -43,6 +52,7 @@ export enum FOREGROUND_TASKS {
DATA_SYNC = 'DATA_SYNC',
FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK',
UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS',
+ UPDATE_AGENT_ACTIVITY = 'UPDATE_AGENT_ACTIVITY',
DELETE_CACHE = 'DELETE_CACHE',
}
@@ -51,7 +61,6 @@ interface ITrackingComponent {
}
let LAST_SYNC_STATUS = 'SKIP';
-const ACTIVITY_TIME_ON_APP = 5; // 5 seconds
const ACTIVITY_TIME_WINDOW = 10; // 10 minutes
const TrackingComponent: React.FC = ({ children }) => {
@@ -94,12 +103,16 @@ const TrackingComponent: React.FC = ({ children }) => {
return;
}
const isActiveOnApp: string | boolean = (await getItem(StorageKeys.IS_USER_ACTIVE)) || false;
+ const userActivityonApp: string =
+ (await getItem(StorageKeys.USER_ACTIVITY_ON_APP)) || AgentActivity.LOW;
+
const geolocation: IGeolocationPayload = {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
timestamp: Date.now(),
isActiveOnApp: Boolean(isActiveOnApp),
+ userActivityOnApp: String(userActivityonApp),
};
dispatch(sendLocationAndActivenessToServer([geolocation]));
} catch (e: any) {
@@ -169,6 +182,7 @@ const TrackingComponent: React.FC = ({ children }) => {
const isForegroundTimeWithInRange =
diffBetweenCurrentTimeAndForegroundTime <= ACTIVITY_TIME_WINDOW;
const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp);
+ const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp();
if (isForegroundTimeWithInRange) {
if (
@@ -184,6 +198,60 @@ const TrackingComponent: React.FC = ({ children }) => {
return;
};
+ const handleUpdateActivity = async () => {
+ const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
+ const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
+ const stateSetTimestamp = await getItem(StorageKeys.STATE_SET_TIMESTAMP);
+
+ if (foregroundTimestamp == null) {
+ console.log('fts set after installation');
+ await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, dayJs().toString());
+ }
+
+ const foregroundTime = dayJs(foregroundTimestamp);
+ const backgroundTime = dayJs(backgroundTimestamp);
+ const stateSetTime = dayJs(stateSetTimestamp);
+
+ const diffBetweenCurrentTimeAndForegroundTime =
+ dayJs().diff(foregroundTime, 'seconds') < 0 ? 0 : dayJs().diff(foregroundTime, 'seconds');
+ const diffBetweenCurrentTimeAndSetStateTime =
+ dayJs().diff(stateSetTime, 'minutes') < 0 ? 0 : dayJs().diff(stateSetTime, 'minutes');
+
+ const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp();
+ const ACTIVITY_TIME_WINDOW_HIGH = getActivityTimeWindowHigh();
+ const ACTIVITY_TIME_WINDOW_MEDIUM = getActivityTimeWindowMedium();
+
+ const isStateSetTimeWithinHighRange =
+ diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_HIGH;
+ const isStateSetTimeWithinMediumRange =
+ diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_MEDIUM;
+ const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp);
+
+ if (AppState.currentState === AppStates.ACTIVE) {
+ if (diffBetweenCurrentTimeAndForegroundTime >= ACTIVITY_TIME_ON_APP) {
+ await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH);
+ return;
+ }
+ return;
+ }
+
+ if (isForegroundTimeAfterBackground) {
+ if (diffBetweenCurrentTimeAndForegroundTime >= ACTIVITY_TIME_ON_APP) {
+ await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH);
+ return;
+ }
+ return;
+ } else if (isStateSetTimeWithinHighRange) {
+ return;
+ } else if (isStateSetTimeWithinMediumRange) {
+ await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.MEDIUM);
+ return;
+ } else {
+ await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.LOW);
+ return;
+ }
+ };
+
const deleteCache = () => {
const directoryPath = RNFS.CachesDirectoryPath;
const currentDate = new Date().getTime();
@@ -239,6 +307,12 @@ const TrackingComponent: React.FC = ({ children }) => {
delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 minutes
onLoop: true,
},
+ {
+ taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVITY,
+ task: handleUpdateActivity,
+ delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 minutes
+ onLoop: true,
+ },
{
taskId: FOREGROUND_TASKS.DELETE_CACHE,
task: deleteCache,
@@ -272,17 +346,39 @@ const TrackingComponent: React.FC = ({ children }) => {
});
}
+ const userActivityUpdateOnBackground = async () => {
+ const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
+ const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
+ const foregroundTime = dayJs(foregroundTimestamp);
+ const backgroundTime = dayJs(backgroundTimestamp);
+ const diffBetweenBackgroundAndForegroundTime = dayJs(backgroundTime).diff(
+ foregroundTime,
+ 'seconds'
+ );
+ const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp();
+
+ if (diffBetweenBackgroundAndForegroundTime >= ACTIVITY_TIME_ON_APP) {
+ await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH);
+ await setItem(StorageKeys.STATE_SET_TIMESTAMP, dayJs().toString());
+ return;
+ }
+ return;
+ };
+
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// App comes to foreground from background
const now = dayJs().toString();
if (nextAppState === AppStates.ACTIVE) {
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now });
handleGetCaseSyncStatus();
dispatch(getConfigData());
CosmosForegroundService.start(tasks);
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
+ userActivityUpdateOnBackground();
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_BACKGROUND, { now });
}
appState.current = nextAppState;
};
@@ -306,12 +402,8 @@ const TrackingComponent: React.FC = ({ children }) => {
useEffect(() => {
let appStateSubscription: NativeEventSubscription;
- CosmosForegroundService.isRunning().then((isFGSRunning) => {
- if (!isFGSRunning) {
- appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
- CosmosForegroundService.start(tasks);
- }
- });
+ appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
+ CosmosForegroundService.start(tasks);
return () => {
appStateSubscription?.remove();
};
diff --git a/src/components/utlis/DeviceUtils.ts b/src/components/utlis/DeviceUtils.ts
index 5443d7d7..926d85a3 100644
--- a/src/components/utlis/DeviceUtils.ts
+++ b/src/components/utlis/DeviceUtils.ts
@@ -7,3 +7,10 @@ export const locationEnabled = (): Promise => DeviceUtilsModule.isLocat
// returns array of all the installed packages.
export const getAllInstalledApp = (): Promise => DeviceUtilsModule.getAllInstalledApp();
+
+// sends feedback data to whatsapp.
+export const sendFeedbackToWhatsapp = (
+ message: string,
+ imageUrl: string,
+ mimeType: string
+): Promise => DeviceUtilsModule.sendFeedbackToWhatsapp(message, imageUrl, mimeType);
diff --git a/src/components/utlis/ScreenshotBlocker.tsx b/src/components/utlis/ScreenshotBlocker.tsx
new file mode 100644
index 00000000..d0269405
--- /dev/null
+++ b/src/components/utlis/ScreenshotBlocker.tsx
@@ -0,0 +1,5 @@
+import { NativeModules } from 'react-native';
+
+const { ScreenshotBlocker } = NativeModules;
+
+export default ScreenshotBlocker;
diff --git a/src/components/utlis/commonFunctions.ts b/src/components/utlis/commonFunctions.ts
index 7cfd9a0d..7cb55682 100644
--- a/src/components/utlis/commonFunctions.ts
+++ b/src/components/utlis/commonFunctions.ts
@@ -368,3 +368,10 @@ export function getDistanceFromLatLonInKm(
const distance = 2 * Math.atan2(Math.sqrt(intermediateResult), Math.sqrt(1 - intermediateResult));
return EARTH_RADIUS * distance;
}
+
+export function insertCommasinAmount(amount: number | undefined) {
+ const reversedAmount = amount?.toString().split('').reverse().join('');
+ const groups = reversedAmount?.match(/.{1,3}/g);
+ const result = groups?.join(',').split('').reverse().join('');
+ return result;
+}
diff --git a/src/hooks/capturingApi.ts b/src/hooks/capturingApi.ts
index df42af7a..4c32cc1b 100644
--- a/src/hooks/capturingApi.ts
+++ b/src/hooks/capturingApi.ts
@@ -13,6 +13,7 @@ export interface IGeolocationPayload {
accuracy: number;
timestamp: number;
isActiveOnApp: boolean;
+ userActivityOnApp: string;
}
export const sendLocationAndActivenessToServer =
diff --git a/src/hooks/useScreenshotTracking.ts b/src/hooks/useScreenshotTracking.ts
index 13db1aca..4a8a34fa 100644
--- a/src/hooks/useScreenshotTracking.ts
+++ b/src/hooks/useScreenshotTracking.ts
@@ -13,7 +13,6 @@ const useScreenshotTracking = () => {
}));
const screenshotEventEmitter = useMemo(() => new NativeEventEmitter(ScreenshotBlocker), []);
-
useEffect(() => {
ScreenshotBlocker.startScreenshotTracking();
diff --git a/src/screens/addressGeolocation/SimilarAddressItem.tsx b/src/screens/addressGeolocation/SimilarAddressItem.tsx
index ecbd8399..a9873961 100644
--- a/src/screens/addressGeolocation/SimilarAddressItem.tsx
+++ b/src/screens/addressGeolocation/SimilarAddressItem.tsx
@@ -155,11 +155,7 @@ function SimilarAddressItem({
) : null}
-
+
{sanitizeString(addressItem?.addressText)}
{
+ return ;
+};
+
const UngroupedAddressContainer: React.FC = ({ route: routeParams }) => {
const {
params: { loanAccountNumber, caseId, customerReferenceId, fetchUngroupedAddress },
@@ -87,6 +88,14 @@ const UngroupedAddressContainer: React.FC = ({ route: routePa
navigateToScreen(PageRouteEnum.PAST_FEEDBACK_DETAIL, { ...params, ...commonParams });
};
+ const handleAccordionExpand = (isExpanded: boolean, addressId: string) => {
+ if (isExpanded) {
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_MORE_ADDRESSES_CLICKED, {
+ addressId,
+ });
+ }
+ };
+
return (
@@ -107,23 +116,76 @@ const UngroupedAddressContainer: React.FC = ({ route: routePa
>
{ungroupedAddressList?.length ? (
- {ungroupedAddressList.map((ungroupedAddressItem: IAddress) => (
+ {ungroupedAddressList.map((ungroupedAddressItem: IAddress, index: number) => (
- {
- handleOpenOldFeedbacks(ungroupedAddressItem);
- }}
- handleCloseRouting={() => {
- navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
- }}
- showSource
- />
+ {!ungroupedAddressItem?.similarAddresses ? (
+ {
+ handleOpenOldFeedbacks(ungroupedAddressItem);
+ }}
+ handleCloseRouting={() => {
+ navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
+ }}
+ showSource
+ />
+ ) : (
+ {
+ handleOpenOldFeedbacks(ungroupedAddressItem);
+ }}
+ handleCloseRouting={() => {
+ navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
+ }}
+ showSource
+ />
+ }
+ onExpanded={(isExpanded: boolean) => {
+ handleAccordionExpand(isExpanded, ungroupedAddressItem?.id);
+ }}
+ >
+ {ungroupedAddressItem?.similarAddresses.length ? (
+
+
+
+ Similar addresses
+
+ {ungroupedAddressItem.similarAddresses.map((similarAddress: IAddress) => (
+
+
+
+ ))}
+
+ ) : null}
+
+ )}
))}
@@ -163,6 +225,28 @@ const styles = StyleSheet.create({
paddingVertical: 16,
marginBottom: 12,
},
+ addressItemContainerAccordian: {
+ paddingHorizontal: 0,
+ paddingTop: 0,
+ paddingBottom: 16,
+ marginBottom: 0,
+ },
+ borderLine: {
+ borderWidth: 0.5,
+ borderColor: COLORS.BORDER.PRIMARY,
+ marginTop: 0,
+ marginBottom: 16,
+ },
+ accordionDetailHeading: {
+ color: COLORS.TEXT.BLACK_24,
+ },
+ card: {
+ padding: 16,
+ marginBottom: 16,
+ borderRadius: 8,
+ backgroundColor: COLORS.BACKGROUND.SILVER,
+ fontSize: 14,
+ },
});
export default UngroupedAddressContainer;
diff --git a/src/screens/addressGeolocation/index.tsx b/src/screens/addressGeolocation/index.tsx
index 3f9c0061..239246f5 100644
--- a/src/screens/addressGeolocation/index.tsx
+++ b/src/screens/addressGeolocation/index.tsx
@@ -87,10 +87,15 @@ const AddressGeolocation: React.FC = ({ route: routeParams
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
});
- const metaAddresses = farAwayAddresses.map((farAwayAddress) => farAwayAddress?.metaAddress);
-
- setUngroupedAddress([...res, ...metaAddresses]);
- return [...res, ...metaAddresses];
+ const metaAddresses = farAwayAddresses.map((farAwayAddress) => {
+ return {
+ ...farAwayAddress?.metaAddress,
+ similarAddresses: farAwayAddress?.similarAddresses,
+ };
+ });
+ const ungroupedAddresses = [...metaAddresses, ...res];
+ setUngroupedAddress(ungroupedAddresses);
+ return ungroupedAddresses;
});
useEffect(() => {
diff --git a/src/screens/allCases/constants.ts b/src/screens/allCases/constants.ts
index 7df28452..ec1d1c1d 100644
--- a/src/screens/allCases/constants.ts
+++ b/src/screens/allCases/constants.ts
@@ -54,7 +54,9 @@ export const EmptyListMessages = {
export const ToastMessages = {
VISIT_PLAN_OFFLINE: 'Please connect to the internet to update the visit plan',
ERROR_COPYING_LAN: 'Error copying LAN!!',
+ ERROR_COPYING_EMPLOYER_NAME: 'Error copying employer name!!',
SUCCESS_COPYING_LAN: 'LAN Copied Successfully!!',
+ SUCCESS_COPYING_EMPLOYER_NAME: 'Employer Name Copied Successfully!!',
FEEDBACK_SUCCESSFUL: 'Feedback submitted successfully!',
FEEDBACK_FAILED: 'Feedback submission failed',
FIRESTORE_SIGNIN_FAILED: 'Error signing in to Firestore',
@@ -75,6 +77,9 @@ export const ToastMessages = {
CASES_DELETION_DISABLED: 'Case deletion is disabled during the generation of visit plan',
GEOLOCATION_COORDINATES_INCORRECT: 'Geolocation not found',
IMAGE_UPLOAD_SUCCESS: 'Your ID card has been sent for approval',
+ WHATSAPP_FEEDBACK_SHARE_SUCCESS: 'Feedback shared successfully via WhatsApp',
+ WHATSAPP_FEEDBACK_SHARE_FAILURE: 'Feedback sharing failed via WhatsApp',
+ WHATSAPP_NOT_INSTALLED: 'WhatsApp is not installed on your device',
};
export enum BOTTOM_TAB_ROUTES {
diff --git a/src/screens/allCases/index.tsx b/src/screens/allCases/index.tsx
index 0fc99dff..2ec22de5 100644
--- a/src/screens/allCases/index.tsx
+++ b/src/screens/allCases/index.tsx
@@ -1,27 +1,27 @@
import React, { useEffect, useMemo } from 'react';
-import { useAppDispatch, useAppSelector } from '../../hooks';
+import { useAppDispatch, useAppSelector } from '@hooks';
import CasesList from './CasesList';
-import { RootState } from '../../store/store';
-import { initCrashlytics } from '../../components/utlis/firebaseUtils';
+import { RootState } from '@store';
+import { initCrashlytics } from '@utils/firebaseUtils';
import Layout from '../layout/Layout';
-import BottomNavigator, { ITabScreen } from '../../../RN-UI-LIB/src/components/bottomNavigator';
-import CasesIcon from '../../../RN-UI-LIB/src/Icons/CasesIcon';
+import BottomNavigator, { ITabScreen } from '@rn-ui-lib/components/bottomNavigator';
+import CasesIcon from '@rn-ui-lib/icons/CasesIcon';
import Profile from '../Profile';
-import ProfileIcon from '../../../RN-UI-LIB/src/Icons/ProfileIcon';
-import VisitPlanIcon from '../../../RN-UI-LIB/src/Icons/VisitPlanIcon';
+import ProfileIcon from '@rn-ui-lib/icons/ProfileIcon';
+import VisitPlanIcon from '@rn-ui-lib/icons/VisitPlanIcon';
import CasesActionButtons from './CasesActionButtons';
-import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader';
-import { getCurrentScreen } from '../../components/utlis/navigationUtlis';
+import FullScreenLoader from '@rn-ui-lib/components/FullScreenLoader';
+import { getCurrentScreen } from '@utils/navigationUtlis';
import {
resetSelectedTodoList,
resetTodoList,
setLoading,
setVisitPlansUpdating,
-} from '../../reducer/allCasesSlice';
-import { addClickstreamEvent } from '../../services/clickstreamEventService';
-import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
+} from '@reducers/allCasesSlice';
+import { addClickstreamEvent } from '@services/clickstreamEventService';
+import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { BOTTOM_TAB_ROUTES } from './constants';
-import { getSelfieDocument } from '../../action/profileActions';
+import { getSelfieDocument } from '@actions/profileActions';
const AllCasesMain = () => {
const { pendingList, pinnedList, completedList, loading } = useAppSelector(
diff --git a/src/screens/caseDetails/Chip.tsx b/src/screens/caseDetails/Chip.tsx
new file mode 100644
index 00000000..7202f19a
--- /dev/null
+++ b/src/screens/caseDetails/Chip.tsx
@@ -0,0 +1,95 @@
+import { StyleProp, TextStyle, TouchableHighlight, View, ViewStyle } from 'react-native';
+import React from 'react';
+import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
+import { toast } from '../../../RN-UI-LIB/src/components/toast';
+import { copyToClipboard } from '../../components/utlis/commonFunctions';
+import Text from '../../../RN-UI-LIB/src/components/Text';
+import CopyIcon from '../../../RN-UI-LIB/src/Icons/CopyIcon';
+
+interface IChip {
+ text: string;
+ containerStyle: StyleProp;
+ subText?: string;
+ numberOfLines?: number;
+ clipCount?: number;
+ showCopyBtn?: boolean;
+ clickstreamEvent?: () => void;
+ successMessage?: string;
+ errorMessage?: string;
+ textStyle?: StyleProp;
+}
+
+const Chip: React.FC = (props) => {
+ const {
+ text,
+ containerStyle,
+ subText,
+ numberOfLines = 1,
+ clipCount = 0,
+ showCopyBtn = false,
+ clickstreamEvent,
+ successMessage = 'Copied Successfully!!',
+ errorMessage = 'Error copying!!',
+ textStyle,
+ } = props;
+
+ const copyData = () => {
+ clickstreamEvent?.();
+ if (!showCopyBtn) return;
+ if (text) {
+ copyToClipboard(text);
+ toast({
+ text1: successMessage,
+ type: 'info',
+ });
+ } else {
+ toast({
+ text1: errorMessage,
+ type: 'error',
+ });
+ }
+ };
+
+ const isClipped = text?.length >= clipCount;
+ const clippedStyle = isClipped ? GenericStyles.fill : {};
+
+ const renderChip = () => {
+ return (
+
+
+
+ {subText && subText}
+ {text}
+
+
+ {showCopyBtn && (
+
+
+
+ )}
+
+ );
+ };
+
+ return showCopyBtn ? (
+
+ {renderChip()}
+
+ ) : (
+ {renderChip()}
+ );
+};
+
+export default Chip;
diff --git a/src/screens/caseDetails/CollectionCaseData.tsx b/src/screens/caseDetails/CollectionCaseData.tsx
index 7ac3ce3f..2764f14e 100644
--- a/src/screens/caseDetails/CollectionCaseData.tsx
+++ b/src/screens/caseDetails/CollectionCaseData.tsx
@@ -5,8 +5,12 @@ import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import Text from '../../../RN-UI-LIB/src/components/Text';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
-import LANChip from './LANChip';
-import Tag from '../../../RN-UI-LIB/src/components/Tag';
+import Chip from './Chip';
+import EmploymentDetails from './EmploymentDetails';
+import { addClickstreamEvent } from '../../services/clickstreamEventService';
+import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
+import { ToastMessages } from '../allCases/constants';
+import { toTileCase } from '../../components/utlis/commonFunctions';
interface ICollectionCaseData {
caseData: CaseDetail;
@@ -23,22 +27,38 @@ const CollectionCaseData: React.FC = ({ caseData }) => {
employmentDetail,
} = caseData;
- const showEmploymentDetails = false;
-
return (
{fatherName && (
- S/O {fatherName}
+ Parent name: {toTileCase(fatherName)}
)}
Current DPD {currentDpd}
- {loanAccountNumber && }
+ {loanAccountNumber && (
+
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_LAN_CLICKED, {
+ lan: loanAccountNumber,
+ })
+ }
+ successMessage={ToastMessages.SUCCESS_COPYING_LAN}
+ errorMessage={ToastMessages.ERROR_COPYING_LAN}
+ clipCount={14}
+ />
+ )}
+
+
+
DPD bucket {dpdBucket}
@@ -46,29 +66,15 @@ const CollectionCaseData: React.FC = ({ caseData }) => {
POS {formatAmount(pos)}
-
- {showEmploymentDetails ? (
-
- {employmentDetail?.employmentType && (
-
- {employmentDetail.employmentType}
-
- )}
- {employmentDetail?.employmentType && employmentDetail?.employerName && (
+ {collectionTag ? (
+ <>
- )}
- {employmentDetail?.employerName && (
-
- {employmentDetail.employerName}
+
+ {collectionTag}
- )}
-
- ) : null}
- {collectionTag ? (
-
- {collectionTag}
-
- ) : null}
+ >
+ ) : null}
+
);
};
diff --git a/src/screens/caseDetails/CollectionCaseDetail.tsx b/src/screens/caseDetails/CollectionCaseDetail.tsx
index e658383e..6afa8c5b 100644
--- a/src/screens/caseDetails/CollectionCaseDetail.tsx
+++ b/src/screens/caseDetails/CollectionCaseDetail.tsx
@@ -217,7 +217,7 @@ const CollectionCaseDetails: React.FC = (props) => {
const commonParams = {
loanAccountNumber: caseDetail.loanAccountNumber,
customerReferenceId: caseDetail.customerReferenceId,
- caseId,
+ caseId: caseId,
};
navigateToScreen(route, { ...params, ...commonParams });
};
@@ -469,6 +469,7 @@ const CollectionCaseDetails: React.FC = (props) => {
diff --git a/src/screens/caseDetails/EmploymentDetails.tsx b/src/screens/caseDetails/EmploymentDetails.tsx
new file mode 100644
index 00000000..0dfbe5a2
--- /dev/null
+++ b/src/screens/caseDetails/EmploymentDetails.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
+import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
+import { addClickstreamEvent } from '../../services/clickstreamEventService';
+import { ToastMessages } from '../allCases/constants';
+import Chip from './Chip';
+
+const EmploymentDetails = ({ employmentDetail }: any) => {
+ if (employmentDetail?.employerName) {
+ return (
+
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_EMPLOYER_NAME_CLICKED, {
+ employerName: employmentDetail.employerName,
+ })
+ }
+ successMessage={ToastMessages.SUCCESS_COPYING_EMPLOYER_NAME}
+ errorMessage={ToastMessages.ERROR_COPYING_EMPLOYER_NAME}
+ textStyle={styles.lineHeight}
+ />
+ );
+ }
+
+ if (employmentDetail?.employmentType) {
+ return (
+
+ );
+ }
+
+ return null;
+};
+
+export default EmploymentDetails;
+
+const styles = StyleSheet.create({
+ lineHeight: { lineHeight: 20, paddingVertical: 1.6 },
+});
diff --git a/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx b/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx
index 3a19a0db..78186917 100644
--- a/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx
+++ b/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx
@@ -25,6 +25,8 @@ import { setFeedbackHistoryLoading } from '../../../reducer/feedbackHistorySlice
import SuspenseLoader from '../../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
import LineLoader from '../../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
import NoPastFeedbackIcon from '../../../assets/icons/NoPastFeedbackIcon';
+import ChevronDown from '../../../assets/icons/ChevronDown';
+import ChevronUp from '../../../assets/icons/ChevronUp';
const FEEDBACK_PAGE_TITLE = 'All feedbacks';
const ADDRESS_FEEDBACK_PAGE_TITLE = 'Address feedback';
@@ -39,13 +41,20 @@ interface IFeedbackDetailContainer {
addressReferenceIds?: string[];
addressText?: string;
activeFeedbackReferenceId?: string;
+ caseId: string;
};
};
}
const FeedbackDetailContainer: React.FC = ({ route: routeParams }) => {
const {
- params: { loanAccountNumber, activeFeedbackReferenceId, addressReferenceIds, addressText },
+ params: {
+ loanAccountNumber,
+ activeFeedbackReferenceId,
+ addressReferenceIds,
+ addressText,
+ caseId,
+ },
} = routeParams;
const isPastFeedbackOnAddress = addressText || addressReferenceIds?.length;
@@ -212,7 +221,12 @@ const FeedbackDetailContainer: React.FC = ({ route: ro
}}
>
= ({ route: ro
key={feedback.referenceId}
feedbackItem={feedback}
isExpanded={isExpanded}
+ caseId={caseId}
/>
}
customExpandUi={{
- whenCollapsed: View more,
- whenExpanded: View less,
+ whenCollapsed: ,
+ whenExpanded: ,
}}
onExpanded={(value) => {
setIsExpanded(value);
@@ -321,6 +336,10 @@ const styles = StyleSheet.create({
lineHeight: 20,
color: COLORS.TEXT.BLUE,
},
+ accordianPadding: {
+ paddingTop: 16,
+ paddingBottom: 8,
+ },
});
export default FeedbackDetailContainer;
diff --git a/src/screens/caseDetails/feedback/FeedbackDetailItem.tsx b/src/screens/caseDetails/feedback/FeedbackDetailItem.tsx
index bcb5e69c..3ca7bfea 100644
--- a/src/screens/caseDetails/feedback/FeedbackDetailItem.tsx
+++ b/src/screens/caseDetails/feedback/FeedbackDetailItem.tsx
@@ -1,5 +1,5 @@
-import React, { ReactNode } from 'react';
-import { View, StyleSheet, TouchableOpacity, Linking } from 'react-native';
+import React, { ReactNode, useEffect, useState } from 'react';
+import { View, StyleSheet, TouchableOpacity, Linking, Platform, NativeModules } from 'react-native';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
@@ -8,25 +8,42 @@ import {
BUSINESS_TIME_FORMAT,
dateFormat,
} from '../../../../RN-UI-LIB/src/utlis/dates';
-import { getGoogleMapUrl, sanitizeString } from '../../../components/utlis/commonFunctions';
-import { FIELD_FEEDBACKS, ICallingFeedback, IFeedback } from '../../../types/feedback.types';
-import { Address as IAddress } from '../interface';
+import {
+ getGoogleMapUrl,
+ insertCommasinAmount,
+ sanitizeString,
+} from '../../../components/utlis/commonFunctions';
+import {
+ FIELD_FEEDBACKS,
+ ICallingFeedback,
+ IFeedback,
+ OPTION_TAG,
+} from '../../../types/feedback.types';
+import { CaseDetail, Address as IAddress } from '../interface';
import MapIcon from '../../../../RN-UI-LIB/src/Icons/MapIcon';
import { FEEDBACK_TYPE } from '../../../types/feedback.types';
import CallIcon from '../../../../RN-UI-LIB/src/Icons/CallIcon';
import { addClickstreamEvent } from '../../../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../../common/Constants';
+import IconLabel from '../../../common/IconLabel';
+import WhatsAppFeedbackShareIcon from '../../../assets/icons/WhatsAppIcon';
+import ReactNativeBlobUtil from 'react-native-blob-util';
+import { useAppSelector } from '../../../hooks';
+import { toast } from '../../../../RN-UI-LIB/src/components/toast';
+import { ToastMessages } from '../../allCases/constants';
+import { sendFeedbackToWhatsapp } from '../../../components/utlis/DeviceUtils';
interface IFeedbackDetailItem {
feedbackItem: IFeedback;
isExpanded: boolean;
+ caseId: string;
}
const feedbackTypeIcon: Record = {
- FIELD_VISIT: ,
- INHOUSE_FIELD_VISIT: ,
- SELF_CALL: ,
- CALL_BRIDGE: ,
+ FIELD_VISIT: ,
+ INHOUSE_FIELD_VISIT: ,
+ SELF_CALL: ,
+ CALL_BRIDGE: ,
};
const getAddress = (address?: IAddress) => {
@@ -47,9 +64,123 @@ const openGeolocation = (latitude: string, longitude: string) => {
return Linking.openURL(geolocationUrl);
};
-const FeedbackDetailItem = ({ feedbackItem, isExpanded }: IFeedbackDetailItem) => {
+function getLocationLink(latitude: string, longitude: string): string {
+ const link = 'https://www.google.com/maps/search/?api=1&query=' + latitude + ',' + longitude;
+ return link;
+}
+
+const sendToWhatsappNative = (
+ message: string,
+ imageUrl: string,
+ mimeType: string,
+ caseDetails: CaseDetail,
+ agentId: string
+) => {
+ sendFeedbackToWhatsapp(message, imageUrl, mimeType)
+ .then((res: boolean) => {
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_SUCCESSFUL, {
+ caseId: caseDetails?.id,
+ agentId: agentId,
+ });
+ })
+ .catch((err: Error) => {
+ if (err.message === '1') {
+ toast({
+ text1: ToastMessages.WHATSAPP_NOT_INSTALLED,
+ type: 'error',
+ });
+ } else {
+ toast({
+ text1: ToastMessages.WHATSAPP_FEEDBACK_SHARE_FAILURE,
+ type: 'error',
+ });
+ }
+ });
+};
+
+const sendToWhatsapp = (feedbackItem: IFeedback, caseDetails: CaseDetail, agentId: string) => {
+ addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_FEEDBACK_CLICKED, {
+ caseId: caseDetails?.id,
+ agentId: agentId,
+ });
+
+ var message = `*Visit Feedback* for ${sanitizeString(caseDetails?.customerName)}
+_${sanitizeString(dateFormat(new Date(feedbackItem?.createdAt), 'DD MMM, YYYY | HH:mm a.'))}_\n
+*LAN*: ${sanitizeString(caseDetails?.loanAccountNumber)}
+*DPD Bucket*: ${sanitizeString(caseDetails?.dpdBucket)}
+*EMI Amount*: ₹${insertCommasinAmount(caseDetails?.outstandingEmiDetails?.[0]?.emiAmount)}\n
+*Disposition*: ${sanitizeString(feedbackItem?.interactionStatus)}`;
+
+ const ptpDate = feedbackItem?.answerViews.filter((answer) => answer.questionName === 'PTP Date');
+ if (ptpDate.length > 0) {
+ message +=
+ ' for ' + sanitizeString(dateFormat(new Date(ptpDate[0].inputDate), 'DD MMM, YYYY')) + '\n\n';
+ } else {
+ message += '\n\n';
+ }
+
+ message += '*Remarks*: ';
+ const answerList = feedbackItem?.answerViews?.filter(
+ (answer) => answer.questionName === 'Comments'
+ );
+ if (answerList.length > 0) {
+ message += sanitizeString(answerList[0]?.inputText) + '\n\n';
+ } else {
+ message += 'N.A.\n\n';
+ }
+
+ {
+ feedbackItem?.metadata?.interactionLatitude &&
+ feedbackItem?.metadata?.interactionLongitude &&
+ FIELD_FEEDBACKS.includes(feedbackItem?.type)
+ ? (message +=
+ '*Location of feedback*: ' +
+ sanitizeString(
+ getLocationLink(
+ feedbackItem?.metadata?.interactionLatitude,
+ feedbackItem?.metadata?.interactionLongitude
+ )
+ ) +
+ '\n\n')
+ : null;
+ }
+
+ const imagesList = feedbackItem?.answerViews.filter(
+ (answer) => answer.questionTag === OPTION_TAG.IMAGE_UPLOAD
+ );
+ var imageUrl = '';
+ const mimeType = 'image/*';
+ if (imagesList.length > 0) {
+ var imageUri = '';
+ imageUri = imagesList[0]?.inputText ? imagesList[0].inputText : '';
+ let imagePath = '';
+ ReactNativeBlobUtil.config({
+ fileCache: true,
+ })
+ .fetch('GET', imageUri)
+ .then((resp: any) => {
+ if (resp.info().status !== 200) {
+ return '';
+ } else {
+ imagePath = resp.path();
+ return resp.readFile('base64');
+ }
+ })
+ .then((base64Data: any) => {
+ imageUrl = base64Data;
+ sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
+ ReactNativeBlobUtil.fs.unlink(imagePath);
+ });
+ } else {
+ sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
+ }
+};
+
+const FeedbackDetailItem = ({ feedbackItem, isExpanded, caseId }: IFeedbackDetailItem) => {
+ const caseDetails = useAppSelector((state) => state.allCases.caseDetails[caseId]);
+ const { agentId } = useAppSelector((state) => ({ agentId: state.user.user?.referenceId!! }));
return (
-
+
{feedbackTypeIcon[feedbackItem.type] ? (
{feedbackTypeIcon[feedbackItem.type]}
@@ -70,13 +201,17 @@ const FeedbackDetailItem = ({ feedbackItem, isExpanded }: IFeedbackDetailItem) =
{sanitizeString(dateFormat(new Date(feedbackItem.createdAt), BUSINESS_DATE_FORMAT))}
- ●
+ ●
{sanitizeString(dateFormat(new Date(feedbackItem.createdAt), BUSINESS_TIME_FORMAT))}
-
{feedbackItem.metadata?.interactionLatitude && FIELD_FEEDBACKS.includes(feedbackItem.type) ? (
-
- openGeolocation(
- feedbackItem.metadata?.interactionLatitude,
- feedbackItem.metadata?.interactionLongitude
- )
- }
- style={[GenericStyles.row, GenericStyles.pv12]}
- >
- Open map
-
+ <>
+
+
+ openGeolocation(
+ feedbackItem.metadata?.interactionLatitude,
+ feedbackItem.metadata?.interactionLongitude
+ )
+ }
+ style={[GenericStyles.row, styles.BtnPadding]}
+ >
+ Open map
+
+ sendToWhatsapp(feedbackItem, caseDetails, agentId)}
+ style={[GenericStyles.row, styles.BtnPadding]}
+ >
+ }
+ textStyle={{ color: COLORS.BASE.BLUE }}
+ />
+
+
+ >
) : null}
);
@@ -121,7 +271,7 @@ const styles = StyleSheet.create({
},
cardLightTitle: {
fontWeight: '400',
- color: '#BCBCBC',
+ color: COLORS.TEXT.BLACK,
},
cardFooterText: {
fontWeight: '400',
@@ -129,6 +279,22 @@ const styles = StyleSheet.create({
},
geolocationBtn: {
color: COLORS.BASE.BLUE,
+ marginRight: 20,
+ },
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginTop: 0,
+ },
+ bullet: {
+ color: COLORS.TEXT.GREY_1,
+ },
+ BtnPadding: {
+ paddingTop: 8,
+ paddingBottom: 8,
+ },
+ addressItem: {
+ paddingHorizontal: 0,
},
});
diff --git a/src/screens/caseDetails/feedback/FeedbackListContainer.tsx b/src/screens/caseDetails/feedback/FeedbackListContainer.tsx
index 721f983b..31565c4a 100644
--- a/src/screens/caseDetails/feedback/FeedbackListContainer.tsx
+++ b/src/screens/caseDetails/feedback/FeedbackListContainer.tsx
@@ -18,6 +18,7 @@ import { getCaseUnifiedData, UnifiedCaseDetailsTypes } from '../../../action/cas
interface IFeedbackListContainer {
loanAccountNumber: string;
feedbackList: (IFeedback | IUnSyncedFeedbackItem)[];
+ caseId: string;
}
interface IOfflineFeedbackListContainer {
@@ -66,6 +67,7 @@ const OfflineFeedbackListContainer: React.FC = ({
const FeedbackListContainer: React.FC = ({
loanAccountNumber,
feedbackList,
+ caseId,
}) => {
const [retryBtnCount, setRetryBtnCount] = useState(0);
@@ -97,6 +99,7 @@ const FeedbackListContainer: React.FC = ({
feedbackItem={feedbackItem}
loanAccountNumber={loanAccountNumber}
showHorizontalLine={++idx !== feedbackList.length}
+ caseId={caseId}
/>
))}
diff --git a/src/screens/caseDetails/feedback/FeedbackListItem.tsx b/src/screens/caseDetails/feedback/FeedbackListItem.tsx
index 3b7c06a7..f7fc51c5 100644
--- a/src/screens/caseDetails/feedback/FeedbackListItem.tsx
+++ b/src/screens/caseDetails/feedback/FeedbackListItem.tsx
@@ -15,11 +15,13 @@ interface IFeedbackListItem {
feedbackItem: IFeedback | IUnSyncedFeedbackItem;
showHorizontalLine?: boolean;
loanAccountNumber: string;
+ caseId: string;
}
const FeedbackListItem: React.FC = ({
feedbackItem,
loanAccountNumber,
+ caseId,
showHorizontalLine = true,
}) => {
const handleRouting = (route: PageRouteEnum, params: object | undefined = undefined) => {
@@ -27,6 +29,7 @@ const FeedbackListItem: React.FC = ({
const commonParams = {
loanAccountNumber,
activeFeedbackReferenceId: (feedbackItem as IFeedback).referenceId,
+ caseId: caseId,
};
navigateToScreen(route, { ...params, ...commonParams });
};
diff --git a/src/services/firebaseFetchAndUpdate.service.ts b/src/services/firebaseFetchAndUpdate.service.ts
new file mode 100644
index 00000000..1816fb35
--- /dev/null
+++ b/src/services/firebaseFetchAndUpdate.service.ts
@@ -0,0 +1,37 @@
+import remoteConfig from '@react-native-firebase/remote-config';
+import {
+ setActivityTimeOnApp,
+ setActivityTimeWindowHigh,
+ setActivityTimeWindowMedium,
+} from '../common/AgentActivityConfigurableConstants';
+
+const FIREBASE_FETCH_TIME = 15 * 60;
+async function handleUpdatedConfigureValuesFromFirebase() {
+ await remoteConfig().fetch(FIREBASE_FETCH_TIME); //15 minutes
+ remoteConfig()
+ .activate()
+ .then((fetchedRemotely) => {
+ if (fetchedRemotely) {
+ console.log('Configs were fetched.');
+ } else {
+ console.log('No configs were fetched.');
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ })
+ .finally(() => {
+ const ACTIVITY_TIME_ON_APP = remoteConfig().getValue('ACTIVITY_TIME_ON_APP').asNumber();
+ const ACTIVITY_TIME_WINDOW_HIGH = remoteConfig()
+ .getValue('ACTIVITY_TIME_WINDOW_HIGH')
+ .asNumber();
+ const ACTIVITY_TIME_WINDOW_MEDIUM = remoteConfig()
+ .getValue('ACTIVITY_TIME_WINDOW_MEDIUM')
+ .asNumber();
+ setActivityTimeOnApp(ACTIVITY_TIME_ON_APP);
+ setActivityTimeWindowHigh(ACTIVITY_TIME_WINDOW_HIGH);
+ setActivityTimeWindowMedium(ACTIVITY_TIME_WINDOW_MEDIUM);
+ });
+}
+
+export default handleUpdatedConfigureValuesFromFirebase;
diff --git a/src/types/addressGeolocation.types.ts b/src/types/addressGeolocation.types.ts
index 98d18a41..03959e8e 100644
--- a/src/types/addressGeolocation.types.ts
+++ b/src/types/addressGeolocation.types.ts
@@ -39,6 +39,7 @@ export interface IAddress {
groupId: string;
primarySource?: PrimarySourcesType;
secondarySource?: string;
+ similarAddresses?: IAddress[];
}
export interface IGroupedAddressesItem {
diff --git a/src/types/agentActivity.ts b/src/types/agentActivity.ts
new file mode 100644
index 00000000..0184175b
--- /dev/null
+++ b/src/types/agentActivity.ts
@@ -0,0 +1,5 @@
+export enum AgentActivity {
+ HIGH = 'HIGH',
+ MEDIUM = 'MEDIUM',
+ LOW = 'LOW',
+}
diff --git a/src/types/storageKeys.ts b/src/types/storageKeys.ts
index c18bd31d..bb480c6a 100644
--- a/src/types/storageKeys.ts
+++ b/src/types/storageKeys.ts
@@ -2,4 +2,6 @@ export enum StorageKeys {
APP_FOREGROUND_TIMESTAMP = 'appForegroundTimestamp',
APP_BACKGROUND_TIMESTAMP = 'appBackgroundTimestamp',
IS_USER_ACTIVE = 'isUserActive',
+ USER_ACTIVITY_ON_APP = 'userActivityOnApp',
+ STATE_SET_TIMESTAMP = 'stateSetTimestamp',
}
diff --git a/tsconfig.json b/tsconfig.json
index bc394ebd..d9248cf8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,9 +5,34 @@
/* Visit https://aka.ms/tsconfig.json to read more about this file */
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
- "lib": ["dom","es5","es2020"],
-
+ "lib": [
+ "dom",
+ "es5",
+ "es2020"
+ ],
/* Completeness */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ "skipLibCheck": true, /* Skip type checking all .d.ts files. */
+ "baseUrl": ".",
+ "paths": {
+ "@root/*": ["."],
+ "@components/*": ["src/components/*"],
+ "@hooks": ["src/hooks/index"],
+ "@hooks/*": ["src/hooks/*"],
+ "@actions/*": ["src/action/*"],
+ "@reducers/*": ["src/reducer/*"],
+ "@constants/*": ["src/constants/*"],
+ "@screens/*": ["src/screens/*"],
+ "@services/*": ["src/services/*"],
+ "@types/*": ["src/types/*"],
+ "@common/*": ["src/common/*"],
+ "@assets/*": ["src/assets/*"],
+ "@store": ["src/store/store"],
+ "@utils/*": ["src/components/utlis/*"],
+ "@rn-ui-lib/components/*": ["RN-UI-LIB/src/components/*"],
+ "@rn-ui-lib/icons/*": ["RN-UI-LIB/src/Icons/*"],
+ "@rn-ui-lib/styles": ["RN-UI-LIB/src/styles/index"],
+ "@rn-ui-lib/colors": ["RN-UI-LIB/src/styles/colors"],
+ "@rn-ui-lib/utils/*": ["RN-UI-LIB/src/utlis/*"],
+ }
},
}
diff --git a/yarn.lock b/yarn.lock
index 5fe23084..27be2e18 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1648,6 +1648,11 @@
resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-17.4.0.tgz#9e1df987183d0ca367d0922a14b14b7a53a140cf"
integrity sha512-RSiBBfyJ3K9G6TQfZc09XaGpxB9xlP5m9DYkqjbNIqnnTiahF90770lTAS65L1Ha78vCwVO2swIlk32XbcMcMQ==
+"@react-native-firebase/remote-config@16.4.6":
+ version "16.4.6"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/remote-config/-/remote-config-16.4.6.tgz#dec215f2448f555cdba893a31f5cdf419b47b33e"
+ integrity sha512-2KPUao9xby+gp+JQUmikx9N0zcCLb0+6GkgI8//sYJ6Z3EaI53kx5kJHJDgYqdjF/zFjv3rm+yhm5LAgARPMHA==
+
"@react-native-google-signin/google-signin@9.0.2":
version "9.0.2"
resolved "https://registry.yarnpkg.com/@react-native-google-signin/google-signin/-/google-signin-9.0.2.tgz#fd9d0cbb58591265c2ea9404b2d2ea7e514b9ea9"
@@ -4700,6 +4705,18 @@ glob@5.0.15:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@7.0.6:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
+ integrity sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.2"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -8231,6 +8248,14 @@ rimraf@~2.6.2:
dependencies:
glob "^7.1.3"
+rn-fetch-blob@0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz#ec610d2f9b3f1065556b58ab9c106eeb256f3cba"
+ integrity sha512-+QnR7AsJ14zqpVVUbzbtAjq0iI8c9tCg49tIoKO2ezjzRunN7YL6zFSFSWZm6d+mE/l9r+OeDM3jmb2tBb2WbA==
+ dependencies:
+ base-64 "0.1.0"
+ glob "7.0.6"
+
route-recognizer@^0.3.3:
version "0.3.4"
resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3"