TP-65692 | master merge

This commit is contained in:
Aman Chaturvedi
2024-09-17 19:55:48 +05:30
20 changed files with 108 additions and 115 deletions

View File

@@ -61,9 +61,44 @@ jobs:
if: ((github.event.inputs.environment == 'Prod' || inputs.environment == 'Prod'))
run: yarn move:prod && appcenter codepush release-react -a nfa-navi.com/nfa-app -d Production -t "${{github.event.inputs.target_versions}}" --description "${{github.event.inputs.description}}"
create_release_tag:
generate_source_map:
needs: generate
runs-on: [default]
if: success() && (github.event.inputs.environment == 'Prod') # Only create source map for Prod releases
steps:
- name: Checkout
uses: actions/checkout@v2
with:
token: ${{ secrets.MY_REPO_PAT }}
submodules: recursive
- name: Set Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Install yarn
run: npm install --global yarn
- name: Install dependency
run: yarn
- name: Generate Android Bundle and Source Map
run: |
npx react-native bundle \
--dev false \
--minify true \
--platform android \
--entry-file index.js \
--reset-cache \
--bundle-output index.android.bundle \
--sourcemap-output index.android.bundle.map
- name: Upload Source Map
uses: actions/upload-artifact@v3
with:
name: source-map
path: index.android.bundle.map
create_release_tag:
needs: generate_source_map
runs-on: [default]
if: success() && (github.event.inputs.environment == 'Prod') # Only create tag for Prod releases
steps:
- name: Checkout

View File

@@ -130,10 +130,20 @@ jobs:
ls
chmod +r ./android/app/build/outputs/apk/${{ github.event.inputs.flavor || inputs.flavor }}${{github.event.inputs.environment || inputs.environment}}/${{github.event.inputs.type || inputs.type}}/app-${{github.event.inputs.flavor}}${{github.event.inputs.environment}}-release.apk
apk_path="./android/app/build/outputs/apk/${{ github.event.inputs.flavor || inputs.flavor }}${{github.event.inputs.environment || inputs.environment}}/${{github.event.inputs.type || inputs.type}}/app-${{ github.event.inputs.flavor || inputs.flavor }}${{github.event.inputs.environment || inputs.environment}}-release.apk"
echo "$apk_path"
# Check if APK exists, exit if not
if [ ! -f "$apk_path" ]; then
echo "Error: APK file not found at $apk_path"
exit 1
fi
chmod +r "$apk_path"
curl --location --request PUT "$upload_url" \
--data-binary "@./android/app/build/outputs/apk/${{ github.event.inputs.flavor || inputs.flavor }}${{github.event.inputs.environment || inputs.environment}}/${{github.event.inputs.type || inputs.type}}/app-fieldAgentsQA-release.apk"
--data-binary "@$apk_path"
echo "upload compleate"

View File

@@ -108,7 +108,7 @@ function App() {
async function setForegroundTimeStampAndClickstream() {
const now = dayJs().toString();
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now });
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now }, true);
}
usePolling(askForPermissions, PERMISSION_CHECK_POLL_INTERVAL);

View File

