216 lines
6.3 KiB
Swift
216 lines
6.3 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
import Combine
|
|
import GoogleMobileAds
|
|
import UserMessagingPlatform
|
|
import AppTrackingTransparency
|
|
import AdSupport
|
|
|
|
@MainActor
|
|
class AdMobService: ObservableObject {
|
|
// MARK: - Published Properties
|
|
|
|
@Published var isConsentObtained = false
|
|
@Published var canShowAds = false
|
|
@Published var isLoading = false
|
|
@Published var shouldRequestNonPersonalizedAds = false
|
|
|
|
// MARK: - Ad Unit IDs
|
|
|
|
// Ad Unit IDs
|
|
#if DEBUG
|
|
static let bannerAdUnitID = "ca-app-pub-3940256099942544/2934735716" // Test ad unit for development
|
|
#else
|
|
static let bannerAdUnitID = "ca-app-pub-1549720748100858/3342431241" // Production ad unit
|
|
#endif
|
|
|
|
// MARK: - Initialization
|
|
|
|
init() {
|
|
setupResetObserver()
|
|
Task {
|
|
await configureConsentAndRequestAdsIfNeeded()
|
|
}
|
|
}
|
|
|
|
// MARK: - Consent Management
|
|
|
|
func requestConsent() async {
|
|
await configureConsentAndRequestAdsIfNeeded()
|
|
}
|
|
|
|
func presentPrivacyOptions() async {
|
|
guard let root = topViewController() else { return }
|
|
await withCheckedContinuation { continuation in
|
|
ConsentForm.presentPrivacyOptionsForm(from: root) { _ in
|
|
continuation.resume()
|
|
}
|
|
}
|
|
updateConsentState()
|
|
}
|
|
|
|
func resetConsent() {
|
|
ConsentInformation.shared.reset()
|
|
UserDefaults.standard.removeObject(forKey: AppConstants.StorageKeys.adConsentObtained)
|
|
isConsentObtained = false
|
|
canShowAds = false
|
|
shouldRequestNonPersonalizedAds = false
|
|
}
|
|
|
|
private func setupResetObserver() {
|
|
NotificationCenter.default.addObserver(
|
|
forName: .didResetData,
|
|
object: nil,
|
|
queue: .main
|
|
) { [weak self] _ in
|
|
guard let self else { return }
|
|
Task { @MainActor in
|
|
self.resetConsent()
|
|
await self.configureConsentAndRequestAdsIfNeeded()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func configureConsentAndRequestAdsIfNeeded() async {
|
|
isLoading = true
|
|
let parameters = RequestParameters()
|
|
await withCheckedContinuation { continuation in
|
|
ConsentInformation.shared.requestConsentInfoUpdate(with: parameters) { _ in
|
|
continuation.resume()
|
|
}
|
|
}
|
|
|
|
if let root = topViewController() {
|
|
await withCheckedContinuation { continuation in
|
|
ConsentForm.loadAndPresentIfRequired(from: root) { _ in
|
|
continuation.resume()
|
|
}
|
|
}
|
|
}
|
|
|
|
updateConsentState()
|
|
if canShowAds {
|
|
await requestTrackingIfNeeded()
|
|
updateConsentState()
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
private func updateConsentState() {
|
|
let consentInfo = ConsentInformation.shared
|
|
canShowAds = consentInfo.canRequestAds
|
|
isConsentObtained = consentInfo.consentStatus == .obtained || consentInfo.consentStatus == .notRequired
|
|
|
|
let trackingDenied: Bool
|
|
if #available(iOS 14.5, *) {
|
|
trackingDenied = ATTrackingManager.trackingAuthorizationStatus != .authorized
|
|
} else {
|
|
trackingDenied = false
|
|
}
|
|
|
|
shouldRequestNonPersonalizedAds = !isConsentObtained || trackingDenied
|
|
UserDefaults.standard.set(isConsentObtained, forKey: AppConstants.StorageKeys.adConsentObtained)
|
|
}
|
|
|
|
private func requestTrackingIfNeeded() async {
|
|
if #available(iOS 14.5, *) {
|
|
_ = await ATTrackingManager.requestTrackingAuthorization()
|
|
}
|
|
}
|
|
|
|
private func topViewController() -> UIViewController? {
|
|
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
let root = scene.windows.first?.rootViewController else {
|
|
return nil
|
|
}
|
|
var current = root
|
|
while let presented = current.presentedViewController {
|
|
current = presented
|
|
}
|
|
return current
|
|
}
|
|
|
|
// MARK: - Analytics
|
|
|
|
func logAdImpression() {
|
|
FirebaseService.shared.logAdImpression(adType: "banner")
|
|
}
|
|
|
|
func logAdClick() {
|
|
FirebaseService.shared.logAdClick(adType: "banner")
|
|
}
|
|
}
|
|
|
|
// MARK: - Banner Ad Coordinator
|
|
|
|
class BannerAdCoordinator: NSObject, BannerViewDelegate {
|
|
weak var adMobService: AdMobService?
|
|
|
|
func bannerViewDidReceiveAd(_ bannerView: BannerView) {
|
|
print("Banner ad received")
|
|
adMobService?.logAdImpression()
|
|
}
|
|
|
|
func bannerView(_ bannerView: BannerView, didFailToReceiveAdWithError error: Error) {
|
|
print("Banner ad failed to load: \(error.localizedDescription)")
|
|
}
|
|
|
|
func bannerViewDidRecordImpression(_ bannerView: BannerView) {
|
|
// Impression recorded
|
|
}
|
|
|
|
func bannerViewDidRecordClick(_ bannerView: BannerView) {
|
|
adMobService?.logAdClick()
|
|
}
|
|
|
|
func bannerViewWillPresentScreen(_ bannerView: BannerView) {
|
|
// Ad will present full screen
|
|
}
|
|
|
|
func bannerViewWillDismissScreen(_ bannerView: BannerView) {
|
|
// Ad will dismiss
|
|
}
|
|
|
|
func bannerViewDidDismissScreen(_ bannerView: BannerView) {
|
|
// Ad dismissed
|
|
}
|
|
}
|
|
|
|
// MARK: - UIKit Banner View Wrapper
|
|
|
|
struct BannerAdView: UIViewRepresentable {
|
|
@EnvironmentObject var adMobService: AdMobService
|
|
|
|
func makeUIView(context: Context) -> BannerView {
|
|
let bannerView = BannerView(adSize: AdSizeBanner)
|
|
bannerView.adUnitID = AdMobService.bannerAdUnitID
|
|
|
|
// Get root view controller
|
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
let rootViewController = windowScene.windows.first?.rootViewController {
|
|
bannerView.rootViewController = rootViewController
|
|
}
|
|
|
|
bannerView.delegate = context.coordinator
|
|
let request = Request()
|
|
if adMobService.shouldRequestNonPersonalizedAds {
|
|
let extras = Extras()
|
|
extras.additionalParameters = ["npa": "1"]
|
|
request.register(extras)
|
|
}
|
|
bannerView.load(request)
|
|
|
|
return bannerView
|
|
}
|
|
|
|
func updateUIView(_ uiView: BannerView, context: Context) {
|
|
// Banner updates automatically
|
|
}
|
|
|
|
func makeCoordinator() -> BannerAdCoordinator {
|
|
let coordinator = BannerAdCoordinator()
|
|
coordinator.adMobService = adMobService
|
|
return coordinator
|
|
}
|
|
}
|