Merge pull request #554 from navi-medici/TP-35796/live-agents-tracking

TP-35796 | Live agents tracking cosmos changes
This commit is contained in:
Aman Singh
2023-08-07 19:42:49 +05:30
committed by GitHub
14 changed files with 124 additions and 37 deletions

View File

@@ -64,7 +64,7 @@ jobs:
java-version: 11
distribution: adopt
- name: Setup Android SDK
uses: android-actions/setup-android@v2
uses: navi-synced-actions/setup-android@v2
- name: Grant execute permission for gradlew
run: chmod +x android/gradlew
- name: Create local.properties

View File

@@ -77,7 +77,7 @@ jobs:
java-version: 11
distribution: adopt
- name: Setup Android SDK
uses: android-actions/setup-android@v2
uses: navi-synced-actions/setup-android@v2
- name: Grant execute permission for gradlew
run: chmod +x android/gradlew
- name: Assemble with Stacktrace - QA Debug

View File

@@ -131,8 +131,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 74
def VERSION_NAME = "2.3.1"
def VERSION_CODE = 75
def VERSION_NAME = "2.3.2"
android {
ndkVersion rootProject.ext.ndkVersion

View File

@@ -59,21 +59,21 @@ public class MainApplication extends Application implements ReactApplication {
@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());
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());
// 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
// 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();
}
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "AV_APP",
"version": "2.3.1",
"version": "2.3.2",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",
@@ -48,6 +48,7 @@
"appcenter-analytics": "^4.4.5",
"appcenter-crashes": "^4.4.5",
"axios": "1.2.1",
"dayjs": "^1.11.9",
"fuzzysort": "2.0.4",
"lottie-react-native": "5.1.4",
"react": "18.1.0",

View File