@@ -134,8 +134,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 190
def VERSION_NAME = "2.13.2"
def VERSION_CODE = 193
def VERSION_NAME = "2.13.5"
android {
ndkVersion rootProject.ext.ndkVersion

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.13.2",
"buildNumber": "190",
"version": "2.13.5",
"buildNumber": "193",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",

View File

@@ -1336,7 +1336,6 @@ export const LocalStorageKeys = {
IMAGE_SYNC_START_TIME: 'imageSyncStartTime',
IMAGE_SYNC_TIME: 'imageSyncTime',
IMAGE_FILES: 'imageFiles',
IS_DATA_SYNC_ALLOWED: 'isDataSyncAllowed',
LAST_VIDEO_SYNC_TIME: 'lastVideoSyncTime',
LAST_AUDIO_SYNC_TIME: 'lastAudioSyncTime',
LAST_ACCOUNTS_SYNC_TIME: 'lastAccountsSyncTime',

View File

@@ -69,7 +69,6 @@ import { sendVideosToServer } from '@services/videoSyncService';
import { getSyncUrl } from '@services/syncJsonDataToBe';
import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils';
import { initialize } from 'react-native-clarity';
import { updateImageUploadComponent } from '@components/form/services/formComponents';
import { getWifiDetailsSyncUrl } from '@components/utlis/WifiDetails';
export enum FOREGROUND_TASKS {
@@ -393,21 +392,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
LitmusExperimentNameMap[LitmusExperimentName.MS_CLARITY],
{ deviceId: GLOBAL.DEVICE_ID }
);
const dataSyncResponse = await getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.COSMOS_DATA_SYNC],
{ 'x-customer-id': GLOBAL.AGENT_ID }
);
const enableFeedbackImageGeotagging = await getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.ENABLE_IMAGE_GEO_TAGGING],
{ 'x-customer-id': GLOBAL.AGENT_ID }
);
getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.COSMOS_CASE_COLLECTION_MANAGER],
{ 'x-customer-id': GLOBAL.AGENT_ID }
).then((response) => {
setAsyncStorageItem(LocalStorageKeys.COSMOS_CASE_COLLECTION_MANAGER_ENABLE, response);
});
updateImageUploadComponent(enableFeedbackImageGeotagging);
if (
MS_CLARITY_PROJECT_ID &&
!GLOBAL.MS_CLARITY_INITIALIZED &&
@@ -420,7 +404,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
initialize(MS_CLARITY_PROJECT_ID);
GLOBAL.MS_CLARITY_INITIALIZED = true;
}
setAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, dataSyncResponse);
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);

View File

@@ -8,14 +8,12 @@ import { GenericObject, GenericType } from '../../common/GenericTypes';
import StarRating from '../../../RN-UI-LIB/src/components/star_rating/StarRating';
import { getPhoneNumberString, memoize } from '../utlis/commonFunctions';
import {
getImageFromDB,
getImageHeightWrtAspectRatio,
} from '../../services/casePayload.transformer';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import GeolocationAddressAnswer from './components/GeolocationAddressAnswer';
import dayjs from 'dayjs';
import { BUSINESS_DATE_FORMAT, CUSTOM_ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
import { IOfflineImage } from './components/imageUpload/interfaces';
const RATING_COMPONENT = 'Rating';
const MAX_RATING = 5;
@@ -33,6 +31,7 @@ interface IAnswerRender {
caseId: string;
journey: string;
metaData?: GenericObject;
questionId: string;
}
interface IDarkBoldText {
@@ -50,7 +49,7 @@ const DarkBoldText = ({ text }: IDarkBoldText) => {
};
const AnswerRender: React.FC<IAnswerRender> = (props) => {
const { answer, visited, section, caseId, journey, metaData } = props;
const { answer, visited, section, caseId, journey, metaData, questionId } = props;
const data = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]);
const caseType = useAppSelector((state) => state.allCases.caseDetails[caseId]?.caseType);
const templateData = useAppSelector((state) => state.case.templateData[caseType]);
@@ -59,23 +58,19 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
const getPhoneNumberStringFromNumber = memoize((phoneNumber: string) => {
return getPhoneNumberString(mobileNumbers?.find((a) => a.referenceId === phoneNumber));
});
const [image, setImage] = React.useState<IOfflineImage | null>(null);
const questions = templateData.questions;
const options = templateData.options;
React.useEffect(() => {
if (answer?.type === AnswerType.image) {
(async () => {
const data = await getImageFromDB(answer.answer);
setImage(data);
})();
}
}, [answer]);
const intermediateDocsToBeUploaded = useAppSelector(
(state) => state.feedbackImages.intermediateDocsToBeUploaded
);
const imageDoc = intermediateDocsToBeUploaded?.[caseId]?.documents?.[questionId];
const imageHeightWrtAspectRatio = getImageHeightWrtAspectRatio(
image?.imageWidth || 0,
image?.imageHeight || 0,
imageDoc?.imageWidth || 0,
imageDoc?.imageHeight || 0,
SCREEN_WIDTH - ANSWER_HORIZONTAL_PADDING
);
@@ -101,6 +96,7 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
journey={journey}
answer={newAnswer as unknown as IAnswer}
metaData={questions[question]?.metadata}
questionId={question}
/>
</View>
);
@@ -126,13 +122,13 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
}
return <DarkBoldText text={answer.answer} />;
case AnswerType.image:
if(!image?.imageData) return null;
if(!imageDoc?.fileUri) return null;
return (
<ImageBackground
style={[styles.image, { height: Number(imageHeightWrtAspectRatio) || 350 }]}
imageStyle={[styles.br8, GenericStyles.mt8]}
source={{
uri: image?.imageData || '',
uri: imageDoc?.fileUri || '',
}}
></ImageBackground>
);

