// DefaultBannerAdEngine.swift // Default implementation of BannerAdEngine // Corresponds to DefaultBannerAdEngine.kt in Android implementation import Foundation import UIKit public class DefaultBannerAdEngine: BannerAdEngine, OrientationAware { private let ad: GuruBannerAd // Constants private enum Action { static let LOAD = 1 static let SHOW = 2 static let HIDE = 3 static let DESTROY = 4 static let RETRY = 5 } 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: TimeInterval = 120000 / 1000.0 private static let SHOW_TIMEOUT_MILLIS: TimeInterval = 120000 / 1000.0 public override var supportedAdPlatforms: Set { return [ad.adPlatform] } public init(viewController: UIViewController, id: Int, ad: GuruBannerAd) { self.ad = ad super.init(viewController: viewController, id: id, adType: .Banner, 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): // Not used for banner ads, but handled for completeness break case .onAdHidden(let ad): // Not used for banner ads, but handled for completeness break case .onUserRewarded(let ad, let reward): // Not used for banner 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 createActiveState() -> Active { return ActiveImpl(self) } public override func createShowingState() -> Showing { return ShowingImpl(self) } public override func createHiddenState() -> Hidden { return HiddenImpl(self) } // MARK: - Request Methods override internal func requestLoad() { sendMessage(what: Action.LOAD) } override internal func requestShow(_ request: BannerShowRequest? = nil) { sendMessage(what: Action.SHOW, obj: request) } override internal func requestHide() { sendMessage(what: Action.HIDE) } 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 class IdleImpl: Idle { private let engine: DefaultBannerAdEngine init(_ engine: DefaultBannerAdEngine) { 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: DefaultBannerAdEngine init(_ engine: DefaultBannerAdEngine) { 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: Int(DefaultBannerAdEngine.LOAD_TIMEOUT_MILLIS * 1000)) } 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: Int(DefaultBannerAdEngine.LOAD_TIMEOUT_MILLIS * 1000)) } 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.hiddenState, 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 ActiveImpl: Active { private let engine: DefaultBannerAdEngine init(_ engine: DefaultBannerAdEngine) { 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 Event.ON_CLICK: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adClicked(ad: ad) } return true case Event.REVENUE_PAID: if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { engine.adRevenuePaid(ad: ad) } return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } class ShowingImpl: Showing { private let engine: DefaultBannerAdEngine init(_ engine: DefaultBannerAdEngine) { 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.adDisplayed(ad: ad) } } public override func processMessage(_ msg: Message) -> Bool { switch msg.what { case Action.HIDE: if engine.ad.hide() { engine.transitionTo(engine.hiddenState) } else { engine.logWarn("Failed to hide banner") } return true case Action.SHOW: engine.logWarn("Already showing! Ignore show request") 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.hiddenState) engine.requestLoad() return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } class HiddenImpl: Hidden { private let engine: DefaultBannerAdEngine init(_ engine: DefaultBannerAdEngine) { 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? BannerShowRequest if engine.ad.show(request) { engine.transitionTo(engine.showingState) } else { engine.logWarn("Failed to show banner") // no FusionAd here // if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { // engine.adDisplayFailed(ad: ad, error: ShowAdRequestError()) // } if(engine.ad.load()) { engine.transitionTo(engine.loadingState) } } return true case Action.HIDE: engine.logWarn("Already hidden! Ignore hide request") return true default: return engine.handleUnhandledMessage(msg) || super.processMessage(msg) } } } func onOrientationUpdate(orientation: ScreenOrientation) { ad.updateOrientation(orientation: orientation) } } // 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)) } }