From 05c28bf002204149278b75bc543449f2c6acb982 Mon Sep 17 00:00:00 2001 From: "aman.singh" Date: Tue, 26 Dec 2023 22:10:47 +0530 Subject: [PATCH] app update architecture| Aman Singh --- App.tsx | 5 ++ android/app/google-services.json | 17 +++-- .../java/com/avapp/DeviceUtilsModule.java | 10 +++ package.json | 1 + src/common/BlockerScreen.tsx | 64 ++++++++++++++++++- src/components/utlis/DeviceUtils.ts | 5 +- src/components/utlis/commonFunctions.ts | 5 ++ src/constants/Global.ts | 6 ++ src/hooks/useFirestoreUpdates.ts | 18 +++++- src/reducer/metadataSlice.ts | 17 ++++- src/reducer/userSlice.ts | 17 +++++ src/screens/Profile/index.tsx | 4 +- src/screens/auth/AuthRouter.tsx | 4 +- 13 files changed, 154 insertions(+), 19 deletions(-) diff --git a/App.tsx b/App.tsx index ca4f86c2..e4477dbb 100644 --- a/App.tsx +++ b/App.tsx @@ -45,6 +45,8 @@ import analytics from '@react-native-firebase/analytics'; import fetchUpdatedRemoteConfig from './src/services/firebaseFetchAndUpdate.service'; import { addClickstreamEvent } from './src/services/clickstreamEventService'; import ScreenshotBlocker from './src/components/utlis/ScreenshotBlocker'; +import { getBuildFlavour } from '@components/utlis/DeviceUtils'; +import { setGlobalBuildFlavour } from '@constants/Global'; initSentry(); @@ -127,6 +129,9 @@ function App() { useEffect(() => { ScreenshotBlocker.unblockScreenshots(); + getBuildFlavour().then((flavour) => { + setGlobalBuildFlavour(flavour); + }); }, []); React.useEffect(() => { diff --git a/android/app/google-services.json b/android/app/google-services.json index 1df006c6..629b987b 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,34 +1,33 @@ { "project_info": { - "project_number": "60755663443", - "project_id": "address-verification-app", - "storage_bucket": "address-verification-app.appspot.com", - "firebase_url": "https://address-verification-app-default-rtdb.firebaseio.com" + "project_number": "136591056725", + "project_id": "field-verification-app", + "storage_bucket": "field-verification-app.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:60755663443:android:4a948ee9d0b4e3098584a6", + "mobilesdk_app_id": "1:136591056725:android:c454085ec6505cc01519dc", "android_client_info": { "package_name": "com.avapp" } }, "oauth_client": [ { - "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_id": "136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c" + "current_key": "AIzaSyBL32d7WRJTcJawKjT1XCEcFbGGQ8wA6j8" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { - "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_id": "136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com", "client_type": 3 } ] @@ -37,4 +36,4 @@ } ], "configuration_version": "1" -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java index 548468ec..12f12fdc 100644 --- a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java +++ b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java @@ -315,4 +315,14 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule { } } + @ReactMethod + public void getBuildFlavour(Promise promise) { + try { + String buildFlavour = BuildConfig.FLAVOR; + promise.resolve(buildFlavour); + } catch (Exception err) { + promise.reject(err); + } + } + } diff --git a/package.json b/package.json index 5d3db15c..672de1b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "AV_APP", "version": "2.6.6", + "buildNumber": "201", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/common/BlockerScreen.tsx b/src/common/BlockerScreen.tsx index 2adb66b4..953c9d50 100644 --- a/src/common/BlockerScreen.tsx +++ b/src/common/BlockerScreen.tsx @@ -2,8 +2,8 @@ import React, { ReactNode, useCallback, useState } from 'react'; import { AppState, Linking } from 'react-native'; import { useSelector } from 'react-redux'; import { RootState } from '../store/store'; -import { UninstallInformation } from '../reducer/metadataSlice'; -import { getAppVersion } from '../components/utlis/commonFunctions'; +import { IAppState, UninstallInformation } from '../reducer/metadataSlice'; +import { getAppVersion, getBuildVersion } from '../components/utlis/commonFunctions'; import BlockerInstructions from './BlockerInstructions'; import { BLOCKER_SCREEN_DATA, CLICKSTREAM_EVENT_NAMES } from './Constants'; import { useAppDispatch, useAppSelector } from '../hooks'; @@ -18,6 +18,7 @@ import handleBlacklistedAppsForBlockingCosmos, { import { addClickstreamEvent } from '@services/clickstreamEventService'; import { setBlacklistedAppsInstalledData } from '@reducers/blacklistedAppsInstalledSlice'; import perf from '@react-native-firebase/perf'; +import { GLOBAL } from '@constants/Global'; interface IBlockerScreen { children?: ReactNode; @@ -31,7 +32,13 @@ const BlockerScreen = (props: IBlockerScreen) => { const { isTimeSynced, isDeviceLocationEnabled } = useAppSelector( (state) => state.foregroundService ); - const { isWifiOrCellularOn } = useAppSelector((state) => state.metadata); + const { isWifiOrCellularOn, appState } = useAppSelector((state) => state.metadata); + + const appUpdate = useSelector((state: RootState) => { + return state.metadata?.appState; + }); + + const [shouldUpdate, setShouldUpdate] = useState(); const [showActionBtnLoader, setShowActionBtnLoader] = useState(false); const dispatch = useAppDispatch(); @@ -66,6 +73,31 @@ const BlockerScreen = (props: IBlockerScreen) => { setForceReinstallData(undefined); }, [JSON.stringify(forceUninstallData || {})]); + + React.useEffect(() => { + console.log("firebase updated", appUpdate) + if (!appUpdate) return; + const buildToCompare = GLOBAL.BUILD_FLAVOUR; + if (!buildToCompare) return; + let flavorToUpdate: IAppState; + if(GLOBAL.BUILD_FLAVOUR.includes('fieldAgents')){ + flavorToUpdate = appUpdate?.fieldAgent; + }else { + flavorToUpdate = appUpdate?.telecallingAgents; + } + console.log("firebase updated :: flavor to update", flavorToUpdate, GLOBAL.BUILD_FLAVOUR.includes('fieldAgents')) + + if (!flavorToUpdate) return; + const currentBuildNumber = getBuildVersion(); + console.log("firebase updated", flavorToUpdate, {currentBuildNumber}) + + if (currentBuildNumber && currentBuildNumber < flavorToUpdate.buildNumber) { + setShouldUpdate(true); + } else { + setShouldUpdate(false); + } + }, [JSON.stringify(appUpdate || {})]); + React.useEffect(() => { const appStateChange = AppState.addEventListener('change', async (change) => { setTimeout(async () => { @@ -92,6 +124,20 @@ const BlockerScreen = (props: IBlockerScreen) => { await Linking.openSettings(); }, []); + const handleAppUpdate = () => { + let appUpdateUrl; + if(GLOBAL.BUILD_FLAVOUR.includes('fieldAgents')){ + appUpdateUrl = appUpdate?.fieldAgent.currentProdAPK; + }else { + appUpdateUrl = appUpdate?.telecallingAgents.currentProdAPK; + } + console.log({appUpdateUrl}) + + if (appUpdateUrl) { + openApkDownloadLink(appUpdateUrl); + } + }; + const handleLocationAccess = async () => { setShowActionBtnLoader(true); const isLocationEnabled = await locationEnabled(); @@ -103,6 +149,18 @@ const BlockerScreen = (props: IBlockerScreen) => { } }; + + if (shouldUpdate) { + const { heading, instructions } = BLOCKER_SCREEN_DATA.UNINSTALL_APP; + return ( + + ); + } + if (forceReinstallData?.reinstall_endpoint) { const { heading, instructions } = BLOCKER_SCREEN_DATA.UNINSTALL_APP; return ( diff --git a/src/components/utlis/DeviceUtils.ts b/src/components/utlis/DeviceUtils.ts index ffd1771a..dd06b97f 100644 --- a/src/components/utlis/DeviceUtils.ts +++ b/src/components/utlis/DeviceUtils.ts @@ -1,3 +1,4 @@ +import { buildFlavour } from '@reducers/metadataSlice'; import { NativeModules } from 'react-native'; const { DeviceUtilsModule } = NativeModules; // this is the same name we returned in getName function. @@ -71,4 +72,6 @@ export const setBottomSheetView = (id: number | null) => noop(); export const clearBottomSheet = () => noop(); -export const alfredSetEmailId = (emailId: string) => noop(); \ No newline at end of file +export const alfredSetEmailId = (emailId: string) => noop(); + +export const getBuildFlavour = (): Promise => DeviceUtilsModule.getBuildFlavour(); \ No newline at end of file diff --git a/src/components/utlis/commonFunctions.ts b/src/components/utlis/commonFunctions.ts index 71db58d0..cb488df9 100644 --- a/src/components/utlis/commonFunctions.ts +++ b/src/components/utlis/commonFunctions.ts @@ -26,6 +26,7 @@ import { IGeolocationCoordinate } from '../../types/addressGeolocation.types'; import { toast } from '@rn-ui-lib/components/toast'; import { sendContentToWhatsapp } from './DeviceUtils'; import { ToastMessages } from '@screens/allCases/constants'; +import VersionNumber from 'react-native-version-number'; const fs = ReactNativeBlobUtil.fs; @@ -243,6 +244,10 @@ export function getAppVersion(): string { return packageJson.version; } +export const getBuildVersion = (): string => { + return packageJson?.buildNumber || VersionNumber.buildVersion; +} + export const getDocumentList = (caseDetails: CaseDetail) => { return caseDetails.caseType === CaseAllocationType.ADDRESS_VERIFICATION_CASE ? caseDetails.customerInfo?.documents diff --git a/src/constants/Global.ts b/src/constants/Global.ts index 8c898702..55874907 100644 --- a/src/constants/Global.ts +++ b/src/constants/Global.ts @@ -1,3 +1,4 @@ +import { buildFlavour } from '@reducers/metadataSlice'; import { isNullOrUndefined } from '../components/utlis/commonFunctions'; export enum DEVICE_TYPE_ENUM { @@ -12,6 +13,7 @@ export const GLOBAL = { DEVICE_TYPE: DEVICE_TYPE_ENUM.MOBILE, IS_IMPERSONATED: false, SELECTED_AGENT_ID: '', + BUILD_FLAVOUR: '' }; interface IGlobalUserData { @@ -32,3 +34,7 @@ export const setGlobalUserData = (userData: IGlobalUserData) => { if (!isNullOrUndefined(isImpersonated)) GLOBAL.IS_IMPERSONATED = isImpersonated ?? false; if (!isNullOrUndefined(selectedAgentId)) GLOBAL.SELECTED_AGENT_ID = `${selectedAgentId}`; }; + +export const setGlobalBuildFlavour = (buildFlavour: buildFlavour) => { + if (buildFlavour) GLOBAL.BUILD_FLAVOUR = buildFlavour; +}; diff --git a/src/hooks/useFirestoreUpdates.ts b/src/hooks/useFirestoreUpdates.ts index c497c195..c14d796c 100644 --- a/src/hooks/useFirestoreUpdates.ts +++ b/src/hooks/useFirestoreUpdates.ts @@ -15,7 +15,7 @@ import { type ILockData, MY_CASE_ITEM, setLockData, VisitPlanStatus } from '../r import { setFilters } from '../reducer/filtersSlice'; import { type FormTemplateV1 } from '../types/template.types'; import { ToastMessages } from '../screens/allCases/constants'; -import { setForceUninstallData } from '../reducer/metadataSlice'; +import { setCurrentProdAPK, setForceUninstallData } from '../reducer/metadataSlice'; import { logError } from '../components/utlis/errorUtils'; import { type GenericFunctionArgs } from '../common/GenericTypes'; import { addClickstreamEvent } from '@services/clickstreamEventService'; @@ -57,7 +57,7 @@ const useFirestoreUpdates = () => { let forceUninstallUnsubscribe: GenericFunctionArgs; let lockUnsubscribe: GenericFunctionArgs; let feedbackFiltersUnsubscribe: GenericFunctionArgs; - + let appUpdateUnsubscribe: GenericFunctionArgs; const dispatch = useAppDispatch(); const showCaseUpdationToast = (newlyAddedCases: number, deletedCases: number) => { @@ -187,6 +187,18 @@ const useFirestoreUpdates = () => { logError(err as Error, errMsg); }; + const handleAppUpdate = ( + snapshot: FirebaseFirestoreTypes.DocumentSnapshot + ) => { + const configData = snapshot.data(); + dispatch(setCurrentProdAPK(configData)); + }; + + const subscribeToAppUpdate = () => { + const collectionPath = 'app-state/app-update'; + return subscribeToDoc(handleAppUpdate, collectionPath); + }; + const signInUserToFirebase = () => { if (!sessionDetails) { return; @@ -290,6 +302,7 @@ const useFirestoreUpdates = () => { collectionTemplateUnsubscribe = subscribeToCollectionTemplate(); lockUnsubscribe = subscribeToLocks(); feedbackFiltersUnsubscribe = subscribeToFeedbackFilters(); + appUpdateUnsubscribe = subscribeToAppUpdate(); } useEffect(() => { @@ -345,6 +358,7 @@ const useFirestoreUpdates = () => { useEffect(() => { forceUninstallUnsubscribe = subscribeToForceUninstall(); + subscribeToAppUpdate(); }, []); }; diff --git a/src/reducer/metadataSlice.ts b/src/reducer/metadataSlice.ts index d099d428..00582643 100644 --- a/src/reducer/metadataSlice.ts +++ b/src/reducer/metadataSlice.ts @@ -5,16 +5,28 @@ export interface UninstallInformation { reinstall_endpoint: string; } +export interface IAppState { + buildNumber: string; + currentProdAPK?: string; +} + interface IMetadata { isOnline: boolean; forceUninstall: Record; isWifiOrCellularOn: boolean; + appState: { + fieldAgent: IAppState, + telecallingAgents: IAppState + }; } +export type buildFlavour = 'fieldAgents' | 'callingAgents'; + const initialState = { isOnline: true, forceUninstall: {}, isWifiOrCellularOn: true, + appState: {} } as IMetadata; const MetadataSlice = createSlice({ @@ -30,9 +42,12 @@ const MetadataSlice = createSlice({ setIsWifiOrCellularOn: (state, action) => { state.isWifiOrCellularOn = action.payload; }, + setCurrentProdAPK(state, action) { + state.appState = action.payload; + } }, }); -export const { setIsOnline, setForceUninstallData, setIsWifiOrCellularOn } = MetadataSlice.actions; +export const { setIsOnline, setForceUninstallData, setIsWifiOrCellularOn, setCurrentProdAPK } = MetadataSlice.actions; export default MetadataSlice.reducer; diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index a10f28f5..63742c13 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -1,6 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { setGlobalUserData } from '../constants/Global'; import { IReportee } from '../screens/allCases/interface'; +import { getAppVersion, getBuildVersion } from '@components/utlis/commonFunctions'; interface ISessionDetails { sessionToken: string; @@ -68,6 +69,10 @@ export interface IUserSlice extends IUser { showAttendanceBanner: boolean; attendanceDate: string; }; + buildInfo: { + buildNumber: string; + buildVersion: string; + }; } const initialState: IUserSlice = { @@ -89,6 +94,10 @@ const initialState: IUserSlice = { showAttendanceBanner: true, attendanceDate: '', }, + buildInfo: { + buildNumber: getBuildVersion(), + buildVersion: getAppVersion(), + } }; export const userSlice = createSlice({ @@ -141,6 +150,13 @@ export const userSlice = createSlice({ setAgentAttendance: (state, action) => { state.agentAttendance = action.payload; }, + setBuildInfo: (state, action) => { + const data = { + buildNumber: getBuildVersion(), + buildVersion: getAppVersion(), + } + state.buildInfo = data; + } }, }); @@ -153,6 +169,7 @@ export const { setIsExternalAgent, setCaseSyncLock, setAgentAttendance, + setBuildInfo } = userSlice.actions; export default userSlice.reducer; diff --git a/src/screens/Profile/index.tsx b/src/screens/Profile/index.tsx index ca891a61..29b64d1f 100644 --- a/src/screens/Profile/index.tsx +++ b/src/screens/Profile/index.tsx @@ -25,7 +25,7 @@ import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import Button from '../../../RN-UI-LIB/src/components/Button'; import { navigateToScreen } from '../../components/utlis/navigationUtlis'; import GroupIcon from '../../../RN-UI-LIB/src/Icons/GroupIcon'; -import { getAppVersion } from '../../components/utlis/commonFunctions'; +import { getAppVersion, getBuildVersion } from '../../components/utlis/commonFunctions'; import VersionNumber from 'react-native-version-number'; import { useFocusEffect } from '@react-navigation/native'; import { CaseDetail } from '../caseDetails/interface'; @@ -261,7 +261,7 @@ const Profile: React.FC = () => { > App Version: {getAppVersion()} Gradle Version: {VersionNumber.appVersion} Gradle Build - No: {VersionNumber.buildVersion} + No: {getBuildVersion()} diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index d65b1912..4c16bea0 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux'; import { getUniqueId, isTablet } from 'react-native-device-info'; import { useAppDispatch } from '../../hooks'; import { type RootState } from '../../store/store'; -import { setAgentAttendance, setDeviceId } from '../../reducer/userSlice'; +import { setAgentAttendance, setBuildInfo, setDeviceId } from '../../reducer/userSlice'; import { DEVICE_TYPE_ENUM, setGlobalUserData } from '../../constants/Global'; import { registerNavigateAndDispatch } from '../../components/utlis/apiHelper'; import ProtectedRouter from './ProtectedRouter'; @@ -26,6 +26,7 @@ import AnswerRender from '../../components/form/AnswerRender'; import useScreenshotTracking from '../../hooks/useScreenshotTracking'; import { getSyncTime } from '@hooks/capturingApi'; + function AuthRouter() { const dispatch = useAppDispatch(); const user = useSelector((state: RootState) => state.user); @@ -37,6 +38,7 @@ function AuthRouter() { if (!deviceId) { getUniqueId().then((id) => dispatch(setDeviceId(id))); } + dispatch(setBuildInfo(null)); }, []); const checkFormSubmission = async () => {