TP-62631 | Exotel call recording

This commit is contained in:
yashmantri
2024-07-04 17:53:23 +05:30
parent 1eaf56904e
commit d8e3ee0b1c
22 changed files with 468 additions and 289 deletions

View File

@@ -10,6 +10,7 @@ import android.app.Application;
import android.content.Context;
import com.avapp.deviceDataSync.DeviceDataSyncPackage;
import com.avapp.phoneStateBroadcastReceiver.PhoneStateModulePackage;
import com.avapp.utils.FirebaseRemoteConfigHelper;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
@@ -56,6 +57,7 @@ public class MainApplication extends Application implements ReactApplication {
packages.add(new DeviceUtilsModulePackage());
packages.add(new ScreenshotBlockerModulePackage());
packages.add(new DeviceDataSyncPackage());
packages.add(new PhoneStateModulePackage());
return packages;
}

View File

@@ -0,0 +1,57 @@
package com.avapp.phoneStateBroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
public class PhoneStateModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
private TelephonyManager telephonyManager;
private String currentCallState = "UNKNOWN";
public PhoneStateModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
telephonyManager = (TelephonyManager) reactContext.getSystemService(Context.TELEPHONY_SERVICE);
}
@Override
public String getName() {
return "PhoneStateModule";
}
@ReactMethod
public void getCurrentCallState(Promise promise) {
int state = telephonyManager.getCallState();
String currentCallState;
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
currentCallState = "IDLE";
break;
case TelephonyManager.CALL_STATE_RINGING:
currentCallState = "RINGING";
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
currentCallState = "OFFHOOK";
break;
default:
currentCallState = "UNKNOWN";
break;
}
promise.resolve(currentCallState);
}
}

View File

@@ -0,0 +1,28 @@
package com.avapp.phoneStateBroadcastReceiver;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PhoneStateModulePackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new PhoneStateModule(reactContext));
return modules;
}
}

View File

@@ -0,0 +1,16 @@
import axiosInstance, { ApiKeys, getApiUrl } from '@components/utlis/apiHelper';
import { logError } from '@components/utlis/errorUtils';
import { setConnectingToCustomerBottomSheet } from '@reducers/activeCallSlice';
import { AppDispatch } from '@store';
export const makeACallToCustomer = (payload: any) => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.CALL_CUSTOMER);
dispatch(setConnectingToCustomerBottomSheet(true));
axiosInstance
.post(url, payload)
.then((res) => {})
.catch((err: Error) => {
logError(err);
})
.finally(() => {});
};

View File

