TP-52572 | merge conflict resolve

This commit is contained in:
Aman Chaturvedi
2024-01-30 15:47:21 +05:30
32 changed files with 832 additions and 356 deletions

View File

@@ -1,4 +1,5 @@
apply plugin: "com.android.application"
apply plugin: "com.google.gms.google-services"
apply plugin: "com.google.firebase.crashlytics"
apply plugin: 'com.google.firebase.firebase-perf'
@@ -133,8 +134,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 120
def VERSION_NAME = "2.7.2"
def VERSION_CODE = 123
def VERSION_NAME = "2.7.5"
android {
ndkVersion rootProject.ext.ndkVersion
@@ -315,7 +316,10 @@ dependencies {
implementation "com.github.anrwatchdog:anrwatchdog:1.4.0"
//implementation 'com.navi.medici:alfred:v1.0.2'
implementation 'com.navi.android:alfred:1.1.1'
implementation(platform("com.google.firebase:firebase-bom:32.2.3"))
implementation("com.google.firebase:firebase-config-ktx")
implementation("com.google.firebase:firebase-analytics-ktx")
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules

View File

@@ -1,14 +1,14 @@
package com.avapp;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static com.avapp.MainApplication.isAlfredEnabledFromFirebase;
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;
@@ -20,26 +20,18 @@ 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 com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.navi.alfred.AlfredManager;
import android.content.pm.PackageInfo;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Environment;
import android.os.Handler;
import android.os.Parcelable;
import android.util.Base64;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
@@ -47,7 +39,6 @@ import java.util.HashMap;
import java.util.List;
import android.net.Uri;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
@@ -154,50 +145,52 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
@ReactMethod
public void setUserId(String userId) {
//AlfredManager.config.setUserId(userId);
if (isAlfredEnabledFromFirebase) {
AlfredManager.INSTANCE.getConfig$navi_alfred_release().setUserId(userId);
}
}
@ReactMethod
public void handleSWWEvent(String message, String stack, String name) { //<String, Object>
HashMap<String, String> properties = new HashMap<>();
properties.put("message", message);
properties.put("stack", stack);
properties.put("name", name);
//AlfredManager.INSTANCE.handleSWWEvent("Cosmos", properties);
if (isAlfredEnabledFromFirebase) {
HashMap<String, String> properties = new HashMap<>();
properties.put("message", message);
properties.put("stack", stack);
properties.put("name", name);
AlfredManager.INSTANCE.handleSWWEvent(properties);
}
}
@ReactMethod
public void setCodePushVersion(String codePushVersion) {
if (codePushVersion != null) {
//AlfredManager.config.setCodePushVersion(codePushVersion);
if (isAlfredEnabledFromFirebase && codePushVersion != null) {
AlfredManager.INSTANCE.getConfig$navi_alfred_release().setCodePushVersion(codePushVersion);
}
}
@ReactMethod
public void setPhoneNumber(String phoneNumber) {
if (phoneNumber != null) {
//AlfredManager.config.setPhoneNumber(phoneNumber);
if (isAlfredEnabledFromFirebase && phoneNumber != null) {
AlfredManager.INSTANCE.getConfig$navi_alfred_release().setPhoneNumber(phoneNumber);
}
}
@ReactMethod
public void setEmailId(String emailId) {
if (emailId != null) {
//AlfredManager.config.setAgentEmailId(emailId);
if (isAlfredEnabledFromFirebase && emailId != null) {
AlfredManager.INSTANCE.getConfig$navi_alfred_release().setAgentEmailId(emailId);
}
}
@ReactMethod
public void setBottomSheetView(Integer refID) {
if (refID != null) {
if (isAlfredEnabledFromFirebase && refID != null) {
UIManagerModule uiManagerModule = RNContext.getNativeModule(UIManagerModule.class);
if (uiManagerModule != null) {
try {
uiManagerModule.addUIBlock(nativeViewHierarchyManager -> {
Log.d("Alfred", "setBottomSheetView nativeViewHierarchyManager:" + nativeViewHierarchyManager);
View view = nativeViewHierarchyManager.resolveView(refID);
Log.d("Alfred", "setBottomSheetView view:" + view);
//AlfredManager.INSTANCE.setBottomSheetView(view);
AlfredManager.INSTANCE.setBottomSheetView(view);
});
} catch (Exception error) {
Log.d("Alfred", "setBottomSheetView error:" + error);
@@ -208,7 +201,9 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
@ReactMethod
public void clearBottomSheet() {
//AlfredManager.INSTANCE.clearBottomSheetView();
if (isAlfredEnabledFromFirebase) {
AlfredManager.INSTANCE.clearBottomSheetView();
}
}
private static File convertBase64ToFile(Context context, String base64Data, String format, String fileName) {

View File

@@ -1,50 +1,137 @@
package com.avapp;
import static com.avapp.MainApplication.isAlfredEnabledFromFirebase;
import com.avapp.utils.AlfredFirebaseHelper;
import com.avapp.utils.FirebaseRemoteConfigHelper;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.navi.alfred.AlfredManager;
import com.navi.alfred.utils.CommonUtilsKt;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.MotionEvent;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "AVAPP";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
* you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
* (Paper).
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new MainActivityDelegate(this, getMainComponentName());
}
public static class MainActivityDelegate extends ReactActivityDelegate {
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
super(activity, mainComponentName);
}
public class MainActivity extends ReactActivity implements AlfredFirebaseHelper {
private static int appInForegroundCounter = 0;
private boolean cruiseApiCalled = false;
public static boolean hasAlfredRecordingStarted = false;
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
return reactRootView;
protected String getMainComponentName() {
return "AVAPP";
}
@Override
protected boolean isConcurrentRootEnabled() {
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
FirebaseRemoteConfigHelper.setAlfredFirebaseHelper(this);
}
}
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
* you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
* (Paper).
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new MainActivityDelegate(this, getMainComponentName());
}
public static class MainActivityDelegate extends ReactActivityDelegate {
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
super(activity, mainComponentName);
}
@Override
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
return reactRootView;
}
@Override
protected boolean isConcurrentRootEnabled() {
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (isAlfredEnabledFromFirebase && AlfredManager.INSTANCE.isAlfredRecordingEnabled() && cruiseApiCalled) {
AlfredManager.INSTANCE.handleTouchEvent(ev, BuildConfig.APP_NAME, BuildConfig.APP_NAME);
}
return super.dispatchTouchEvent(ev);
}
@Override
protected void onStart() {
super.onStart();
appInForegroundCounter++;
}
@Override
protected void onResume() {
super.onResume();
if (isAlfredEnabledFromFirebase && !hasAlfredRecordingStarted) {
callCruiseAndStartAlfredRecording();
}
}
@Override
protected void onStop() {
super.onStop();
appInForegroundCounter--;
if (isAlfredEnabledFromFirebase && AlfredManager.INSTANCE.isAlfredRecordingEnabled() && cruiseApiCalled) {
if (!isAppInForeground()) {
AlfredManager.INSTANCE.stopRecording();
hasAlfredRecordingStarted = false;
}
}
}
public static Boolean isAppInForeground() {
return appInForegroundCounter >= 1;
}
@Override
public void callCruiseAndStartAlfredRecording() {
if (cruiseApiCalled) {
startAlfredRecording();
} else {
AlfredManager.INSTANCE.getAlfredCruiseInfo(response -> {
cruiseApiCalled = true;
startAlfredRecording();
return null;
});
}
}
public void startAlfredRecording() {
if (AlfredManager.INSTANCE.isAlfredRecordingEnabled() && !hasAlfredRecordingStarted) {
try {
AlfredManager.INSTANCE.startRecording(
this,
this.getWindow().getDecorView().getRootView(),
BuildConfig.APP_NAME,
BuildConfig.APP_NAME,
this
);
hasAlfredRecordingStarted = true;
} catch (Exception e) {
CommonUtilsKt.log(e);
}
}
}
}

View File

@@ -1,9 +1,15 @@
package com.avapp;
import static com.avapp.utils.Constants.APP_IN_FOREGROUND;
import static com.avapp.utils.Constants.LINE_NUMBER;
import static com.avapp.utils.Constants.METHOD_NAME;
import static com.avapp.utils.Constants.STACK_TRACE;
import static com.google.firebase.analytics.FirebaseAnalytics.Param.SCREEN_NAME;
import android.app.Application;
import android.content.Context;
import com.avapp.utils.FirebaseRemoteConfigHelper;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
@@ -12,100 +18,169 @@ import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.soloader.SoLoader;
import com.avapp.newarchitecture.MainApplicationReactNativeHost;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import com.github.anrwatchdog.ANRWatchDog;
import com.microsoft.codepush.react.CodePush;
import com.navi.alfred.AlfredConfig;
import com.navi.alfred.AlfredManager;
import android.database.CursorWindow;
import java.lang.reflect.Field;
import java.util.Map;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
public static boolean isAlfredEnabledFromFirebase = false;
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new DeviceUtilsModulePackage());
packages.add(new ScreenshotBlockerModulePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
private final ReactNativeHost mNewArchitectureNativeHost =
new MainApplicationReactNativeHost(this);
@Override
public ReactNativeHost getReactNativeHost() {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
return mNewArchitectureNativeHost;
} else {
return mReactNativeHost;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new DeviceUtilsModulePackage());
packages.add(new ScreenshotBlockerModulePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
private final ReactNativeHost mNewArchitectureNativeHost =
new MainApplicationReactNativeHost(this);
@Override
public ReactNativeHost getReactNativeHost() {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
return mNewArchitectureNativeHost;
} else {
return mReactNativeHost;
}
}
@Override
public void onCreate() {
super.onCreate();
// If you opted-in for the New Architecture, we enable the TurboModule system
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
@Override
public void onCreate() {
super.onCreate();
// If you opted-in for the New Architecture, we enable the TurboModule system
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
FirebaseRemoteConfigHelper.init();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
AlfredConfig alfredConfig = new AlfredConfig(BuildConfig.APP_NAME, String.valueOf(BuildConfig.VERSION_CODE), BuildConfig.VERSION_NAME, BuildConfig.BUILD_FLAVOR, BuildConfig.API_KEY);
AlfredManager.INSTANCE.init(alfredConfig, this);
setupAlfredANRWatchDog(alfredConfig);
setupAlfredCrashReporting(alfredConfig);
// https://github.com/rt2zz/redux-persist/issues/284#issuecomment-1011214066
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 20 * 1024 * 1024); //20MB
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
// https://github.com/rt2zz/redux-persist/issues/284#issuecomment-1011214066
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 20 * 1024 * 1024); //20MB
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.avapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Class<?> aClass = Class.forName("com.avapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
private void setupAlfredANRWatchDog(AlfredConfig alfredConfig) {
new ANRWatchDog().setIgnoreDebugger(true).setReportMainThreadOnly().setANRListener(error -> {
if (error.getCause().getStackTrace().length == 0) {
return;
}
Map<String, String> anrEventProperties = new HashMap<>();
anrEventProperties.put(SCREEN_NAME, BuildConfig.APP_NAME);
anrEventProperties.put(METHOD_NAME, error.getCause().getStackTrace()[0].getMethodName());
anrEventProperties.put(LINE_NUMBER, String.valueOf(error.getCause().getStackTrace()[0].getLineNumber()));
anrEventProperties.put(APP_IN_FOREGROUND, MainActivity.isAppInForeground().toString());
if (isAlfredEnabledFromFirebase && AlfredManager.INSTANCE.isAlfredRecordingEnabled() && alfredConfig.getAnrEnableStatus()) {
anrEventProperties.put(STACK_TRACE, error.getCause().getStackTrace()[0].toString());
AlfredManager.INSTANCE.handleAnrEvent(anrEventProperties);
}
}).start();
}
private void setupAlfredCrashReporting(AlfredConfig alfredConfig) {
Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
if ((exception.getStackTrace() == null) || (exception.getStackTrace().length == 0)) {
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, exception);
}
return;
}
try {
Map<String, String> crashEventProperties = new HashMap<>();
crashEventProperties.put(SCREEN_NAME, BuildConfig.APP_NAME);
crashEventProperties.put(METHOD_NAME, exception.getStackTrace()[0].getMethodName());
crashEventProperties.put(LINE_NUMBER, String.valueOf(exception.getStackTrace()[0].getLineNumber()));
crashEventProperties.put(APP_IN_FOREGROUND, MainActivity.isAppInForeground().toString());
if (isAlfredEnabledFromFirebase && AlfredManager.INSTANCE.isAlfredRecordingEnabled() && alfredConfig.getCrashEnableStatus()) {
StackTraceElement stackTraceElement = exception.getStackTrace()[0];
if (stackTraceElement != null) {
crashEventProperties.put(STACK_TRACE, stackTraceElement.toString());
}
AlfredManager.INSTANCE.handleCrashEvent(crashEventProperties);
}
} finally {
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, exception);
}
}
});
}
}

View File

@@ -0,0 +1,5 @@
package com.avapp.utils;
public interface AlfredFirebaseHelper {
void callCruiseAndStartAlfredRecording();
}

View File

@@ -0,0 +1,89 @@
package com.avapp.utils;
import static com.avapp.MainActivity.hasAlfredRecordingStarted;
import static com.avapp.MainApplication.isAlfredEnabledFromFirebase;
import com.avapp.BuildConfig;
import com.avapp.R;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
public class FirebaseRemoteConfigHelper {
private static FirebaseRemoteConfig remoteConfig;
private static final long CONFIG_SYNC_INTERVAL = 60 * 60;
public static final String DISABLE_ALFRED_LOGS = "DISABLE_ALFRED_LOGS";
public static final String ALFRED_ENABLED = "ALFRED_ENABLED";
private static AlfredFirebaseHelper alfredFirebaseHelper;
public static void setAlfredFirebaseHelper(AlfredFirebaseHelper alfredFirebaseHelper) {
FirebaseRemoteConfigHelper.alfredFirebaseHelper = alfredFirebaseHelper;
}
public static void init() {
remoteConfig = getFirebaseRemoteConfig();
}
private static FirebaseRemoteConfig getFirebaseRemoteConfig() {
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder().setMinimumFetchIntervalInSeconds(BuildConfig.DEBUG ? 0 : CONFIG_SYNC_INTERVAL).build();
remoteConfig.setConfigSettingsAsync(configSettings);
remoteConfig.setDefaultsAsync(R.xml.default_xml_config);
remoteConfig.fetchAndActivate().addOnCompleteListener(task -> {
isAlfredEnabledFromFirebase = FirebaseRemoteConfigHelper.getBoolean(ALFRED_ENABLED);
if (alfredFirebaseHelper != null && isAlfredEnabledFromFirebase && !hasAlfredRecordingStarted) {
alfredFirebaseHelper.callCruiseAndStartAlfredRecording();
}
});
return remoteConfig;
}
private static FirebaseRemoteConfigValue getRawValue(String key) {
try {
FirebaseRemoteConfigValue rawValue = remoteConfig.getValue(key);
return rawValue.getSource() == FirebaseRemoteConfig.VALUE_SOURCE_STATIC ? null : rawValue;
} catch (Exception e) {
return null;
}
}
public static Boolean getRawBoolean(String key) {
FirebaseRemoteConfigValue rawValue = getRawValue(key);
return rawValue != null ? rawValue.asBoolean() : null;
}
public static String getString(String key) {
try {
return remoteConfig.getString(key);
} catch (Exception e) {
return FirebaseRemoteConfig.DEFAULT_VALUE_FOR_STRING;
}
}
public static boolean getBoolean(String key) {
try {
return remoteConfig.getBoolean(key);
} catch (Exception e) {
return FirebaseRemoteConfig.DEFAULT_VALUE_FOR_BOOLEAN;
}
}
public static double getDouble(String key) {
try {
return remoteConfig.getDouble(key);
} catch (Exception e) {
return FirebaseRemoteConfig.DEFAULT_VALUE_FOR_DOUBLE;
}
}
public static long getLong(String key) {
try {
return remoteConfig.getLong(key);
} catch (Exception e) {
return FirebaseRemoteConfig.DEFAULT_VALUE_FOR_LONG;
}
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~
~ * Copyright © 2019-2023 by Navi Technologies Limited
~ * All rights reserved. Strictly confidential
~
-->
<defaultsMap>
<entry>
<key>ALFRED_ENABLED</key>
<value>false</value>
</entry>
</defaultsMap>

View File

@@ -39,7 +39,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
hermesEnabled=true
MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=Navi@123

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.7.2",
"buildNumber": "120",
"version": "2.7.5",
"buildNumber": "123",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",
@@ -9,13 +9,13 @@
"android:prod": "yarn move:prod && react-native run-android",
"android-field:dev": "yarn move:dev && react-native run-android --variant=fieldAgentsQADebug",
"android-field:qa": "yarn move:qa && react-native run-android --variant=fieldAgentsQADebug",
"android-field:prod": "yarn move:prod && react-native run-android --variant=fieldAgentsQADebug",
"android-field:prod": "yarn move:prod && react-native run-android --variant=fieldAgentsProdDebug",
"release-field:dev": "yarn move:dev && react-native run-android --variant=fieldAgentsQARelease && cd android && ./gradlew assemblefieldAgentsQARelease",
"release-field:qa": "yarn move:qa && react-native run-android --variant=fieldAgentsQARelease && cd android && ./gradlew assemblefieldAgentsQARelease",
"release-field:prod": "yarn move:prod && react-native run-android --variant=fieldAgentsProdRelease && cd android && ./gradlew assemblefieldAgentsProdRelease",
"android-calling:dev": "yarn move:dev && react-native run-android --variant=callingAgentsQADebug",
"android-calling:qa": "yarn move:qa && react-native run-android --variant=callingAgentsQADebug",
"android-calling:prod": "yarn move:prod && react-native run-android --variant=callingAgentsQADebug",
"android-calling:prod": "yarn move:prod && react-native run-android --variant=callingAgentsProdDebug",
"release-calling:dev": "yarn move:dev && react-native run-android --variant=callingAgentsQARelease && cd android && ./gradlew assemblecallingAgentsQARelease",
"release-calling:qa": "yarn move:qa && react-native run-android --variant=callingAgentsQARelease && cd android && ./gradlew assemblecallingAgentsQARelease",
"release-calling:prod": "yarn move:dev && react-native run-android --variant=callingAgentsProdRelease && cd android && ./gradlew assemblecallingAgentsProdRelease",

View File

@@ -8,6 +8,7 @@ import {
setAuthData,
setSelectedAgent,
setIsExternalAgent,
setIsAgentPerformanceDashboardVisible,
} from '../reducer/userSlice';
import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper';
import {
@@ -279,8 +280,10 @@ export const getAgentDetail = (callbackFn?: () => void) => (dispatch: AppDispatc
if (response.status === API_STATUS_CODE.OK) {
const roles: string[] = response?.data?.roles || [];
const isExternalAgent: boolean = response?.data?.isExternalAgent;
const isAgentPerformanceDashboardVisible: boolean = response?.data?.featureFlags?.fieldAgentPerformanceDashboardEnabled || false;
dispatch(setAgentRole(roles));
dispatch(setIsExternalAgent(isExternalAgent));
dispatch(setIsAgentPerformanceDashboardVisible(isAgentPerformanceDashboardVisible));
}
})
.finally(() => {

View File

@@ -81,6 +81,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
description: 'Cases unpinned',
},
FA_VISIT_PLAN_UPDATED: { name: 'FA_VISIT_PLAN_UPDATED', description: 'Updated cases pinned' },
FA_VISIT_PLAN_BUTTON_CLICKED : {
name: 'FA_VISIT_PLAN_BUTTON_CLICKED',
description : 'Click on visit plan button on bottom navigation bar',
},
// Login screen
AV_LOGIN_SCREEN_LOAD: { name: 'FA_LOGIN_SCREEN_LOAD', description: 'Login page loaded' },
@@ -155,6 +159,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_CASE_LIST_FILTER_BUTTON_CLICKED',
description: 'Case list filter CTA clicked',
},
FA_CASE_LIST_BUTTON_CLICKED :{
name : 'FA_CASE_LIST_BUTTON_CLICKED',
description :'Clicks on case list button on bottom navigation bar',
},
// TODO LIST
AV_TODO_LIST_LOAD: {
@@ -241,6 +249,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
//PROFILE PAGE
AV_PROFILE_PAGE_LOADED: { name: 'FA_PROFILE_PAGE_LOADED', description: 'Profile page loaded' },
FA_PROFILE_PAGE_BUTTON_CLICKED :{
name : 'FA_PROFILE_PAGE_BUTTON_CLICKED',
description : 'Click on profile page button on bottom navigation bar',
},
AV_PROFILE_PAGE_LOGOUT_BUTTON_CLICKED: {
name: 'FA_PROFILE_PAGE_LOGOUT_BUTTON_CLICKED',
description: 'Logout CTA clicked',
@@ -366,6 +378,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_SEND_PAYMENT_LINK_FAILED_LIMIT_REACHED',
description: 'FA_SEND_PAYMENT_LINK_FAILED_LIMIT_REACHED',
},
FA_TOTAL_OUTSTANDING_BREAKUP_LOADED: {
name : 'FA_TOTAL_OUTSTANDING_BREAKUP_LOADED',
description : 'Outstanding breakup is loaded',
},
FA_TOTAL_OUTSTANDING_BREAKUP_CLICKED: {
name: 'FA_TOTAL_OUTSTANDING_BREAKUP_CLICKED',
description: 'FA_TOTAL_OUTSTANDING_BREAKUP_CLICKED',
@@ -378,10 +394,19 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_VIEW_MORE_ADDRESSES_CLICKED',
description: 'FA_VIEW_MORE_ADDRESSES_CLICKED',
},
FA_ADDITIONAL_ADDRESSES_BUTTON_CLICKED : {
name : 'FA_ADDITIONAL_ADDRESSES_BUTTON_CLICKED',
description: 'Click on additional addresses button in all addresses screen',
},
FA_VIEW_ADDRESSES_FAILED: {
name: 'FA_VIEW_ADDRESSES_FAILED',
description: 'FA_VIEW_ADDRESSES_FAILED',
},
FA_VIEW_EMI_SCHEDULE_LOADED : {
name: 'FA_VIEW_EMI_SCHEDULE_LOADED',
description: 'EMI schedule page is loaded',
},
FA_VIEW_EMI_SCHEDULE_CLICKED: {
name: 'FA_VIEW_EMI_SCHEDULE_CLICKED',
description: 'FA_VIEW_EMI_SCHEDULE_CLICKED',

View File

@@ -8,10 +8,11 @@ interface FloatingBannerCtaProps {
onPressHandler: () => void;
icon?: JSX.Element;
containerStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<ViewStyle>;
}
const FloatingBannerCta = (props: FloatingBannerCtaProps) => {
const { icon, title, onPressHandler, containerStyle } = props;
const { icon, title, onPressHandler, containerStyle, textStyle } = props;
return (
<TouchableOpacity
activeOpacity={0.8}
@@ -19,7 +20,7 @@ const FloatingBannerCta = (props: FloatingBannerCtaProps) => {
onPress={onPressHandler}
>
{icon ? icon : null}
<Text style={GenericStyles.pl6}>{title}</Text>
<Text style={[GenericStyles.pl6, textStyle ]}>{title}</Text>
</TouchableOpacity>
);
};

View File

@@ -75,6 +75,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const dispatch = useAppDispatch();
const appState = useRef(AppState.currentState);
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
const deviceId = useAppSelector((state) => state?.user?.deviceId);
const caseSyncLock = useAppSelector((state) => state?.user?.caseSyncLock);
const lastFirebaseResyncTimestamp = useAppSelector((state) => state?.metadata?.lastFirebaseResyncTimestamp);
@@ -122,6 +123,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
timestamp: Date.now(),
isActiveOnApp: Boolean(isActiveOnApp),
userActivityOnApp: String(userActivityonApp),
deviceId : deviceId,
};
dispatch(setDeviceGeolocationsBuffer(geolocation));
dispatch(sendLocationAndActivenessToServer([geolocation]));

View File

@@ -1,6 +1,6 @@
import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
import React, { useMemo } from 'react';
import { IGeolocation } from '../../../screens/caseDetails/interface';
import { GeolocationSource, IGeolocation } from '../../../screens/caseDetails/interface';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { getDistanceFromLatLonInKm, getGoogleMapUrl } from '../../utlis/commonFunctions';
import Text from '../../../../RN-UI-LIB/src/components/Text';
@@ -41,7 +41,7 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
isFeedbackView,
handlePageRouting,
}) => {
const { latitude, longitude, capturedTimestamp, tag, id } = address;
const { latitude, longitude, capturedTimestamp, tag, id, primarySource } = address;
const { deviceGeolocationCoordinate, prefilledAddressScreenTemplate } = useAppSelector(
(state) => ({
deviceGeolocationCoordinate: state.foregroundService?.deviceGeolocationCoordinate,
@@ -134,7 +134,7 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
{tag}
</Text>
{roundoffRelativeDistanceBwLatLong ? (
<Text>({roundoffRelativeDistanceBwLatLong}km away)</Text>
<Text>({roundoffRelativeDistanceBwLatLong} km away)</Text>
) : null}
</View>
<View style={GenericStyles.row}>
@@ -145,6 +145,12 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
<Text small>{addressTime}</Text>
</View>
) : null}
{(primarySource && primarySource === GeolocationSource.DATA_SUTRAM) ? (
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
<View style={styles.circleSeparator} />
<Text small>Skip Tracing</Text>
</View>
) : null}
</View>
{!isFeedbackView ? (
isFeedbackPresent ? (

View File

@@ -4,37 +4,37 @@ import { NativeModules } from 'react-native';
const { DeviceUtilsModule } = NativeModules; // this is the same name we returned in getName function.
// returns true if enabled, and false if disabled.
export const locationEnabled = (): Promise<boolean> => DeviceUtilsModule?.isLocationEnabled();
export const locationEnabled = (): Promise<boolean> => DeviceUtilsModule.isLocationEnabled();
// returns array of all the installed packages.
export const getAllInstalledApp = (): Promise<string> => DeviceUtilsModule?.getAllInstalledApp();
export const getAllInstalledApp = (): Promise<string> => DeviceUtilsModule.getAllInstalledApp();
// export const alfredHandleSWWEvent = (error: Error) => {
// const { message = '', stack = '', name = '' } = error;
export const alfredHandleSWWEvent = (error: Error) => {
const { message = '', stack = '', name = '' } = error;
// DeviceUtilsModule.handleSWWEvent(message, stack, name);
// };
DeviceUtilsModule.handleSWWEvent(message, stack, name);
};
// export const handleCrash = () => DeviceUtilsModule.handleCrash();
export const handleCrash = () => DeviceUtilsModule.handleCrash();
// export const handleANR = () => DeviceUtilsModule.handleANR();
export const handleANR = () => DeviceUtilsModule.handleANR();
// export const alfredSetPhoneNumber = (phoneNumber: string) =>
// DeviceUtilsModule.setPhoneNumber(phoneNumber);
export const alfredSetPhoneNumber = (phoneNumber: string) =>
DeviceUtilsModule.setPhoneNumber(phoneNumber);
// export const alfredSetCodePushVersion = (codePushVersion: string) =>
// DeviceUtilsModule.setCodePushVersion(codePushVersion);
export const alfredSetCodePushVersion = (codePushVersion: string) =>
DeviceUtilsModule.setCodePushVersion(codePushVersion);
// export const alfredSetUserId = (userId: string) => DeviceUtilsModule.setUserId(userId);
export const alfredSetUserId = (userId: string) => DeviceUtilsModule.setUserId(userId);
// export const sendBottomSheetOpenSignal = (e: boolean) =>
// DeviceUtilsModule.sendBottomSheetOpenSignal(e);
export const sendBottomSheetOpenSignal = (e: boolean) =>
DeviceUtilsModule.sendBottomSheetOpenSignal(e);
// export const setBottomSheetView = (id: number | null) => DeviceUtilsModule.setBottomSheetView(id);
export const setBottomSheetView = (id: number | null) => DeviceUtilsModule.setBottomSheetView(id);
// export const clearBottomSheet = () => DeviceUtilsModule.clearBottomSheet();
export const clearBottomSheet = () => DeviceUtilsModule.clearBottomSheet();
// export const alfredSetEmailId = (emailId: string) => DeviceUtilsModule.setEmailId(emailId);
export const alfredSetEmailId = (emailId: string) => DeviceUtilsModule.setEmailId(emailId);
// sends content to whatsapp.
export const sendContentToWhatsapp = (
@@ -46,32 +46,4 @@ export const sendContentToWhatsapp = (
): Promise<boolean> =>
DeviceUtilsModule?.sendContentToWhatsapp(message, imageUrl, mimeType, format, fileName);
const noop = () => {};
export const alfredHandleSWWEvent = (error: Error) => {
const { message = '', stack = '', name = '' } = error;
noop();
};
export const handleCrash = () => noop();
export const handleANR = () => noop();
export const alfredSetPhoneNumber = (phoneNumber: string) =>
noop();
export const alfredSetCodePushVersion = (codePushVersion: string) =>
noop();
export const alfredSetUserId = (userId: string) => noop();
export const sendBottomSheetOpenSignal = (e: boolean) =>
noop();
export const setBottomSheetView = (id: number | null) => noop();
export const clearBottomSheet = () => noop();
export const alfredSetEmailId = (emailId: string) => noop();
export const getBuildFlavour = (): Promise<buildFlavour> => DeviceUtilsModule.getBuildFlavour();
export const getBuildFlavour = (): Promise<buildFlavour> => DeviceUtilsModule.getBuildFlavour();

View File

@@ -16,6 +16,7 @@ export interface IGeolocationPayload {
isActiveOnApp: boolean;
userActivityOnApp: string;
blacklistedAppsInstalled: Apps[];
deviceId : string;
}
export const sendLocationAndActivenessToServer =

View File

@@ -69,6 +69,7 @@ export interface IUserSlice extends IUser {
showAttendanceBanner: boolean;
attendanceDate: string;
};
isAgentPerformanceDashboardVisible: boolean;
}
const initialState: IUserSlice = {
@@ -90,6 +91,7 @@ const initialState: IUserSlice = {
showAttendanceBanner: true,
attendanceDate: '',
},
isAgentPerformanceDashboardVisible: false,
};
export const userSlice = createSlice({
@@ -141,6 +143,9 @@ export const userSlice = createSlice({
},
setAgentAttendance: (state, action) => {
state.agentAttendance = action.payload;
},
setIsAgentPerformanceDashboardVisible: (state, action) => {
state.isAgentPerformanceDashboardVisible = action.payload;
}
},
});
@@ -154,6 +159,7 @@ export const {
setIsExternalAgent,
setCaseSyncLock,
setAgentAttendance,
setIsAgentPerformanceDashboardVisible
} = userSlice.actions;
export default userSlice.reducer;

View File

@@ -375,12 +375,7 @@ export const formatNumberWithSuffix = (number: number): string => {
export const isAgentDashboardVisible = () => {
const user = useAppSelector((state) => state.user);
const roles = user?.agentRoles;
const isFieldAgent = roles?.includes(IUserRole.ROLE_FIELD_AGENT);
return isFieldAgent && roles?.length === 1;
return user?.isAgentPerformanceDashboardVisible || false;
};
export const sanatizeSuspisiousFeedback = (feedbackPercent: number) => {

View File

@@ -1,74 +1,207 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { Pressable, SafeAreaView, StyleSheet, View } from 'react-native';
import React, { useEffect, useMemo, useState } from 'react';
import NaviLogoWithTextIcon from '../../../RN-UI-LIB/src/Icons/NaviLogoWithTextIcon';
import Avatar from '../../../RN-UI-LIB/src/components/Avatar';
import { GenericStyles, SCREEN_WIDTH } from '../../../RN-UI-LIB/src/styles';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import Text from '../../../RN-UI-LIB/src/components/Text';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { useAppSelector } from '../../hooks';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { BUSINESS_DATE_FORMAT, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates';
import { NAVI_AGENCY_CODE } from '../../common/Constants';
import Layout from '@screens/layout/Layout';
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
import { goBack, popToScreen } from '@components/utlis/navigationUtlis';
import { getSyncTime } from '@hooks/capturingApi';
import { logError } from '@components/utlis/errorUtils';
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
import RetryIcon from '@assets/icons/RetryIcon';
import ScreenshotBlocker from '@components/utlis/ScreenshotBlocker';
import { getSelfieDocument } from '@actions/profileActions';
const PAGE_TITLE = 'Agent ID card';
const backHandler = () => {
goBack();
};
const getTodaysDate = async () => {
const timestamp = await getSyncTime();
const date = new Date(timestamp);
return date;
};
const AgentIdCard = () => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(true);
const [retry, setRetry] = useState(false);
const setLoadingFalse = () => setLoading(false);
const setRetryTrue = () => setRetry(true);
const onRetry = () => {
dispatch(getSelfieDocument());
};
useEffect(() => {
ScreenshotBlocker.blockScreenshots();
return () => {
ScreenshotBlocker.unblockScreenshots();
}
}, []);
const [todaysDate, setTodaysDate] = useState(new Date());
const fetchTodaysDate = async () => {
try {
const date = await getTodaysDate();
setTodaysDate(date);
} catch (error) {
logError(error as Error);
}
};
useEffect(() => {
fetchTodaysDate();
}, [])
const {
originalImageUri,
optimizedImageUri,
agentName,
agentPhone,
validationDate,
agencyName,
agencyCode,
} = useAppSelector((state) => ({
originalImageUri: state.profile.originalImageUri,
agentName: state.user.user?.name!!,
agentPhone: state.user.user?.phoneNumber,
validationDate: state.profile.validationDate,
agencyName: state.profile.agencyName,
agencyCode: state.profile.agencyCode,
optimizedImageUri: state.profile.optimizedImageUri,
}));
const showRetry = useMemo(() => {
return !originalImageUri && !optimizedImageUri;
}, [optimizedImageUri, originalImageUri]);
const loaderConf = useMemo(()=>{
return [
{ width: 75, height: 25, style: { marginTop: 10, marginBottom: 20 } },
{ width: 0, height: 0, style: { marginTop: 280 } },
{ width: 150, height: 18, style: { marginTop: 15 } },
{ width: 200, height: 18, style: { marginTop: 20 } },
{ width: 100, height: 18, style: { marginTop: 20 } },
]
},[])
return (
<View style={[GenericStyles.p24, GenericStyles.alignCenter, { width: SCREEN_WIDTH - 32 }]}>
<NaviLogoWithTextIcon />
{agencyName && agencyCode !== NAVI_AGENCY_CODE ? (
<>
<Text small bold style={styles.greyColor}>
in association with
</Text>
<Text dark bold>
{agencyName}
</Text>
</>
) : null}
<View style={GenericStyles.mv16}>
<Avatar
loading={false}
size={160}
name={agentName}
dataURI={originalImageUri}
fallbackDataUri={optimizedImageUri}
/>
</View>
<Text small bold style={styles.greyColor}>
Valid as of {dateFormat(new Date(validationDate), BUSINESS_DATE_FORMAT)}
</Text>
<Heading type="h4" dark bold style={GenericStyles.mv4}>
{agentName}
</Heading>
<Text style={GenericStyles.mb4}>Collection agent</Text>
<Text light bold>
{agentPhone}
</Text>
</View>
<Layout>
<SafeAreaView>
<NavigationHeader onBack={backHandler} title={PAGE_TITLE} />
<View style={[GenericStyles.p24, GenericStyles.alignCenter, styles.container]}>
<View style={styles.avatarImage}>
<Avatar
loading={false}
size={280}
name={agentName}
dataURI={originalImageUri}
fallbackDataUri={optimizedImageUri}
onSuccess={setLoadingFalse}
onErrorFallback={setRetryTrue}
/>
</View>
{loading ? (
<>
{loaderConf.map((loaderProps, index) => (
<>
<LineLoader
key={index}
width={loaderProps.width}
height={loaderProps.height}
style={[loaderProps.style, styles.lineLoader]}
/>
</>
))}
{(retry || showRetry && (
<View style={styles.retryContainer}>
<Pressable
style={[GenericStyles.row, GenericStyles.alignCenter]}
onPress={onRetry}
>
<RetryIcon />
<Text style={[GenericStyles.ml4, styles.retryText]}>Retry loading ID card</Text>
</Pressable>
</View>
)) || null}
</>
) : (
<>
<NaviLogoWithTextIcon />
{agencyName && agencyCode !== NAVI_AGENCY_CODE ? (
<>
<Text small bold style={styles.greyColor}>
in association with
</Text>
<Text dark bold>
{agencyName}
</Text>
</>
) : null}
<View style={styles.avatarMargin}>
<Text style={styles.greyColor}>
Valid on {dateFormat(new Date(todaysDate), BUSINESS_DATE_FORMAT)}
</Text>
<Heading type="h4" dark bold style={GenericStyles.mv4}>
{agentName}
</Heading>
<Text style={styles.greyColor}>
{agentPhone}
</Text>
</View>
</>
)}
</View>
</SafeAreaView>
</Layout>
);
};
export default AgentIdCard;
const styles = StyleSheet.create({
container: {
...GenericStyles.relative,
},
greyColor: {
color: COLORS.TEXT.GREY,
},
lineLoader: {
borderRadius: 20,
},
avatarImage: {
...GenericStyles.absolute,
marginTop: 70,
},
avatarMargin: {
alignItems: 'center',
marginTop: 310,
},
retryContainer: {
marginTop: 30,
},
retryText: {
color: COLORS.TEXT.BLUE,
},
});

View File

@@ -1,11 +1,10 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import {
Alert,
Linking,
Pressable,
ScrollView,
StyleSheet,
TouchableHighlight,
TouchableOpacity,
View,
} from 'react-native';
@@ -33,18 +32,16 @@ import CaseItem from '../allCases/CaseItem';
import { IUserRole, MY_CASE_ITEM } from '../../reducer/userSlice';
import QuestionMarkIcon from '../../assets/icons/QuestionMarkIcon';
import IDCardImageCapture from './IDCardImageCapture';
import AgentIdCard from './AgentIdCard';
import TranslucentModal from '../../../RN-UI-LIB/src/components/TranslucentModal/TranslucentModal';
import ArrowSolidIcon from '../../../RN-UI-LIB/src/Icons/ArrowSolidIcon';
import { getSelfieDocument } from '../../action/profileActions';
import { ImageApprovalStatus } from '../../reducer/profileSlice';
import FloatingBannerCta from '@common/FloatingBannerCta';
import AttendanceIcon from '@assets/icons/AttendanceIcon';
import GoogleFormModal from '@screens/allCases/GoogleFormModal';
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
const Profile: React.FC = () => {
const [buttonPressedCount, setButtonPressedCount] = useState(0);
const [showIdCard, setShowIdCard] = useState(false);
const dispatch = useAppDispatch();
const {
originalImageUri,
@@ -129,16 +126,12 @@ const Profile: React.FC = () => {
const helpButtonClickHandler = () => Linking.openURL(supportLink);
const toggleIdCard = () => setShowIdCard((prev) => !prev);
const hideUploadImageBtn =
approvalStatus === ImageApprovalStatus.PENDING ||
approvalStatus === ImageApprovalStatus.APPROVED;
const handleViewIdCard = () => {
toggleIdCard();
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED);
};
const showCompletedCases = !isTeamLead || selectedAgent?.referenceId === MY_CASE_ITEM.referenceId;
@@ -157,20 +150,22 @@ const Profile: React.FC = () => {
showAvatarIcon
bottomActionable={
originalImageUri && pendingCases?.length ? (
<TouchableHighlight
onPress={handleViewIdCard}
underlayColor={COLORS.HIGHLIGHTER.LIGHT_BUTTON}
style={styles.bottomActionable}
<Pressable
onPress={() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED);
navigateToScreen(PageRouteEnum.AGENT_ID_CARD);
}}
style={styles.bottomActionable}
>
<View style={[GenericStyles.row, GenericStyles.alignStart]}>
<Text small style={styles.whiteText}>
View ID card
</Text>
<View style={{ transform: [{ rotate: '180deg' }] }}>
<ArrowSolidIcon fillColor={COLORS.TEXT.WHITE} />
<View style={[GenericStyles.row, GenericStyles.alignStart]}>
<Text small style={styles.whiteText}>
View ID card
</Text>
<View style={{ transform: [{ rotate: '180deg' }] }}>
<ArrowSolidIcon fillColor={COLORS.TEXT.WHITE} />
</View>
</View>
</View>
</TouchableHighlight>
</Pressable>
) : null
}
rightActionable={
@@ -280,15 +275,7 @@ const Profile: React.FC = () => {
)}
<GoogleFormModal showForm={showForm} setShowForm={setShowForm} />
{pendingCases?.length ? (
<TranslucentModal
visible={showIdCard}
onRequestClose={() => setShowIdCard(false)}
flipAnimation
>
<AgentIdCard />
</TranslucentModal>
) : null}
</View>
);
};

View File

@@ -210,11 +210,12 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
<TouchableOpacity
activeOpacity={0.7}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
onPress={() =>
onPress={() =>{
handleRouting(PageRouteEnum.ADDITIONAL_ADDRESSES, {
fetchUngroupedAddress, ungroupedAddressFeedbacks
})
}
});
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADDITIONAL_ADDRESSES_BUTTON_CLICKED);
}}
>
<Text style={styles.actionBtn}>View all addresses</Text>
</TouchableOpacity>

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useAppDispatch, useAppSelector } from '@hooks';
import CasesList from './CasesList';
import Text from '../../../RN-UI-LIB/src/components/Text';
import { RootState } from '@store';
import { initCrashlytics } from '@utils/firebaseUtils';
import Layout from '../layout/Layout';
@@ -25,13 +24,14 @@ import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper';
import DashboardIcon from '../../assets/icons/DashboardIcon';
import DashBoardScreens from '../Dashboard/DashBoardScreens';
import { isAgentDashboardVisible } from '@screens/Dashboard/utils';
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { AppState, AppStateStatus, StyleSheet } from 'react-native';
import { COLORS } from '@rn-ui-lib/colors';
import { GenericStyles } from '@rn-ui-lib/styles';
import DailyCommitmentIcon from '@rn-ui-lib/icons/DailyCommitmentIcon';
import AddCommitment from '../dailyCommitment/DailyCommitmentBottomSheet';
import DailyCommitmentBottomSheet from '../dailyCommitment/DailyCommitmentBottomSheet';
import { getVisibilityStatus } from '@screens/dailyCommitment/actions';
import { logError } from '@components/utlis/errorUtils';
import FloatingBannerCta from '@common/FloatingBannerCta';
import { AppStates } from '@interfaces/appStates';
const AllCasesMain = () => {
const { pendingList, pinnedList, completedList, loading } = useAppSelector(
@@ -47,42 +47,57 @@ const AllCasesMain = () => {
const [isCommitmentFormVisible, setIsCommitmentFormVisible] = useState(false);
const [isCommitmentSubmitted, setIsCommitmentSubmitted] = useState(true);
const handleTabClick = (nextTab: BOTTOM_TAB_ROUTES) => {
if (nextTab === BOTTOM_TAB_ROUTES.Dashboard) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_BUTTON_CLICKED, {});
}
else if (nextTab === BOTTOM_TAB_ROUTES.Profile) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PROFILE_PAGE_BUTTON_CLICKED);
}
else if (nextTab === BOTTOM_TAB_ROUTES.Cases) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CASE_LIST_BUTTON_CLICKED);
}
else if (nextTab === BOTTOM_TAB_ROUTES.VisitPlan) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_BUTTON_CLICKED);
}
};
const getVisibility = () => {
getVisibilityStatus()
.then((response) => {
setIsCommitmentSubmitted(response?.isCommitmentFilled);
setIsCommitmentFormVisible(response?.isCommitmentBannerVisible);
})
.catch((err) => {
logError(err);
});
};
useEffect(() => {
getVisibilityStatus()
.then((response) => {
setIsCommitmentSubmitted(response?.isCommitmentFilled);
setIsCommitmentFormVisible(response?.isCommitmentBannerVisible);
})
.catch((err) => {
logError(err);
});
}, [isCommitmentSubmitted,isCommitmentFormVisible]);
getVisibility();
}, []);
const shouldShowBanner = useMemo(() => {
return !isTeamLead && !isCommitmentSubmitted && isCommitmentFormVisible;
}, [isCommitmentSubmitted, isCommitmentFormVisible, isTeamLead]);
return !isCommitmentSubmitted && isCommitmentFormVisible;
}, [isCommitmentSubmitted, isCommitmentFormVisible]);
const openCommitmentScreen = () => {
setOpenBottomSheet(true);
};
const CommitmentComponent = () => {
return (
<>
<TouchableOpacity
activeOpacity={0.8}
style={[GenericStyles.centerAlignedRow, styles.container]}
onPress={openCommitmentScreen}
>
<DailyCommitmentIcon />
<Text bold dark style={[GenericStyles.ml8, GenericStyles.mr8, styles.updateText]}>
Update your daily commitment
</Text>
</TouchableOpacity>
</>
);
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === AppStates.ACTIVE) {
setTimeout(getVisibility, 5000);
}
};
useEffect(() => {
const appStateListener = AppState.addEventListener('change', handleAppStateChange);
return () => {
appStateListener.remove();
}
}, []);
const HOME_SCREENS: ITabScreen[] = useMemo(() => {
const bottomSheetScreens = [
{
@@ -90,7 +105,13 @@ const AllCasesMain = () => {
component: () => (
<>
<CasesList casesList={[...pendingList, ...pinnedList, ...completedList]} allCasesView />
{shouldShowBanner ? <CommitmentComponent /> : null}
{shouldShowBanner ? <FloatingBannerCta
title={"Update your daily commitment"}
onPressHandler={openCommitmentScreen}
containerStyle={styles.container}
icon={<DailyCommitmentIcon />}
textStyle={styles.titleText}
/> : null}
</>
),
icon: CasesIcon,
@@ -102,7 +123,13 @@ const AllCasesMain = () => {
component: () => (
<>
<CasesList casesList={pinnedList} isVisitPlan />
{shouldShowBanner ? <CommitmentComponent /> : null}
{shouldShowBanner ? <FloatingBannerCta
title={"Update your daily commitment"}
onPressHandler={openCommitmentScreen}
containerStyle={styles.container}
icon={<DailyCommitmentIcon />}
textStyle={styles.titleText}
/> : null}
</>
),
icon: VisitPlanIcon,
@@ -115,7 +142,13 @@ const AllCasesMain = () => {
component: () => (
<>
<DashBoardScreens />
{shouldShowBanner ? <CommitmentComponent /> : null}
{shouldShowBanner ? <FloatingBannerCta
title={"Update your daily commitment"}
onPressHandler={openCommitmentScreen}
containerStyle={styles.container}
icon={<DailyCommitmentIcon />}
textStyle={styles.titleText}
/> : null}
</>
),
icon: DashboardIcon,
@@ -127,7 +160,13 @@ const AllCasesMain = () => {
component: () => (
<>
<Profile />
{shouldShowBanner ? <CommitmentComponent /> : null}
{shouldShowBanner ? <FloatingBannerCta
title={"Update your daily commitment"}
onPressHandler={openCommitmentScreen}
containerStyle={styles.container}
icon={<DailyCommitmentIcon />}
textStyle={styles.titleText}
/> : null}
</>
),
icon: ProfileIcon,
@@ -147,8 +186,7 @@ const AllCasesMain = () => {
currentTab: getCurrentScreen()?.name,
nextTab,
});
if (nextTab === BOTTOM_TAB_ROUTES.Dashboard)
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_BUTTON_CLICKED, {});
handleTabClick(nextTab);
};
useEffect(() => {
@@ -168,7 +206,11 @@ const AllCasesMain = () => {
onTabPress={(e) => onTabPressHandler(e)}
/>
<CasesActionButtons />
<AddCommitment openBottomSheet={openBottomSheet} setOpenBottomSheet={setOpenBottomSheet} />
{!isCommitmentSubmitted && isCommitmentFormVisible && <DailyCommitmentBottomSheet
openBottomSheet={openBottomSheet}
setOpenBottomSheet={setOpenBottomSheet}
onSuccessCallback={() => { getVisibility();}}
/>}
</Layout>
);
};
@@ -177,8 +219,11 @@ const styles = StyleSheet.create({
backgroundColor: COLORS.BACKGROUND.TEAL,
height: 40,
},
updateText: {
titleText: {
color: COLORS.TEXT.TEAL,
marginLeft: 2,
marginRight: 8,
fontFamily: 'Inter-Bold',
},
});

View File

@@ -97,7 +97,7 @@ function AuthRouter() {
}
alfredSetCodePushVersion(getAppVersion());
NetworkStatusService.listenForOnline(dispatch);
}, []);
}, [user.user]);
// for setting user token in global.ts for api calling's
setGlobalUserData({

View File

@@ -21,14 +21,21 @@ import { CaptureGeolocation, DeviceLocation } from '@components/form/services/ge
import { setDeviceGeolocation } from '@reducers/foregroundServiceSlice';
import NearbyCases from '@screens/allCases/NearbyCases';
import usePolling from '@hooks/usePolling';
import { getFirestoreResyncIntervalInMinutes } from '@common/AgentActivityConfigurableConstants';
import useResyncFirebase from '@hooks/useResyncFirebase';
import CaseDetailStack from '@screens/caseDetails/CaseDetailStack';
import FullScreenLoader from '@rn-ui-lib/components/FullScreenLoader';
import { getFirestoreResyncIntervalInMinutes } from '@common/AgentActivityConfigurableConstants';
import AgentIdCard from '@screens/Profile/AgentIdCard';
const Stack = createNativeStackNavigator();
export enum PageRouteEnum {
HOME = 'Home',
CASE_DETAIL_STACK = 'caseDetailStack',
TODO_LIST = 'TodoList',
COMPLETED_CASES = 'CompletedCases',
NOTIFICATIONS = 'Notifications',
IMPERSONATED_LOGIN = 'ImpersonatedUserLogin',
PAYMENTS = 'registerPayments',
ADDRESS_GEO = 'addressGeolocation',
NEW_ADDRESS = 'newAddress',
@@ -41,6 +48,7 @@ export enum PageRouteEnum {
FILTERED_CASES = 'filteredCases',
NEARBY_CASES = 'nearbyCases',
GEOLOCATION_OLD_FEEDBACKS = 'geolocationOldFeedbacks',
AGENT_ID_CARD = 'AgentIdCard',
}
export const DEFAULT_SCREEN_OPTIONS: NativeStackNavigationOptions = {
@@ -109,8 +117,8 @@ const ProtectedRouter = () => {
screenOptions={DEFAULT_SCREEN_OPTIONS}
screenListeners={getScreenFocusListenerObj}
>
<Stack.Screen name="Home" component={AllCasesMain} />
<Stack.Screen name="caseDetailStack" component={CaseDetailStack} />
<Stack.Screen name={PageRouteEnum.HOME} component={AllCasesMain} />
<Stack.Screen name={PageRouteEnum.CASE_DETAIL_STACK} component={CaseDetailStack} />
<Stack.Screen
name={PageRouteEnum.NEARBY_CASES}
component={NearbyCases}
@@ -119,10 +127,11 @@ const ProtectedRouter = () => {
animation: 'slide_from_right',
}}
/>
<Stack.Screen name="TodoList" component={TodoList} />
<Stack.Screen name="CompletedCases" component={CompletedCase} />
<Stack.Screen name="Notifications" component={Notifications} />
<Stack.Screen name="ImpersonatedUserLogin" component={ImpersonatedUser} />
<Stack.Screen name={PageRouteEnum.TODO_LIST} component={TodoList} />
<Stack.Screen name={PageRouteEnum.COMPLETED_CASES} component={CompletedCase} />
<Stack.Screen name={PageRouteEnum.NOTIFICATIONS} component={Notifications} />
<Stack.Screen name={PageRouteEnum.IMPERSONATED_LOGIN} component={ImpersonatedUser} />
<Stack.Screen name={PageRouteEnum.AGENT_ID_CARD} component={AgentIdCard} />
</Stack.Navigator>
);
};

View File

@@ -49,7 +49,7 @@ const CaseDetails: React.FC<ICaseDetails> = (props) => {
setShowCallingBottomSheet(true);
};
const opacityAnimation = useRef(new Animated.Value(0.8, {useNativeDriver: true})).current;
const opacityAnimation = useRef(new Animated.Value(0.8)).current;
const slideInUpJourney = useRef(new Animated.Value(5, { useNativeDriver: true })).current;
const duration = 300;
@@ -134,4 +134,4 @@ export const styles = StyleSheet.create({
},
});
export default CaseDetails;
export default CaseDetails;

View File

@@ -151,7 +151,7 @@ export enum DOCUMENT_TYPE {
export enum LegalDocumentFullName {
LDN = 'Loan_Demand_Notice',
LRN = 'Loan_Recovery_Notice',
LRN = 'Loan_Recall_Notice',
}
export enum DocumentTitle {

View File

@@ -7,10 +7,7 @@ import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { Controller, FieldErrors, useForm } from 'react-hook-form';
import { isValidAmountEntered } from '../../components/utlis/commonFunctions';
import { addCommitmentApi, getTodaysPtpAmount} from './actions';
import Heading from '@rn-ui-lib/components/Heading';
import DailyCommitmentIcon from '@rn-ui-lib/icons/DailyCommitmentIcon';
import BottomSheetWrapper from '@common/BottomSheetWrapper';
import CloseIcon from '@rn-ui-lib/icons/CloseIcon';
import { COLORS } from '@rn-ui-lib/colors';
import { EXAMPLE_VALUES } from './constants';
import { useAppDispatch} from '@hooks';
@@ -20,7 +17,8 @@ import { IAddCommitment, IAddCommitmentProps } from '@interfaces/commitmentTrack
import HeaderComponent from './headerComponent';
import { logError } from '@components/utlis/errorUtils';
const AddCommitment: React.FC<IAddCommitmentProps> = ({ openBottomSheet, setOpenBottomSheet }) => {
const DailyCommitmentBottomSheet: React.FC<IAddCommitmentProps> = (
{ openBottomSheet, setOpenBottomSheet, onSuccessCallback }) => {
const [loading, setLoading] = useState(false);
const {
control,
@@ -32,7 +30,7 @@ const AddCommitment: React.FC<IAddCommitmentProps> = ({ openBottomSheet, setOpen
mode: 'onChange',
});
const [submitErrors, setSubmitErrors] = useState<FieldErrors<IAddCommitment>>({});
const [todaysPtpAmount, setTodaysPtpAmount] = useState(1000);
const [todaysPtpAmount, setTodaysPtpAmount] = useState(0);
useEffect(() => {
getTodaysPtpAmount()
.then((response) => {
@@ -79,9 +77,11 @@ const AddCommitment: React.FC<IAddCommitmentProps> = ({ openBottomSheet, setOpen
dispatch(
addCommitmentApi(data, () => {
setLoading(false);
})
);
onBack();
}, () => {
onSuccessCallback && onSuccessCallback();
onBack();
}
));
};
const handleError = (data: FieldErrors<IAddCommitment>) => {
setSubmitErrors(data);
@@ -218,4 +218,4 @@ const styles = StyleSheet.create({
},
});
export default AddCommitment;
export default DailyCommitmentBottomSheet;

View File

@@ -11,18 +11,17 @@ import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { AppDispatch } from '@store';
import { IAddCommitment } from '@interfaces/commitmentTracker.types';
import { getSyncTime } from '@hooks/capturingApi';
import { logError } from '@components/utlis/errorUtils';
export const getTodaysPtpAmount = async () => {
try {
const url = getApiUrl(ApiKeys.GET_PTP_AMOUNT);
const response = await axiosInstance.get(url);
return response?.data;
} catch (err) {
logError(err as Error, 'Error fetching today\'s PTP amount:');
return COMMITMENT_TYPE.EMPTY_PTP_AMOUNT;
}
try {
const url = getApiUrl(ApiKeys.GET_PTP_AMOUNT);
const response = await axiosInstance.get(url);
return response?.data;
} catch (err) {
logError(err as Error, 'Error fetching today\'s PTP amount:');
return COMMITMENT_TYPE.EMPTY_PTP_AMOUNT;
}
};
export const getVisibilityStatus = async () => {
@@ -36,10 +35,10 @@ export const getVisibilityStatus = async () => {
};
export const addCommitmentApi =
(data: IAddCommitment, afterApiCallback?: GenericFunctionArgs) => async (dispatch: AppDispatch) => {
(data: IAddCommitment, afterApiCallback?: GenericFunctionArgs, onSuccessCallback?: GenericFunctionArgs) =>
async (dispatch: AppDispatch) => {
const { cashCommitted, visitsCommitted } = data;
const url = getApiUrl(ApiKeys.DAILY_COMMITMENT);
const timestamp = await getSyncTime();
const payload = data;
axiosInstance
.post(url, payload)
@@ -49,6 +48,9 @@ export const addCommitmentApi =
type: 'info',
text1: COMMITMENT_TYPE.COMMITMENT_SUBMITTED_SUCCESSFULLY,
});
if (typeof afterApiCallback === 'function') {
onSuccessCallback && onSuccessCallback();
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_DAILY_COMMITMENT_SUBMITTED);
}
})

View File

@@ -1,5 +1,5 @@
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import React from 'react';
import React, { useEffect } from 'react';
import BottomSheet from '../../../RN-UI-LIB/src/components/bottom_sheet/BottomSheet';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import CloseIcon from '../../../RN-UI-LIB/src/Icons/CloseIcon';
@@ -12,6 +12,8 @@ import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import { getNumberWithRankSuffix } from '../../../RN-UI-LIB/src/utlis/common';
import { getDynamicBottomSheetHeightPercentageFn } from '../../components/utlis/commonFunctions';
import BottomSheetWrapper from '../../common/BottomSheetWrapper';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
interface IEmiBreakupBottomSheet {
openBottomSheet: boolean;
@@ -30,6 +32,15 @@ const EmiBreakupBottomSheet: React.FC<IEmiBreakupBottomSheet> = (props) => {
totalOverDueAmount,
totalUnpaidEmiAmount,
} = props;
useEffect(() => {
if( openBottomSheet) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TOTAL_OUTSTANDING_BREAKUP_LOADED);
}
}, [openBottomSheet]);
const height = getDynamicBottomSheetHeightPercentageFn();
return (
<BottomSheetWrapper

View File

@@ -100,6 +100,10 @@ const EmiSchedule: React.FC<IEmiSchedule> = (props) => {
onRefresh();
}
}, [isOnline]);
useEffect(() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TOTAL_OUTSTANDING_BREAKUP_LOADED);
},[]);
if (!isOnline && !emiData) {
return (

View File

@@ -1,6 +1,9 @@
import { GenericFunctionArgs } from "@common/GenericTypes";
export interface IAddCommitmentProps {
openBottomSheet: boolean;
setOpenBottomSheet: React.Dispatch<React.SetStateAction<boolean>>;
onSuccessCallback?: GenericFunctionArgs;
}
export interface IAddCommitment {