diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4ef7106c..450a5c86 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -170,6 +170,7 @@ 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; 5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */; }; 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; + 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; }; 5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */; }; 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; }; @@ -763,6 +764,7 @@ 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = ""; }; + 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = ""; }; 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = ""; }; 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = ""; }; 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = ""; }; @@ -1399,6 +1401,7 @@ 27F973512466071600CAB5C5 /* Behaviors */ = { isa = PBXGroup; children = ( + 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */, 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */, 0A1C30972620F61A00B47F3B /* Protocols */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, @@ -2777,6 +2780,7 @@ D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */, AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, + 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */, D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */, 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */, 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, diff --git a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift new file mode 100644 index 00000000..4e0f407c --- /dev/null +++ b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift @@ -0,0 +1,89 @@ +// +// RefreshableMoleculeBehavior.swift +// MVMCoreUI +// +// Created by Kyle Hedden on 9/12/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +public class PollingBehaviorModel: PageBehaviorModelProtocol { + public class var identifier: String { "pollingBehavior" } + public var shouldAllowMultipleInstances: Bool { true } + + public let refreshInterval: TimeInterval + public let refreshAction: ActionOpenPageModel + + @DecodableDefault.False public var runWhileHidden: Bool + @DecodableDefault.False public var refreshOnFirstLoad: Bool + @DecodableDefault.False public var refreshOnShown: Bool + + @DecodableDefault.False public var alwaysUseFallbackResponse: Bool + public let fallbackResponse: JSONValueDictionary? +} + +public class PollingBehavior: NSObject, PageVisibilityBehavior { + + var model: PollingBehaviorModel + var delegateObject: MVMCoreUIDelegateObject? + var lastRefresh = Date.distantPast + var pollTimer: DispatchSourceTimer? + + var remainingTimeToRefresh: TimeInterval { + lastRefresh.timeIntervalSinceNow - model.refreshInterval + } + + var firstTimeLoad = true + + var refreshOnShown: Bool { + if model.refreshOnFirstLoad && firstTimeLoad { + return true + } + firstTimeLoad = false + return model.refreshOnShown + } + + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.model = model as! PollingBehaviorModel + self.delegateObject = delegateObject + } + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + resumePollingTimer(withRemainingTime: refreshOnShown ? 0 : remainingTimeToRefresh, refreshAction: model.refreshAction, interval: model.refreshInterval) + } + + public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { + pollTimer?.cancel() + } + + func resumePollingTimer(withRemainingTime timeRemaining: TimeInterval, refreshAction: ActionOpenPageModel, interval: TimeInterval) { + let delegateObject = DelegateObject.create(withDelegateForAll: self) + pollTimer?.cancel() + pollTimer = DispatchSource.makeTimerSource() + pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval) + pollTimer?.setEventHandler(qos:.utility) { + Task { + try? await MVMCoreActionHandler.shared()?.handleAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) + } + } + pollTimer?.resume() + } + + deinit { + pollTimer?.cancel() + } + +} + +extension PollingBehavior: MVMCoreLoadDelegateProtocol { + + public func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) { + + if error != nil { + // Apply the default response. + } + } + +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 483ecd93..c9f057d8 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -229,6 +229,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) ModelRegistry.register(handler: GetNotificationAuthStatusBehavior.self, for: GetNotificationAuthStatusBehaviorModel.self) ModelRegistry.register(handler: ReplaceableMoleculeBehavior.self, for: ReplaceableMoleculeBehaviorModel.self) + ModelRegistry.register(handler: PollingBehavior.self, for: PollingBehaviorModel.self) } open override class func registerActions() {