@@ -4,7 +4,7 @@ import { logError } from '../components/utlis/errorUtils';
import {
ITelephoneNumbers,
setCallHistory,
setTelephoneNumbers,
setTelephoneDetails,
} from '@reducers/telephoneNumbersSlice';
import { isFunction } from '@components/utlis/commonFunctions';
@@ -84,9 +84,12 @@ export const fetchTelephoneNumber =
new: false,
},
],
agentCallActivity: null,
agentCallActivity: {
genuineCallCount: 3,
enableFeature: false,
},
};
dispatch(setTelephoneNumbers({ caseId: caseId, telephoneNumbers: response.telephones }));
dispatch(setTelephoneDetails({ caseId: caseId, data: response }));
})
.catch((err: Error) => {
logError(err);

View File

@@ -0,0 +1,5 @@
import { NativeModules } from 'react-native';
const { PhoneStateModule } = NativeModules;
export default PhoneStateModule;

View File

@@ -85,6 +85,7 @@ export enum ApiKeys {
DUE_AMOUNT_SUMMARY = 'DUE_AMOUNT_SUMMARY',
FEE_WAIVER_HISTORY = 'FEE_WAIVER_HISTORY',
FEE_WAIVER_V2 = 'FEE_WAIVER_V2',
CALL_CUSTOMER = 'CALL_CUSTOMER',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -161,6 +162,7 @@ API_URLS[ApiKeys.GET_UPDATE_COUNT] = '/support-requests/summary';
API_URLS[ApiKeys.DUE_AMOUNT_SUMMARY] = '/collection-cases/{loanAccountNumber}/amount-summary';
API_URLS[ApiKeys.FEE_WAIVER_HISTORY] = '/collection-cases/{loanAccountNumber}/waiver-history';
API_URLS[ApiKeys.FEE_WAIVER_V2] = '/loan/request/{loanAccountNumber}/adjust-component/v2';
API_URLS[ApiKeys.CALL_CUSTOMER] = '/call';
export const API_STATUS_CODE = {
OK: 200,

View File

@@ -2,10 +2,11 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = {
phoneNumber: '98989898998',
referenceId: 'fdc99092-6155-438f-895e-81dc75d3efe0',
customerName: 'Test User',
caseId: '2875116',
showConnectingToCustomerBottomSheet: false,
showCallCannotInitiateBottomSheet: true,
showCallCannotInitiateBottomSheet: false,
};
export const activeCallSlice = createSlice({

View File

@@ -24,6 +24,10 @@ interface ITelephoneNumbersSlice {
callHistory: {
[key: string]: any[];
};
callActivity: {
[key: string]: any;
};
callAttemptedOn: string;
}
interface SetTelephoneNumbersActionType {
@@ -36,6 +40,8 @@ interface SetTelephoneNumbersActionType {
const initialState: ITelephoneNumbersSlice = {
telephoneNumbers: {},
callHistory: {},
callActivity: {},
callAttemptedOn: '',
};
export const telephoneNumbersSlice = createSlice({
@@ -48,9 +54,20 @@ export const telephoneNumbersSlice = createSlice({
setCallHistory: (state, action) => {
state.callHistory[action?.payload?.caseId] = action?.payload?.callHistory;
},
setCallActivity: (state, action) => {
state.callActivity[action?.payload?.caseId] = action.payload.agentCallActivity;
},
setTelephoneDetails: (state, action) => {
state.telephoneNumbers[action?.payload?.caseId] = action?.payload?.data?.telephones;
state.callActivity[action?.payload?.caseId] = action?.payload?.data?.agentCallActivity;
},
setCallAttemptedOn: (state, action) => {
state.callAttemptedOn = action.payload;
},
},
});
export const { setTelephoneNumbers, setCallHistory } = telephoneNumbersSlice.actions;
export const { setTelephoneNumbers, setCallHistory, setTelephoneDetails, setCallAttemptedOn } =
telephoneNumbersSlice.actions;
export default telephoneNumbersSlice.reducer;

View File

@@ -6,8 +6,7 @@ import NavigationHeader, { Icon } from '../../../RN-UI-LIB/src/components/Naviga
import TextInput from '../../../RN-UI-LIB/src/components/TextInput';
import Button from '../../../RN-UI-LIB/src/components/Button';
import { popToScreen } from '../../components/utlis/navigationUtlis';
import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles';
import Dropdown from '../../../RN-UI-LIB/src/components/dropdown/Dropdown';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { Controller, useForm } from 'react-hook-form';
import { Source, Tag } from './const';
import DropdownItem from '../registerPayements/DropdownItem';
@@ -84,117 +83,99 @@ const AddNewNumber: React.FC<IAddNewNumber> = (props) => {
return (
<Layout>
<SafeAreaView
style={[
GenericStyles.whiteBackground,
{
height: '100%',
},
]}
>
<SafeAreaView style={GenericStyles.fill}>
<NavigationHeader title="Adding new number" icon={Icon.close} onBack={onBack} />
<View style={[GenericStyles.ph16, GenericStyles.pv24]}>
<View
style={[
GenericStyles.ph16,
GenericStyles.pt16,
GenericStyles.pb24,
getShadowStyle(8),
GenericStyles.whiteBackground,
]}
>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Add new number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
value={value}
onChangeText={(number) => {
onChange(number);
trigger();
}}
keyboardType="numeric"
placeholder="Enter here"
maxLength={10}
/>
)}
name="number"
rules={{
required: true,
minLength: 10,
min: 10,
}}
/>
</View>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Source of the number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<DropDownWrapper
placeholder="Select"
onValueChange={(number) => {
onChange(number);
trigger();
}}
bottomSheetHeight={getBottomSheetHeight(Object.values(Source).length)}
header="Source of the number"
value={value ?? ''}
>
{SourceChildComponents}
</DropDownWrapper>
)}
name="source"
rules={{ required: true }}
/>
</View>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Tag number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<DropDownWrapper
placeholder="Select"
onValueChange={(number) => {
onChange(number);
trigger();
}}
bottomSheetHeight={getBottomSheetHeight(Object.values(Tag).length)}
header="Tag number"
value={value ?? ''}
>
{TagChildComponents}
</DropDownWrapper>
)}
name="tag"
rules={{ required: true }}
/>
<View style={[GenericStyles.fill, GenericStyles.justifyContentFlexEnd]}>
<View style={[GenericStyles.ph16, GenericStyles.pv16]}>
<View style={GenericStyles.pt16}>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Add new number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
value={value}
onChangeText={(number) => {
onChange(number);
trigger();
}}
keyboardType="numeric"
placeholder="Enter here"
maxLength={10}
/>
)}
name="number"
rules={{
required: true,
minLength: 10,
min: 10,
}}
/>
</View>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Source of the number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<DropDownWrapper
placeholder="Select"
onValueChange={(number) => {
onChange(number);
trigger();
}}
containerStyle={GenericStyles.whiteBackground}
bottomSheetHeight={getBottomSheetHeight(Object.values(Source).length)}
header="Source of the number"
value={value ?? ''}
>
{SourceChildComponents}
</DropDownWrapper>
)}
name="source"
rules={{ required: true }}
/>
</View>
<View style={[GenericStyles.mb24]}>
<Text bold dark style={[GenericStyles.mb8]}>
Tag number
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<DropDownWrapper
placeholder="Select"
onValueChange={(number) => {
onChange(number);
trigger();
}}
containerStyle={GenericStyles.whiteBackground}
bottomSheetHeight={getBottomSheetHeight(Object.values(Tag).length)}
header="Tag number"
value={value ?? ''}
>
{TagChildComponents}
</DropDownWrapper>
)}
name="tag"
rules={{ required: true }}
/>
</View>
</View>
</View>
</View>
<View
style={[
GenericStyles.mt24,
GenericStyles.w100,
styles.buttonContainer,
GenericStyles.ph16,
]}
>
<Button
variant="primary"
title="Add"
disabled={!isValid || !(getValues('number')?.length > 0)}
style={[GenericStyles.w100]}
onPress={addCtaHandler}
showLoader={loading}
/>
<View style={[GenericStyles.mb24, GenericStyles.w100, GenericStyles.ph16]}>
<Button
variant="primary"
title="Add"
disabled={!isValid || !(getValues('number')?.length > 0)}
style={[GenericStyles.w100]}
onPress={addCtaHandler}
showLoader={loading}
/>
</View>
</View>
</SafeAreaView>
</Layout>
@@ -212,10 +193,6 @@ const styles = StyleSheet.create({
shadowRadius: 20,
borderRadius: 8,
},
buttonContainer: {
bottom: 24,
position: 'absolute',
},
});
export default AddNewNumber;

