-
Notifications
You must be signed in to change notification settings - Fork 464
Description
-
[ v ] - Initial Setup: The app has been built and successfully obtained a VoIP token.
-
[ v ] - App State: The app is swiped out of the multitasking screen (background or terminated state).
-
[ v ] - VoIP Notification: The VoIP notification is sent to the backend (BE).
-
[ v ] - CallKeep Screen: The CallKeep screen appears natively on the device when the notification is received.
-
[ v ] - Accept Call: When the user accepts the call by pressing the "Accept" button, the app is woken up from the background/terminated state.
-
[ x ] - Issue: After the app wakes up, the screen appears blank (white screen) with almost no content. The login screen fails to load properly.


#import "AppDelegate.h"
#import <Firebase.h>
#import <UserNotifications/UserNotifications.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTPushNotificationManager.h>
#import <RNBranch/RNBranch.h>
#import <React/RCTLinkingManager.h>
// ✅ Thêm PushKit + CallKeep
#import <PushKit/PushKit.h>
#import "RNCallKeep.h"
#import "RNVoipPushNotificationManager.h"
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutPlugin/SKDescriptorMapper.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#endif
#endif
#if DEBUG && __has_include(<EXDevLauncher/EXDevLauncherController.h>)
#import <EXDevLauncher/EXDevLauncherController.h>
#endif
@interface AppDelegate () // ✅ giữ nguyên y chang
@EnD
@implementation AppDelegate
-
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ===== 1. Firebase setup =====
[FIRApp configure];// ===== 2. iOS Push setup =====
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
UNAuthorizationOptionSound |
UNAuthorizationOptionBadge;
[center requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"🔔 Push permission granted");
} else {
NSLog(@"🚫 Push permission denied: %@", error);
}
}];
[application registerForRemoteNotifications];// ✅ PushKit VoIP Registry
PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
voipRegistry.delegate = self;
voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];// ===== 3. React Native + Branch setup =====
self.moduleName = @"Bluefish";
NSString *foxCodeFromBundle = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"fox_code"];
NSString *foxCode = foxCodeFromBundle ?: @"debug";[RNBranch initSessionWithLaunchOptions:launchOptions isReferrable:YES];
RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge
moduleName:self.moduleName
initialProperties:@{
@"foxCode": foxCode,
@"appName": @"Bluefish"
}];
rootView.backgroundColor = [UIColor colorNamed:@"ThemeColors"];self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [self.reactDelegate createRootViewController];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];// Keep splash screen
UIView *launchScreenView = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
launchScreenView.frame = self.window.bounds;
rootView.loadingView = launchScreenView;[self initializeFlipper:application];
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
#pragma mark – Flipper
- (void)initializeFlipper:(UIApplication *)application {
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
#endif
#endif
}
#pragma mark – URL handlers for Branch
-
(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
#if DEBUG
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
return YES;
}
#endif
NSLog(@"🔥 App nhận deeplink: %@", url.absoluteString);
return [RCTLinkingManager application:app openURL:url options:options];
} -
(BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler {
return [RNCallKeep application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
#pragma mark – APNs registration
-
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[FIRMessaging messaging].APNSToken = deviceToken;
[RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
} -
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
[RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error];
NSLog(@"❌ Failed to register for APNs: %@", error);
}
#pragma mark – UNUserNotificationCenterDelegate
-
(void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
completionHandler(UNNotificationPresentationOptionAlert |
UNNotificationPresentationOptionSound |
UNNotificationPresentationOptionBadge);
} -
(void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
[RCTPushNotificationManager didReceiveRemoteNotification:userInfo fetchCompletionHandler:^(UIBackgroundFetchResult result){}];
completionHandler();
}
#pragma mark – Legacy RN PushNotificationManager
-
(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
} -
(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"📩 Background Push Payload Received: %@", userInfo);
[RCTPushNotificationManager didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
} -
(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
[RCTPushNotificationManager didReceiveLocalNotification:notification];
}
#pragma mark – React Native bridge
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
#pragma mark – PushKit VoIP
-
(void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:type];// ✅ Log VoIP Token rõ ràng 64 ký tự
NSString *voipToken = [credentials.token.description stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"📞 VoIP Push token: %@", voipToken);
} -
(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:type];
// ✅ Nếu app bị kill → cần tạo lại window và rootView
if (!self.window || !self.window.rootViewController) {
NSLog(@"🧠 App was killed. Reinitializing window and rootView...");
RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:nil];
RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge
moduleName:self.moduleName
initialProperties:nil];
rootView.backgroundColor = [UIColor whiteColor];self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootVC = [self.reactDelegate createRootViewController];
rootVC.view = rootView;
self.window.rootViewController = rootVC;
[self.window makeKeyAndVisible];
}NSDictionary *content = [payload.dictionaryPayload valueForKey:@"aps"];
NSDictionary *alert = [content valueForKey:@"alert"];
NSDictionary *items = [alert valueForKey:@"items"];NSString *uuid = [[[NSUUID UUID] UUIDString] lowercaseString];
NSString *callerName = [items valueForKey:@"peer_caller_id_name"] ?: @"Unknown";
NSString *handle = [items valueForKey:@"peer_caller_id_number"] ?: @"Unknown";
NSDictionary *extra = payload.dictionaryPayload;[RNCallKeep reportNewIncomingCall:uuid
handle:handle
handleType:@"generic"
hasVideo:NO
localizedCallerName:callerName
supportsHolding:YES
supportsDTMF:YES
supportsGrouping:YES
supportsUngrouping:YES
fromPushKit:YES
payload:extra
withCompletionHandler:completion];// ✅ ✅ CHỜ BRIDGE KHỞI ĐỘNG SAU COLD START
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:nil];
if (bridge != nil) {
[bridge enqueueJSCall:@"RCTDeviceEventEmitter"
method:@"emit"
args:@[@"BridgeReadyAfterVoIP", @{}]
completion:NULL];NSLog(@"✅ BridgeReadyAfterVoIP event sent to JS");
} else {
NSLog(@"❌ Failed to initialize RN bridge after VoIP push");
}
});
}
#import <Firebase.h>
#import <Expo/Expo.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
// ✅ Thêm từ mẫu
#import <PushKit/PushKit.h>
#import <UserNotifications/UserNotifications.h>
#import "RNCallKeep.h"
#import "RNVoipPushNotificationManager.h"
@interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate>
@Property (nonatomic, strong) UIWindow *window;
Hope everyone gives their opinions to help overcome this problem. Thank God, Amen.