diff --git a/README.md b/README.md
index 51210f4..f681dd4 100644
--- a/README.md
+++ b/README.md
@@ -123,9 +123,9 @@ function displayNotification(uid:string, avatar?:string, timeout?:number, foregr
| `channelName` | `string` | Channel name of the notification. | Yes |
| `notificationIcon` | `string` | Icon of the notification (mipmap). | Yes |
| `notificationTitle` | `string` | Title of the notification. | Yes |
-| `notificationBody` | `string` | Body text of the notification. | Yes |
-| `answerText` | `string` | Label for the answer button. | Yes |
-| `declineText` | `string` | Label for the decline button. | Yes |
+| `notificationBody` | `string` | Body text of the notification. On Android 12 and above, if the notificationBody is empty, the incoming call notification will display the description from CallStyle instead of this property. | No |
+| `answerText` | `string` | Label for the answer button. On Android 12 and above, the incoming call notification displays the answerText from CallStyle instead of this property. | Yes |
+| `declineText` | `string` | Label for the decline button. On Android 12 and above, the incoming call notification displays the declineText from CallStyle instead of this property. | Yes |
| `notificationColor` | `string` (optional) | Color of the notification. | No |
| `notificationSound` | `string` (optional) | Sound for the notification (raw). | No |
| `mainComponent` | `string` (optional) | Main component name for a custom incoming call screen. | No |
diff --git a/example/android/app/src/main/res/raw/skype_ring.mp3 b/example/android/app/src/main/res/raw/skype_ring.mp3
new file mode 100644
index 0000000..a770eb2
Binary files /dev/null and b/example/android/app/src/main/res/raw/skype_ring.mp3 differ
diff --git a/example/package.json b/example/package.json
index 99d1ee4..8f2963b 100644
--- a/example/package.json
+++ b/example/package.json
@@ -10,10 +10,13 @@
"build:ios": "cd ios && xcodebuild -workspace FullScreenNotificationIncomingCallExample.xcworkspace -scheme FullScreenNotificationIncomingCallExample -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"
},
"dependencies": {
+ "@react-native-community/checkbox": "^0.5.17",
"@react-native-community/masked-view": "^0.1.11",
+ "@react-native-picker/picker": "^2.9.0",
"@react-navigation/native": "^5.8.0",
"@react-navigation/stack": "^5.10.0",
"@types/react": "18.3.1",
+ "add": "^2.0.6",
"react": "18.2.0",
"react-native": "0.74.1",
"react-native-background-timer": "^2.4.1",
@@ -22,7 +25,8 @@
"react-native-permissions": "^3.8.0",
"react-native-safe-area-context": "^4.4.1",
"react-native-screens": "3.29.0",
- "uuid-random": "^1.3.2"
+ "uuid-random": "^1.3.2",
+ "yarn": "^1.22.22"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/example/src/CustomIncomingCall/index.tsx b/example/src/CustomIncomingCall/index.tsx
index ad6e023..624d37e 100644
--- a/example/src/CustomIncomingCall/index.tsx
+++ b/example/src/CustomIncomingCall/index.tsx
@@ -1,29 +1,46 @@
import * as React from 'react';
-import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import type { CustomIncomingActivityProps } from 'react-native-full-screen-notification-incoming-call';
import RNNotificationCall from '../../../src/index';
+
export default function CustomIncomingCall(props: CustomIncomingActivityProps) {
console.log('props===', props);
const payload = JSON.parse(props.payload);
console.log('payload', payload);
+
return (
- {
- RNNotificationCall.declineCall(props.uuid, props.payload);
+ {/* Caller Info */}
+ {props.name || 'Unknown Caller'}
+
- Decline
-
- {
- RNNotificationCall.answerCall(props.uuid, props.payload);
- }}
- >
- Answer
-
+ style={styles.callerImage}
+ />
+
+ {/* Decline and Answer Buttons */}
+
+ {
+ RNNotificationCall.declineCall(props.uuid, props.payload);
+ }}
+ >
+ Decline
+
+
+ {
+ RNNotificationCall.answerCall(props.uuid, props.payload);
+ }}
+ >
+ Answer
+
+
);
}
@@ -33,11 +50,43 @@ const styles = StyleSheet.create({
flex: 1,
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: 'white',
+ backgroundColor: '#f2f2f2',
+ paddingHorizontal: 20,
+ },
+ callerName: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginVertical: 10,
+ color: '#333',
+ },
+ callerImage: {
+ width: 100,
+ height: 100,
+ borderRadius: 50,
+ marginBottom: 30,
+ },
+ buttonContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '80%',
+ marginTop: 30,
+ },
+ button: {
+ flex: 1,
+ alignItems: 'center',
+ paddingVertical: 15,
+ marginHorizontal: 10,
+ borderRadius: 10,
+ },
+ declineButton: {
+ backgroundColor: '#ff4d4d',
+ },
+ answerButton: {
+ backgroundColor: '#4CAF50',
},
- box: {
- width: 60,
- height: 60,
- marginVertical: 20,
+ buttonText: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
},
});
diff --git a/example/src/Detail/index.tsx b/example/src/Detail/index.tsx
index 11c7f8d..fc4a2a6 100644
--- a/example/src/Detail/index.tsx
+++ b/example/src/Detail/index.tsx
@@ -3,18 +3,62 @@ import { useNavigation } from '@react-navigation/native';
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
import { CallKeepService } from '../services/CallKeepService';
-export default function Detail() {
+export default function InCallScreen() {
const navigation = useNavigation();
+ const [isMuted, setIsMuted] = React.useState(false); // Mute state
+ const [isOnHold, setIsOnHold] = React.useState(false); // Hold state
+
+ // Handle end call
+ const handleEndCall = () => {
+ CallKeepService.instance().endAllCall();
+ navigation.goBack();
+ };
+
+ // Handle mute toggle
+ const handleMute = () => {
+ setIsMuted((prev) => !prev);
+ // Implement mute logic if needed here (e.g., CallKeepService mute call)
+ };
+
+ // Handle hold toggle
+ const handleHold = () => {
+ setIsOnHold((prev) => !prev);
+ // Implement hold logic if needed here
+ };
+
return (
- {
- navigation.goBack();
- CallKeepService.instance().endAllCall();
- }}
- >
- Go back
-
+
+ {/* Display the caller's info or call status */}
+ In Call
+ Caller: John Doe
+
+ {/* Call Control Buttons */}
+
+
+ {isMuted ? 'Unmute' : 'Mute'}
+
+
+
+
+ {isOnHold ? 'Resume' : 'Hold'}
+
+
+
+
+ End Call
+
+
+
);
}
@@ -22,13 +66,48 @@ export default function Detail() {
const styles = StyleSheet.create({
container: {
flex: 1,
+ justifyContent: 'center',
alignItems: 'center',
+ backgroundColor: '#333',
+ },
+ inCallContainer: {
justifyContent: 'center',
- backgroundColor: 'blue',
+ alignItems: 'center',
+ },
+ callStatus: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ marginBottom: 20,
+ },
+ callerInfo: {
+ fontSize: 18,
+ color: 'white',
+ marginBottom: 40,
+ },
+ inCallButtons: {
+ flexDirection: 'row', // Use row to align buttons horizontally
+ justifyContent: 'space-around', // Space between buttons
+ width: '80%', // Give enough space for buttons to fit
+ },
+ button: {
+ width: 80, // Give each button a fixed width
+ paddingVertical: 15,
+ borderRadius: 8,
+ alignItems: 'center',
+ },
+ muteButton: {
+ backgroundColor: '#FFC107', // Yellow for mute
+ },
+ holdButton: {
+ backgroundColor: '#2196F3', // Blue for hold
+ },
+ endCallButton: {
+ backgroundColor: '#FF5722', // Red for end call
},
- box: {
- width: 60,
- height: 60,
- marginVertical: 20,
+ buttonText: {
+ color: 'white',
+ fontSize: 14,
+ fontWeight: 'bold',
},
});
diff --git a/example/src/Home/index.tsx b/example/src/Home/index.tsx
index f93b63a..291aa6d 100644
--- a/example/src/Home/index.tsx
+++ b/example/src/Home/index.tsx
@@ -1,54 +1,172 @@
-import * as React from 'react';
-import RNNotificationCall from '../../../src/index';
-import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ TextInput,
+ Image,
+ StyleSheet,
+ TouchableOpacity,
+} from 'react-native';
import ramdomUuid from 'uuid-random';
import { useNavigation } from '@react-navigation/native';
import { CallKeepService } from '../services/CallKeepService';
+import CheckBox from '@react-native-community/checkbox';
+import { Picker } from '@react-native-picker/picker';
CallKeepService.instance().setupCallKeep();
-export default function Home() {
+
+// Define ringtone channel IDs
+const ringtoneChannelIds = {
+ default: 'com.abc.incomingcall',
+ skype_ring: 'com.skype.incomingcall',
+};
+
+const IncomingCallDemo = () => {
const navigation = useNavigation();
CallKeepService.navigation = navigation;
- const display = () => {
- // Start a timer that runs once after X milliseconds
- //rest of code will be performing for iOS on background too
- const uuid = ramdomUuid();
- CallKeepService.instance().displayCall(uuid);
- };
- const onHide = () => {
- RNNotificationCall.hideNotification();
+
+ const [callerName, setCallerName] = useState('John Doe');
+ const [callerImageURL, setCallerImageURL] = useState(
+ 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQKet-b99huP_BtZT_HUqvsaSz32lhrcLtIDQ&s'
+ );
+ const [selectedRingtone, setSelectedRingtone] = useState<
+ 'default' | 'skype_ring'
+ >('default');
+ const [useCustomIncomingCallUI, setUseCustomIncomingCallUI] = useState(false);
+ const [isVideoCall, setIsVideoCall] = useState(true);
+
+ // Function to trigger the incoming call notification
+ const displayIncomingCall = () => {
+ const callUUID = ramdomUuid();
+ CallKeepService.instance().displayCall({
+ uuid: callUUID,
+ handle: 'number',
+ localizedCallerName: callerName,
+ handleType: 'number',
+ callerImage: callerImageURL,
+ hasVideo: isVideoCall,
+ other: {
+ ringtone: selectedRingtone !== 'default' ? selectedRingtone : null,
+ mainComponent: useCustomIncomingCallUI ? 'MyReactNativeApp' : null,
+ channelId:
+ ringtoneChannelIds[selectedRingtone] || ringtoneChannelIds.default,
+ },
+ });
};
+
return (
- navigation.navigate('Detail')}
- style={styles.button}
- >
- Go to detail
-
-
- Display
-
+ Incoming Call Notification
+
+ {/* Input for Caller Name */}
+ Caller Name:
+
+
+ {/* Input for Caller Image */}
+ Caller Image URL:
+
+
+
+ {!!callerImageURL && (
+
+ )}
+
+
+ {/* Ringtone Picker */}
+ Ringtone:
+
+
+
+
+
+
-
- Hide
+ {/* Checkbox for Is Video Incoming */}
+ Is Video Call:
+
+
+ {/* Checkbox for Custom UI */}
+ Use Custom Incoming Call UI:
+
+
+ {/* Button to Simulate Incoming Call */}
+
+ Display Incoming Call
);
-}
+};
const styles = StyleSheet.create({
container: {
flex: 1,
+ padding: 20,
+ },
+ header: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginBottom: 20,
+ textAlign: 'center',
+ },
+ label: {
+ fontSize: 16,
+ marginVertical: 10,
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#ddd',
+ borderRadius: 8,
+ padding: 10,
+ marginBottom: 10,
+ color: 'black',
+ },
+ imagePreviewContainer: {
alignItems: 'center',
- justifyContent: 'center',
+ marginBottom: 20,
+ },
+ imagePreview: {
+ width: 100,
+ height: 100,
+ borderRadius: 50,
},
- box: {
- width: 60,
- height: 60,
- marginVertical: 20,
+ pickerContainer: {
+ borderColor: 'gray',
+ borderWidth: 1,
},
button: {
- marginVertical: 10,
+ backgroundColor: '#4CAF50',
+ padding: 15,
+ borderRadius: 8,
+ alignItems: 'center',
+ marginTop: 20,
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
},
});
+
+export default IncomingCallDemo;
diff --git a/example/src/services/CallKeepService.ts b/example/src/services/CallKeepService.ts
index 52d987f..8efe0b6 100644
--- a/example/src/services/CallKeepService.ts
+++ b/example/src/services/CallKeepService.ts
@@ -4,6 +4,7 @@ import RNNotificationCall, {
type DeclinePayload,
} from '../../../src/index';
import RNCallKeep from 'react-native-callkeep';
+import type { HandleType } from 'react-native-callkeep';
import {
check,
PERMISSIONS,
@@ -15,7 +16,7 @@ const appName = 'Incoming-Test';
const isAndroid = Platform.OS === 'android';
const answerOption = {
channelId: 'com.abc.incomingcall',
- channelName: 'Incoming video call',
+ channelName: 'Incoming Call',
notificationIcon: 'ic_launcher', //mipmap
notificationTitle: 'Linh Vo',
answerText: 'Answer',
@@ -28,6 +29,7 @@ const answerOption = {
export class CallKeepService {
private static _instance?: CallKeepService;
static navigation: any;
+ private static otherInformation: any;
constructor() {
//setup callkeep
// this.setupCallKeep();
@@ -105,25 +107,35 @@ export class CallKeepService {
RNCallKeep.addEventListener('endCall', this.onCallKeepEndCallAction);
if (isAndroid) {
//event only on android
- RNCallKeep.addEventListener('showIncomingCallUi', ({ callUUID }) => {
- RNNotificationCall.displayNotification(
- callUUID,
- 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQKet-b99huP_BtZT_HUqvsaSz32lhrcLtIDQ&s',
- 30000,
- {
- ...answerOption,
- channelId: 'com.abc.incomingcall',
- channelName: 'Incoming video call',
- notificationTitle: 'Linh Vo',
- notificationBody: 'Incoming video call',
- isVideo: true,
- // mainComponent: "MyReactNativeApp",
- payload: {
- extra: 'extra',
- },
- }
- );
- });
+ RNCallKeep.addEventListener(
+ 'showIncomingCallUi',
+ // @ts-ignore:next-line
+ ({ callUUID, name, hasVideo = 'false' }) => {
+ const isVideo = hasVideo === 'true';
+ RNNotificationCall.displayNotification(
+ callUUID,
+ 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQKet-b99huP_BtZT_HUqvsaSz32lhrcLtIDQ&s',
+ 30000,
+ {
+ ...answerOption,
+ channelId:
+ CallKeepService.otherInformation?.channelId ||
+ 'com.abc.incomingcall',
+ channelName: 'Incoming Call',
+ notificationTitle: name,
+ notificationBody: isVideo
+ ? 'Incoming video call'
+ : 'Incoming call',
+ isVideo: isVideo,
+ mainComponent: CallKeepService.otherInformation?.mainComponent,
+ notificationSound: CallKeepService.otherInformation?.ringtone,
+ payload: {
+ extra: 'extra',
+ },
+ }
+ );
+ }
+ );
// Listen to headless action events
RNNotificationCall.addEventListener(
'endCall',
@@ -167,18 +179,37 @@ export class CallKeepService {
//You need to call RNCallKeep.endCall(callUUID) to end call
}
- async displayCall(uuid: string) {
+ async displayCall(data: {
+ uuid: string;
+ handle: string;
+ localizedCallerName: string;
+ handleType: HandleType;
+ hasVideo: boolean;
+ callerImage?: string;
+ other?: any;
+ }) {
+ const {
+ uuid,
+ handle,
+ localizedCallerName,
+ handleType,
+ hasVideo,
+ callerImage,
+ other,
+ } = data;
const granted = await check(PERMISSIONS.ANDROID.READ_PHONE_NUMBERS);
//only display call when permission granted
if (granted !== RESULTS.GRANTED) return;
- console.log('display call', uuid);
+ CallKeepService.otherInformation = other;
RNCallKeep.displayIncomingCall(
uuid,
- 'Linh Vo',
- 'Linh Vo',
- 'number',
- true,
- undefined
+ handle,
+ localizedCallerName,
+ handleType,
+ hasVideo,
+ {
+ callerImage,
+ }
);
}
endAllCall() {
diff --git a/src/index.tsx b/src/index.tsx
index c763a24..b8d2ed5 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -57,7 +57,7 @@ export interface ForegroundOptionsModel {
/** Title of the notification */
notificationTitle: string;
/** Body of the notification */
- notificationBody: string;
+ notificationBody?: string | null;
/** Label for the answer button */
answerText: string;
/** Label for the decline button */
@@ -76,7 +76,7 @@ export interface ForegroundOptionsModel {
/**
* Properties for the custom incoming activity
*/
-export interface CustomIncomingActivityProps {
+export interface CustomIncomingActivityProps extends ForegroundOptionsModel {
/**
* Unique identifier for the call.
* This ID helps to distinguish between different call instances.
@@ -86,6 +86,8 @@ export interface CustomIncomingActivityProps {
info?: string;
/** Unique identifier for the call */
uuid: string;
+ /** Caller name */
+ name: string;
/**
* Additional data related to the call (optional).
* This can be any JSON string containing extra information about the call.
diff --git a/yarn.lock b/yarn.lock
index 7859a00..04e1e35 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2615,6 +2615,20 @@ __metadata:
languageName: node
linkType: hard
+"@react-native-community/checkbox@npm:^0.5.17":
+ version: 0.5.17
+ resolution: "@react-native-community/checkbox@npm:0.5.17"
+ peerDependencies:
+ react: "*"
+ react-native: ">= 0.62"
+ react-native-windows: ">=0.62"
+ peerDependenciesMeta:
+ react-native-windows:
+ optional: true
+ checksum: 98cddff11b976a1f712bfc4d7de946d5e102108bedc016bbcb668b3917757dd5092b5b93f651f740508dd8437ba3a0ce5ca15cef3ebf7bcfc15a490b0b7d0d6b
+ languageName: node
+ linkType: hard
+
"@react-native-community/cli-clean@npm:13.6.6":
version: 13.6.6
resolution: "@react-native-community/cli-clean@npm:13.6.6"
@@ -2806,6 +2820,16 @@ __metadata:
languageName: node
linkType: hard
+"@react-native-picker/picker@npm:^2.9.0":
+ version: 2.9.0
+ resolution: "@react-native-picker/picker@npm:2.9.0"
+ peerDependencies:
+ react: "*"
+ react-native: "*"
+ checksum: 3f88ecbad85f071861bbd16c0d4891dd5eff13d638e6ecfe68da279a07e0a31dfa7844ae329fe2a2144695456f1c96d1be38e10185475dd164f8e918d6d083a1
+ languageName: node
+ linkType: hard
+
"@react-native/assets-registry@npm:0.74.83":
version: 0.74.83
resolution: "@react-native/assets-registry@npm:0.74.83"
@@ -3633,6 +3657,13 @@ __metadata:
languageName: node
linkType: hard
+"add@npm:^2.0.6":
+ version: 2.0.6
+ resolution: "add@npm:2.0.6"
+ checksum: e2d23d40494565dfed4acd65e478570c444db5ac6c053551ed429c39ea0f2c99d83df63e7befec936df601827d2254d06a2fb6f7dcfd2022e810b25eab818b8c
+ languageName: node
+ linkType: hard
+
"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1":
version: 7.1.1
resolution: "agent-base@npm:7.1.1"
@@ -11094,13 +11125,16 @@ __metadata:
"@babel/core": ^7.20.0
"@babel/preset-env": ^7.20.0
"@babel/runtime": ^7.20.0
+ "@react-native-community/checkbox": ^0.5.17
"@react-native-community/masked-view": ^0.1.11
+ "@react-native-picker/picker": ^2.9.0
"@react-native/babel-preset": 0.74.83
"@react-native/metro-config": 0.74.83
"@react-native/typescript-config": 0.74.83
"@react-navigation/native": ^5.8.0
"@react-navigation/stack": ^5.10.0
"@types/react": 18.3.1
+ add: ^2.0.6
babel-plugin-module-resolver: ^5.0.0
react: 18.2.0
react-native: 0.74.1
@@ -11111,6 +11145,7 @@ __metadata:
react-native-safe-area-context: ^4.4.1
react-native-screens: 3.29.0
uuid-random: ^1.3.2
+ yarn: ^1.22.22
languageName: unknown
linkType: soft
@@ -13755,6 +13790,16 @@ __metadata:
languageName: node
linkType: hard
+"yarn@npm:^1.22.22":
+ version: 1.22.22
+ resolution: "yarn@npm:1.22.22"
+ bin:
+ yarn: bin/yarn.js
+ yarnpkg: bin/yarn.js
+ checksum: 59aeef5ccfd3347287f939448e6d3594f0a42f74025b9bdc2a277641c1d4070c07a38b6e7c35e695f77410b0269a5a43c78535786564f86f39c9f781e6efa311
+ languageName: node
+ linkType: hard
+
"yn@npm:3.1.1":
version: 3.1.1
resolution: "yn@npm:3.1.1"