Initial commit

This commit is contained in:
Wira Basalamah
2026-04-24 04:55:24 +07:00
commit 8f0b001501
128 changed files with 9366 additions and 0 deletions

193
Emoney Info/SceneDelegate.swift Executable file
View File

@ -0,0 +1,193 @@
//
// 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 = ["7b42e513861d6b0c9f07529b748930c0"]
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() }
}
}
}