View File

@@ -101,6 +101,7 @@ const Submit: React.FC<ISubmit> = (props) => {
journey={journey}
answer={answer as unknown as IAnswer}
metaData={questions[question]?.metadata}
questionId={question}
/>
<SeparatorBorderComponent />
</View>

View File

@@ -73,7 +73,7 @@ const TextInput: React.FC<ITextInput> = (props) => {
maxLength={question.metadata.validators?.phoneNumber?.value as number}
/>
)}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage
show={

View File

@@ -10,7 +10,6 @@ import { useAppDispatch, useAppSelector } from '../../../../hooks';
import { RootState } from '../../../../store/store';
import { AnswerType } from '../../interface';
import ErrorMessage from '../ErrorMessage';
import withObservables from '@nozbe/with-observables';
import OfflineImageDAO from '../../../../wmDB/dao/OfflineImageDAO';
import { CLICKSTREAM_EVENT_NAMES, PrefixJpegBase64Image } from '../../../../common/Constants';
import { addClickstreamEvent, ClickstreamDesc } from '../../../../services/clickstreamEventService';
@@ -24,10 +23,16 @@ import { capturePhoto } from '@components/utlis/ImageUtlis';
import CameraIcon from '@rn-ui-lib/icons/CameraIcon';
import { getImageHeightWrtAspectRatio } from '@services/casePayload.transformer';
import { IImageUpload, ImageValidationError } from './interfaces';
import { getImageUri } from './utils';
import dayjs from 'dayjs';
import ImagePlaceholder from './ImagePlaceholder';
import { IMAGE_FORMAT_JPEG, IMAGE_PADDING, IMAGE_QUALITY, MAX_HEIGHT, MAX_WIDTH, PhotoUploadErrorMessages } from './constants';
import {
IMAGE_FORMAT_JPEG,
IMAGE_PADDING,
IMAGE_QUALITY,
MAX_HEIGHT,
MAX_WIDTH,
PhotoUploadErrorMessages,
} from './constants';
import { toast } from '@rn-ui-lib/components/toast';
import { IImageDetails, LAUNCH_REQUEST } from '@rn-ui-lib/components/photoUpload/PhotoUpload';
import UploadIcon from '@rn-ui-lib/icons/UploadIcon';
@@ -46,6 +51,10 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
const deviceGeolocationCoordinate = useAppSelector(
(state) => state.foregroundService.deviceGeolocationCoordinate
);
const intermediateDocsToBeUploaded = useAppSelector(
(state) => state.feedbackImages.intermediateDocsToBeUploaded
);
const imageDoc = intermediateDocsToBeUploaded?.[caseId]?.documents?.[questionId];
const { latitude, longitude, timestamp } = deviceGeolocationCoordinate || {};
const question = template.questions[questionId as keyof typeof template.questions];
const dataFromRedux = useSelector(
@@ -67,11 +76,17 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
return null;
}
const addOriginalFileUriToDocs = (caseId: string, fileUri: string, questionKey: string) => {
const addOriginalFileUriToDocs = (
caseId: string,
fileUri: string,
questionKey: string,
imageWidth: number,
imageHeight: number
) => {
if (!fileUri) {
return;
}
dispatch(addIntermediateDocument({ caseId, fileUri, questionKey }));
dispatch(addIntermediateDocument({ caseId, fileUri, questionKey, imageWidth, imageHeight }));
};
const handleImageDelete = () => {
@@ -171,7 +186,6 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
});
const { base64 = '', uri = '', imageWidth, imageHeight } = result;
handlePictureClickClickstream();
addOriginalFileUriToDocs(caseId, uri, questionId);
if (!base64) {
return;
}
@@ -194,6 +208,7 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
await OfflineImageDAO.addImage(base64Image, uri, uniqueId, imageWidth, imageHeight);
toast({ type: 'success', text1: 'Geolocation & Timestamp added successfully' });
setImageLoading(false);
addOriginalFileUriToDocs(caseId, uri, questionId, imageWidth, imageHeight);
} catch (err: unknown) {
const error = err as ImageValidationError;
handlePictureClickClickstream(error);
@@ -206,7 +221,11 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
}
};
const { uri, imageWidth = 0, imageHeight = 0 } = getImageUri(props.offlineImages, imageId) || {};
const {
fileUri,
imageHeight = 350,
imageWidth = 350
} = imageDoc || {};
const imageHeightWrtAspectRatio = getImageHeightWrtAspectRatio(
imageWidth,
@@ -337,7 +356,7 @@ const ImageUploadV2: React.FC<IImageUpload> = (props) => {
style={[styles.image, { height: Number(imageHeightWrtAspectRatio) || 350 }]}
imageStyle={GenericStyles.br8}
source={{
uri,
uri: fileUri,
}}
onError={(error) => handleError('Error in image rendering')}
>
@@ -399,8 +418,5 @@ const styles = StyleSheet.create({
borderRadius: 8,
},
});
const enhance = withObservables([], () => ({
offlineImages: OfflineImageDAO.observeOfflineImage(),
}));
export default enhance(ImageUploadV2);
export default ImageUploadV2;

View File

@@ -77,7 +77,9 @@ const Widget: React.FC<IWidget> = (props) => {
const templateData = useAppSelector((state) => state.case?.templateData?.[caseType]);
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 intermediateDocsToBeUploaded = useAppSelector(
(state) => state.feedbackImages?.intermediateDocsToBeUploaded
);
const name = getWidgetNameFromRoute(props.route.name, caseType);
const { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name];
@@ -402,6 +404,7 @@ const Widget: React.FC<IWidget> = (props) => {
onBack={handleCloseIconPress}
/>
<ScrollView
keyboardShouldPersistTaps="handled"
contentContainerStyle={[
GenericStyles.p16,
GenericStyles.silverBackground,

View File

@@ -16,7 +16,7 @@ export const FormComponentList = {
TextInput,
TextArea,
RadioButton,
ImageUpload,
ImageUpload: ImageUploadV2,
Checkbox,
Rating,
Dropdown,
@@ -28,10 +28,3 @@ export const FormComponentList = {
Amount: TextInput,
};
export const updateImageUploadComponent = (enableImageGeoTagging: boolean) => {
if (enableImageGeoTagging) {
FormComponentList.ImageUpload = ImageUploadV2;
} else {
FormComponentList.ImageUpload = ImageUpload;
}
};

View File

@@ -79,7 +79,7 @@ const getCaseListComponents = (casesList: ICaseItem[], caseDetails: Record<strin
const pinnedList: ICaseItem[] = [];
casesList.forEach((item) => {
const { caseReferenceId, pinRank } = item;
const { caseStatus } = caseDetails[caseReferenceId];
const { caseStatus } = caseDetails[caseReferenceId] || {};
const isCaseCompleted = COMPLETED_STATUSES.includes(caseStatus);
isCaseCompleted
? completedList.push(item)
@@ -161,7 +161,7 @@ const allCasesSlice = createSlice({
switch (updateType) {
case FirestoreUpdateTypes.MODIFIED: {
const index = state.casesList?.findIndex(
(caseItem) => caseItem.caseReferenceId === caseId
(caseItem) => caseItem.caseReferenceId?.toString() === caseId?.toString()
);
if (index !== -1) {
if (pinRank && !state.casesList[index].pinRank) {
@@ -235,7 +235,7 @@ const allCasesSlice = createSlice({
}
case FirestoreUpdateTypes.REMOVED: {
const index = state.casesList.findIndex(
(caseItem) => caseItem.caseReferenceId === caseId
(caseItem) => caseItem.caseReferenceId?.toString() === caseId?.toString()
);
const currentScreen = getCurrentScreen();
// Redirect to home screen if the case deletes which the agent is seeing

View File

@@ -3,6 +3,8 @@ import { isEmpty } from '../../RN-UI-LIB/src/utlis/common';
export interface IDocument {
fileUri?: string;
imageWidth?: number;
imageHeight?: number;
}
interface IDocumentDetail {
@@ -25,12 +27,14 @@ const feedbackImagesSlice = createSlice({
initialState,
reducers: {
addIntermediateDocument: (state, action) => {
const { caseId, questionKey, fileUri } = action.payload;
const { caseId, questionKey, fileUri, imageWidth, imageHeight } = action.payload;
if(!caseId) return;
const doc = {
questionKey,
fileUri,
originalImageDocumentReferenceId: '',
imageWidth,
imageHeight
};
if (state.intermediateDocsToBeUploaded?.[caseId]?.documents) {
state.intermediateDocsToBeUploaded[caseId].documents[questionKey] = doc;

View File

@@ -135,11 +135,6 @@ function AuthRouter() {
CosmosForegroundService.stopAll();
}
});
getLitmusExperimentResult(LitmusExperimentNameMap[LitmusExperimentName.COSMOS_DATA_SYNC], {
'x-customer-id': GLOBAL.AGENT_ID,
}).then((response) => {
setAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, response);
});
}
}, [isLoggedIn]);

View File

@@ -32,7 +32,6 @@ import getLitmusExperimentResult, {
LitmusExperimentNameMap,
} from '@services/litmusExperiments.service';
import { GLOBAL } from '@constants/Global';
import { updateImageUploadComponent } from '@components/form/services/formComponents';
const Stack = createNativeStackNavigator();
@@ -66,22 +65,6 @@ const ProtectedRouter = () => {
useEffect(() => {
if (isOnline) {
dispatch(getNotifications());
getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.ENABLE_IMAGE_GEO_TAGGING],
{
'x-customer-id': GLOBAL.AGENT_ID,
}
).then((response) => {
updateImageUploadComponent(response);
});
getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.COSMOS_CASE_COLLECTION_MANAGER],
{
'x-customer-id': GLOBAL.AGENT_ID,
}
).then((response) => {
setAsyncStorageItem(LocalStorageKeys.COSMOS_CASE_COLLECTION_MANAGER_ENABLE, response);
});
}
}, [isOnline]);

View File

@@ -72,11 +72,6 @@ export const prepareAudioForUpload = async () => {
export const sendAudiosToServer = async () => {
// check if there are any files to upload
const isDataSyncEnabled = await getAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, true) ?? false;
if (!isDataSyncEnabled) return;
const zipFiles = FileDB.getFiles((files) => files.mimeType === MimeTypes.ZIP && files.type === 'AUDIOS');
if (zipFiles.length === 0) {

View File

@@ -14,11 +14,6 @@ export const minutesAgo = (minutes: number) => {
}
export const imageSyncService = async () => {
const isDataSyncAllowed = await getAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, true) ?? false;
if (!isDataSyncAllowed) return;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_DEVICE_DATA_SYNC_START);
const endTime = Date.now();
@@ -112,11 +107,6 @@ export const prepareImagesForUpload = async () => {
export const sendImagesToServer = async () => {
// check if there are any files to upload
const isDataSyncAllowed = await getAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, true) ?? false;
if (!isDataSyncAllowed) return;
const zipFiles = FileDB.getFiles((files) => files.mimeType === MimeTypes.ZIP && files.type === 'IMAGES');
if (zipFiles.length === 0) {

View File

@@ -71,18 +71,8 @@ export const prepareVideosForUpload = async () => {
export const sendVideosToServer = async () => {
// check if there are any files to upload
const isDataSyncEnabled = await getAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, true) ?? false;
if (!isDataSyncEnabled) return;
const zipFiles = FileDB.getFiles((files) => files.mimeType === MimeTypes.ZIP && files.type === 'VIDEOS');
if (zipFiles.length === 0) {
prepareVideosForUpload();
return;