@@ -1,22 +1,23 @@
import { ReactNode, useEffect, useRef } from 'react';
import { NativeEventSubscription } from 'react-native';
import { type ReactNode, useEffect, useRef } from 'react';
import { type NativeEventSubscription, AppState, type AppStateStatus } from 'react-native';
import dayJs from 'dayjs';
import { setItem, getItem } from '../components/utlis/storageHelper';
import UnstoppableService, {
IForegroundTask,
type IForegroundTask,
} from '../services/foregroundServices/foreground.service';
import useIsOnline from '../hooks/useIsOnline';
import { getSyncTime, sendLocationToServer } from '../hooks/capturingApi';
import { getSyncTime, sendLocationAndActivenessToServer } from '../hooks/capturingApi';
import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions';
import { setDeviceGeolocation, setIsTimeSynced } from '../reducer/foregroundServiceSlice';
import { CaptureGeolocation } from '../components/form/services/geoLocation.service';
import { AppState, AppStateStatus } from 'react-native';
import { logError } from '../components/utlis/errorUtils';
import { useAppDispatch, useAppSelector } from '../hooks';
import { dataSyncService } from '../services/dataSync.service';
import { DATA_SYNC_TIME_INTERVAL, IS_DATA_SYNC_REQUIRED } from '../constants/config';
import useIsLocationEnabled from '../hooks/useIsLocationEnabled';
import {
ISyncCaseIdPayload,
ISyncedCases,
type ISyncCaseIdPayload,
type ISyncedCases,
SyncStatus,
fetchCasesToSync,
getCasesSyncStatus,
@@ -27,12 +28,15 @@ import { syncCasesByFallback } from '../reducer/allCasesSlice';
import { MILLISECONDS_IN_A_MINUTE } from '../../RN-UI-LIB/src/utlis/common';
import { setLockData } from '../reducer/userSlice';
import { getConfigData } from '../action/configActions';
import { AppStates } from '../types/appStates';
import { StorageKeys } from '../types/storageKeys';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
TIME_SYNC = 'TIME_SYNC',
DATA_SYNC = 'DATA_SYNC',
FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK',
UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS',
}
interface ITrackingComponent {
@@ -40,6 +44,8 @@ interface ITrackingComponent {
}
let LAST_SYNC_STATUS = 'SKIP';
const ACTIVITY_TIME_ON_APP = 5; // 5 seconds
const ACTIVITY_TIME_WINDOW = 10; // 10 minutes
const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const isOnline = useIsOnline();
@@ -52,7 +58,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
pendingList = [],
pinnedList = [],
} = useAppSelector((state) => ({
referenceId: state.user.user?.referenceId!!,
referenceId: state.user.user?.referenceId!,
pendingList: state.allCases.pendingList,
pinnedList: state.allCases.pinnedList,
}));
@@ -71,8 +77,8 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const handleSendGeolocation = async () => {
try {
const location = await CaptureGeolocation.fetchLocation(Date.now() + '', 0, appState.current);
if (location) {
const location = await CaptureGeolocation.fetchLocation(`${Date.now()}`, 0, appState.current);
if (location != null) {
dispatch(
setDeviceGeolocation({
latitude: location.latitude,
@@ -81,7 +87,9 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
})
);
if (user.isLoggedIn) {
await sendLocationToServer(location);
const isUserActive: string | boolean =
(await getItem(StorageKeys.IS_USER_ACTIVE)) || false;
await sendLocationAndActivenessToServer(location, Boolean(isUserActive));
}
}
} catch (e: any) {
@@ -121,6 +129,39 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
}
};
const handleUpdateActiveness = async () => {
if (AppState.currentState === AppStates.ACTIVE) {
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
return;
}
const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
const foregroundTime = dayJs(foregroundTimestamp);
const backgroundTime = dayJs(backgroundTimestamp);
const diffBetweenBackgroundAndForegroundTime = dayJs(backgroundTime).diff(
foregroundTime,
'seconds'
);
const diffBetweenCurrentTimeAndForegroundTime =
dayJs().diff(foregroundTime, 'minutes') < 0 ? 0 : dayJs().diff(foregroundTime, 'minutes');
const isForegroundTimeWithInRange =
diffBetweenCurrentTimeAndForegroundTime <= ACTIVITY_TIME_WINDOW;
const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp);
if (isForegroundTimeWithInRange) {
if (
isForegroundTimeAfterBackground ||
diffBetweenBackgroundAndForegroundTime >= ACTIVITY_TIME_ON_APP
) {
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
return;
}
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
}
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
return;
};
const tasks: IForegroundTask[] = [
{
taskId: FOREGROUND_TASKS.TIME_SYNC,
@@ -140,6 +181,18 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
onLoop: true,
},
{
taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK,
task: handleGetCaseSyncStatus,
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
onLoop: true,
},
{
taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVENESS,
task: handleUpdateActiveness,
delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 minutes
onLoop: true,
},
];
if (IS_DATA_SYNC_REQUIRED) {
@@ -153,11 +206,16 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// App comes to foreground from background
if (nextAppState === 'active') {
const now = dayJs().toString();
if (nextAppState === AppStates.ACTIVE) {
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
handleGetCaseSyncStatus();
dispatch(getConfigData());
UnstoppableService.start(tasks);
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
}
appState.current = nextAppState;
};
@@ -183,10 +241,8 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
if (isOnline) {
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
UnstoppableService.start(tasks);
} else {
if (UnstoppableService.isRunning()) {
UnstoppableService.stopAll();
}
} else if (UnstoppableService.isRunning()) {
UnstoppableService.stopAll();
}
return () => {
appStateSubscription?.remove();

View File

@@ -0,0 +1,6 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
export const setItem = async (key: string, value: string): Promise<void> => {
await AsyncStorage.setItem(key, value);
};
export const getItem = async (key: string): Promise<any> => await AsyncStorage.getItem(key);

View File

@@ -1,7 +1,10 @@
import { GeoCoordinates } from 'react-native-geolocation-service';
import { type GeoCoordinates } from 'react-native-geolocation-service';
import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
export const sendLocationToServer = async (location: GeoCoordinates) => {
export const sendLocationAndActivenessToServer = async (
location: GeoCoordinates,
isActive: boolean
) => {
try {
const response = await axiosInstance.post(
getApiUrl(ApiKeys.SEND_LOCATION),
@@ -11,6 +14,7 @@ export const sendLocationToServer = async (location: GeoCoordinates) => {
longitude: location?.longitude,
accuracy: location?.accuracy,
timestamp: new Date().getTime(),
isActiveOnApp: isActive,
},
],
{

View File

@@ -12,6 +12,11 @@ export interface User {
sessionToken: string;
}
export interface AppState {
appForegroundTimestamp: string;
appBackgroundTimestamp: string;
}
export interface CommonState {
userData: User;
clickstreamEvents: IClickstreamEvents[];

View File

@@ -1,4 +1,5 @@
import { createSlice } from '@reduxjs/toolkit';
export interface UninstallInformation {
last_operational_time: any;
reinstall_endpoint: string;

View File

@@ -16,7 +16,7 @@ interface IUserDetails {
referenceId: string;
phoneNumber: string;
realms: string[];
roles: Array<IUserRole>;
roles: IUserRole[];
groups: string[];
name: string;
createdAt: string;

4
src/types/appStates.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum AppStates {
ACTIVE = 'active',
BACKGROUND = 'background',
}

5
src/types/storageKeys.ts Normal file
View File

@@ -0,0 +1,5 @@
export enum StorageKeys {
APP_FOREGROUND_TIMESTAMP = 'appForegroundTimestamp',
APP_BACKGROUND_TIMESTAMP = 'appBackgroundTimestamp',
IS_USER_ACTIVE = 'isUserActive',
}

View File

@@ -3399,6 +3399,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dayjs@1.11.9:
version "1.11.9"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
dayjs@^1.8.15:
version "1.11.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"