diff --git a/RN-UI-LIB b/RN-UI-LIB index 9d8a8dcd..fe382947 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit 9d8a8dcdc335140a0e37b257bbc6f9087afca546 +Subproject commit fe38294725897a50a1e2954c40643efcf43cf097 diff --git a/android/app/build.gradle b/android/app/build.gradle index 06c8039e..f808aefa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 diff --git a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java index 12f12fdc..4a129db4 100644 --- a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java +++ b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java @@ -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) { // - HashMap properties = new HashMap<>(); - properties.put("message", message); - properties.put("stack", stack); - properties.put("name", name); - //AlfredManager.INSTANCE.handleSWWEvent("Cosmos", properties); + if (isAlfredEnabledFromFirebase) { + HashMap 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) { diff --git a/android/app/src/main/java/com/avapp/MainActivity.java b/android/app/src/main/java/com/avapp/MainActivity.java index 36966102..1ca94f05 100644 --- a/android/app/src/main/java/com/avapp/MainActivity.java +++ b/android/app/src/main/java/com/avapp/MainActivity.java @@ -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); } - } -} \ No newline at end of file + + /** + * 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); + } + } + } +} diff --git a/android/app/src/main/java/com/avapp/MainApplication.java b/android/app/src/main/java/com/avapp/MainApplication.java index c64cc713..498c3c53 100644 --- a/android/app/src/main/java/com/avapp/MainApplication.java +++ b/android/app/src/main/java/com/avapp/MainApplication.java @@ -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 getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List 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 getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List 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(); + } + } } - } -} \ No newline at end of file + + private void setupAlfredANRWatchDog(AlfredConfig alfredConfig) { + new ANRWatchDog().setIgnoreDebugger(true).setReportMainThreadOnly().setANRListener(error -> { + if (error.getCause().getStackTrace().length == 0) { + return; + } + Map 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 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); + } + } + }); + } +} diff --git a/android/app/src/main/java/com/avapp/utils/AlfredFirebaseHelper.java b/android/app/src/main/java/com/avapp/utils/AlfredFirebaseHelper.java new file mode 100644 index 00000000..a19d33a7 --- /dev/null +++ b/android/app/src/main/java/com/avapp/utils/AlfredFirebaseHelper.java @@ -0,0 +1,5 @@ +package com.avapp.utils; + +public interface AlfredFirebaseHelper { + void callCruiseAndStartAlfredRecording(); +} diff --git a/android/app/src/main/java/com/avapp/utils/FirebaseRemoteConfigHelper.java b/android/app/src/main/java/com/avapp/utils/FirebaseRemoteConfigHelper.java new file mode 100644 index 00000000..d9dab9ca --- /dev/null +++ b/android/app/src/main/java/com/avapp/utils/FirebaseRemoteConfigHelper.java @@ -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; + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/xml/default_xml_config.xml b/android/app/src/main/res/xml/default_xml_config.xml new file mode 100644 index 00000000..9f68f020 --- /dev/null +++ b/android/app/src/main/res/xml/default_xml_config.xml @@ -0,0 +1,15 @@ + + + + + + + ALFRED_ENABLED + false + + \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index f15ed84b..f2ce2c37 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -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 diff --git a/package.json b/package.json index 7bece810..565e817c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/action/authActions.ts b/src/action/authActions.ts index 4d8b2b14..579db69c 100644 --- a/src/action/authActions.ts +++ b/src/action/authActions.ts @@ -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(() => { diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 76d203d1..d52b33f6 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -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', diff --git a/src/common/FloatingBannerCta.tsx b/src/common/FloatingBannerCta.tsx index d257693d..8eb339b7 100644 --- a/src/common/FloatingBannerCta.tsx +++ b/src/common/FloatingBannerCta.tsx @@ -8,10 +8,11 @@ interface FloatingBannerCtaProps { onPressHandler: () => void; icon?: JSX.Element; containerStyle?: StyleProp; + textStyle?: StyleProp; } const FloatingBannerCta = (props: FloatingBannerCtaProps) => { - const { icon, title, onPressHandler, containerStyle } = props; + const { icon, title, onPressHandler, containerStyle, textStyle } = props; return ( { onPress={onPressHandler} > {icon ? icon : null} - {title} + {title} ); }; diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 1ab2608a..334da4ad 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -75,6 +75,7 @@ const TrackingComponent: React.FC = ({ 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 = ({ children }) => { timestamp: Date.now(), isActiveOnApp: Boolean(isActiveOnApp), userActivityOnApp: String(userActivityonApp), + deviceId : deviceId, }; dispatch(setDeviceGeolocationsBuffer(geolocation)); dispatch(sendLocationAndActivenessToServer([geolocation])); diff --git a/src/components/form/components/GeolocationAddress.tsx b/src/components/form/components/GeolocationAddress.tsx index dff1429c..639fbc4c 100644 --- a/src/components/form/components/GeolocationAddress.tsx +++ b/src/components/form/components/GeolocationAddress.tsx @@ -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 = ({ 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 = ({ {tag} {roundoffRelativeDistanceBwLatLong ? ( - ({roundoffRelativeDistanceBwLatLong}km away) + ({roundoffRelativeDistanceBwLatLong} km away) ) : null} @@ -145,6 +145,12 @@ const GeolocationAddress: React.FC = ({ {addressTime} ) : null} + {(primarySource && primarySource === GeolocationSource.DATA_SUTRAM) ? ( + + + Skip Tracing + + ) : null} {!isFeedbackView ? ( isFeedbackPresent ? ( diff --git a/src/components/utlis/DeviceUtils.ts b/src/components/utlis/DeviceUtils.ts index dd06b97f..df16842c 100644 --- a/src/components/utlis/DeviceUtils.ts +++ b/src/components/utlis/DeviceUtils.ts @@ -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 => DeviceUtilsModule?.isLocationEnabled(); +export const locationEnabled = (): Promise => DeviceUtilsModule.isLocationEnabled(); // returns array of all the installed packages. -export const getAllInstalledApp = (): Promise => DeviceUtilsModule?.getAllInstalledApp(); +export const getAllInstalledApp = (): Promise => 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 => 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 => DeviceUtilsModule.getBuildFlavour(); \ No newline at end of file +export const getBuildFlavour = (): Promise => DeviceUtilsModule.getBuildFlavour(); diff --git a/src/hooks/capturingApi.ts b/src/hooks/capturingApi.ts index e411b9e6..7699684b 100644 --- a/src/hooks/capturingApi.ts +++ b/src/hooks/capturingApi.ts @@ -16,6 +16,7 @@ export interface IGeolocationPayload { isActiveOnApp: boolean; userActivityOnApp: string; blacklistedAppsInstalled: Apps[]; + deviceId : string; } export const sendLocationAndActivenessToServer = diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index ac16270c..f84be2af 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -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; diff --git a/src/screens/Dashboard/utils.ts b/src/screens/Dashboard/utils.ts index 5675b347..531d2b6d 100644 --- a/src/screens/Dashboard/utils.ts +++ b/src/screens/Dashboard/utils.ts @@ -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) => { diff --git a/src/screens/Profile/AgentIdCard.tsx b/src/screens/Profile/AgentIdCard.tsx index 9e340059..cabff550 100644 --- a/src/screens/Profile/AgentIdCard.tsx +++ b/src/screens/Profile/AgentIdCard.tsx @@ -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 ( - - - {agencyName && agencyCode !== NAVI_AGENCY_CODE ? ( - <> - - in association with - - - {agencyName} - - - ) : null} - - - - - Valid as of {dateFormat(new Date(validationDate), BUSINESS_DATE_FORMAT)} - - - {agentName} - - Collection agent - - {agentPhone} - - + + + + + + + + {loading ? ( + <> + {loaderConf.map((loaderProps, index) => ( + <> + + + ))} + {(retry || showRetry && ( + + + + Retry loading ID card + + + )) || null} + + ) : ( + <> + + {agencyName && agencyCode !== NAVI_AGENCY_CODE ? ( + <> + + in association with + + + {agencyName} + + + ) : null} + + + Valid on {dateFormat(new Date(todaysDate), BUSINESS_DATE_FORMAT)} + + + {agentName} + + + {agentPhone} + + + + )} + + + ); }; 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, + }, }); + + diff --git a/src/screens/Profile/index.tsx b/src/screens/Profile/index.tsx index 29b64d1f..f4546b2b 100644 --- a/src/screens/Profile/index.tsx +++ b/src/screens/Profile/index.tsx @@ -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 ? ( - { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED); + navigateToScreen(PageRouteEnum.AGENT_ID_CARD); + }} + style={styles.bottomActionable} > - - - View ID card - - - + + + View ID card + + + + - - + ) : null } rightActionable={ @@ -280,15 +275,7 @@ const Profile: React.FC = () => { )} - {pendingCases?.length ? ( - setShowIdCard(false)} - flipAnimation - > - - - ) : null} + ); }; diff --git a/src/screens/addressGeolocation/index.tsx b/src/screens/addressGeolocation/index.tsx index 8163204f..254207bf 100644 --- a/src/screens/addressGeolocation/index.tsx +++ b/src/screens/addressGeolocation/index.tsx @@ -210,11 +210,12 @@ const AddressGeolocation: React.FC = ({ route: routeParams + onPress={() =>{ handleRouting(PageRouteEnum.ADDITIONAL_ADDRESSES, { fetchUngroupedAddress, ungroupedAddressFeedbacks - }) - } + }); + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADDITIONAL_ADDRESSES_BUTTON_CLICKED); + }} > View all addresses diff --git a/src/screens/allCases/index.tsx b/src/screens/allCases/index.tsx index e42f7632..ef6ef6c9 100644 --- a/src/screens/allCases/index.tsx +++ b/src/screens/allCases/index.tsx @@ -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 ( - <> - - - - Update your daily commitment - - - - ); + + 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: () => ( <> - {shouldShowBanner ? : null} + {shouldShowBanner ? } + textStyle={styles.titleText} + /> : null} ), icon: CasesIcon, @@ -102,7 +123,13 @@ const AllCasesMain = () => { component: () => ( <> - {shouldShowBanner ? : null} + {shouldShowBanner ? } + textStyle={styles.titleText} + /> : null} ), icon: VisitPlanIcon, @@ -115,7 +142,13 @@ const AllCasesMain = () => { component: () => ( <> - {shouldShowBanner ? : null} + {shouldShowBanner ? } + textStyle={styles.titleText} + /> : null} ), icon: DashboardIcon, @@ -127,7 +160,13 @@ const AllCasesMain = () => { component: () => ( <> - {shouldShowBanner ? : null} + {shouldShowBanner ? } + 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)} /> - + {!isCommitmentSubmitted && isCommitmentFormVisible && { getVisibility();}} + />} ); }; @@ -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', }, }); diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index b41606b4..471edd59 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -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({ diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index 70cf6f26..4e632c38 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -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} > - - + + { animation: 'slide_from_right', }} /> - - - - + + + + + ); }; diff --git a/src/screens/caseDetails/CaseDetails.tsx b/src/screens/caseDetails/CaseDetails.tsx index d516fbe5..5f2374e6 100644 --- a/src/screens/caseDetails/CaseDetails.tsx +++ b/src/screens/caseDetails/CaseDetails.tsx @@ -49,7 +49,7 @@ const CaseDetails: React.FC = (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; \ No newline at end of file diff --git a/src/screens/caseDetails/interface.ts b/src/screens/caseDetails/interface.ts index 28d99cec..437096b5 100644 --- a/src/screens/caseDetails/interface.ts +++ b/src/screens/caseDetails/interface.ts @@ -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 { diff --git a/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx b/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx index 6db7762d..c52a75f7 100644 --- a/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx +++ b/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx @@ -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 = ({ openBottomSheet, setOpenBottomSheet }) => { +const DailyCommitmentBottomSheet: React.FC = ( + { openBottomSheet, setOpenBottomSheet, onSuccessCallback }) => { const [loading, setLoading] = useState(false); const { control, @@ -32,7 +30,7 @@ const AddCommitment: React.FC = ({ openBottomSheet, setOpen mode: 'onChange', }); const [submitErrors, setSubmitErrors] = useState>({}); - const [todaysPtpAmount, setTodaysPtpAmount] = useState(1000); + const [todaysPtpAmount, setTodaysPtpAmount] = useState(0); useEffect(() => { getTodaysPtpAmount() .then((response) => { @@ -79,9 +77,11 @@ const AddCommitment: React.FC = ({ openBottomSheet, setOpen dispatch( addCommitmentApi(data, () => { setLoading(false); - }) - ); - onBack(); + }, () => { + onSuccessCallback && onSuccessCallback(); + onBack(); + } + )); }; const handleError = (data: FieldErrors) => { setSubmitErrors(data); @@ -218,4 +218,4 @@ const styles = StyleSheet.create({ }, }); -export default AddCommitment; +export default DailyCommitmentBottomSheet; diff --git a/src/screens/dailyCommitment/actions.ts b/src/screens/dailyCommitment/actions.ts index e44a97fd..ba17af57 100644 --- a/src/screens/dailyCommitment/actions.ts +++ b/src/screens/dailyCommitment/actions.ts @@ -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); } }) diff --git a/src/screens/emiSchedule/EmiBreakupBottomSheet.tsx b/src/screens/emiSchedule/EmiBreakupBottomSheet.tsx index 0befdb6b..1def908e 100644 --- a/src/screens/emiSchedule/EmiBreakupBottomSheet.tsx +++ b/src/screens/emiSchedule/EmiBreakupBottomSheet.tsx @@ -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 = (props) => { totalOverDueAmount, totalUnpaidEmiAmount, } = props; + + + useEffect(() => { + if( openBottomSheet) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TOTAL_OUTSTANDING_BREAKUP_LOADED); + } + }, [openBottomSheet]); + + const height = getDynamicBottomSheetHeightPercentageFn(); return ( = (props) => { onRefresh(); } }, [isOnline]); + + useEffect(() => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TOTAL_OUTSTANDING_BREAKUP_LOADED); + },[]); if (!isOnline && !emiData) { return ( diff --git a/src/types/commitmentTracker.types.ts b/src/types/commitmentTracker.types.ts index 1f4c30ab..86ef8c19 100644 --- a/src/types/commitmentTracker.types.ts +++ b/src/types/commitmentTracker.types.ts @@ -1,6 +1,9 @@ +import { GenericFunctionArgs } from "@common/GenericTypes"; + export interface IAddCommitmentProps { openBottomSheet: boolean; setOpenBottomSheet: React.Dispatch>; + onSuccessCallback?: GenericFunctionArgs; } export interface IAddCommitment {