Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
whoscall
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Wei Han
whoscall
Commits
7a905f79
Commit
7a905f79
authored
Sep 03, 2025
by
Wei Han
Committed by
alep
Sep 04, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
outgoing call bug fixed
parent
f67df9f1
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
150 additions
and
58 deletions
+150
-58
App.tsx
App.tsx
+91
-36
IncomingCallService.kt
...oid/app/src/main/java/com/whoscall/IncomingCallService.kt
+7
-1
MainApplication.kt
android/app/src/main/java/com/whoscall/MainApplication.kt
+1
-1
MyAppPackage.kt
android/app/src/main/java/com/whoscall/MyAppPackage.kt
+17
-0
OverlayPermissionModule.kt
...app/src/main/java/com/whoscall/OverlayPermissionModule.kt
+27
-0
Podfile
ios/Podfile
+0
-6
Podfile.lock
ios/Podfile.lock
+1
-1
project.pbxproj
ios/whoscall.xcodeproj/project.pbxproj
+0
-0
Info.plist
ios/whoscall/Info.plist
+0
-5
CallDirectoryHandler.swift
ios/whoscallDirectoryExtension/CallDirectoryHandler.swift
+4
-1
Info.plist
ios/whoscallDirectoryExtension/Info.plist
+2
-7
No files found.
App.tsx
View file @
7a905f79
import
React
,
{
useEffect
,
useState
,
useCallback
}
from
'react'
;
import
{
View
,
Text
,
Button
,
StyleSheet
,
Alert
,
Linking
,
Platform
,
NativeModules
}
from
'react-native'
;
import
{
check
,
request
,
PERMISSIONS
,
RESULTS
}
from
'react-native-permissions'
;
import
AsyncStorage
from
'@react-native-async-storage/async-storage'
;
//
import AsyncStorage from '@react-native-async-storage/async-storage';
import
DeviceInfo
from
'react-native-device-info'
;
const
permissionsList
=
[
const
permissionsList
=
Platform
.
select
({
android
:
[
{
key
:
'READ_PHONE_STATE'
,
android
:
PERMISSIONS
.
ANDROID
.
READ_PHONE_STATE
},
{
key
:
'READ_CALL_LOG'
,
android
:
PERMISSIONS
.
ANDROID
.
READ_CALL_LOG
},
{
key
:
'Display Over Other Apps'
,
android
:
null
},
// overlay special case
];
],
ios
:
[{
key
:
'Call Directory Extension'
,
ios
:
true
}],
})
!
;
const
{
OverlayPermission
}
=
NativeModules
;
async
function
saveVIPNumber
(
name
:
string
,
number
:
string
)
{
type
CallDirectoryManagerType
=
{
getEnabledStatusForExtension
(
identifier
:
string
):
Promise
<
number
>
;
reloadExtension
(
identifier
:
string
):
Promise
<
boolean
>
;
};
const
CallDirectoryManager
:
CallDirectoryManagerType
|
undefined
=
NativeModules
.
CallDirectoryManager
;
// async function saveVIPNumber(name: string, number: string) {
// try {
// await AsyncStorage.setItem('vipName', name);
// await AsyncStorage.setItem('vipNumber', number);
// const {VIPStorage} = NativeModules;
// if (!VIPStorage) {
// console.error("❌ VIPStorage is undefined. Did you register StoragePackage in MainApplication?");
// return;
// }
// await VIPStorage.saveVIP(name, number);
// console.log('VIPStorage from NativeModules:', NativeModules.VIPStorage);
// } catch (error) {
// console.error('Failed to save VIP info:', error);
// }
// }
async
function
checkOverlayPermission
()
{
if
(
Platform
.
OS
===
'android'
)
{
try
{
await
AsyncStorage
.
setItem
(
'vipName'
,
name
);
await
AsyncStorage
.
setItem
(
'vipNumber'
,
number
);
const
{
VIPStorage
}
=
NativeModules
;
if
(
!
VIPStorage
)
{
console
.
error
(
"❌ VIPStorage is undefined. Did you register StoragePackage in MainApplication?"
);
return
;
const
granted
:
boolean
=
await
OverlayPermission
.
hasPermission
();
if
(
!
granted
)
{
Alert
.
alert
(
'Overlay Permission Required'
,
'This app needs permission to display over other apps.'
,
[{
text
:
'Open Settings'
,
onPress
:
openOverlaySettings
}]
);
}
await
VIPStorage
.
saveVIP
(
name
,
number
);
console
.
log
(
'VIPStorage from NativeModules:'
,
NativeModules
.
VIPStorage
);
}
catch
(
error
)
{
console
.
error
(
'Failed to save VIP info:'
,
error
);
return
granted
;
}
catch
(
e
)
{
console
.
error
(
'Error checking overlay permission:'
,
e
);
return
false
;
}
}
else
{
return
true
;
// iOS doesn't require this
}
}
const
openOverlaySettings
=
async
()
=>
{
if
(
Platform
.
OS
===
'android'
)
{
const
pkg
=
DeviceInfo
.
getBundleId
();
// gets your app package name
try
{
await
Linking
.
openSettings
();
// fallback
await
Linking
.
openURL
(
`package:
${
pkg
}
`
);
}
catch
(
err
)
{
console
.
warn
(
'Failed to open overlay settings:'
,
err
);
}
}
};
export
default
function
App
()
{
const
[
statuses
,
setStatuses
]
=
useState
<
Record
<
string
,
string
>>
({});
const
checkAllPermissions
=
useCallback
(
async
()
=>
{
const
newStatuses
:
Record
<
string
,
string
>
=
{};
if
(
Platform
.
OS
===
'ios'
)
{
try
{
if
(
CallDirectoryManager
)
{
const
status
=
await
CallDirectoryManager
.
getEnabledStatusForExtension
(
"com.whoscall.app.CallDirectoryExtension"
);
newStatuses
[
'Call Directory Extension'
]
=
status
===
2
?
RESULTS
.
GRANTED
:
RESULTS
.
DENIED
;
}
else
{
console
.
warn
(
"⚠️ CallDirectoryManager native module not available"
);
newStatuses
[
'Call Directory Extension'
]
=
RESULTS
.
DENIED
;
}
}
catch
(
e
)
{
console
.
error
(
"Error checking Call Directory extension:"
,
e
);
newStatuses
[
'Call Directory Extension'
]
=
RESULTS
.
DENIED
;
}
}
else
{
for
(
const
perm
of
permissionsList
)
{
if
(
perm
.
key
===
'Display Over Other Apps'
)
{
// Overlay not checkable in RN
newStatuses
[
perm
.
key
]
=
RESULTS
.
DENIED
;
const
granted
=
await
checkOverlayPermission
();
newStatuses
[
perm
.
key
]
=
granted
?
RESULTS
.
GRANTED
:
RESULTS
.
DENIED
;
continue
;
}
const
result
=
await
check
(
perm
.
android
!
);
if
(
'android'
in
perm
&&
perm
.
android
)
{
const
result
=
await
check
(
perm
.
android
);
newStatuses
[
perm
.
key
]
=
result
;
}
}
setStatuses
(
newStatuses
);
...
...
@@ -53,43 +114,37 @@ export default function App() {
'This app needs permission to display over other apps.'
,
[{
text
:
'Open Settings'
,
onPress
:
openOverlaySettings
}]
);
}
else
{
const
result
=
await
request
(
perm
.
android
!
);
}
else
if
(
'android'
in
perm
&&
perm
.
android
)
{
const
result
=
await
request
(
perm
.
android
);
setStatuses
(
prev
=>
({
...
prev
,
[
perm
.
key
]:
result
}));
}
}
if
(
Platform
.
OS
as
string
===
'ios'
)
{
Alert
.
alert
(
"Enable Caller ID"
,
"Go to Settings → Phone → Call Blocking & Identification and enable [Your App] to see caller ID labels."
);
}
}
}
},[]);
},
[]);
useEffect
(()
=>
{
console
.
log
(
"PermissionsScreen mounted"
);
console
.
log
(
"All NativeModules keys:"
,
Object
.
keys
(
NativeModules
));
console
.
log
(
"VIPStorage:"
,
NativeModules
.
VIPStorage
);
//
console.log("VIPStorage:", NativeModules.VIPStorage);
saveVIPNumber
(
'VIP User'
,
'+60123456789'
);
//
saveVIPNumber('VIP User', '+60123456789');
checkAllPermissions
();
},[
checkAllPermissions
]);
// Open Overlay Settings
const
openOverlaySettings
=
async
()
=>
{
if
(
Platform
.
OS
===
'android'
)
{
const
pkg
=
DeviceInfo
.
getBundleId
();
// gets your app package name
const
url
=
`package:
${
pkg
}
`
;
try
{
await
Linking
.
openSettings
();
// fallback
await
Linking
.
openURL
(
`android.settings.action.MANAGE_OVERLAY_PERMISSION/
${
url
}
`
);
}
catch
(
err
)
{
console
.
warn
(
'Failed to open overlay settings:'
,
err
);
}
}
};
const
allGranted
=
Object
.
values
(
statuses
).
every
(
s
=>
s
===
RESULTS
.
GRANTED
);
return
(
<
View
style=
{
styles
.
container
}
>
<
Text
style=
{
styles
.
title
}
>
Permissions Status
</
Text
>
{
permissionsList
.
map
(
perm
=>
(
{
(
permissionsList
??
[])
.
map
(
perm
=>
(
<
View
key=
{
perm
.
key
}
style=
{
styles
.
row
}
>
<
Text
style=
{
styles
.
text
}
>
{
perm
.
key
}
</
Text
>
<
Text
...
...
android/app/src/main/java/com/whoscall/IncomingCallService.kt
View file @
7a905f79
...
...
@@ -13,10 +13,16 @@ import android.content.SharedPreferences
class
IncomingCallService
:
CallScreeningService
()
{
override
fun
onScreenCall
(
callDetails
:
Details
)
{
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
Q
)
{
if
(
callDetails
.
callDirection
!=
Details
.
DIRECTION_INCOMING
)
{
// Not an incoming call, ignore
return
}
}
val
number
=
callDetails
.
handle
?.
schemeSpecificPart
?:
"Unknown"
// Lookup VIPs
val
prefs
=
getSharedPreferences
(
"WhoscallPrefs"
,
MODE_PRIVATE
)
//
val prefs = getSharedPreferences("WhoscallPrefs", MODE_PRIVATE)
val
vipNumber
=
"+60108222562"
val
vipName
=
"Arvin"
...
...
android/app/src/main/java/com/whoscall/MainApplication.kt
View file @
7a905f79
...
...
@@ -19,7 +19,7 @@ class MainApplication : Application(), ReactApplication {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add
(
StoragePackage
())
add
(
MyAppPackage
())
}
override
fun
getJSMainModuleName
():
String
=
"index"
...
...
android/app/src/main/java/com/whoscall/MyAppPackage.kt
0 → 100644
View file @
7a905f79
// MyAppPackage.kt
package
com.whoscall
import
com.facebook.react.ReactPackage
import
com.facebook.react.bridge.NativeModule
import
com.facebook.react.bridge.ReactApplicationContext
import
com.facebook.react.uimanager.ViewManager
class
MyAppPackage
:
ReactPackage
{
override
fun
createNativeModules
(
reactContext
:
ReactApplicationContext
):
List
<
NativeModule
>
{
return
listOf
(
OverlayPermissionModule
(
reactContext
))
}
override
fun
createViewManagers
(
reactContext
:
ReactApplicationContext
):
List
<
ViewManager
<*,
*>>
{
return
emptyList
()
}
}
android/app/src/main/java/com/whoscall/OverlayPermissionModule.kt
0 → 100644
View file @
7a905f79
// OverlayPermissionModule.kt
package
com.whoscall
import
com.facebook.react.bridge.ReactApplicationContext
import
com.facebook.react.bridge.ReactContextBaseJavaModule
import
com.facebook.react.bridge.ReactMethod
import
com.facebook.react.bridge.Promise
import
android.os.Build
import
android.provider.Settings
class
OverlayPermissionModule
(
reactContext
:
ReactApplicationContext
)
:
ReactContextBaseJavaModule
(
reactContext
)
{
override
fun
getName
():
String
{
return
"OverlayPermission"
}
@ReactMethod
fun
hasPermission
(
promise
:
Promise
)
{
val
canDraw
=
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
M
)
{
Settings
.
canDrawOverlays
(
reactApplicationContext
)
}
else
{
true
}
promise
.
resolve
(
canDraw
)
}
}
ios/Podfile
View file @
7a905f79
...
...
@@ -32,10 +32,4 @@ target 'whoscall' do
# :ccache_enabled => true
)
end
permissions_path
=
'../node_modules/react-native-permissions/ios'
pod
'Permission-Contacts'
,
:path
=>
"
#{
permissions_path
}
/Contacts.podspec"
pod
'Permission-Phone'
,
:path
=>
"
#{
permissions_path
}
/Phone.podspec"
pod
'Permission-Notifications'
,
:path
=>
"
#{
permissions_path
}
/Notifications.podspec"
end
ios/Podfile.lock
View file @
7a905f79
...
...
@@ -2645,7 +2645,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca
RCT-Folly:
59ec0ac1f2f39672a0c6e6cecdd39383b764646f
RCT-Folly:
846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a
RCTTypeSafety: 720403058b7c1380c6a3ae5706981d6362962c89
...
...
ios/whoscall.xcodeproj/project.pbxproj
View file @
7a905f79
This diff is collapsed.
Click to expand it.
ios/whoscall/Info.plist
View file @
7a905f79
...
...
@@ -31,11 +31,6 @@
<
k
e
y
>
NSAllowsLocalNetworking
<
/k
e
y
>
<
tru
e
/
>
<
/
d
i
c
t
>
<
k
e
y
>
NSUserActivityTypes
<
/k
e
y
>
<
a
rr
a
y
>
<
string
>
INStartAudioCallIntent
<
/string
>
<
string
>
INStartVideoCallIntent
<
/string
>
<
/
a
rr
a
y
>
<
k
e
y
>
NSLocationWhenInUseUsageDescription
<
/k
e
y
>
<
string
><
/string
>
<
k
e
y
>
RCTNewArchEnabled
<
/k
e
y
>
...
...
ios/whoscallDirectoryExtension/CallDirectoryHandler.swift
View file @
7a905f79
#if os(iOS)
import
Foundation
import
CallKit
...
...
@@ -18,7 +19,7 @@ class CallDirectoryHandler: CXCallDirectoryProvider {
// Example: add VIP numbers
// Numbers must be E.164 format (+60 for Malaysia)
let
vipNumbers
:
[(
number
:
Int64
,
label
:
String
)]
=
[
(
60189884118
,
"
VIP:
Wei Yi"
),
(
60189884118
,
"Wei Yi"
),
(
60123456789
,
"Spam Caller"
),
]
...
...
@@ -46,3 +47,4 @@ extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate {
NSLog
(
"Call Directory extension error:
\(
error
.
localizedDescription
)
"
)
}
}
#endif
\ No newline at end of file
ios/whoscallDirectoryExtension/Info.plist
View file @
7a905f79
...
...
@@ -4,13 +4,6 @@
<
d
i
c
t
>
<
k
e
y
>
NSExtension
<
/k
e
y
>
<
d
i
c
t
>
<
k
e
y
>
NSExtensionPointIdentifier
<
/k
e
y
>
<
string
>
com.apple.callkit.call-directory
<
/string
>
<
k
e
y
>
NSExtensionPrincipalClass
<
/k
e
y
>
<
string
>
$
(
PRODUCT_MODULE_NAME
)
.CallDirectoryHandler
<
/string
>
<
/
d
i
c
t
>
<
k
e
y
>
NSExtension
<
/k
e
y
>
<
d
i
c
t
>
<
k
e
y
>
NSExtensionAttributes
<
/k
e
y
>
<
d
i
c
t
>
<
k
e
y
>
CXCallDirectoryExtensionAttributes
<
/k
e
y
>
...
...
@@ -24,5 +17,7 @@
<
k
e
y
>
NSExtensionPrincipalClass
<
/k
e
y
>
<
string
>
$
(
PRODUCT_MODULE_NAME
)
.CallDirectoryHandler
<
/string
>
<
/
d
i
c
t
>
<
k
e
y
>
RCTNewArchEnabled
<
/k
e
y
>
<
tru
e
/
>
<
/
d
i
c
t
>
<
/plist
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment