Commit 7a905f79 authored by Wei Han's avatar Wei Han Committed by alep

outgoing call bug fixed

parent f67df9f1
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
......
......@@ -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"
......
......@@ -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"
......
// 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()
}
}
// 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)
}
}
......@@ -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
......@@ -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
......
This diff is collapsed.
......@@ -31,11 +31,6 @@
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSUserActivityTypes</key>
<array>
<string>INStartAudioCallIntent</string>
<string>INStartVideoCallIntent</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>RCTNewArchEnabled</key>
......
#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
......@@ -4,13 +4,6 @@
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.callkit.call-directory</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CallDirectoryHandler</string>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>CXCallDirectoryExtensionAttributes</key>
......@@ -24,5 +17,7 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CallDirectoryHandler</string>
</dict>
<key>RCTNewArchEnabled</key>
<true/>
</dict>
</plist>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment