// DefaultInterstitialAdEngine.swift // Default implementation of InterstitialAdEngine // Corresponds to DefaultInterstitialAdEngine.kt in Android implementation import Foundation import UIKit public class DefaultInterstitialAdEngine: InterstitialAdEngine { private let ad: GuruInterstitialAd // Constants private enum Action { static let LOAD = 1 static let SHOW = 2 static let DESTROY = 3 static let RETRY = 4 } private enum Event { static let ON_LOADED = 100 static let ON_LOAD_FAILED = 101 static let ON_DISPLAYED = 102 static let ON_DISPLAY_FAILED = 103 static let ON_CLICK = 104 static let ON_HIDDEN = 105 static let ON_USER_REWARDED = 106 static let REVENUE_PAID = 107 static let LOAD_TIMEOUT = 108 static let SHOW_TIMEOUT = 109 } private static let LOAD_TIMEOUT_MILLIS: Int = 120_000 private static let SHOW_TIMEOUT_MILLIS: Int = 120_000 public override var supportedAdPlatforms: Set { return [ad.adPlatform] } public init(viewController: UIViewController, id: Int, ad: GuruInterstitialAd) { self.ad = ad super.init(viewController: viewController, id: id, adType: .Interstitial, strategyName: ad.adPlatform.name) setupAdListener() initialize() } private func setupAdListener() { ad.listener = FusionAdListenerImpl { [weak self] event in guard let self = self else { return } switch event { case .onAdLoaded(let ad): self.sendMessage(what: Event.ON_LOADED, obj: EventParams(ad: ad)) case .onAdDisplayed(let ad): self.sendMessage(what: Event.ON_DISPLAYED, obj: EventParams(ad: ad)) case .onAdHidden(let ad): self.sendMessage(what: Event.ON_HIDDEN, obj: EventParams(ad: ad)) case .onUserRewarded(let ad, let reward): // Not used for interstitial ads, but handled for completeness break case .onAdClicked(let ad): self.sendMessage(what: Event.ON_CLICK, obj: EventParams(ad: ad)) case .onAdLoadFailed(let loadFailedInfo): self.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(loadFailedInfo: loadFailedInfo)) case .onAdDisplayFailed(let ad, let error): self.sendMessage(what: Event.ON_DISPLAY_FAILED, obj: EventParams(ad: ad, displayFailedInfo: error)) case .onAdRevenuePaid(let ad): self.sendMessage(what: Event.REVENUE_PAID, obj: EventParams(ad: ad)) } } } // MARK: - State Classes public override func createIdleState() -> Idle { return IdleImpl(self) } public override func createLoadingState() -> Loading { return LoadingImpl(self) } public override func createLoadedState() -> Loaded { return LoadedImpl(self) } public override func createShowingState() -> Showing { return ShowingImpl(self) } // MARK: - Request Methods override internal func requestLoad() { sendMessage(what: Action.LOAD) } override internal func requestShow(request: InterstitialShowRequest?) { sendMessage(what: Action.SHOW, obj: request) } override internal func requestDestroy() { sendMessage(what: Action.DESTROY) } // MARK: - Helper Methods private func handleUnhandledMessage(_ msg: Message) -> Bool { switch msg.what { case Action.DESTROY: ad.destroy() transitionTo(idleState) return true default: return false } } // MARK: - State Implementations private class IdleImpl: Idle { private let engine: DefaultInterstitialAdEngine init(_ engine: DefaultInterstitialAdEngine) { self.engine = engine super.init() } public override func enter(from: IState?, params: Any?) { super.enter(from: from, params: params) } public override func processMessage(_ msg: Message) -> Bool { switch msg.what { case Action.LOAD: if engine.ad.load() { engine.transitionTo(engine.loadingState) } else { engine.adLoadFailed(loadFailedInfo: LoadFailedInfo( engineId: engine.id, adPlatform: engine.ad.adPlatform, adType: engine.adType, adUnitId: engine.ad.adUnitId, error: LoadAdRequestError() )) } return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } class LoadingImpl: Loading { private var retryCount = 0 private let engine: DefaultInterstitialAdEngine init(_ engine: DefaultInterstitialAdEngine) { self.engine = engine super.init() } public override func enter(from: IState?, params: Any?) { super.enter(from: from, params: params) retryCount = 0 // Set load timeout engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultInterstitialAdEngine.LOAD_TIMEOUT_MILLIS) } public override func exit(to: IState?) { engine.removeMessages(what: Event.LOAD_TIMEOUT) engine.removeMessages(what: Action.RETRY) retryCount = 0 super.exit(to: to) } public override func processMessage(_ msg: Message) -> Bool { switch msg.what { case Action.RETRY: retryCount += 1 if !engine.ad.load() { engine.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams( loadFailedInfo: LoadFailedInfo( engineId: engine.id, adPlatform: engine.ad.adPlatform, adType: engine.adType, adUnitId: engine.ad.adUnitId, error: LoadAdRequestError() ) )) } else { engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultInterstitialAdEngine.LOAD_TIMEOUT_MILLIS) } return true case Action.LOAD: engine.logWarn("Already loading! Ignore load request") return true case Event.LOAD_TIMEOUT: engine.removeMessages(what: Action.RETRY) engine.sendMessage(what: Action.RETRY) return true case Event.ON_LOADED: if let eventParams = msg.obj as? EventParams, eventParams.ad != nil { engine.transitionTo(engine.loadedState, params: eventParams) } engine.deferMessage(msg) return true case Event.ON_LOAD_FAILED: let params = msg.obj as? EventParams engine.adLoadFailed(loadFailedInfo: params?.loadFailedInfo ?? LoadFailedInfo( engineId: engine.id, adPlatform: engine.ad.adPlatform, adType: engine.adType, adUnitId: engine.ad.adUnitId, error: LoadAdRequestError() )) engine.transitionTo(engine.idleState) return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } class LoadedImpl: Loaded { private let engine: DefaultInterstitialAdEngine init(_ engine: DefaultInterstitialAdEngine) { self.engine = engine super.init() } public override func enter(from: IState?, params: Any?) { super.enter(from: from, params: params) if let eventParams = params as? EventParams, let ad = eventParams.ad { engine.adLoaded(ad: ad) } } public override func processMessage(_ msg: Message) -> Bool { switch msg.what { case Action.SHOW: let request = msg.obj as? InterstitialShowRequest if engine.ad.show(request) { engine.transitionTo(engine.showingState) } else { engine.logWarn("Failed to show interstitial ad") // no FusionAd here // engine.adDisplayFailed(ad: engine.ad, error: ShowAdRequestError()) if(engine.ad.load()) { engine.transitionTo(engine.loadingState) } } return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } class ShowingImpl: Showing { private let engine: DefaultInterstitialAdEngine init(_ engine: DefaultInterstitialAdEngine) { self.engine = engine super.init() } public override func enter(from: IState?, params: Any?) { super.enter(from: from, params: params) engine.sendMessageDelayed(what: Event.SHOW_TIMEOUT, delayed: .milliseconds(SHOW_TIMEOUT_MILLIS)) } public override func processMessage(_ msg: Message) -> Bool { switch msg.what { case Event.ON_DISPLAYED: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adDisplayed(ad: ad) } engine.removeMessages(what: Event.SHOW_TIMEOUT) return true case Event.ON_CLICK: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adClicked(ad: ad) } return true case Event.ON_HIDDEN: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adHidden(ad: ad) engine.transitionTo(engine.idleState) } return true case Event.REVENUE_PAID: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adRevenuePaid(ad: ad) } return true case Event.ON_DISPLAY_FAILED: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo) } engine.transitionTo(engine.idleState) engine.requestLoad() return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } } // Helper class to implement the FusionAdListener protocol private class FusionAdListenerImpl: FusionAdListener { enum Event { case onAdLoaded(ad: FusionAd) case onAdDisplayed(ad: FusionAd) case onAdHidden(ad: FusionAd) case onUserRewarded(ad: FusionAd, reward: FusionReward?) case onAdClicked(ad: FusionAd) case onAdLoadFailed(loadFailedInfo: LoadFailedInfo) case onAdDisplayFailed(ad: FusionAd, error: FusionError?) case onAdRevenuePaid(ad: FusionAd) } private let handler: (Event) -> Void init(handler: @escaping (Event) -> Void) { self.handler = handler } func onAdLoaded(ad: FusionAd) { handler(.onAdLoaded(ad: ad)) } func onAdDisplayed(ad: FusionAd) { handler(.onAdDisplayed(ad: ad)) } func onAdHidden(ad: FusionAd) { handler(.onAdHidden(ad: ad)) } func onUserRewarded(ad: FusionAd, reward: FusionReward?) { handler(.onUserRewarded(ad: ad, reward: reward)) } func onAdClicked(ad: FusionAd) { handler(.onAdClicked(ad: ad)) } func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) { handler(.onAdLoadFailed(loadFailedInfo: loadFailedInfo)) } func onAdDisplayFailed(ad: FusionAd, error: FusionError?) { handler(.onAdDisplayFailed(ad: ad, error: error)) } func onAdRevenuePaid(ad: FusionAd) { handler(.onAdRevenuePaid(ad: ad)) } }