Merge branch 'master' of https://github.com/navi-medici/address-verification-app into TP-38645
This commit is contained in:
16
App.tsx
16
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();
|
||||
};
|
||||
|
||||
Submodule RN-UI-LIB updated: 4c7f4f6880...8876fa8a05
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> ->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
21 -> lollipop 28 ->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<queries>
|
||||
<package android:name="com.whatsapp" />
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
@@ -55,7 +60,17 @@
|
||||
<service android:name="com.supersami.foregroundservice.ForegroundService" android:foregroundServiceType="location"></service>
|
||||
<service android:name="com.supersami.foregroundservice.ForegroundServiceTask" android:foregroundServiceType="location"></service>
|
||||
|
||||
<activity
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait"
|
||||
@@ -74,5 +89,6 @@
|
||||
<data android:scheme="cosmosapp" android:host="cosmos" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -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<PackageInfo> 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.avapp;
|
||||
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
|
||||
14
src/assets/icons/ChevronDown.tsx
Normal file
14
src/assets/icons/ChevronDown.tsx
Normal file
@@ -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 (
|
||||
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
||||
<Path
|
||||
d="M11.9958 13.9585C11.907 13.9585 11.8238 13.9445 11.7461 13.9165C11.6684 13.8891 11.5963 13.842 11.5297 13.7755L8.44982 10.6985C8.32774 10.5765 8.26958 10.424 8.27535 10.2408C8.28068 10.0581 8.34439 9.90571 8.46647 9.78374C8.58856 9.66176 8.74394 9.60078 8.93261 9.60078C9.12129 9.60078 9.27667 9.66176 9.39875 9.78374L11.9958 12.3784L14.6095 9.7671C14.7316 9.64513 14.8843 9.58681 15.0677 9.59213C15.2506 9.5979 15.4031 9.66176 15.5252 9.78374C15.6473 9.90571 15.7083 10.0609 15.7083 10.2494C15.7083 10.4379 15.6473 10.5932 15.5252 10.7151L12.462 13.7755C12.3954 13.842 12.3232 13.8891 12.2455 13.9165C12.1678 13.9445 12.0846 13.9585 11.9958 13.9585Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
export default ChevronDown;
|
||||
14
src/assets/icons/ChevronUp.tsx
Normal file
14
src/assets/icons/ChevronUp.tsx
Normal file
@@ -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 (
|
||||
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
||||
<Path
|
||||
d="M8.466 13.767a.63.63 0 0 1-.183-.465.63.63 0 0 1 .183-.465l3.057-3.055a.587.587 0 0 1 .216-.141.742.742 0 0 1 .25-.041c.088 0 .171.013.249.04.077.029.15.076.216.142l3.073 3.072a.609.609 0 0 1 .183.448.637.637 0 0 1-.2.465.63.63 0 0 1-.465.183.63.63 0 0 1-.465-.183l-2.592-2.59-2.608 2.607a.61.61 0 0 1-.449.182.638.638 0 0 1-.465-.199Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
export default ChevronUp;
|
||||
11
src/assets/icons/WhatsAppFeedbackShareIcon.tsx
Normal file
11
src/assets/icons/WhatsAppFeedbackShareIcon.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import Svg, { Path, SvgProps } from 'react-native-svg';
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg xmlns="http://www.w3.org/2000/svg" fill="none" {...props}>
|
||||
<Path
|
||||
fill="#25D366"
|
||||
d="M8 1.6A6.4 6.4 0 0 0 1.6 8c0 1.2.337 2.32.912 3.28l-.855 3.12 3.187-.837c.932.53 2.007.837 3.156.837A6.4 6.4 0 0 0 8 1.6ZM5.81 5.014c.103 0 .21 0 .302.004.115.003.239.011.358.274.141.313.449 1.097.488 1.176.04.08.068.173.013.277-.052.107-.08.172-.156.266-.08.092-.167.206-.239.275-.08.08-.162.167-.07.325s.41.678.881 1.097c.606.54 1.117.707 1.275.786.16.08.251.067.343-.04.095-.103.397-.46.503-.619.104-.159.21-.131.354-.08.147.053.926.437 1.085.516.159.08.263.12.303.184.041.066.041.384-.09.754-.132.37-.78.727-1.07.752-.292.027-.565.132-1.903-.395-1.612-.635-2.63-2.287-2.709-2.393-.08-.104-.646-.86-.646-1.638 0-.781.41-1.164.553-1.323a.583.583 0 0 1 .424-.198Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default SvgComponent;
|
||||
19
src/common/AgentActivityConfigurableConstants.ts
Normal file
19
src/common/AgentActivityConfigurableConstants.ts
Normal file
@@ -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;
|
||||
};
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<ITrackingComponent> = ({ children }) => {
|
||||
@@ -94,12 +103,16 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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();
|
||||
};
|
||||
|
||||
@@ -7,3 +7,10 @@ export const locationEnabled = (): Promise<boolean> => DeviceUtilsModule.isLocat
|
||||
|
||||
// returns array of all the installed packages.
|
||||
export const getAllInstalledApp = (): Promise<string> => DeviceUtilsModule.getAllInstalledApp();
|
||||
|
||||
// sends feedback data to whatsapp.
|
||||
export const sendFeedbackToWhatsapp = (
|
||||
message: string,
|
||||
imageUrl: string,
|
||||
mimeType: string
|
||||
): Promise<boolean> => DeviceUtilsModule.sendFeedbackToWhatsapp(message, imageUrl, mimeType);
|
||||
|
||||
5
src/components/utlis/ScreenshotBlocker.tsx
Normal file
5
src/components/utlis/ScreenshotBlocker.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const { ScreenshotBlocker } = NativeModules;
|
||||
|
||||
export default ScreenshotBlocker;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface IGeolocationPayload {
|
||||
accuracy: number;
|
||||
timestamp: number;
|
||||
isActiveOnApp: boolean;
|
||||
userActivityOnApp: string;
|
||||
}
|
||||
|
||||
export const sendLocationAndActivenessToServer =
|
||||
|
||||
@@ -13,7 +13,6 @@ const useScreenshotTracking = () => {
|
||||
}));
|
||||
|
||||
const screenshotEventEmitter = useMemo(() => new NativeEventEmitter(ScreenshotBlocker), []);
|
||||
|
||||
useEffect(() => {
|
||||
ScreenshotBlocker.startScreenshotTracking();
|
||||
|
||||
|
||||
@@ -155,11 +155,7 @@ function SimilarAddressItem({
|
||||
<Tag variant={TagVariant.yellow} text={contactabilityStatus} />
|
||||
</View>
|
||||
) : null}
|
||||
<Text
|
||||
numberOfLines={3}
|
||||
ellipsizeMode="tail"
|
||||
style={[styles.textContainer, styles.cardLightTitle, { color: COLORS.TEXT.BLACK }]}
|
||||
>
|
||||
<Text style={[styles.textContainer, styles.cardLightTitle, { color: COLORS.TEXT.BLACK }]}>
|
||||
{sanitizeString(addressItem?.addressText)}
|
||||
</Text>
|
||||
<Text
|
||||
|
||||
@@ -4,8 +4,7 @@ import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader
|
||||
import { goBack, navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import useIsOnline from '../../hooks/useIsOnline';
|
||||
import OfflineScreen from '../../common/OfflineScreen';
|
||||
import { getUngroupedAddress } from '../../action/addressGeolocationAction';
|
||||
import { type IAddress } from '../../types/addressGeolocation.types';
|
||||
import { IGroupedAddressesItem, type IAddress } from '../../types/addressGeolocation.types';
|
||||
import AddressItem from './AddressItem';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { PageRouteEnum } from '../auth/ProtectedRouter';
|
||||
@@ -16,10 +15,8 @@ import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/Su
|
||||
import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import filterFarAwayMetaAddresses from './utils/FilterFarAwayMetaAddresses';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { CaseAllocationType } from '../allCases/interface';
|
||||
import SimilarAddressItem from './SimilarAddressItem';
|
||||
import Accordion from '../../../RN-UI-LIB/src/components/accordian/Accordian';
|
||||
|
||||
const PAGE_TITLE = 'Additional addresses';
|
||||
|
||||
@@ -34,6 +31,10 @@ interface IUngroupedAddress {
|
||||
};
|
||||
}
|
||||
|
||||
const SeparatorBorderComponent = () => {
|
||||
return <View style={styles.borderLine} />;
|
||||
};
|
||||
|
||||
const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routeParams }) => {
|
||||
const {
|
||||
params: { loanAccountNumber, caseId, customerReferenceId, fetchUngroupedAddress },
|
||||
@@ -87,6 +88,14 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ 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 (
|
||||
<View style={GenericStyles.fill}>
|
||||
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
|
||||
@@ -107,23 +116,76 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
|
||||
>
|
||||
{ungroupedAddressList?.length ? (
|
||||
<View>
|
||||
{ungroupedAddressList.map((ungroupedAddressItem: IAddress) => (
|
||||
{ungroupedAddressList.map((ungroupedAddressItem: IAddress, index: number) => (
|
||||
<View>
|
||||
<AddressItem
|
||||
caseId={caseId}
|
||||
showRelativeDistance
|
||||
containerStyle={styles.addressItemContainer}
|
||||
key={ungroupedAddressItem?.id}
|
||||
addressItem={ungroupedAddressItem}
|
||||
showActionButtons
|
||||
handleOldFeedbackRouting={() => {
|
||||
handleOpenOldFeedbacks(ungroupedAddressItem);
|
||||
}}
|
||||
handleCloseRouting={() => {
|
||||
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
|
||||
}}
|
||||
showSource
|
||||
/>
|
||||
{!ungroupedAddressItem?.similarAddresses ? (
|
||||
<AddressItem
|
||||
caseId={caseId}
|
||||
showRelativeDistance
|
||||
containerStyle={styles.addressItemContainer}
|
||||
key={ungroupedAddressItem?.id}
|
||||
addressItem={ungroupedAddressItem}
|
||||
showActionButtons
|
||||
handleOldFeedbackRouting={() => {
|
||||
handleOpenOldFeedbacks(ungroupedAddressItem);
|
||||
}}
|
||||
handleCloseRouting={() => {
|
||||
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
|
||||
}}
|
||||
showSource
|
||||
/>
|
||||
) : (
|
||||
<Accordion
|
||||
accordionStyle={{ paddingTop: 16 }}
|
||||
isExpansionDisabled={!ungroupedAddressItem?.similarAddresses.length}
|
||||
touchableOpacity={0.8}
|
||||
touchableDelay={50}
|
||||
accordionHeader={
|
||||
<AddressItem
|
||||
caseId={caseId}
|
||||
showRelativeDistance
|
||||
containerStyle={styles.addressItemContainerAccordian}
|
||||
key={ungroupedAddressItem?.id}
|
||||
addressItem={ungroupedAddressItem}
|
||||
showActionButtons
|
||||
handleOldFeedbackRouting={() => {
|
||||
handleOpenOldFeedbacks(ungroupedAddressItem);
|
||||
}}
|
||||
handleCloseRouting={() => {
|
||||
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
|
||||
}}
|
||||
showSource
|
||||
/>
|
||||
}
|
||||
onExpanded={(isExpanded: boolean) => {
|
||||
handleAccordionExpand(isExpanded, ungroupedAddressItem?.id);
|
||||
}}
|
||||
>
|
||||
{ungroupedAddressItem?.similarAddresses.length ? (
|
||||
<View>
|
||||
<SeparatorBorderComponent />
|
||||
<Text
|
||||
style={[
|
||||
styles.textContainer,
|
||||
styles.accordionDetailHeading,
|
||||
GenericStyles.pb8,
|
||||
]}
|
||||
>
|
||||
Similar addresses
|
||||
</Text>
|
||||
{ungroupedAddressItem.similarAddresses.map((similarAddress: IAddress) => (
|
||||
<View style={GenericStyles.fill} key={similarAddress?.id}>
|
||||
<SimilarAddressItem
|
||||
addressItem={similarAddress}
|
||||
containerStyle={styles.card}
|
||||
showSource
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</Accordion>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -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;
|
||||
|
||||
@@ -87,10 +87,15 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ 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(() => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
95
src/screens/caseDetails/Chip.tsx
Normal file
95
src/screens/caseDetails/Chip.tsx
Normal file
@@ -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<ViewStyle>;
|
||||
subText?: string;
|
||||
numberOfLines?: number;
|
||||
clipCount?: number;
|
||||
showCopyBtn?: boolean;
|
||||
clickstreamEvent?: () => void;
|
||||
successMessage?: string;
|
||||
errorMessage?: string;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
const Chip: React.FC<IChip> = (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 (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.chip,
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
]}
|
||||
>
|
||||
<View style={clippedStyle}>
|
||||
<Text
|
||||
numberOfLines={numberOfLines}
|
||||
ellipsizeMode={isClipped ? 'tail' : undefined}
|
||||
small
|
||||
style={textStyle}
|
||||
>
|
||||
{subText && subText}
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
{showCopyBtn && (
|
||||
<View style={GenericStyles.ml4}>
|
||||
<CopyIcon />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return showCopyBtn ? (
|
||||
<TouchableHighlight style={[containerStyle, clippedStyle]} onPress={copyData}>
|
||||
{renderChip()}
|
||||
</TouchableHighlight>
|
||||
) : (
|
||||
<View style={[containerStyle, clippedStyle]}>{renderChip()}</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Chip;
|
||||
@@ -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<ICollectionCaseData> = ({ caseData }) => {
|
||||
employmentDetail,
|
||||
} = caseData;
|
||||
|
||||
const showEmploymentDetails = false;
|
||||
|
||||
return (
|
||||
<View>
|
||||
{fatherName && (
|
||||
<Text style={[styles.headerText]} small>
|
||||
S/O {fatherName}
|
||||
Parent name: {toTileCase(fatherName)}
|
||||
</Text>
|
||||
)}
|
||||
<View style={[GenericStyles.row, GenericStyles.mv8]}>
|
||||
<Text style={[GenericStyles.chip, GenericStyles.whiteBackground]} small>
|
||||
Current DPD {currentDpd}
|
||||
</Text>
|
||||
{loanAccountNumber && <LANChip loanAccountNumber={loanAccountNumber} />}
|
||||
{loanAccountNumber && (
|
||||
<Chip
|
||||
text={loanAccountNumber}
|
||||
showCopyBtn
|
||||
containerStyle={GenericStyles.ml8}
|
||||
subText="LAN "
|
||||
clickstreamEvent={() =>
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_LAN_CLICKED, {
|
||||
lan: loanAccountNumber,
|
||||
})
|
||||
}
|
||||
successMessage={ToastMessages.SUCCESS_COPYING_LAN}
|
||||
errorMessage={ToastMessages.ERROR_COPYING_LAN}
|
||||
clipCount={14}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mt4]}>
|
||||
<EmploymentDetails employmentDetail={employmentDetail} />
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mt8]}>
|
||||
<Text style={[styles.greyText]} small>
|
||||
DPD bucket {dpdBucket}
|
||||
</Text>
|
||||
@@ -46,29 +66,15 @@ const CollectionCaseData: React.FC<ICollectionCaseData> = ({ caseData }) => {
|
||||
<Text style={[styles.greyText]} small>
|
||||
POS {formatAmount(pos)}
|
||||
</Text>
|
||||
</View>
|
||||
{showEmploymentDetails ? (
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mt4]}>
|
||||
{employmentDetail?.employmentType && (
|
||||
<Text style={[styles.greyText]} small>
|
||||
{employmentDetail.employmentType}
|
||||
</Text>
|
||||
)}
|
||||
{employmentDetail?.employmentType && employmentDetail?.employerName && (
|
||||
{collectionTag ? (
|
||||
<>
|
||||
<View style={styles.lineStyle} />
|
||||
)}
|
||||
{employmentDetail?.employerName && (
|
||||
<Text style={[styles.greyText]} small numberOfLines={1}>
|
||||
{employmentDetail.employerName}
|
||||
<Text style={[styles.greyText]} small>
|
||||
{collectionTag}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
) : null}
|
||||
{collectionTag ? (
|
||||
<Text style={[styles.greyText]} small>
|
||||
{collectionTag}
|
||||
</Text>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -217,7 +217,7 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
const commonParams = {
|
||||
loanAccountNumber: caseDetail.loanAccountNumber,
|
||||
customerReferenceId: caseDetail.customerReferenceId,
|
||||
caseId,
|
||||
caseId: caseId,
|
||||
};
|
||||
navigateToScreen(route, { ...params, ...commonParams });
|
||||
};
|
||||
@@ -469,6 +469,7 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
<FeedbackListContainer
|
||||
feedbackList={[...getUnSyncedFeedback(), ...feedbackList]?.splice(0, 5)}
|
||||
loanAccountNumber={caseDetail?.loanAccountNumber as string}
|
||||
caseId={caseId}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
47
src/screens/caseDetails/EmploymentDetails.tsx
Normal file
47
src/screens/caseDetails/EmploymentDetails.tsx
Normal file
@@ -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 (
|
||||
<Chip
|
||||
text={employmentDetail.employerName}
|
||||
numberOfLines={0}
|
||||
showCopyBtn
|
||||
containerStyle={GenericStyles.mr8}
|
||||
clipCount={42}
|
||||
clickstreamEvent={() =>
|
||||
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 (
|
||||
<Chip
|
||||
text={employmentDetail.employmentType}
|
||||
containerStyle={GenericStyles.mr8}
|
||||
clipCount={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default EmploymentDetails;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
lineHeight: { lineHeight: 20, paddingVertical: 1.6 },
|
||||
});
|
||||
@@ -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<IFeedbackDetailContainer> = ({ 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<IFeedbackDetailContainer> = ({ route: ro
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
accordionStyle={[GenericStyles.pv12, GenericStyles.br8, getShadowStyle(4)]}
|
||||
accordionStyle={[
|
||||
GenericStyles.br8,
|
||||
GenericStyles.ph16,
|
||||
styles.accordianPadding,
|
||||
getShadowStyle(4),
|
||||
]}
|
||||
isActive={feedback.referenceId === activeFeedbackReferenceId}
|
||||
touchableDelay={50}
|
||||
touchableOpacity={0.8}
|
||||
@@ -221,11 +235,12 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
key={feedback.referenceId}
|
||||
feedbackItem={feedback}
|
||||
isExpanded={isExpanded}
|
||||
caseId={caseId}
|
||||
/>
|
||||
}
|
||||
customExpandUi={{
|
||||
whenCollapsed: <Text style={styles.accordionExpandBtn}>View more</Text>,
|
||||
whenExpanded: <Text style={styles.accordionExpandBtn}>View less</Text>,
|
||||
whenCollapsed: <ChevronDown />,
|
||||
whenExpanded: <ChevronUp />,
|
||||
}}
|
||||
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;
|
||||
|
||||
@@ -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<FEEDBACK_TYPE, ReactNode> = {
|
||||
FIELD_VISIT: <MapIcon />,
|
||||
INHOUSE_FIELD_VISIT: <MapIcon />,
|
||||
SELF_CALL: <CallIcon fillColor={COLORS.TEXT.BLUE} />,
|
||||
CALL_BRIDGE: <CallIcon fillColor={COLORS.TEXT.BLUE} />,
|
||||
FIELD_VISIT: <MapIcon fillColor={COLORS.TEXT.LIGHT} />,
|
||||
INHOUSE_FIELD_VISIT: <MapIcon fillColor={COLORS.TEXT.LIGHT} />,
|
||||
SELF_CALL: <CallIcon fillColor={COLORS.TEXT.LIGHT} />,
|
||||
CALL_BRIDGE: <CallIcon fillColor={COLORS.TEXT.LIGHT} />,
|
||||
};
|
||||
|
||||
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 (
|
||||
<View style={[GenericStyles.ph8]}>
|
||||
<View style={[styles.addressItem]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
{feedbackTypeIcon[feedbackItem.type] ? (
|
||||
<View style={GenericStyles.mr8}>{feedbackTypeIcon[feedbackItem.type]}</View>
|
||||
@@ -70,13 +201,17 @@ const FeedbackDetailItem = ({ feedbackItem, isExpanded }: IFeedbackDetailItem) =
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
style={[styles.textContainer, styles.cardFooterText, GenericStyles.fontSize12]}
|
||||
style={[
|
||||
styles.textContainer,
|
||||
styles.cardFooterText,
|
||||
GenericStyles.fontSize12,
|
||||
GenericStyles.mb12,
|
||||
]}
|
||||
>
|
||||
{sanitizeString(dateFormat(new Date(feedbackItem.createdAt), BUSINESS_DATE_FORMAT))}
|
||||
●
|
||||
<Text style={styles.bullet}> ● </Text>
|
||||
{sanitizeString(dateFormat(new Date(feedbackItem.createdAt), BUSINESS_TIME_FORMAT))}
|
||||
</Text>
|
||||
<View style={[GenericStyles.borderTop, GenericStyles.w100, GenericStyles.mv16]} />
|
||||
<Text
|
||||
numberOfLines={isExpanded ? 100 : 3}
|
||||
ellipsizeMode="tail"
|
||||
@@ -93,18 +228,33 @@ const FeedbackDetailItem = ({ feedbackItem, isExpanded }: IFeedbackDetailItem) =
|
||||
</Text>
|
||||
|
||||
{feedbackItem.metadata?.interactionLatitude && FIELD_FEEDBACKS.includes(feedbackItem.type) ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
openGeolocation(
|
||||
feedbackItem.metadata?.interactionLatitude,
|
||||
feedbackItem.metadata?.interactionLongitude
|
||||
)
|
||||
}
|
||||
style={[GenericStyles.row, GenericStyles.pv12]}
|
||||
>
|
||||
<Text style={[styles.textContainer, styles.geolocationBtn]}>Open map</Text>
|
||||
</TouchableOpacity>
|
||||
<>
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
openGeolocation(
|
||||
feedbackItem.metadata?.interactionLatitude,
|
||||
feedbackItem.metadata?.interactionLongitude
|
||||
)
|
||||
}
|
||||
style={[GenericStyles.row, styles.BtnPadding]}
|
||||
>
|
||||
<Text style={[styles.textContainer, styles.geolocationBtn]}>Open map</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() => sendToWhatsapp(feedbackItem, caseDetails, agentId)}
|
||||
style={[GenericStyles.row, styles.BtnPadding]}
|
||||
>
|
||||
<IconLabel
|
||||
text="Share"
|
||||
icon={<WhatsAppFeedbackShareIcon />}
|
||||
textStyle={{ color: COLORS.BASE.BLUE }}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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<IOfflineFeedbackListContainer> = ({
|
||||
const FeedbackListContainer: React.FC<IFeedbackListContainer> = ({
|
||||
loanAccountNumber,
|
||||
feedbackList,
|
||||
caseId,
|
||||
}) => {
|
||||
const [retryBtnCount, setRetryBtnCount] = useState(0);
|
||||
|
||||
@@ -97,6 +99,7 @@ const FeedbackListContainer: React.FC<IFeedbackListContainer> = ({
|
||||
feedbackItem={feedbackItem}
|
||||
loanAccountNumber={loanAccountNumber}
|
||||
showHorizontalLine={++idx !== feedbackList.length}
|
||||
caseId={caseId}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -15,11 +15,13 @@ interface IFeedbackListItem {
|
||||
feedbackItem: IFeedback | IUnSyncedFeedbackItem;
|
||||
showHorizontalLine?: boolean;
|
||||
loanAccountNumber: string;
|
||||
caseId: string;
|
||||
}
|
||||
|
||||
const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
feedbackItem,
|
||||
loanAccountNumber,
|
||||
caseId,
|
||||
showHorizontalLine = true,
|
||||
}) => {
|
||||
const handleRouting = (route: PageRouteEnum, params: object | undefined = undefined) => {
|
||||
@@ -27,6 +29,7 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
const commonParams = {
|
||||
loanAccountNumber,
|
||||
activeFeedbackReferenceId: (feedbackItem as IFeedback).referenceId,
|
||||
caseId: caseId,
|
||||
};
|
||||
navigateToScreen(route, { ...params, ...commonParams });
|
||||
};
|
||||
|
||||
37
src/services/firebaseFetchAndUpdate.service.ts
Normal file
37
src/services/firebaseFetchAndUpdate.service.ts
Normal file
@@ -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;
|
||||
@@ -39,6 +39,7 @@ export interface IAddress {
|
||||
groupId: string;
|
||||
primarySource?: PrimarySourcesType;
|
||||
secondarySource?: string;
|
||||
similarAddresses?: IAddress[];
|
||||
}
|
||||
|
||||
export interface IGroupedAddressesItem {
|
||||
|
||||
5
src/types/agentActivity.ts
Normal file
5
src/types/agentActivity.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum AgentActivity {
|
||||
HIGH = 'HIGH',
|
||||
MEDIUM = 'MEDIUM',
|
||||
LOW = 'LOW',
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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/*"],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
25
yarn.lock
25
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"
|
||||
|
||||
Reference in New Issue
Block a user