// // SceneDelegate.swift // Emoney Info // // Created by Wira Irawan on 22/07/24. // import UIKit import AppTrackingTransparency import GoogleMobileAds class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? private var hasRequestedTrackingAuthorization = false private var isTrackingAuthorizationRequestInFlight = false private var pendingTrackingCompletions: [() -> Void] = [] private var hasStartedAdMob = false private weak var homeVC: HomeViewController? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let tabBar = MainTabBarController() tabBar.viewControllers = [ makeHomeVC(tabBar: tabBar), makeSettingsVC() ] let window = UIWindow(windowScene: windowScene) window.rootViewController = tabBar window.makeKeyAndVisible() self.window = window } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. requestTrackingAuthorizationIfNeeded() } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } // MARK: - VC Factories private func makeHomeVC(tabBar: MainTabBarController) -> HomeViewController { let vc = HomeViewController() homeVC = vc vc.onScanTapped = { [weak vc] in guard let vc, let callback = vc as? ApduCallback else { return } self.startNfcScan(callback: callback) } vc.onViewHistoryTapped = { [weak vc] in let historyVC = HistoryHostingController() historyVC.riwayatList = vc?.latestRiwayatList ?? [] historyVC.cardLabel = vc?.cardTypeText ?? "" historyVC.balanceText = vc?.balanceText ?? "" historyVC.cardNumber = vc?.latestCardNumber ?? "" historyVC.modalPresentationStyle = .fullScreen vc?.present(historyVC, animated: true) } vc.onSettingsTapped = { [weak tabBar] in tabBar?.selectedIndex = 1 } return vc } private func makeSettingsVC() -> UINavigationController { let vc = SettingsViewController() vc.onShowCardNumberChanged = { isOn in UserDefaults.standard.set(isOn, forKey: "masked") NotificationCenter.default.post(name: Notification.Name("refreshScreen"), object: nil) } vc.onHelpCenterTapped = { [weak vc] in let faqVC = FAQViewController() vc?.navigationController?.pushViewController(faqVC, animated: true) } vc.onAboutTapped = { [weak vc] in let aboutVC = AboutViewController() vc?.navigationController?.pushViewController(aboutVC, animated: true) } let nav = UINavigationController(rootViewController: vc) nav.setNavigationBarHidden(true, animated: false) return nav } private func requestTrackingAuthorizationIfNeeded(completion: (() -> Void)? = nil) { if let completion { pendingTrackingCompletions.append(completion) } guard #available(iOS 14, *) else { startAdMobSDKIfNeeded() flushPendingTrackingCompletions(after: 0) return } guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { // User already responded to ATT in a previous session — start AdMob immediately. startAdMobSDKIfNeeded() flushPendingTrackingCompletions(after: 0) return } guard !hasRequestedTrackingAuthorization else { flushPendingTrackingCompletions(after: 0) return } hasRequestedTrackingAuthorization = true isTrackingAuthorizationRequestInFlight = true // Short delay so the root view controller completes its appearance // transition before iOS presents the ATT dialog. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { ATTrackingManager.requestTrackingAuthorization { _ in DispatchQueue.main.async { self.isTrackingAuthorizationRequestInFlight = false // Start AdMob only after the user has responded to ATT, // ensuring the SDK never collects data before consent is given. self.startAdMobSDKIfNeeded() self.flushPendingTrackingCompletions(after: 1.2) } } } } private func startAdMobSDKIfNeeded() { guard !hasStartedAdMob else { return } hasStartedAdMob = true // GADMobileAds.sharedInstance().requestConfiguration.testDeviceIdentifiers = ["ae454ddf2186e5ac7871bf705de41098"] GADMobileAds.sharedInstance().start { [weak self] _ in DispatchQueue.main.async { self?.homeVC?.loadBannerAd() } } } private func startNfcScan(callback: ApduCallback) { let beginScan = { let api = UnifiedNfcApi() api.setCallback(apduCallback: callback) api.searchCard() } if #available(iOS 14, *), ATTrackingManager.trackingAuthorizationStatus == .notDetermined, isTrackingAuthorizationRequestInFlight { pendingTrackingCompletions.append(beginScan) return } requestTrackingAuthorizationIfNeeded(completion: beginScan) } private func flushPendingTrackingCompletions(after delay: TimeInterval) { let completions = pendingTrackingCompletions pendingTrackingCompletions.removeAll() guard !completions.isEmpty else { return } DispatchQueue.main.asyncAfter(deadline: .now() + delay) { completions.forEach { $0() } } } }