From 6463c24ffa8b6c75dad95128542dd0fd5dfd95d7 Mon Sep 17 00:00:00 2001 From: Aman Singh Date: Mon, 16 Jan 2023 15:23:57 +0530 Subject: [PATCH] Animation case detail || Aman Singh (#61) * case details, animation and data validations * changed linting issue * changed submodule path --- ProtectedRouter.tsx | 14 +- RN-UI-LIB | 2 +- src/action/dataActions.ts | 1 + src/components/utlis/apiHelper.ts | 2 +- src/reducer/allCasesSlice.ts | 17 +- src/reducer/caseReducre.ts | 12 +- src/screens/allCases/CaseItemAvatar.tsx | 77 +++--- src/screens/caseDetails/CaseDetailHeader.tsx | 64 +++++ src/screens/caseDetails/CaseDetails.tsx | 133 +++++++++-- .../caseDetails/UserDetailsSection.tsx | 223 +++++++++++------- .../caseDetails/interactionsHandler.tsx | 1 + .../journeyStepper/TaskStepper.tsx | 25 +- 12 files changed, 400 insertions(+), 171 deletions(-) create mode 100644 src/screens/caseDetails/CaseDetailHeader.tsx diff --git a/ProtectedRouter.tsx b/ProtectedRouter.tsx index 7332f183..9eb15146 100644 --- a/ProtectedRouter.tsx +++ b/ProtectedRouter.tsx @@ -1,25 +1,25 @@ +import crashlytics from '@react-native-firebase/crashlytics'; +import { RouteProp } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import React from 'react'; import { getUniqueId } from 'react-native-device-info'; import { useSelector } from 'react-redux'; import { _map } from './RN-UI-LIB/src/utlis/common'; +import { GenericType } from './src/common/GenericTypes'; import Widget from './src/components/form'; import { setGlobalUserData } from './src/constants/Global'; import RealTemplate from './src/data/RealTemplateData.json'; import { useAppDispatch } from './src/hooks'; +import useFirestoreUpdates from './src/hooks/useFirestoreUpdates'; import { setDeviceId } from './src/reducer/userSlice'; import AllCasesMain from './src/screens/allCases'; import CaseDetails from './src/screens/caseDetails/CaseDetails'; +import interactionsHandler from './src/screens/caseDetails/interactionsHandler'; import Login from './src/screens/login'; import OtpInput from './src/screens/login/OtpInput'; +import Profile from './src/screens/Profile'; import TodoList from './src/screens/todoList/TodoList'; import { RootState } from './src/store/store'; -import Profile from './src/screens/Profile'; -import interactionsHandler from './src/screens/caseDetails/interactionsHandler'; -import useFirestoreUpdates from './src/hooks/useFirestoreUpdates'; -import crashlytics from '@react-native-firebase/crashlytics'; -import { RouteProp } from '@react-navigation/native'; -import { GenericType } from './src/common/GenericTypes'; const ANIMATION_DURATION = 300; @@ -80,7 +80,7 @@ const ProtectedRouter = () => { options={{ header: () => null, animationDuration: ANIMATION_DURATION, - animation: 'slide_from_right', + animation: 'none', }} listeners={getScreenFocusListenerObj} /> diff --git a/RN-UI-LIB b/RN-UI-LIB index f536a952..6aaa1d01 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit f536a952aeb1489a81e03f5ed8f62f3710a94119 +Subproject commit 6aaa1d0174a91747e4803f6c8f49f4325c83a57b diff --git a/src/action/dataActions.ts b/src/action/dataActions.ts index 9efcde99..248d8798 100644 --- a/src/action/dataActions.ts +++ b/src/action/dataActions.ts @@ -37,6 +37,7 @@ export const getAllCases = export const getAllCaseDetails = (data: Array) => (dispatch: AppDispatch) => { + if(!data) return; const caseList = data.map(caseItem => caseItem?.caseReferenceId); const url = getApiUrl(ApiKeys.CASE_DETAIL); axiosInstance diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index baa0d4ff..85d11695 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -127,7 +127,7 @@ axiosInstance.interceptors.response.use( ) { const errorString = getErrorMessage(error); if (!config.headers.donotHandleError) { - toast({ type: 'error', text1: JSON.stringify(errorString) }); + toast({ type: 'error', text1:errorString || "Something went wrong" }); } if (response.status === 401) { diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index 24638c9d..60c724f6 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -1,13 +1,13 @@ import { createSlice } from '@reduxjs/toolkit'; -import { getCurrentScreen, navigateToScreen } from '../components/utlis/navigationUtlis'; -import { CurrentTask, ICaseItem, IFilter } from '../screens/allCases/interface'; -import { - FirestoreUpdateTypes, -} from '../common/Constants'; -import { CaseDetail } from '../screens/caseDetails/interface'; -import { CaseUpdates } from '../hooks/useFirestoreUpdates'; -import { updateRecordsOnCustomerDbFromCaseList } from '../components/utlis/customerDbHelper'; import { _map } from '../../RN-UI-LIB/src/utlis/common'; +import { + FirestoreUpdateTypes +} from '../common/Constants'; +import { updateRecordsOnCustomerDbFromCaseList } from '../components/utlis/customerDbHelper'; +import { getCurrentScreen, navigateToScreen } from '../components/utlis/navigationUtlis'; +import { CaseUpdates } from '../hooks/useFirestoreUpdates'; +import { CurrentTask, ICaseItem, IFilter } from '../screens/allCases/interface'; +import { CaseDetail } from '../screens/caseDetails/interface'; export type ICasesMap = { [key: string]: ICaseItem }; @@ -80,6 +80,7 @@ const allCasesSlice = createSlice({ }, setCasesListData: (state, action) => { const { allCases } = action.payload; + if(!allCases) return; // TODO add type const listData: Array = []; if (allCases?.length) { diff --git a/src/reducer/caseReducre.ts b/src/reducer/caseReducre.ts index 72108d97..3bd8586c 100644 --- a/src/reducer/caseReducre.ts +++ b/src/reducer/caseReducre.ts @@ -24,6 +24,7 @@ interface ICaseReducer { toBeSynced: any; allCases: Array; templateData: any; + showAlternateText: string; } const initialState = { @@ -32,6 +33,7 @@ const initialState = { toBeSynced: {}, allCases: [], templateData: {}, + showAlternateText: "" } as ICaseReducer; export const caseSlice = createSlice({ @@ -77,22 +79,18 @@ export const caseSlice = createSlice({ updateTemplateData: (state, action) => { state.templateData = action.payload; }, - setAllCases: (state, action) => { - state.allCases = action.payload; - }, - updateDate: (state, action) => { - const { date } = action.payload; - state.loanInfo.allocationDate = date; + updateAlternateHeader: (state, action) => { + state.showAlternateText = action.payload; }, }, }); export const { updateInteraction, - setAllCases, deleteInteraction, updateTemplateData, deleteJourney, + updateAlternateHeader } = caseSlice.actions; export default caseSlice.reducer; diff --git a/src/screens/allCases/CaseItemAvatar.tsx b/src/screens/allCases/CaseItemAvatar.tsx index 1dae8feb..8ef89292 100644 --- a/src/screens/allCases/CaseItemAvatar.tsx +++ b/src/screens/allCases/CaseItemAvatar.tsx @@ -1,18 +1,20 @@ import React from 'react'; -import {ICaseItem} from './interface'; -import RoundCheckIcon from '../../icons/RoundCheckIcon'; -import {useAppDispatch, useAppSelector} from '../../hooks'; -import {CaseDetail} from '../caseDetails/interface'; +import { StyleSheet, View } from 'react-native'; import Avatar from '../../../RN-UI-LIB/src/components/Avatar'; import UnsyncedIcon from '../../../RN-UI-LIB/src/Icons/UnsyncedIcon'; -import {StyleSheet, View} from 'react-native'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { getSignedURLs } from '../../action/dataActions'; import { getImage64FromCaseId } from '../../components/utlis/customerDbHelper'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import RoundCheckIcon from '../../icons/RoundCheckIcon'; +import { CaseDetail } from '../caseDetails/interface'; +import { ICaseItem } from './interface'; interface ICaseItemAvatar { caseSelected?: boolean; caseData: ICaseItem; size?: number; + showBorder?: boolean; } const MAX_API_CALL = 3; @@ -20,11 +22,12 @@ const MAX_API_CALL = 3; const CaseItemAvatar: React.FC = ({ caseSelected = false, caseData, - size = 32 + size = 32, + showBorder = false, }) => { const dispatch = useAppDispatch(); - const {caseReferenceId} = caseData; + const { caseReferenceId } = caseData; const caseDetails: CaseDetail = useAppSelector( state => state.allCases.caseDetails?.[caseReferenceId], ); @@ -34,50 +37,54 @@ const CaseItemAvatar: React.FC = ({ const isSynced = caseDetails?.isSynced; - React.useEffect(() => { if (caseDetails?.customerInfo?.imageURL) { (async () => { let image64 = await getImage64FromCaseId(caseDetails?.id); - if (image64) { // if image exist in WM db, then setting the base64 + if (image64) { + // if image exist in WM db, then setting the base64 setImageUrl(image64); } else { - setImageUrl(caseDetails.customerInfo.imageURL) + setImageUrl(caseDetails.customerInfo.imageURL); } - })(); + })(); } }, [caseDetails?.customerInfo?.imageURL]); const onError = async () => { if (apiErrorCount < MAX_API_CALL) { - dispatch(getSignedURLs([imageUrl])).then((resp) => { + dispatch(getSignedURLs([imageUrl])).then(resp => { if (resp?.[imageUrl]) { - setApiErrorCount(apiErrorCount => apiErrorCount+1); - setImageUrl(resp[imageUrl]) + setApiErrorCount(apiErrorCount => apiErrorCount + 1); + setImageUrl(resp[imageUrl]); } - }) + }); } - } + }; return ( - { - !caseSelected ? ( - - - {!isSynced ? ( - - - - ) : null} - - ) : - } + {!caseSelected ? ( + + + {!isSynced ? ( + + + + ) : null} + + ) : ( + + )} ); }; @@ -88,6 +95,10 @@ const styles = StyleSheet.create({ left: 14, top: 14, }, + border: { + borderColor: COLORS.BORDER.BLUE, + borderWidth: 2 + }, }); export default CaseItemAvatar; diff --git a/src/screens/caseDetails/CaseDetailHeader.tsx b/src/screens/caseDetails/CaseDetailHeader.tsx new file mode 100644 index 00000000..9cf6cf7b --- /dev/null +++ b/src/screens/caseDetails/CaseDetailHeader.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useRef } from 'react'; +import { Animated, StyleSheet } from 'react-native'; +import Avatar from '../../../RN-UI-LIB/src/components/Avatar'; +import Heading from '../../../RN-UI-LIB/src/components/Heading'; +import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { goBack } from '../../components/utlis/navigationUtlis'; +import { useAppSelector } from '../../hooks'; +import { CaseDetail } from './interface'; + +const AlternateHeader: React.FC<{ imageUrl: string; name: string }> = props => { + const { name, imageUrl } = props; + const slideInUpAnimation = useRef(new Animated.Value(10)).current; + + + useEffect(() => { + Animated.timing(slideInUpAnimation, { + toValue: 0, + duration: 300, + useNativeDriver: true + }).start() + }, []) + + return ( + + + + {name} + + + ); +}; + +const CaseDetailHeader: React.FC<{ caseDetail: CaseDetail }> = props => { + const { caseDetail } = props; + + const showHeader = useAppSelector(state => state.case.showAlternateText); + return ( + + ) : ( + '' + ) + } + onBack={goBack} + /> + ); +}; + +export default CaseDetailHeader; + +const styles = StyleSheet.create({}); diff --git a/src/screens/caseDetails/CaseDetails.tsx b/src/screens/caseDetails/CaseDetails.tsx index dbd91943..0a033de4 100644 --- a/src/screens/caseDetails/CaseDetails.tsx +++ b/src/screens/caseDetails/CaseDetails.tsx @@ -1,22 +1,27 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { + Animated, RefreshControl, SafeAreaView, ScrollView, + StatusBar, StyleSheet, - View, + View } from 'react-native'; -import Heading from '../../../RN-UI-LIB/src/components/Heading'; -import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; -import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader'; +import Button from '../../../RN-UI-LIB/src/components/Button'; import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader'; -import {GenericStyles, SCREEN_WIDTH} from '../../../RN-UI-LIB/src/styles'; -import {COLORS} from '../../../RN-UI-LIB/src/styles/colors'; +import { + GenericStyles, + getShadowStyle, SCREEN_WIDTH +} from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { getSingleCaseDetail } from '../../action/dataActions'; -import { navigateToScreen } from '../../components/utlis/navigationUtlis'; -import {useAppDispatch, useAppSelector} from '../../hooks'; +import { useAppDispatch, useAppSelector } from '../../hooks'; import useRefresh from '../../hooks/useRefresh'; -import CaseDetailsHeader from './CaseDetailsHeader'; +import { updateAlternateHeader } from '../../reducer/caseReducre'; +import { CaseStatuses } from '../allCases/interface'; +import CaseDetailsHeader from './CaseDetailHeader'; +import CallingBottomSheet from './journeyStepper/CallingBottomSheet'; import TaskStepper from './journeyStepper/TaskStepper'; import TaskLoader from './TaskLoader'; import UserDetailsSection from './UserDetailsSection'; @@ -31,39 +36,118 @@ interface ICaseDetails { const CaseDetails: React.FC = props => { const { route: { - params: {caseId}, + params: { caseId }, }, } = props; const dispatch = useAppDispatch(); - const detailObject = useAppSelector( + const caseDetail = useAppSelector( state => state.allCases.caseDetails[caseId], ); + const [showCallingBottomSheet, setShowCallingBottomSheet] = + React.useState(false); + const offset = useRef(new Animated.Value(0)).current; + const [showHeader, setShowHeader] = React.useState(false); + const { refreshing, onRefresh } = useRefresh(() => + dispatch(getSingleCaseDetail([caseId])), + ); + + const handleCustomerCall = () => { + // if(mobileNumbers?.length > 0) { + // setShowPhoneNumberBottomSheet(true) + // return; + // } + // setSelectedPhoneNumber(mobileNumbers[0]); + setShowCallingBottomSheet(true); + }; + + const opacityAnimation = useRef(new Animated.Value(0)).current; + + const isCaseClosedOrCompleted = + caseDetail.caseStatus === CaseStatuses.CLOSED || + caseDetail.caseStatus === CaseStatuses.FORCE_CLOSE || + caseDetail.caseStatus === CaseStatuses.EXPIRED; + + const duration = 600; + + const slideInUpJourney = new Animated.Value(1000); + + useEffect(() => { + Animated.timing(slideInUpJourney, { + toValue: -58, + duration: duration, + useNativeDriver: true, + }).start(); + + Animated.timing(opacityAnimation, { + toValue: 1, + duration: duration, + useNativeDriver: true, + }).start(); + }, []); - const {refreshing, onRefresh} = useRefresh(() => dispatch(getSingleCaseDetail([caseId]))); return ( + { + const scrolling = event.nativeEvent.contentOffset.y; + if (scrolling > 19) { + dispatch(updateAlternateHeader(caseDetail?.customerInfo?.customerName)); + } else { + dispatch(updateAlternateHeader("")); + } + }} refreshControl={ }> - navigateToScreen("Home")} /> - {/* */} - - + + + }> - + - + + + {!isCaseClosedOrCompleted && ( + +