View File

@@ -9,20 +9,20 @@ import CallingFlow from './CallingFlow';
import { ICallCustomer } from './CallingFlow/interfaces';
const CallCustomer: React.FC<ICallCustomer> = (props) => {
// const {
// route: {
// params: { caseId },
// },
// } = props;
const caseId = '2875116';
const caseDetail =
useAppSelector((state: RootState) => state?.allCases?.caseDetails[caseId]) || {};
const {
route: {
params: { caseId },
},
} = props;
// const caseId = '2875116';
const customerName =
useAppSelector((state: RootState) => state?.allCases?.caseDetails[caseId]?.customerName);
const isExternalAgent = useAppSelector((state: RootState) => state?.user?.isExternalAgent);
return (
<Layout>
<NavigationHeader title={`Call ${caseDetail?.customerName}`} onBack={goBack} />
<NavigationHeader title={`Call ${customerName}`} onBack={goBack} />
{!isExternalAgent ? <CallingFlow caseId={caseId} /> : <CustomerNumbers caseId={caseId} />}
</Layout>
);

View File

@@ -5,11 +5,12 @@ import Button from '@rn-ui-lib/components/Button';
import Text from '@rn-ui-lib/components/Text';
import { GenericStyles } from '@rn-ui-lib/styles';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { G, Mask, Path, Rect, Svg } from 'react-native-svg';
import { Pressable, StyleSheet, View } from 'react-native';
import { RootState } from '@store';
import { useAppDispatch, useAppSelector } from '@hooks';
import { setCallCannotInitiateBottomSheet } from '@reducers/activeCallSlice';
import CallCannotInitiatedIcon from '@rn-ui-lib/icons/CallCannotInitiatedIcon';
import CrossIcon_20 from '@rn-ui-lib/icons/CrossIcon_20';
const CallCannotInitiateBottomSheet = () => {
const showCallCannotInitiateBottomSheet =
@@ -22,44 +23,40 @@ const CallCannotInitiateBottomSheet = () => {
dispatch(setCallCannotInitiateBottomSheet(!showCallCannotInitiateBottomSheet));
};
const height = getDynamicBottomSheetHeightPercentageFn(100, 70);
const height = getDynamicBottomSheetHeightPercentageFn(100, 60);
return (
<BottomSheetWrapper
heightPercentage={height(5)}
visible={!showCallCannotInitiateBottomSheet}
heightPercentage={height(4.5)}
visible={showCallCannotInitiateBottomSheet}
HeaderNode={() => (
<View style={[GenericStyles.alignItemsFlexEnd, GenericStyles.ph16]}>
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Mask
id="mask0_17037_101993"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="20"
height="20"
>
<Rect width="20" height="20" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_17037_101993)">
<Path
d="M9.9987 11.1654L5.91536 15.2487C5.76259 15.4015 5.56814 15.4779 5.33203 15.4779C5.09592 15.4779 4.90148 15.4015 4.7487 15.2487C4.59592 15.0959 4.51953 14.9015 4.51953 14.6654C4.51953 14.4293 4.59592 14.2348 4.7487 14.082L8.83203 9.9987L4.7487 5.91536C4.59592 5.76259 4.51953 5.56814 4.51953 5.33203C4.51953 5.09592 4.59592 4.90148 4.7487 4.7487C4.90148 4.59592 5.09592 4.51953 5.33203 4.51953C5.56814 4.51953 5.76259 4.59592 5.91536 4.7487L9.9987 8.83203L14.082 4.7487C14.2348 4.59592 14.4293 4.51953 14.6654 4.51953C14.9015 4.51953 15.0959 4.59592 15.2487 4.7487C15.4015 4.90148 15.4779 5.09592 15.4779 5.33203C15.4779 5.56814 15.4015 5.76259 15.2487 5.91536L11.1654 9.9987L15.2487 14.082C15.4015 14.2348 15.4779 14.4293 15.4779 14.6654C15.4779 14.9015 15.4015 15.0959 15.2487 15.2487C15.0959 15.4015 14.9015 15.4779 14.6654 15.4779C14.4293 15.4779 14.2348 15.4015 14.082 15.2487L9.9987 11.1654Z"
fill="#969696"
/>
</G>
</Svg>
</View>
<Pressable
onPress={hideBottomSheet}
style={[GenericStyles.alignItemsFlexEnd, GenericStyles.ph16]}
>
<CrossIcon_20 />
</Pressable>
)}
setVisible={hideBottomSheet}
>
<View style={[GenericStyles.ph16]}>
<View style={{ paddingHorizontal: 30 }}>
<Text style={styles.headerTxt}>Call cannot be initiated!</Text>
<Text style={styles.descriptionTxt}>
There is already an ongoing call with <Text style={styles.name}>Abhinav</Text> on{' '}
<Text style={styles.phoneNumber}>XXXXXX9544</Text>. Try calling after ending the current
call
</Text>
<View
style={[
GenericStyles.ph16,
GenericStyles.pv24,
GenericStyles.fill,
GenericStyles.justifyContentSpaceBetween,
]}
>
<View style={GenericStyles.alignCenter}>
<CallCannotInitiatedIcon />
<View style={{ paddingHorizontal: 30, paddingTop: 24 }}>
<Text style={styles.headerTxt}>Call cannot be initiated!</Text>
<Text style={styles.descriptionTxt}>
There is already an ongoing call with <Text style={styles.name}>Abhinav</Text> on{' '}
<Text style={styles.phoneNumber}>XXXXXX9544</Text>. Try calling after ending the
current call
</Text>
</View>
</View>
<Button title="Understood" onPress={hideBottomSheet} style={[GenericStyles.w100]} />

View File

@@ -5,64 +5,70 @@ import Button from '@rn-ui-lib/components/Button';
import Text from '@rn-ui-lib/components/Text';
import { GenericStyles } from '@rn-ui-lib/styles';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { G, Mask, Path, Rect, Svg } from 'react-native-svg';
import { Pressable, StyleSheet, View } from 'react-native';
import { useAppDispatch, useAppSelector } from '@hooks';
import { RootState } from '@store';
import { setConnectingToCustomerBottomSheet } from '@reducers/activeCallSlice';
import ConnectingToCustomerIcon from '@rn-ui-lib/icons/ConnectingToCustomerIcon';
import CrossIcon_20 from '@rn-ui-lib/icons/CrossIcon_20';
import { IConnectingToCustomerBottomSheet } from '../interfaces';
const ConnectingToCustomerBottomSheet = () => {
const ConnectingToCustomerBottomSheet = (props: IConnectingToCustomerBottomSheet) => {
const { caseId } = props;
const showConnectingToCustomerBottomSheet =
useAppSelector((state: RootState) => state?.activeCall?.showConnectingToCustomerBottomSheet) ||
false;
const customerName = useAppSelector(
(state: RootState) => state?.allCases?.caseDetails[caseId]?.customerName
);
const callAttemptedOn = useAppSelector(
(state: RootState) => state?.telephoneNumbers?.callAttemptedOn
);
const dispatch = useAppDispatch();
const hideBottomSheet = () => {
dispatch(setConnectingToCustomerBottomSheet(false));
};
const height = getDynamicBottomSheetHeightPercentageFn(100, 70);
const height = getDynamicBottomSheetHeightPercentageFn(100, 60);
return (
<BottomSheetWrapper
heightPercentage={height(5)}
heightPercentage={height(4.5)}
visible={showConnectingToCustomerBottomSheet}
HeaderNode={() => (
<View style={[GenericStyles.alignItemsFlexEnd, GenericStyles.ph16]}>
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Mask
id="mask0_17037_101993"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="20"
height="20"
>
<Rect width="20" height="20" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_17037_101993)">
<Path
d="M9.9987 11.1654L5.91536 15.2487C5.76259 15.4015 5.56814 15.4779 5.33203 15.4779C5.09592 15.4779 4.90148 15.4015 4.7487 15.2487C4.59592 15.0959 4.51953 14.9015 4.51953 14.6654C4.51953 14.4293 4.59592 14.2348 4.7487 14.082L8.83203 9.9987L4.7487 5.91536C4.59592 5.76259 4.51953 5.56814 4.51953 5.33203C4.51953 5.09592 4.59592 4.90148 4.7487 4.7487C4.90148 4.59592 5.09592 4.51953 5.33203 4.51953C5.56814 4.51953 5.76259 4.59592 5.91536 4.7487L9.9987 8.83203L14.082 4.7487C14.2348 4.59592 14.4293 4.51953 14.6654 4.51953C14.9015 4.51953 15.0959 4.59592 15.2487 4.7487C15.4015 4.90148 15.4779 5.09592 15.4779 5.33203C15.4779 5.56814 15.4015 5.76259 15.2487 5.91536L11.1654 9.9987L15.2487 14.082C15.4015 14.2348 15.4779 14.4293 15.4779 14.6654C15.4779 14.9015 15.4015 15.0959 15.2487 15.2487C15.0959 15.4015 14.9015 15.4779 14.6654 15.4779C14.4293 15.4779 14.2348 15.4015 14.082 15.2487L9.9987 11.1654Z"
fill="#969696"
/>
</G>
</Svg>
</View>
<Pressable
onPress={hideBottomSheet}
style={[GenericStyles.alignItemsFlexEnd, GenericStyles.ph16]}
>
<CrossIcon_20 />
</Pressable>
)}
setVisible={hideBottomSheet}
>
<View style={[GenericStyles.ph16]}>
<View style={{ paddingHorizontal: 52 }}>
<Text style={styles.headerTxt}>Connecting you with the customer...</Text>
<Text style={styles.descriptionTxt}>
You will receive a call from NAVI. Post this a call will go to{' '}
<Text style={styles.name}>Abhinav</Text> on{' '}
<Text style={styles.phoneNumber}>XXXXXX9544</Text>
</Text>
<View
style={[
GenericStyles.ph16,
GenericStyles.pv24,
GenericStyles.fill,
GenericStyles.justifyContentSpaceBetween,
]}
>
<View style={GenericStyles.alignCenter}>
<ConnectingToCustomerIcon />
<View style={{ paddingHorizontal: 52, paddingTop: 24 }}>
<Text style={styles.headerTxt}>Connecting you with the customer...</Text>
<Text style={styles.descriptionTxt}>
You will receive a call from NAVI. Post this a call will go to{' '}
<Text style={styles.name}>{customerName ?? '--'}</Text> on{' '}
<Text style={styles.phoneNumber}>{callAttemptedOn ?? '--'}</Text>
</Text>
</View>
</View>
<Button title="Understood" onPress={() => {}} style={[GenericStyles.w100]} />
<Button title="Understood" onPress={hideBottomSheet} style={[GenericStyles.w100]} />
</View>
</BottomSheetWrapper>
);

View File

@@ -9,10 +9,25 @@ import { StyleSheet, View } from 'react-native';
import { getPhoneSourceString } from '@components/utlis/commonFunctions';
import { ICallHistoryItem } from '../interfaces';
import { convertSecondsToDuration, getCallTypeIcon } from '../utils';
import { setCallAttemptedOn } from '@reducers/telephoneNumbersSlice';
import { makeACallToCustomer } from '@actions/callRecordingActions';
import { useAppDispatch, useAppSelector } from '@hooks';
const CallHistoryItem = (props: ICallHistoryItem) => {
const { callHistory, isLastElement } = props;
const { telephoneNumber, sources, callDuration, timestamp, callType } = callHistory || {};
const { callHistory, isLastElement, caseId } = props;
const { telephoneNumber, sources, callDuration, timestamp, callType, telephoneReferenceId } =
callHistory || {};
const loanAccountNumber = useAppSelector(
(state) => state?.allCases?.caseDetails?.[caseId]?.loanAccountNumber
);
const dispatch = useAppDispatch();
const callCustomer = () => {
dispatch(setCallAttemptedOn(telephoneNumber));
dispatch(makeACallToCustomer({ referenceId: telephoneReferenceId, loanAccountNumber }));
};
return (
<View>
<View style={[GenericStyles.row, GenericStyles.w100]}>
@@ -31,14 +46,7 @@ const CallHistoryItem = (props: ICallHistoryItem) => {
GenericStyles.justifyContentSpaceBetween,
]}
>
<IconButton
style={{ height: 36, width: 36 }}
icon={<CallIcon />}
round
onPress={() => {
console.log('object');
}}
/>
<IconButton style={styles.iconButton} icon={<CallIcon />} round onPress={callCustomer} />
<Text style={styles.sources}>{convertSecondsToDuration(callDuration)}</Text>
</View>
</View>
@@ -67,5 +75,9 @@ const styles = StyleSheet.create({
color: COLORS.TEXT.GREY_4,
marginTop: 4,
},
iconButton: {
height: 36,
width: 36,
},
});
export default CallHistoryItem;

View File

@@ -7,6 +7,7 @@ import CallHistoryItem from './CallHistoryItem';
import { useAppDispatch, useAppSelector } from '@hooks';
import { fetchCallHistory } from '@actions/fetchTelephoneNumber';
import { ICallHistory } from '../interfaces';
import NoCallLogsIcon from '@rn-ui-lib/icons/NoCallLogsIcon';
const CallHistory = (props: ICallHistory) => {
const { caseId } = props;
@@ -22,6 +23,7 @@ const CallHistory = (props: ICallHistory) => {
const [loading, setLoading] = useState(false);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchCallHistory({ loanAccountNumber, caseId, setLoading }));
}, []);
@@ -42,9 +44,15 @@ const CallHistory = (props: ICallHistory) => {
if (!callHistoryDetails?.length)
return (
<View
style={[GenericStyles.fill, GenericStyles.centerAlignedRow, GenericStyles.whiteBackground]}
style={[
GenericStyles.fill,
GenericStyles.centerAlignedRow,
GenericStyles.columnDirection,
GenericStyles.whiteBackground,
]}
>
<Text>No call logs exist yet.</Text>
<NoCallLogsIcon />
<Text style={GenericStyles.pt7}>No call logs exist</Text>
</View>
);
@@ -58,6 +66,7 @@ const CallHistory = (props: ICallHistory) => {
<CallHistoryItem
callHistory={callHistory}
isLastElement={index === callHistoryDetails.length - 1}
caseId={caseId}
/>
))}
</View>

View File

@@ -5,9 +5,18 @@ import RenderIcons from '@screens/caseDetails/journeyStepper/RenderIcon';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { IPhoneNumberItem } from '../interfaces';
import { getPhoneSourceString } from '@components/utlis/commonFunctions';
import { useAppSelector } from '@hooks';
import { RootState } from '@store';
const PhoneNumberItem = (props: IPhoneNumberItem) => {
const { number, sourceText } = props;
const { phoneNumber, caseId } = props;
const { number, sources, referenceId } = phoneNumber;
const sourceText = getPhoneSourceString(sources);
const telephoneReferenceId = useAppSelector((state: RootState) => state?.activeCall?.referenceId);
return (
<View>
<View style={[GenericStyles.borderTop, GenericStyles.w100]} />
@@ -20,15 +29,17 @@ const PhoneNumberItem = (props: IPhoneNumberItem) => {
]}
>
<View style={GenericStyles.flex50}>
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mb4]}>
<View style={styles.activeCallCircle} />
<Text style={styles.activeCall}>Active call</Text>
</View>
{telephoneReferenceId === referenceId ? (
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mb4]}>
<View style={styles.activeCallCircle} />
<Text style={styles.activeCall}>Active call</Text>
</View>
) : null}
<Text style={styles.phoneNumber}>{number}</Text>
<Text style={styles.sources}>{sourceText}</Text>
</View>
<View style={[GenericStyles.flex50, GenericStyles.alignItemsFlexEnd]}>
<RenderIcons mobileNumber={number} />
<RenderIcons mobileNumber={number} referenceId={referenceId} caseId={caseId} />
</View>
</View>
</View>

