diff --git a/android/app/build.gradle b/android/app/build.gradle index 99dec776..9e85e81a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -134,8 +134,8 @@ def reactNativeArchitectures() { return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } -def VERSION_CODE = 165 -def VERSION_NAME = "2.10.11" +def VERSION_CODE = 166 +def VERSION_NAME = "2.11.0" android { ndkVersion rootProject.ext.ndkVersion diff --git a/package.json b/package.json index f28a8ee1..f514f213 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "AV_APP", - "version": "2.10.11", - "buildNumber": "165", + "version": "2.11.0", + "buildNumber": "166", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/action/authActions.ts b/src/action/authActions.ts index 15cd137b..7dcd5167 100644 --- a/src/action/authActions.ts +++ b/src/action/authActions.ts @@ -37,6 +37,7 @@ import CosmosForegroundService from '../services/foregroundServices/foreground.s import { resetReportees } from '../reducer/reporteesSlice'; import { resetPerformanceData } from '@reducers/agentPerformanceSlice'; import { clearStorageEngine, getStorageEngine } from '../PersistStorageEngine'; +import { resetNearbyCasesData } from '@reducers/nearbyCasesSlice'; export interface GenerateOTPPayload { phoneNumber: string; @@ -228,6 +229,7 @@ export const handleLogout = () => async (dispatch: AppDispatch) => { dispatch(resetProfileData()); dispatch(resetReportees()); dispatch(resetPerformanceData()); + dispatch(resetNearbyCasesData()); dispatch(setSelectedAgent(MY_CASE_ITEM)); } catch (err) { logError(err as Error, 'Logout clear session details error'); diff --git a/src/assets/icons/LocationDistanceIcon.tsx b/src/assets/icons/LocationDistanceIcon.tsx new file mode 100644 index 00000000..ae5e0b6c --- /dev/null +++ b/src/assets/icons/LocationDistanceIcon.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Svg, { Mask, Rect, G, Path, Text, TSpan } from 'react-native-svg'; +import { COLORS } from '@rn-ui-lib/colors'; + +interface ILocationDistanceIcon { + iconColor?: string; + backgroundColor?: string; +} + +const LocationDistanceIcon = (props: ILocationDistanceIcon) => { + const iconColor = props?.iconColor || COLORS.TEXT.BLUE_DARK_2; + const backgroundColor = props?.backgroundColor || COLORS.BACKGROUND.BLUE; + + return ( + + + + + + + + + + + + ); +}; + +export default LocationDistanceIcon; diff --git a/src/assets/icons/NewLocationIcon.tsx b/src/assets/icons/NewLocationIcon.tsx new file mode 100644 index 00000000..84c3d631 --- /dev/null +++ b/src/assets/icons/NewLocationIcon.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Svg, { Mask, Rect, G, Path } from 'react-native-svg'; + +const NewLocationIcon = () => ( + + + + + + + + +); + +export default NewLocationIcon; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 3f3a7e0d..0800b1d8 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -167,6 +167,10 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_CASE_LIST_BUTTON_CLICKED', description: 'Clicks on case list button on bottom navigation bar', }, + FA_CASE_LIST_SORT_TAB_CHANGED: { + name: 'FA_CASE_LIST_SORT_TAB_CHANGED', + description: 'Case list sort tab changed', + }, // TODO LIST AV_TODO_LIST_LOAD: { @@ -704,6 +708,22 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_NEARBY_CASE_CLICKED', description: 'FA_NEARBY_CASE_CLICKED', }, + FA_NEARBY_CASES_BANNER_DISPLAY: { + name: 'FA_NEARBY_CASES_BANNER_DISPLAY', + description: 'New Location detected banner is set to be displayed', + }, + FA_NEARBY_CASES_BANNER_PULLED_TO_REFRESH: { + name: 'FA_NEARBY_CASES_BANNER_PULLED_TO_REFRESH', + description: 'User pulled the banner to refresh the list', + }, + FA_NEARBY_CASES_UPDATE_SUCCESS: { + name: 'FA_NEARBY_CASES_UPDATE_SUCCESS', + description: 'Successfully updated Nearby Cases list', + }, + FA_NEARBY_CASES_UPDATE_FAILED: { + name: 'FA_NEARBY_CASES_UPDATE_FAILED', + description: 'Failed to update Nearby Cases list', + }, FA_DAILY_COMMITMENT_CLICKED: { name: 'FA_DAILY_COMMITMENT_CLICKED', @@ -1065,8 +1085,10 @@ export const getPrefixBase64Image = (contentType: MimeType) => { export const PrefixJpegBase64Image = getPrefixBase64Image(MimeType['image/jpeg']); -export const HEADER_HEIGHT_MAX = 132; -export const HEADER_HEIGHT_MIN = 80; +export const HEADER_HEIGHT_MAX = 200; +export const HEADER_HEIGHT_MIN = 148; +export const NEARBY_CASES_HEADER_HEIGHT_MAX = 248; +export const NEARBY_CASES_HEADER_HEIGHT_MIN = 196; export const VISIT_PLAN_HEADER_HEIGHT_MAX = 183; export const VISIT_PLAN_HEADER_HEIGHT_MIN = 130; export const HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS = HEADER_HEIGHT_MAX + 50; @@ -1074,6 +1096,7 @@ export const HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS = HEADER_HEIGHT_MIN + 50; export const VISIT_PLAN_HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER_HEIGHT_MAX + 50; export const VISIT_PLAN_HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER_HEIGHT_MIN + 50; export const HEADER_SCROLL_DISTANCE = (HEADER_HEIGHT_MAX - HEADER_HEIGHT_MIN) * 2; +export const NEARBY_CASES_HEADER_SCROLL_DISTANCE = (NEARBY_CASES_HEADER_HEIGHT_MAX - NEARBY_CASES_HEADER_HEIGHT_MIN) * 2; export const HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS = (HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS - HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS) * 2; diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 4d4a659c..16361cf5 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -11,7 +11,10 @@ import CosmosForegroundService, { } from '../services/foregroundServices/foreground.service'; import useIsOnline from '../hooks/useIsOnline'; import { getSyncTime, sendCurrentGeolocationAndBuffer } from '../hooks/capturingApi'; -import { isTimeDifferenceWithinRange, setAsyncStorageItem } from '../components/utlis/commonFunctions'; +import { + isTimeDifferenceWithinRange, + setAsyncStorageItem, +} from '../components/utlis/commonFunctions'; import { setIsTimeSynced } from '../reducer/foregroundServiceSlice'; import { logError } from '../components/utlis/errorUtils'; import { useAppDispatch, useAppSelector } from '../hooks'; @@ -49,13 +52,21 @@ import { GlobalImageMap } from './CachedImage'; import { addClickstreamEvent } from '../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './Constants'; import useResyncFirebase from '@hooks/useResyncFirebase'; -import { imageSyncService, prepareImagesForUpload, sendImagesToServer } from '@services/imageSyncService'; +import { + imageSyncService, + prepareImagesForUpload, + sendImagesToServer, +} from '@services/imageSyncService'; import { getImages } from '@components/utlis/ImageUtlis'; -import getLitmusExperimentResult, { LitmusExperimentName, LitmusExperimentNameMap } from '@services/litmusExperiments.service'; +import getLitmusExperimentResult, { + LitmusExperimentName, + LitmusExperimentNameMap, +} from '@services/litmusExperiments.service'; import { GLOBAL } from '@constants/Global'; import { sendAudiosToServer } from '@services/audioSyncService'; import { sendVideosToServer } from '@services/videoSyncService'; import { getSyncUrl } from '@services/syncJsonDataToBe'; +import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils'; export enum FOREGROUND_TASKS { GEOLOCATION = 'GEOLOCATION', @@ -72,6 +83,7 @@ export enum FOREGROUND_TASKS { VIDEO_UPLOAD_JOB = 'VIDEO_UPLOAD_JOB', AUDIO_UPLOAD_JOB = 'AUDIO_UPLOAD_JOB', DATA_SYNC_JOB = 'DATA_SYNC_JOB', + NEARBY_CASES_GEOLOCATION_CHECK = 'NEARBY_CASES_GEOLOCATION_CHECK', } interface ITrackingComponent { @@ -305,7 +317,13 @@ const TrackingComponent: React.FC = ({ children }) => { task: getSyncUrl, delay: getCalendarAndAccountsUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 12 hours onLoop: true, - } + }, + { + taskId: FOREGROUND_TASKS.NEARBY_CASES_GEOLOCATION_CHECK, + task: handleCheckAndUpdatePullToRefreshStateForNearbyCases, + delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes + onLoop: true, + }, ]; if (!isTeamLead) { diff --git a/src/components/form/components/RadioButton.tsx b/src/components/form/components/RadioButton.tsx index 86069ecb..d68533aa 100644 --- a/src/components/form/components/RadioButton.tsx +++ b/src/components/form/components/RadioButton.tsx @@ -151,7 +151,7 @@ const RadioButton: React.FC = (props) => { key={option} id={option as string} value={options[option]?.text} - containerStyle={GenericStyles.whiteBackground} + containerStyle={[GenericStyles.whiteBackground]} /> ))} diff --git a/src/components/form/index.tsx b/src/components/form/index.tsx index eb9a87d5..25435d0c 100644 --- a/src/components/form/index.tsx +++ b/src/components/form/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import { useForm } from 'react-hook-form'; import { ScrollView, StyleSheet, View } from 'react-native'; import Geolocation from 'react-native-geolocation-service'; @@ -12,7 +12,7 @@ import { syncCaseDetail } from '../../action/dataActions'; import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { useAppDispatch, useAppSelector } from '../../hooks'; import useIsOnline from '../../hooks/useIsOnline'; -import {getUpdatedCollectionCaseDetail, setIsFeedbackSubmitting, updateCaseDetail} from '../../reducer/allCasesSlice'; +import { getUpdatedCollectionCaseDetail, updateCaseDetail } from '../../reducer/allCasesSlice'; import { deleteInteraction, deleteJourney, updateInteraction } from '../../reducer/caseReducer'; import { CaseAllocationType } from '../../screens/allCases/interface'; import { getUnSyncedCase } from '../../screens/caseDetails/interactionsHandler'; @@ -41,6 +41,7 @@ import NavigationHeader, { Icon } from '../../../RN-UI-LIB/src/components/Naviga import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack'; import { useNavigation, useRoute } from '@react-navigation/native'; import { NUDGE_BOTTOM_SHEET_DEFAULT_STATE } from './constants'; +import {useBackHandler} from "@hooks/useBackHandler"; interface IWidget { route: { @@ -63,6 +64,7 @@ const Widget: React.FC = (props) => { NUDGE_BOTTOM_SHEET_DEFAULT_STATE ); const isOnline = useIsOnline(); + const [isSubmitting, setIsSubmitting] = useState(false); const { params } = props.route; const { caseId, journey, handleCloseRouting } = params; const caseKey = useRef(''); @@ -73,7 +75,6 @@ const Widget: React.FC = (props) => { const caseData = useAppSelector((state) => state.allCases?.caseDetails?.[caseId]); const dataToBeValidated = useAppSelector((state) => state.case?.caseForm?.[caseId]?.[journey]); const intermediateDocsToBeUploaded = useAppSelector((state) => state.feedbackImages?.intermediateDocsToBeUploaded); - const isFeedbackSubmitting = useAppSelector((state) => state?.allCases?.isFeedbackSubmitting); const name = getWidgetNameFromRoute(props.route.name, caseType); const { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name]; @@ -93,6 +94,20 @@ const Widget: React.FC = (props) => { setIsJourneyFirstScreen(isFirst); }, [templateData, name]); + const handleBackPress = useCallback(() => { + if (isSubmitting) { + toast({ + type: 'info', + text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS, + }); + return true; + } + + return false; + }, [isSubmitting]); + + useBackHandler(handleBackPress); + const { control, setValue, @@ -194,7 +209,7 @@ const Widget: React.FC = (props) => { }; const onSuccessfulSubmit = (data: any, interactionId: string, nextActions?: any) => { - dispatch(setIsFeedbackSubmitting(false)); + setIsSubmitting(false); setNudgeBottomSheetDetails(NUDGE_BOTTOM_SHEET_DEFAULT_STATE); navigateToScreen(CaseDetailStackEnum.COLLECTION_CASE_DETAIL, { journey: journey, @@ -220,7 +235,7 @@ const Widget: React.FC = (props) => { } const submitJourneyWithGeoLocation = (data: any, _: any, submitViaNudge?: boolean) => { - dispatch(setIsFeedbackSubmitting(true)); + setIsSubmitting(true); addClickstreamEvent( submitViaNudge ? CLICKSTREAM_EVENT_NAMES.FA_SUBMIT_ANYWAYS_CLICKED @@ -235,11 +250,13 @@ const Widget: React.FC = (props) => { if (location) { return handleSubmitJourney(data, location); } + }).catch((err) => { + setIsSubmitting(false); }); }; const onErrorSubmit = (errObj: GenericType, data?: GenericType, interactionId?: string) => { - dispatch(setIsFeedbackSubmitting(false)); + setIsSubmitting(false); if (nudgeBottomSheetDetails?.showNudgeBottomSheet) { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SUBMIT_ANYWAYS_FAILED, { caseId, @@ -310,6 +327,7 @@ const Widget: React.FC = (props) => { type: 'info', text1: ToastMessages.FEEDBACK_SUBMITTED_OFFLINE, }); + setIsSubmitting(false); } }; @@ -329,6 +347,14 @@ const Widget: React.FC = (props) => { }, []); const handleCloseIconPress = () => { + if (isSubmitting) { + toast({ + type: 'info', + text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS, + }); + return; + } + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_CLOSE_CLICKED, { caseId, }); @@ -414,15 +440,15 @@ const Widget: React.FC = (props) => { style={[styles.autoFlex, styles.mH16]} title={'Back'} testID={'test_back'} - disabled={isLeaf && isFeedbackSubmitting} + disabled={isLeaf && isSubmitting} onPress={handleBackButton} leftIcon={} />