View File

@@ -8,7 +8,6 @@ import {
StyleSheet,
View,
} from 'react-native';
import { getPhoneSourceString } from '@components/utlis/commonFunctions';
import Text from '@rn-ui-lib/components/Text';
import { GenericStyles } from '@rn-ui-lib/styles';
import { COLORS } from '@rn-ui-lib/colors';
@@ -26,6 +25,12 @@ const CustomerNumbers = (props: ICustomerNumbers) => {
const [isRefreshing, setIsRefreshing] = useState(false);
const isExternalAgent = useAppSelector((state: RootState) => state?.user?.isExternalAgent);
const genuineCallCount = useAppSelector(
(state: RootState) => state?.telephoneNumbers?.callActivity?.[caseId]?.genuineCallCount
);
const enableAllTheFeatures = useAppSelector(
(state: RootState) => state?.telephoneNumbers?.callActivity?.[caseId]?.enableFeature
);
const handleAddNewNumberCta = () => {
navigateToScreen(CaseDetailStackEnum.ADD_NEW_NUMBER, { caseId: caseId });
@@ -35,7 +40,7 @@ const CustomerNumbers = (props: ICustomerNumbers) => {
refetchMobileNumbers(setIsRefreshing);
};
const showCallDetailsNudge = !isExternalAgent; // TODO Exotel: Add condition here
const showCallDetailsNudge = !isExternalAgent && enableAllTheFeatures; // TODO Exotel: Add condition here
if (loading)
return (
@@ -75,7 +80,7 @@ const CustomerNumbers = (props: ICustomerNumbers) => {
]}
>
<Text style={styles.greyColor}>Call customer to enable all features</Text>
<Text style={styles.greyColor}>0/2</Text>
<Text style={styles.greyColor}>{genuineCallCount}/2</Text>
</View>
) : null}
<Pressable
@@ -91,8 +96,8 @@ const CustomerNumbers = (props: ICustomerNumbers) => {
<AddNumberIcon />
<Text style={styles.addNewNumberCta}>Add new number</Text>
</Pressable>
{mobileNumbers.map(({ number, sources }) => (
<PhoneNumberItem number={number} sourceText={getPhoneSourceString(sources)} />
{mobileNumbers.map((number) => (
<PhoneNumberItem phoneNumber={number} caseId={caseId} />
))}
</View>
</ScrollView>

View File

@@ -33,7 +33,7 @@ const CallingFlow = (props: ICallingFlow) => {
<CallHistory caseId={caseId} />
)}
</View>
<ConnectingToCustomerBottomSheet />
<ConnectingToCustomerBottomSheet caseId={caseId} />
<CallCannotInitiateBottomSheet />
</>
);

View File

@@ -1,14 +1,15 @@
import { ITelephoneNumbers } from '@reducers/telephoneNumbersSlice';
import { IPhoneSources } from '../interface';
export interface IPhoneNumberItem {
number: string;
sourceText: string;
phoneNumber: ITelephoneNumbers;
caseId: string;
}
export interface ICallCustomer {
route?: {
params?: {
caseId?: string;
route: {
params: {
caseId: string;
};
};
}
@@ -22,12 +23,14 @@ export enum CallType {
export interface ICallHistoryItem {
callHistory: {
telephoneNumber: string;
telephoneReferenceId: string;
sources: IPhoneSources[];
callDuration: number;
timestamp: number;
callType: CallType;
};
isLastElement: boolean;
caseId: string;
}
export interface ICallHistory {
@@ -46,3 +49,24 @@ export enum CallCustomerTabs {
export interface ICallingFlow {
caseId: string;
}
export enum IconType {
WHATSAPP = 'WHATSAPP',
CALL = 'CALL',
COPY = 'COPY',
}
export interface IRenderIcons {
mobileNumber: string;
referenceId: string;
caseId: string;
}
export enum PhoneStateType {
IDLE = 'IDLE',
UNKNOWN = 'UNKNOWN',
}
export interface IConnectingToCustomerBottomSheet {
caseId: string;
}

View File

@@ -1,52 +1,78 @@
import React from 'react';
import { Linking, TouchableOpacity, View } from 'react-native';
import { Linking, StyleSheet, View } from 'react-native';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { WhatsAppText } from '../../../common/Constants';
import { copyToClipboard } from '../../../components/utlis/commonFunctions';
import { toast } from '../../../../RN-UI-LIB/src/components/toast';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
import CopyIconFilled from '../../../assets/icons/CopyIconFilled';
import WhatsAppIcon_20 from '../../../assets/icons/WhatsAppIcon_20';
import { IconProps } from '../../../../RN-UI-LIB/src/Icons/types';
import IconButton from '@rn-ui-lib/components/IconButton';
import CallIcon from './CallIcon';
import { useAppSelector } from '@hooks';
import { useAppDispatch, useAppSelector } from '@hooks';
import { RootState } from '@store';
import PhoneStateModule from '@components/utlis/PhoneState';
import { setCallCannotInitiateBottomSheet } from '@reducers/activeCallSlice';
import { logError } from '@components/utlis/errorUtils';
import { makeACallToCustomer } from '@actions/callRecordingActions';
import { IRenderIcons, IconType, PhoneStateType } from '../CallingFlow/interfaces';
import { setCallAttemptedOn } from '@reducers/telephoneNumbersSlice';
const Icons = {
WHATSAPP: WhatsAppIcon_20,
COPY: CopyIconFilled,
CALL: CallIcon,
};
const Actions = [
{
id: 1,
icon: <WhatsAppIcon_20 />,
type: IconType.WHATSAPP,
},
enum IconType {
WHATSAPP = 'WHATSAPP',
CALL = 'CALL',
COPY = 'COPY',
}
{
id: 2,
icon: <CopyIconFilled />,
type: IconType.COPY,
},
{
id: 3,
icon: <CallIcon />,
type: IconType.CALL,
},
];
interface IRenderIcons {
mobileNumber: string;
closeSheet?: () => void;
}
const RenderIcons: React.FC<IRenderIcons> = ({ mobileNumber, closeSheet }) => {
const lengthOfArray = Object.keys(Icons).length;
const RenderIcons: React.FC<IRenderIcons> = ({ mobileNumber, referenceId, caseId }) => {
const loanAccountNumber = useAppSelector(
(state) => state?.allCases?.caseDetails?.[caseId]?.loanAccountNumber
);
const enableAllTheFeatures = useAppSelector(
(state: RootState) => state?.telephoneNumbers?.callActivity?.[caseId]?.enableFeature
);
const lengthOfArray = Actions.length;
const isExternalAgent = useAppSelector((state: RootState) => state?.user?.isExternalAgent);
const handleIconPress = (mobileNumber: string, icon: React.FC<IconProps>) => {
switch (icon) {
case Icons.CALL:
if(isExternalAgent) {
const dispatch = useAppDispatch();
const handleIconPress = (actionType: IconType) => {
switch (actionType) {
case IconType.CALL:
if (isExternalAgent) {
Linking.openURL(`tel:${mobileNumber}`);
} else {
// TODO Exotel: Call API to connect the call
return;
}
PhoneStateModule.getCurrentCallState()
.then((callState: string) => {
if (callState === PhoneStateType.IDLE || callState === PhoneStateType.UNKNOWN) {
dispatch(setCallAttemptedOn(mobileNumber))
dispatch(makeACallToCustomer({ referenceId, loanAccountNumber }));
} else {
dispatch(setCallCannotInitiateBottomSheet(true));
}
})
.catch((error: Error) => {
logError(error, 'Error in getting call state');
});
break;
case Icons.WHATSAPP:
case IconType.WHATSAPP:
const phoneNumber = mobileNumber?.startsWith('+91') ? mobileNumber : `+91${mobileNumber}`;
Linking.openURL(`whatsapp://send?phone=${phoneNumber}&text=${WhatsAppText}`);
break;
case Icons.COPY:
case IconType.COPY:
copyToClipboard(mobileNumber);
toast({
text1: 'Phone Number Copied',
@@ -54,53 +80,26 @@ const RenderIcons: React.FC<IRenderIcons> = ({ mobileNumber, closeSheet }) => {
});
break;
}
closeSheet?.();
};
return (
<View style={[GenericStyles.centerAlignedRow]}>
{Object.values(Icons).map((Icon, index) => {
{Actions?.map((actionDetails, index) => {
return (
// <TouchableOpacity
// key={index}
// style={[
// index !== 0 ? GenericStyles.ml10 : null,
// index !== lengthOfArray - 1 ? GenericStyles.mr10 : null,
// GenericStyles.p8,
// GenericStyles.border,
// {borderRadius: 50},
// GenericStyles.centerAligned,
// {
// height: 36,
// width: 36,
// },
// ]}
// onPress={() => {
// handleIconPress(mobileNumber, Icon);
// }}
// >
// <View>
// <Icon size={14} fillColor={COLORS.BASE.BLUE} />
// </View>
// </TouchableOpacity>
<IconButton
style={[
index !== 0 ? GenericStyles.ml10 : null,
index !== lengthOfArray - 1 ? GenericStyles.mr10 : null,
GenericStyles.p8,
GenericStyles.border,
{ borderRadius: 50 },
GenericStyles.centerAligned,
{
height: 36,
width: 36,
},
styles.iconContainer,
]}
onPress={() => {
handleIconPress(mobileNumber, Icon);
handleIconPress(actionDetails?.type);
}}
disabled
icon={<Icon size={20} fillColor={COLORS.BASE.BLUE} />}
disabled={actionDetails?.type !== IconType.CALL ? !enableAllTheFeatures : false}
icon={actionDetails?.icon}
/>
);
})}
@@ -108,4 +107,12 @@ const RenderIcons: React.FC<IRenderIcons> = ({ mobileNumber, closeSheet }) => {
);
};
const styles = StyleSheet.create({
iconContainer: {
borderRadius: 50,
height: 36,
width: 36,
},
});
export default RenderIcons;

View File

@@ -10,9 +10,9 @@ import React from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
const CallInfo = () => {
const phoneNumber = useAppSelector((state: RootState) => state.activeCall.phoneNumber);
const customerName = useAppSelector((state: RootState) => state.activeCall.customerName);
const caseId = useAppSelector((state: RootState) => state.activeCall.caseId);
const phoneNumber = useAppSelector((state: RootState) => state?.activeCall?.phoneNumber);
const customerName = useAppSelector((state: RootState) => state?.activeCall?.customerName);
const caseId = useAppSelector((state: RootState) => state?.activeCall?.caseId);
const onPressHandler = () => {
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {

View File

@@ -7,7 +7,7 @@ const Layout: React.FC<PropsWithChildren> = (props) => {
return (
<>
<Offline />
<CallInfo />
{/* <CallInfo /> */}
{props.children}
</>
);