Compare commits
76 Commits
release/11
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e1581e28e | ||
|
|
8f2483eb1b | ||
|
|
12d17dbca8 | ||
|
|
c360c75512 | ||
|
|
d57a86e140 | ||
|
|
214ccfd8f6 | ||
|
|
31aa1d6df7 | ||
|
|
817d7900f4 | ||
|
|
4b35a8bb2d | ||
|
|
a29fa60e5e | ||
|
|
3103f8dd89 | ||
|
|
2b36711bca | ||
|
|
a570f3c0e6 | ||
|
|
038fb8cdf7 | ||
|
|
dd2b0f9de5 | ||
|
|
d61cb6cfb9 | ||
|
|
cf3d7810d4 | ||
|
|
3c8af09752 | ||
|
|
b219f3f75d | ||
|
|
fba2e80b8a | ||
|
|
460aa06678 | ||
|
|
c18ce5e811 | ||
|
|
4bf30550ef | ||
|
|
317815d44a | ||
|
|
3054f7e62d | ||
|
|
25e05c6c53 | ||
|
|
a1c6332131 | ||
|
|
9744fc5333 | ||
|
|
8a92c9cdd3 | ||
|
|
a00eff7cf1 | ||
|
|
0104570bc9 | ||
|
|
134ce14e5a | ||
|
|
220b8530da | ||
|
|
05df4ad6ef | ||
|
|
8f88e94ed8 | ||
|
|
d2572c4543 | ||
|
|
71689b957b | ||
|
|
009bb01e1b | ||
|
|
4bded0a433 | ||
|
|
20d4d323e0 | ||
|
|
62a5799312 | ||
|
|
e8552b0aa2 | ||
|
|
dde3e565a3 | ||
|
|
62b7955406 | ||
|
|
a5763d4516 | ||
|
|
9ff641060e | ||
|
|
85747b146e | ||
|
|
9b718ce0d4 | ||
|
|
39a8451314 | ||
|
|
c38030bdb8 | ||
|
|
09ff3b6457 | ||
|
|
a428d5f7ce | ||
|
|
fe7e98f615 | ||
|
|
f7a348a8b0 | ||
|
|
58f7abb5fc | ||
|
|
262881f52b | ||
|
|
c4b6122d4c | ||
|
|
0f89fb51d2 | ||
|
|
c63a7f6d90 | ||
|
|
fac3e77984 | ||
|
|
f6f272f727 | ||
|
|
19c277d68e | ||
|
|
64aba6a100 | ||
|
|
25b79530eb | ||
|
|
8235aff75f | ||
|
|
d08a8f6782 | ||
|
|
4fc4aa21f3 | ||
|
|
4bdd93dbe5 | ||
|
|
3b410fb522 | ||
|
|
c90f267599 | ||
|
|
f89bad1c7a | ||
|
|
54e2ecb313 | ||
|
|
b9097361ab | ||
|
|
a16e09c569 | ||
|
|
84f4a1ac46 | ||
|
|
8c32dbbd7d |
@ -42,6 +42,7 @@
|
||||
2723337B28BD534D004EAEE0 /* MVMCoreEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */; };
|
||||
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */; };
|
||||
5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */; };
|
||||
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */; };
|
||||
6042E8FC2B317B190031644B /* MVMCoreLoggingHandlerHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
605A9A2A2ABD712F00487E47 /* MVMCoreLoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */; };
|
||||
6079EDCE2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */; };
|
||||
@ -96,6 +97,7 @@
|
||||
AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; };
|
||||
AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */; };
|
||||
AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F1289212CA00919EEB /* MVMError.swift */; };
|
||||
AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; };
|
||||
AF686FDA2A8A876A008F666A /* NavigationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FD92A8A876A008F666A /* NavigationOperation.swift */; };
|
||||
@ -193,6 +195,7 @@
|
||||
581FABEE2A71D0E6003A8508 /* mvmcore_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore_dev.xcconfig; sourceTree = "<group>"; };
|
||||
5836B8E22A4338DF002553D9 /* mvmcore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore.xcconfig; sourceTree = "<group>"; };
|
||||
5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Safe.swift"; sourceTree = "<group>"; };
|
||||
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = "<group>"; };
|
||||
6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreLoggingHandlerHelper.h; sourceTree = "<group>"; };
|
||||
605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingHandler.swift; sourceTree = "<group>"; };
|
||||
6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -252,6 +255,7 @@
|
||||
AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = "<group>"; };
|
||||
AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = "<group>"; };
|
||||
AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = "<group>"; };
|
||||
AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreCache+Extension.swift"; sourceTree = "<group>"; };
|
||||
AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = "<group>"; };
|
||||
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = "<group>"; };
|
||||
AF686FD92A8A876A008F666A /* NavigationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationOperation.swift; sourceTree = "<group>"; };
|
||||
@ -289,7 +293,6 @@
|
||||
AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGetterUtility.h; sourceTree = "<group>"; };
|
||||
AFBB96AF1FBA3B590008D868 /* MVMCoreGetterUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreGetterUtility.m; sourceTree = "<group>"; };
|
||||
AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreActionDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VZWAuthentication.framework; path = ../../SharedFrameworks/VZWAuthentication.framework; sourceTree = "<group>"; };
|
||||
AFBB96E91FBA4A260008D868 /* MFHardCodedServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFHardCodedServerResponse.h; sourceTree = "<group>"; };
|
||||
AFBB96EA1FBA4A260008D868 /* MFHardCodedServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFHardCodedServerResponse.m; sourceTree = "<group>"; };
|
||||
AFEA17A6209B6A1C00BC6740 /* MVMCoreBlockOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreBlockOperation.h; sourceTree = "<group>"; };
|
||||
@ -394,6 +397,7 @@
|
||||
children = (
|
||||
AF60A7F1289212CA00919EEB /* MVMError.swift */,
|
||||
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */,
|
||||
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */,
|
||||
881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */,
|
||||
881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */,
|
||||
881D26921FCC9D180079C521 /* MVMCoreOperation.h */,
|
||||
@ -646,7 +650,6 @@
|
||||
AF43A6FC1FBE2F2A008E9347 /* Reachability */,
|
||||
AF43A5C01FBB76D5008E9347 /* CoreGraphics.framework */,
|
||||
AF43A5BF1FBB76C3008E9347 /* UIKit.framework */,
|
||||
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -656,6 +659,7 @@
|
||||
children = (
|
||||
AF43A7091FC4F415008E9347 /* MVMCoreCache.h */,
|
||||
AF43A7081FC4F415008E9347 /* MVMCoreCache.m */,
|
||||
AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */,
|
||||
605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */,
|
||||
6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */,
|
||||
D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */,
|
||||
@ -874,6 +878,7 @@
|
||||
AFA4931E29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift in Sources */,
|
||||
1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */,
|
||||
946EE1AB237B5C940036751F /* Decoder.swift in Sources */,
|
||||
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */,
|
||||
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */,
|
||||
AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */,
|
||||
AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */,
|
||||
@ -902,6 +907,7 @@
|
||||
AFBB96351FBA34310008D868 /* MVMCoreErrorConstants.m in Sources */,
|
||||
AF43A5881FBB67D6008E9347 /* MVMCoreActionUtility.m in Sources */,
|
||||
AFED77A61FCCA29400BAE689 /* MVMCoreViewControllerStoryBoardMappingObject.m in Sources */,
|
||||
AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */,
|
||||
016CF36925FA6DD400B82A1F /* ClientParameterHandler.swift in Sources */,
|
||||
5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */,
|
||||
AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */,
|
||||
@ -1107,7 +1113,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
|
||||
INFOPLIST_FILE = MVMCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 3.1;
|
||||
@ -1134,7 +1140,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
|
||||
INFOPLIST_FILE = MVMCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 3.1;
|
||||
|
||||
@ -64,4 +64,19 @@ public struct ActionActionsModel: ActionModelProtocol {
|
||||
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return actions.isEqual(to: model.actions)
|
||||
&& concurrent == model.concurrent
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
extension ActionActionsModel: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
return "\(Self.self) [\(actions)]"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionBackModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,4 +28,11 @@ public struct ActionCallModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& callNumber == model.callNumber
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionCancelModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,4 +42,14 @@ public struct ActionContactModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& phoneNumber == model.phoneNumber
|
||||
&& firstName == model.firstName
|
||||
&& lastName == model.lastName
|
||||
&& approach == model.approach
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
public struct ActionNoopModel: ActionModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -24,4 +25,12 @@ public struct ActionNoopModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,10 +83,10 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol {
|
||||
}
|
||||
// Adds any client parameters to the request parameters.
|
||||
if let parametersToFetch = model.clientParameters,
|
||||
let fetchedParameters = try await ClientParameterHandler().getClientParameters(
|
||||
let fetchedParameters = await ClientParameterHandler().getClientParameters(
|
||||
with: parametersToFetch,
|
||||
requestParameters: requestParameters.parameters as? [String : Any] ?? [:],
|
||||
actionId: MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? "unknown",
|
||||
actionId: actionUUID,
|
||||
showLoadingOverlay: !requestParameters.backgroundRequest) {
|
||||
requestParameters.add(fetchedParameters)
|
||||
}
|
||||
|
||||
@ -115,4 +115,31 @@ public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol,
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
try container.encodeIfPresent(fallbackResponse, forKey: .fallbackResponse)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return pageType == model.pageType
|
||||
&& baseURL == model.baseURL
|
||||
&& appContext == model.appContext
|
||||
&& requestURL == model.requestURL
|
||||
&& modules == model.modules
|
||||
&& presentationStyle == model.presentationStyle
|
||||
&& tabBarIndex == model.tabBarIndex
|
||||
&& background == model.background
|
||||
&& clientParameters == model.clientParameters
|
||||
&& customTimeoutTime == model.customTimeoutTime
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& fallbackResponse == model.fallbackResponse
|
||||
}
|
||||
}
|
||||
|
||||
extension ActionOpenPageModel: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
if let requestURL {
|
||||
return "\(Self.self) for \(pageType) @ \(requestURL)"
|
||||
} else {
|
||||
return "\(Self.self) for \(pageType)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,4 +30,13 @@ public struct ActionOpenSMSModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
&& model.phoneNumber == phoneNumber
|
||||
&& model.message == message
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,9 +54,9 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol {
|
||||
|
||||
/// Opens the url using UIApplication open(url:). Throws URLError.failedToOpen if it fails.
|
||||
@MainActor
|
||||
public static func open(url: URL) async throws {
|
||||
public static func open(url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async throws {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
UIApplication.shared.open(url, options: [:]) { successful in
|
||||
UIApplication.shared.open(url, options: options) { successful in
|
||||
if successful {
|
||||
continuation.resume()
|
||||
} else {
|
||||
@ -76,12 +76,13 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol {
|
||||
// Try loading the app url first, otherwise fall back to browser url.
|
||||
if let appURL = model.appURL {
|
||||
do {
|
||||
try await ActionOpenUrlHandler.open(url: appURL)
|
||||
try await ActionOpenUrlHandler.open(url: appURL, options: model.appURLOptions?.options ?? [:])
|
||||
return
|
||||
} catch {
|
||||
// Log error and continue
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)")
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) {
|
||||
let isUsingUniversalLinksOnly = model.appURLOptions?.options[.universalLinksOnly] as? Bool ?? false
|
||||
if !isUsingUniversalLinksOnly, let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) {
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,4 +60,13 @@ open class ActionOpenUrlModel: ActionModelProtocol {
|
||||
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& browserUrl == model.browserUrl
|
||||
&& appURL == model.appURL
|
||||
&& appURLOptions == model.appURLOptions
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionPreviousSubmitModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,11 +25,16 @@ open class ActionRestartHandler: MVMCoreActionHandlerProtocol {
|
||||
return
|
||||
}
|
||||
continuation.resume(throwing: MVMCoreError.errorObject(error))
|
||||
} else {
|
||||
// Restarts the app (forcing any passed in page types).
|
||||
MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true)
|
||||
continuation.resume()
|
||||
return
|
||||
}
|
||||
if let sessionObject = MVMCoreSessionObject.sharedGlobal() {
|
||||
if model.hardReset {
|
||||
sessionObject.clearPersistentCache()
|
||||
}
|
||||
// Restarts the app (forcing any passed in page types).
|
||||
sessionObject.restartSession(withPageType: model.pageType, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true)
|
||||
}
|
||||
continuation.resume()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ public struct ActionRestartModel: ActionModelProtocol {
|
||||
public static var identifier: String = "restart"
|
||||
public var actionType: String = ActionRestartModel.identifier
|
||||
public var requestURL: URL?
|
||||
@DecodableDefault.True public var hardReset: Bool
|
||||
public var extraParameters: JSONValueDictionary?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
@ -26,10 +27,20 @@ public struct ActionRestartModel: ActionModelProtocol {
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, hardReset: Bool? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
self.pageType = pageType
|
||||
self.requestURL = requestUrl
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
self.hardReset = hardReset ?? true
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& requestURL == model.requestURL
|
||||
&& pageType == model.pageType
|
||||
&& hardReset == model.hardReset
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,10 @@ public struct ActionSettingModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2020 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
public struct ActionShareItemModel: Codable {
|
||||
public struct ActionShareItemModel: Codable, Equatable {
|
||||
|
||||
public enum SharedType: String, Codable {
|
||||
case text
|
||||
@ -14,14 +14,14 @@ public struct ActionShareItemModel: Codable {
|
||||
}
|
||||
|
||||
public var type: SharedType
|
||||
public var value: Any
|
||||
public var value: AnyHashable // Common Equatable type between String and URL.
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case value
|
||||
}
|
||||
|
||||
public init(type: SharedType, value: Any) {
|
||||
public init(type: SharedType, value: AnyHashable) {
|
||||
self.type = type
|
||||
self.value = value
|
||||
}
|
||||
@ -47,6 +47,11 @@ public struct ActionShareItemModel: Codable {
|
||||
try container.encode(value as! URL, forKey: .value)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: ActionShareItemModel, rhs: ActionShareItemModel) -> Bool {
|
||||
return lhs.type == rhs.type
|
||||
&& lhs.value == rhs.value
|
||||
}
|
||||
}
|
||||
|
||||
public struct ActionShareModel: ActionModelProtocol {
|
||||
@ -101,7 +106,7 @@ public struct ActionShareModel: ActionModelProtocol {
|
||||
private init(deprecatedFrom decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self)
|
||||
let type = try typeContainer.decode(ActionShareItemModel.SharedType.self, forKey: .sharedType)
|
||||
var value: Any
|
||||
var value: AnyHashable
|
||||
switch type {
|
||||
case .url:
|
||||
value = try typeContainer.decode(URL.self, forKey: .sharedText)
|
||||
@ -116,4 +121,11 @@ public struct ActionShareModel: ActionModelProtocol {
|
||||
try container.encode(actionType, forKey: .actionType)
|
||||
try container.encode(items, forKey: .items)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& items == model.items
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,9 +85,9 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws {
|
||||
try Task.checkCancellation()
|
||||
var (additionalData, uuid) = MVMCoreActionHandler.setUUID(additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "Begin Action: \(model)", additionalData: additionalData)
|
||||
defer {
|
||||
MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "End Action: \(model)", additionalData: additionalData)
|
||||
}
|
||||
let json = try additionalData.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model)
|
||||
|
||||
@ -148,12 +148,18 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
}
|
||||
|
||||
static public func log(string: String, additionalData: [AnyHashable: Any]?) {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)")
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("ActionHandler: UUID: \(getUUID(additionalData: additionalData) ?? "untracked"), \(string)", category: String(describing: Self.self))
|
||||
}
|
||||
|
||||
fileprivate func logActionError(_ error: Error, _ actionType: String?, _ additionalData: [AnyHashable: Any]?, _ delegateObject: DelegateObject?) {
|
||||
MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData)
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) {
|
||||
if let fromBridge = additionalData?["fromBridge"] as? Bool, fromBridge, let browserUrl = additionalData?["browserUrl"] as? String {
|
||||
errorObject.requestUrl = browserUrl
|
||||
}
|
||||
if let humanReadableMessage = (error as? HumanReadableDecodingErrorProtocol)?.readableDescription {
|
||||
errorObject.messageToLog = "Failed to decode the \(actionType ?? "") action model. " + humanReadableMessage
|
||||
}
|
||||
defaultHandleActionError(errorObject, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
@ -176,7 +182,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
} catch {
|
||||
let actionType = json?.optionalStringForKey(KeyActionType)
|
||||
switch error {
|
||||
case ModelRegistry.Error.decoderError, is DecodingError:
|
||||
case is ModelRegistry.Error, is DecodingError:
|
||||
MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionFailedToDecode(pageType: pageType(from: delegateObject), error: error))
|
||||
logActionError(error, actionType, additionalData, delegateObject)
|
||||
case ModelRegistry.Error.decoderErrorModelNotMapped:
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
import Foundation
|
||||
|
||||
/// A model for UIApplication.OpenExternalURLOptionsKey
|
||||
open class OpenUrlOptionsModel: Codable {
|
||||
open class OpenUrlOptionsModel: Codable, Equatable {
|
||||
|
||||
public var options: [UIApplication.OpenExternalURLOptionsKey: Any]
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -42,4 +43,16 @@ open class OpenUrlOptionsModel: Codable {
|
||||
try container.encode(universalLinksValue, forKey: .universalLinksOnly)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: OpenUrlOptionsModel, rhs: OpenUrlOptionsModel) -> Bool {
|
||||
return lhs.options.allSatisfy { pair in
|
||||
switch(pair.key) {
|
||||
case .universalLinksOnly:
|
||||
return rhs.options[pair.key] as? Bool == pair.value as? Bool
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
- (void)getJsonData:(nonnull MVMCoreRequestParameters *)requestParameters forUrl:(nonnull NSURL *)url completion:(nonnull void (^)(NSData * _Nullable data, MVMCoreErrorObject *_Nullable error))completion;
|
||||
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:completion:));
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections actionId:(nonnull NSString *)actionId completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:actionId:completion:));
|
||||
|
||||
// Creates a request object with the given parameters.
|
||||
- (void)transformToRequestWithParameters:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSURLRequest * _Nullable request, MVMCoreErrorObject *_Nullable error))completion;
|
||||
|
||||
@ -190,7 +190,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))closure {}
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *)excludeSections actionId:(NSString *)actionId completion:(void (^)(NSDictionary * _Nullable))completion {}
|
||||
|
||||
- (void)getJsonDictionary:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completion {
|
||||
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
|
||||
@ -206,7 +206,7 @@
|
||||
}
|
||||
|
||||
// Sets up the Initial parameters.
|
||||
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters completion:^(NSDictionary * _Nullable initialParameters) {
|
||||
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters actionId:requestParameters.identifier ?: [NSUUID UUID].UUIDString completion:^(NSDictionary * _Nullable initialParameters) {
|
||||
if (initialParameters) {
|
||||
[parameters setObject:initialParameters forKey:@"InitialParams"];
|
||||
}
|
||||
@ -232,7 +232,7 @@
|
||||
return;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
MVMCoreLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
|
||||
MVMCoreNetworkLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
|
||||
#endif
|
||||
|
||||
// Standard condensed to send to the server.
|
||||
@ -309,15 +309,15 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
MVMCoreLog(@"********************************* Cookie Sent *********************************");
|
||||
MVMCoreNetworkLog(@"********************************* Cookie Sent *********************************");
|
||||
[[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL] enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
MVMCoreLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
|
||||
MVMCoreNetworkLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
|
||||
}];
|
||||
|
||||
NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
|
||||
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
|
||||
MVMCoreLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
|
||||
MVMCoreNetworkLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
|
||||
|
||||
NSDate *startTimeDate = [NSDate dateWithTimeIntervalSinceReferenceDate:startTime];
|
||||
|
||||
@ -330,7 +330,7 @@
|
||||
[trackInfo setObject:error.localizedDescription forKey:@"error"];
|
||||
}
|
||||
|
||||
MVMCoreLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
|
||||
MVMCoreNetworkLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
|
||||
|
||||
id jsonObject = nil;
|
||||
MVMCoreErrorObject *errorObject = nil;
|
||||
@ -354,7 +354,7 @@
|
||||
// Log the response pretty.
|
||||
NSData *prettyData = [NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:&error];
|
||||
NSString *responseString = [[NSString alloc] initWithData:prettyData encoding:NSUTF8StringEncoding];
|
||||
MVMCoreLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
|
||||
MVMCoreNetworkLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
|
||||
}
|
||||
} else {
|
||||
// Empty response.
|
||||
@ -385,6 +385,7 @@
|
||||
} else {
|
||||
MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO];
|
||||
loadOperation.identifier = requestParameters.identifier;
|
||||
[loadOperation startLoadingAnimationIfNeeded];
|
||||
[self.blockingLoadQueue addOperation:loadOperation];
|
||||
return loadOperation;
|
||||
}
|
||||
@ -399,6 +400,8 @@
|
||||
|
||||
- (MVMCoreLoadRequestOperation *)loadObject:(nonnull MVMCoreLoadObject *)loadObject {
|
||||
MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithLoadObject:loadObject backgroundLoad:NO];
|
||||
loadOperation.identifier = loadObject.requestParameters.identifier;
|
||||
[loadOperation startLoadingAnimationIfNeeded];
|
||||
[self.blockingLoadQueue addOperation:loadOperation];
|
||||
return loadOperation;
|
||||
}
|
||||
|
||||
@ -38,6 +38,42 @@ public enum PopBackError: MVMError, CustomStringConvertible {
|
||||
|
||||
@objc
|
||||
public extension MVMCoreLoadRequestOperation {
|
||||
|
||||
/// Attempt to navigate to the controller with the given load object. Return the controller that we navigated to if successful.
|
||||
@objc
|
||||
@MainActor
|
||||
func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? {
|
||||
guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true,
|
||||
loadObject.requestParameters?.replaceViewControllerIfOnStackGoToOnly == true,
|
||||
let pageType = loadObject.pageType else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made. pageType:\(String(describing: loadObject.pageType))")
|
||||
return nil
|
||||
}
|
||||
let template = loadObject.pageJSON?.optionalStringForKey("template")
|
||||
guard let controllerMappingObject = MVMCoreViewControllerMappingObject.shared()?.getViewControllerMapping(forTemplate: template, pageType: pageType) else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)")
|
||||
return nil
|
||||
}
|
||||
var controllerType: AnyClass?
|
||||
if let programmaticMapping = controllerMappingObject as? MVMCoreViewControllerProgrammaticMappingObject {
|
||||
controllerType = programmaticMapping.viewControllerClass
|
||||
} else if let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) {
|
||||
// Need to create the view controller to fetch the type.
|
||||
controllerType = type(of: newVC)
|
||||
} else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let viewController = await NavigationHandler.shared().navigateToViewController(of: pageType, controllerType: controllerType) else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): No matching controller found in the hierarchy. Will need to create a new controller. pageType:\(pageType) controllerType:\(String(describing: controllerType)).")
|
||||
return nil
|
||||
}
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Navigated to controller. pageType:\(pageType) controllerType:\(String(describing: controllerType))")
|
||||
stopLoadingAnimationIfNeeded()
|
||||
return viewController
|
||||
}
|
||||
|
||||
@objc
|
||||
func popBackToPage(for loadObject: MVMCoreLoadObject) {
|
||||
Task(priority: .high) {
|
||||
@ -71,13 +107,9 @@ public extension MVMCoreLoadRequestOperation {
|
||||
func navigate(with navigationOperation: NavigationOperation, loadObject: MVMCoreLoadObject?) async {
|
||||
// stop any loading animation we may have started if we are about to display
|
||||
cancellable = NavigationHandler.shared().onNavigation
|
||||
.filter { $0.0 == .willNavigate }
|
||||
.sink { (event, operation) in
|
||||
if navigationOperation == operation,
|
||||
!self.backgroundLoad,
|
||||
!(loadObject?.requestParameters?.noloadingOverlay ?? false) {
|
||||
MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(false)
|
||||
}
|
||||
.filter { $0.0 == .willNavigate && navigationOperation == $0.1 }
|
||||
.sink { [weak self] (event, operation) in
|
||||
self?.stopLoadingAnimationIfNeeded()
|
||||
}
|
||||
await NavigationHandler.shared().navigate(with: navigationOperation)
|
||||
cancellable = nil
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
@interface MVMCoreLoadRequestOperation : MVMCoreOperation
|
||||
|
||||
@property (nullable, strong, nonatomic) MVMCoreRequestParameters *requestParameters;
|
||||
/// For load objects as in input parameter. Does not attach self generated load objects.
|
||||
@property (nullable, strong, nonatomic) MVMCoreLoadObject *loadObject;
|
||||
@property (nullable, strong, nonatomic) NSDictionary *dataForPage;
|
||||
@property (nullable, strong, nonatomic) DelegateObject *delegateObject;
|
||||
@ -42,6 +41,12 @@
|
||||
// Initializes the operation with the load object, data for page, and mvm view controller to handle the loading with. Can be used for loading a screen without going to the cache or server.
|
||||
- (nullable instancetype)initWithLoadObject:(nullable MVMCoreLoadObject *)loadObject backgroundLoad:(BOOL)backgroundLoad;
|
||||
|
||||
/// Begins the loading animation if needed.
|
||||
- (void)startLoadingAnimationIfNeeded;
|
||||
|
||||
/// Ends the loading animation if needed.
|
||||
- (void)stopLoadingAnimationIfNeeded;
|
||||
|
||||
/* Checks the cache for the data and calls the completion handler with any found data.
|
||||
* @param completionHandler The block that gets called with any fetched data. */
|
||||
+ (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler;
|
||||
@ -90,6 +95,8 @@
|
||||
*/
|
||||
+ (void)removeCaches:(nullable NSDictionary *)cacheDictionary;
|
||||
|
||||
+ (void)notifyListenersOfNewResponse:(nullable NSDictionary *)pages modules:(nullable NSDictionary *)modules systemParameters:(nullable NSDictionary *)systemParameters loadObject:(nonnull MVMCoreLoadObject *)loadObject;
|
||||
|
||||
/** Creates the view controller based on the load object passed in.
|
||||
* @param loadObject The load data from the cache or server.
|
||||
* @param completionHandler The completion handler to load once finished. Returns any loaded view controller and the load.*/
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
@property (nonatomic, readwrite) BOOL alertToShow;
|
||||
@property (strong, nonatomic, nullable) MVMCoreErrorObject *errorForAlertToShow;
|
||||
|
||||
@property (nonatomic, readwrite) BOOL loadingAnimationRunning;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreLoadRequestOperation
|
||||
@ -64,14 +66,13 @@
|
||||
- (void)cancel {
|
||||
[super cancel];
|
||||
[self.sessionTask cancel];
|
||||
[self stopLoadingAnimationIfNeeded];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
||||
// Adds a loading overlay if necessary.
|
||||
if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) {
|
||||
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading];
|
||||
}
|
||||
[self startLoadingAnimationIfNeeded];
|
||||
|
||||
[super start];
|
||||
}
|
||||
@ -83,11 +84,9 @@
|
||||
- (void)markAsFinished {
|
||||
|
||||
// stop any loading animation we may have started
|
||||
if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) {
|
||||
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:NO];
|
||||
}
|
||||
[self stopLoadingAnimationIfNeeded];
|
||||
|
||||
MVMCoreLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
|
||||
MVMCoreNetworkLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
|
||||
[super markAsFinished];
|
||||
}
|
||||
|
||||
@ -108,7 +107,7 @@
|
||||
}
|
||||
|
||||
- (void)main {
|
||||
MVMCoreLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
|
||||
MVMCoreNetworkLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
|
||||
|
||||
[self.requestParameters resolveURL:[MVMCoreSessionObject sharedGlobal]];
|
||||
|
||||
@ -140,14 +139,15 @@
|
||||
|
||||
// Log if loaded from cache.
|
||||
if (pageFromCache) {
|
||||
MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
|
||||
MVMCoreNetworkLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
|
||||
}
|
||||
if (modulesFromCache) {
|
||||
MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
|
||||
MVMCoreNetworkLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
|
||||
}
|
||||
|
||||
// Create a load object from any data we fetched.
|
||||
MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache];
|
||||
self.loadObject = loadObject;
|
||||
|
||||
// Check if we need to go to server for missing data.
|
||||
MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject];
|
||||
@ -162,22 +162,22 @@
|
||||
if(!self.backgroundLoad && loadObject.requestParameters.pageType) {
|
||||
[[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString];
|
||||
}
|
||||
|
||||
|
||||
// Send a new request to the server.
|
||||
[MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) {
|
||||
|
||||
|
||||
#if ENABLE_HARD_CODED_RESPONSE
|
||||
if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) {
|
||||
json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0";
|
||||
|
||||
|
||||
if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) {
|
||||
[[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache];
|
||||
}
|
||||
|
||||
|
||||
// Process the data retrieved from the server.
|
||||
[MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) {
|
||||
|
||||
@ -202,6 +202,20 @@
|
||||
|
||||
#pragma mark - Load Functions
|
||||
|
||||
- (void)startLoadingAnimationIfNeeded {
|
||||
if (self.loadingAnimationRunning) { return; }
|
||||
if (self.backgroundLoad) { return; }
|
||||
if (self.requestParameters.noloadingOverlay) { return; }
|
||||
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading];
|
||||
self.loadingAnimationRunning = YES;
|
||||
}
|
||||
|
||||
- (void)stopLoadingAnimationIfNeeded {
|
||||
if (!self.loadingAnimationRunning) { return; }
|
||||
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES];
|
||||
self.loadingAnimationRunning = NO;
|
||||
}
|
||||
|
||||
+ (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler {
|
||||
|
||||
if (requestParameters.neverLoadFromCache) {
|
||||
@ -430,6 +444,10 @@
|
||||
NSDictionary *systemParameters = [jsonDictionary dict:KeySystemParameters];
|
||||
loadObject.systemParametersJSON = systemParameters.count > 0 ? systemParameters : nil;
|
||||
|
||||
if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(willProcessLoadObject:)]) {
|
||||
[[MVMCoreObject sharedInstance].globalLoadDelegate willProcessLoadObject:loadObject];
|
||||
}
|
||||
|
||||
// module items are cached.
|
||||
MVMCoreErrorObject *moduleCachingError = nil;
|
||||
BOOL shouldContinue = [MVMCoreLoadRequestOperation cacheModules:modules loadObject:loadObject error:&moduleCachingError];
|
||||
@ -581,9 +599,18 @@
|
||||
};
|
||||
|
||||
if (!error.nativeDrivenErrorScreen) {
|
||||
|
||||
// Server driven screen, create normally
|
||||
[MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler];
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[loadObject.operation goToViewControllerWithLoadObject:loadObject completionHandler:^(UIViewController * _Nullable viewController) {
|
||||
[MVMCoreDispatchUtility performBlockInBackground:^{
|
||||
if (viewController) {
|
||||
[MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:viewController errorObject:nil];
|
||||
} else {
|
||||
[MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
} else {
|
||||
// Get the proper native error screen from the delegate
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
@ -659,7 +686,16 @@
|
||||
|
||||
if (obj && [obj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *responseInfo = [obj dict:KeyResponseInfo];
|
||||
if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
|
||||
//Response Info is missing but errorObject should be created with generic message + code + domain
|
||||
if (responseInfo == nil) {
|
||||
errorObject = [[MVMCoreLoadHandler sharedGlobal]
|
||||
errorForLoadObject:loadObject
|
||||
withTitle:nil
|
||||
message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical]
|
||||
messageToLog:[NSString stringWithFormat:@"Module %@ is missing a %@ object.", key, KeyResponseInfo]
|
||||
code:ErrorCodeJSONNotDictionary
|
||||
domain:ErrorDomainServer];
|
||||
} else if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
|
||||
errorObject = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:
|
||||
[[MVMCoreErrorObject alloc]
|
||||
initWithTitle:[responseInfo stringForKey:KeyErrorHeading]
|
||||
@ -669,7 +705,6 @@
|
||||
domain:ErrorDomainServer
|
||||
location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]];
|
||||
}
|
||||
|
||||
// Caches each dictionary from the array.
|
||||
[[MVMCoreCache sharedCache] addModuleToCache:obj module:key queue:nil waitUntilFinished:YES completionBlock:NULL];
|
||||
} else {
|
||||
@ -795,7 +830,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
MVMCoreLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog);
|
||||
MVMCoreNetworkLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog);
|
||||
|
||||
if (showAlertForErrorIfApplicable && (!loadObject.operation.backgroundLoad || loadObject.requestParameters.allowAlertsIfBackgroundRequest) && !loadObject.requestParameters.handleErrorsSilently && !error.silentError && !error.errorScreenError) {
|
||||
|
||||
|
||||
@ -79,8 +79,13 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) {
|
||||
|
||||
// Determines how it is loaded.
|
||||
@property (nonatomic) MFLoadStyle loadStyle;
|
||||
|
||||
/// Determines if we should search the stack for a controller with the same pageType or just load a new controller with style. Default true
|
||||
@property (nonatomic) BOOL replaceViewIfOnStackElseLoadWithStyle;
|
||||
|
||||
/// Determines if, when replaceViewIfOnStackElseLoadWithStyle is true, we should create a new controller or only pop back to the existing controller. Default true
|
||||
@property (nonatomic) BOOL replaceViewControllerIfOnStackGoToOnly;
|
||||
|
||||
// A flag for if a tab was pressed to cause this load. This will ensure that we do not load a new tab page.
|
||||
@property (nonatomic) BOOL tabWasPressed;
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
// Default load style.
|
||||
self.loadStyle = MFLoadStyleDefault;
|
||||
self.replaceViewIfOnStackElseLoadWithStyle = YES;
|
||||
self.replaceViewControllerIfOnStackGoToOnly = YES;
|
||||
self.identifier = [[NSUUID UUID] UUIDString];
|
||||
}
|
||||
return self;
|
||||
@ -189,6 +190,7 @@
|
||||
copyObject.contextRoot = [self.contextRoot copy];
|
||||
copyObject.loadStyle = self.loadStyle;
|
||||
copyObject.replaceViewIfOnStackElseLoadWithStyle = self.replaceViewIfOnStackElseLoadWithStyle;
|
||||
copyObject.replaceViewControllerIfOnStackGoToOnly = self.replaceViewControllerIfOnStackGoToOnly;
|
||||
copyObject.noViewControllerToLoad = self.noViewControllerToLoad;
|
||||
copyObject.dontDisplayViewController = self.dontDisplayViewController;
|
||||
copyObject.noloadingOverlay = self.noloadingOverlay;
|
||||
|
||||
@ -41,6 +41,9 @@
|
||||
/// Checks to see if the operation has content to show.
|
||||
- (BOOL)hasContentToShow:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)error;
|
||||
|
||||
/// Notifies the delegate we are about to process the load object.
|
||||
- (void)willProcessLoadObject:(nonnull MVMCoreLoadObject *)loadObject;
|
||||
|
||||
#if ENABLE_HARD_CODED_RESPONSE
|
||||
- (nullable NSDictionary *)getJSONForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters;
|
||||
- (nonnull NSDictionary *)modifyJSON:(nonnull NSDictionary *)json;
|
||||
|
||||
@ -8,13 +8,16 @@
|
||||
|
||||
public protocol MVMCoreLoggingDelegateProtocol {
|
||||
|
||||
// Can be used to log different actions performed by the core.
|
||||
/// Can be used to log different actions performed by the core.
|
||||
func handleDebugMessage(_ message: String?)
|
||||
|
||||
/// Can be used to log a message under a particular cagetory.
|
||||
func handleDebugMessage(_ message: String, category: String?)
|
||||
|
||||
// Can be used to choose how to log error objects.
|
||||
/// Can be used to choose how to log error objects.
|
||||
func addError(toLog errorObject: MVMCoreErrorObject)
|
||||
|
||||
// Log that the load has finished.
|
||||
/// Log that the load has finished.
|
||||
func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?)
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
|
||||
- (nullable NSArray*)getAccessibilityElements; //AccessibilityElements that are owned by Manager.
|
||||
|
||||
/// Attempt to navigate to the controller. Return the controller that we navigated to if successful.
|
||||
- (void)navigateToViewControllerOfPageType:(nonnull NSString *)pageType controllerType:(_Nullable Class)controllerType completionHandler:(void (^ __nullable)(UIViewController * _Nullable viewController))completionHandler;
|
||||
|
||||
@optional
|
||||
|
||||
/// Notifies the manager that the controller received new data.
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
public protocol ActionModelProtocol: ModelProtocol {
|
||||
public protocol ActionModelProtocol: ModelProtocol, CustomDebugStringConvertible {
|
||||
|
||||
var actionType: String { get }
|
||||
var extraParameters: JSONValueDictionary? { get set }
|
||||
@ -33,4 +33,15 @@ public extension ActionModelProtocol {
|
||||
static var categoryName: String {
|
||||
return "\(ActionModelProtocol.self)"
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
return actionType
|
||||
}
|
||||
|
||||
func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,4 +29,11 @@ public class ActionRunJavaScriptModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return jsCallback == model.jsCallback
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ClientParameterModel: Codable {
|
||||
public class ClientParameterModel: Equatable, Codable {
|
||||
|
||||
var timeout: Double?
|
||||
var list: [ClientParameterModelProtocol]
|
||||
|
||||
@ -33,4 +34,9 @@ public class ClientParameterModel: Codable {
|
||||
try container.encodeIfPresent(timeout, forKey: .timeout)
|
||||
try container.encodeModels(list, forKey: .list)
|
||||
}
|
||||
|
||||
public static func == (lhs: ClientParameterModel, rhs: ClientParameterModel) -> Bool {
|
||||
return lhs.list.isEqual(to: rhs.list)
|
||||
&& lhs.timeout == rhs.timeout
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,4 +30,9 @@ public extension ClientParameterModelProtocol {
|
||||
static var categoryName: String {
|
||||
return "\(ClientParameterModelProtocol.self)"
|
||||
}
|
||||
|
||||
func isEqual(to model: any ModelProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return type == model.type
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ModelProtocol: Codable {
|
||||
public protocol ModelProtocol: ModelComparisonProtocol, Codable {
|
||||
|
||||
/// The key name of the molecule
|
||||
static var identifier: String { get }
|
||||
@ -50,3 +50,102 @@ extension ModelProtocol {
|
||||
try unkeyedContainer.encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ModelComparisonProtocol {
|
||||
/// Shallow checks if the current model is equal to another model. Defaults to false unless implemented otherwise.
|
||||
func isEqual(to model: ModelComparisonProtocol) -> Bool
|
||||
}
|
||||
|
||||
extension ModelComparisonProtocol {
|
||||
public func isEqual(to model: ModelComparisonProtocol) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional {
|
||||
/// Checks if the current model is equal to another model.
|
||||
func isEqual(to model: ModelComparisonProtocol?) -> Bool {
|
||||
guard let self = self as? ModelComparisonProtocol else {
|
||||
return model == nil
|
||||
}
|
||||
guard let model = model else {
|
||||
return false
|
||||
}
|
||||
return self.isEqual(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Collection {
|
||||
/// Checks if all the models in the given collection match another given collection.
|
||||
func isEqual(to models: [ModelComparisonProtocol]) -> Bool {
|
||||
guard count == models.count, let self = self as? [ModelComparisonProtocol] else { return false }
|
||||
return models.indices.allSatisfy { index in
|
||||
self[index].isEqual(to: models[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional where Wrapped: Collection {
|
||||
/// Checks if the current model is equal to another model.
|
||||
func isEqual(to models: [ModelComparisonProtocol]?) -> Bool {
|
||||
guard let self = self as? [ModelComparisonProtocol] else {
|
||||
return models == nil
|
||||
}
|
||||
guard let models = models else {
|
||||
return false
|
||||
}
|
||||
return self.isEqual(to: models)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional {
|
||||
/// Checks if
|
||||
func matchExistence(with anotherOptional: Optional) -> Bool {
|
||||
switch(self) {
|
||||
case .none:
|
||||
return anotherOptional == nil
|
||||
case .some(_):
|
||||
return anotherOptional != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func == (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs.isEqual(to: rhs)
|
||||
case (.none, .none):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func != (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
func == (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs == rhs
|
||||
case (.none, .none):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func != (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
func == (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
|
||||
return lhs.elementsEqual(rhs, by: { (lhsElement, rhsElement) -> Bool in
|
||||
return lhsElement == rhsElement
|
||||
})
|
||||
}
|
||||
|
||||
func != (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
@ -50,9 +50,9 @@ public struct ModelRegistry {
|
||||
|
||||
/// A convenience wrapping function where error handling is managed within the class.
|
||||
/// - Parameter type: Takes an object of ModelProtocol.self which is used to register
|
||||
public static func register<M: ModelProtocol>(_ type: M.Type) {
|
||||
public static func register<M: ModelProtocol>(_ type: M.Type, allowsReplace: Bool = false) {
|
||||
do {
|
||||
try throwable_register(type: type)
|
||||
try throwable_register(type: type, allowsReplace: allowsReplace)
|
||||
} catch {
|
||||
handleError(error)
|
||||
}
|
||||
@ -211,7 +211,7 @@ public struct ModelRegistry {
|
||||
|
||||
public static func getCodingKey<T>(for type: T.Type) throws -> AnyCodingKey {
|
||||
guard let category = getCategory(for: type) else {
|
||||
throw ModelRegistry.Error.decoderOther(message: "decodeModelsIfPresent only works for objects implementing the ModelProtocol protocol")
|
||||
throw ModelRegistry.Error.decoderOther(message: "Category hasn’t been registered for the CodingKey for this type: \(String(describing: type.self))")
|
||||
}
|
||||
|
||||
return AnyCodingKey(category.codingKey)
|
||||
|
||||
106
MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift
Normal file
106
MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift
Normal file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// Cache.swift
|
||||
// JSONCreator
|
||||
//
|
||||
// Created by Matt Bruce on 3/20/24.
|
||||
// Copyright © 2024 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
public enum CacheError: Error {
|
||||
case serializationFailed
|
||||
case deserializationFailed
|
||||
case dataNotFound
|
||||
case dataExpired
|
||||
case saveFailed(Error)
|
||||
case loadFailed(Error)
|
||||
}
|
||||
|
||||
public class CachedData: Codable {
|
||||
public var data: [String: AnyHashable]
|
||||
public var expirationDate: Date
|
||||
enum CodingKeys: CodingKey {
|
||||
case data, expirationDate
|
||||
}
|
||||
|
||||
public init(data: [String: AnyHashable], expirationDate: Date) {
|
||||
self.data = data
|
||||
self.expirationDate = expirationDate
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(expirationDate, forKey: .expirationDate)
|
||||
let dataAsData = try JSONSerialization.data(withJSONObject: data, options: [])
|
||||
try container.encode(dataAsData, forKey: .data)
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
expirationDate = try values.decode(Date.self, forKey: .expirationDate)
|
||||
let dataAsData = try values.decode(Data.self, forKey: .data)
|
||||
guard let jsonData = try JSONSerialization.jsonObject(with: dataAsData, options: []) as? [String: AnyHashable] else {
|
||||
throw CacheError.deserializationFailed
|
||||
}
|
||||
data = jsonData
|
||||
}
|
||||
}
|
||||
|
||||
@objc public class PersistentCacheManager: NSObject {
|
||||
@objc public static let shared = PersistentCacheManager()
|
||||
private let fileManager = FileManager.default
|
||||
@objc public lazy var cacheDirectory = { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Atomic")}()
|
||||
|
||||
private override init() {}
|
||||
|
||||
@objc public func save(data: [String: AnyHashable], forKey key: String, path: URL, expirationDate: Date) throws {
|
||||
let cachedData = CachedData(data: data, expirationDate: expirationDate)
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: path.deletingLastPathComponent().relativePath, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete])
|
||||
let dataToSave = try JSONEncoder().encode(cachedData)
|
||||
try dataToSave.write(to: path, options: [.atomic, .completeFileProtection])
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: SAVED TO PERSISTENT CACHE, key:\(key), path:\(path)")
|
||||
} catch is EncodingError {
|
||||
throw CacheError.serializationFailed
|
||||
} catch {
|
||||
throw CacheError.saveFailed(error)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func load(forKey key: String, path: URL) throws -> [String: AnyHashable] {
|
||||
do {
|
||||
let data = try Data(contentsOf: path)
|
||||
let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data)
|
||||
if Date() < decodedCachedData.expirationDate {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: LOADED FROM PERSISTENT CACHE, key:\(key), path:\(path)")
|
||||
return decodedCachedData.data
|
||||
} else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key), path:\(path)")
|
||||
throw CacheError.dataExpired
|
||||
}
|
||||
} catch {
|
||||
// Remove item from the cache on any failure.
|
||||
try fileManager.removeItem(at: path)
|
||||
|
||||
switch error {
|
||||
case is DecodingError:
|
||||
throw CacheError.deserializationFailed
|
||||
default:
|
||||
throw CacheError.loadFailed(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func remove(forKey key: String) throws {
|
||||
let filePath = self.filePath(forKey: key)
|
||||
try fileManager.removeItem(at: filePath)
|
||||
}
|
||||
|
||||
@objc public func removeAll() throws {
|
||||
try FileManager.default.removeItem(at: cacheDirectory)
|
||||
}
|
||||
|
||||
private func filePath(forKey key: String) -> URL {
|
||||
return cacheDirectory.appendingPathComponent("\(key).json")
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,9 @@
|
||||
#import <AVKit/AVKit.h>
|
||||
@class MVMCoreErrorObject;
|
||||
|
||||
extern NSString * _Nonnull const KeyCachePolicy;
|
||||
extern NSString * _Nonnull const KeyCacheExpiry;
|
||||
|
||||
//block returned when getting image
|
||||
//parameters are UIImage object for the image, NSData for gif images, UIImage object for the image, A BOOL to indicate if it is a fall back image.
|
||||
typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOOL);
|
||||
@ -31,10 +34,28 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
|
||||
#pragma mark - Page and Module Handling
|
||||
|
||||
// Checks the set of pageTypes to be cached for the given pageType.
|
||||
- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType;
|
||||
- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType;
|
||||
|
||||
// Checks the set of modules to be cached for the given module
|
||||
- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module;
|
||||
- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName;
|
||||
|
||||
/// Returns if the json is expired or not.
|
||||
- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
/// Returns the expiry time for the object.
|
||||
- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
/// Checks if the page is to be persistently cached.
|
||||
- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType;
|
||||
|
||||
/// Checks if the module is to be persistently cached.
|
||||
- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module;
|
||||
|
||||
/// Can override the path for the page to be cached. Currently Cache/Atomic/Pages/pageType
|
||||
- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType;
|
||||
|
||||
/// Can override the path for the page to be cached. Currently Cache/Atomic/Modules/moduleName
|
||||
- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName;
|
||||
|
||||
// For pages external to the mobile first framework to be added to the list to not cache
|
||||
- (void)addPageTypesToNotCache:(nullable NSArray <NSString *>*)array;
|
||||
@ -53,6 +74,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
|
||||
// Gets a json dictionary from the cache with all the requested modules. Pass in the block that you want to run once the dictionary is received. This will be run on a background thread.
|
||||
- (void)fetchJSONForModules:(nullable NSArray *)modules completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler;
|
||||
|
||||
/// Returns a page JSON from the persistent cache.
|
||||
- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType;
|
||||
|
||||
/// Returns a module JSON from the persistent cache.
|
||||
- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName;
|
||||
|
||||
#pragma mark - Advanced Fetch
|
||||
|
||||
// Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait.
|
||||
@ -75,6 +102,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
|
||||
// Adds a json dictionary to the cache by modules.
|
||||
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
/// Adds the json to the persistent cache by pageType.
|
||||
- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate;
|
||||
|
||||
/// Adds the json to the persistent cache by module.
|
||||
- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate;
|
||||
|
||||
#pragma mark - Advanced Insertion
|
||||
|
||||
// Adds a json dictionary to the cache by pageType. Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait.
|
||||
@ -111,6 +144,15 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
|
||||
// Removes the json for modules. Pass in the block that you want to run once the dictionary is received and which queue to run it on.
|
||||
- (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock;
|
||||
|
||||
/// Clears the persistent JSON cache
|
||||
- (void)clearPersistentJSONCache;
|
||||
|
||||
// Removes a json dictionary from the cache by pageType.
|
||||
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
// Removes a json dictionary from the cache by moduleType.
|
||||
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
#pragma mark Image Functions
|
||||
|
||||
/// Register a bundle as one to search for images in.
|
||||
|
||||
@ -17,6 +17,9 @@
|
||||
#import "MVMCoreErrorConstants.h"
|
||||
#import "MVMCoreLoggingHandlerHelper.h"
|
||||
|
||||
NSString * _Nonnull const KeyCachePolicy = @"cachePolicy";
|
||||
NSString * _Nonnull const KeyCacheExpiry = @"expiry";
|
||||
|
||||
@interface MVMCoreCache ()
|
||||
|
||||
// The cache for json.
|
||||
@ -91,12 +94,55 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
return _modulesToNotCache;
|
||||
}
|
||||
|
||||
- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType {
|
||||
return ![self.pageTypesToNotCache containsObject:pageType];
|
||||
- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType {
|
||||
if ([self.pageTypesToNotCache containsObject:pageType]) { return NO; }
|
||||
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
|
||||
return shouldCache == nil || shouldCache.boolValue;
|
||||
}
|
||||
|
||||
- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module {
|
||||
return ![self.modulesToNotCache containsObject:module];
|
||||
- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName {
|
||||
if ([self.modulesToNotCache containsObject:moduleName]) { return NO; }
|
||||
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
|
||||
return shouldCache == nil || shouldCache.boolValue;
|
||||
}
|
||||
|
||||
- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary {
|
||||
NSDate *expirationDate = [self getExpirationDateForJSON:jsonDictionary];
|
||||
NSDate *now = [NSDate date];
|
||||
if ([now compare:expirationDate] == NSOrderedDescending) {
|
||||
[MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: NEW DATA ALREADY EXPIRED %@ now:%@ expirationDate:%@",jsonDictionary,now,expirationDate]];
|
||||
}
|
||||
return [now compare:expirationDate] == NSOrderedDescending;
|
||||
}
|
||||
|
||||
- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy];
|
||||
NSTimeInterval interval = [[cachePolicy string:KeyCacheExpiry] doubleValue] / 1000;
|
||||
return [NSDate dateWithTimeIntervalSince1970:interval];
|
||||
}
|
||||
|
||||
- (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy];
|
||||
if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) {
|
||||
return NO;
|
||||
}
|
||||
return ![self isJSONExpired:jsonDictionary];
|
||||
}
|
||||
|
||||
- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType {
|
||||
return [self shouldPersistentlyCacheJSON:jsonDictionary];
|
||||
}
|
||||
|
||||
- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module {
|
||||
return [self shouldPersistentlyCacheJSON:jsonDictionary];
|
||||
}
|
||||
|
||||
- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType {
|
||||
return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Pages"] URLByAppendingPathComponent:pageType] URLByAppendingPathExtension:@"json"];
|
||||
}
|
||||
|
||||
- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName {
|
||||
return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Modules"] URLByAppendingPathComponent:moduleName]URLByAppendingPathExtension:@"json"];
|
||||
}
|
||||
|
||||
- (void)addPageTypesToNotCache:(nullable NSArray <NSString *>*)array {
|
||||
@ -125,6 +171,24 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[self fetchJSONForModules:modules queue:self.completionQueue waitUntilFinished:NO completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType {
|
||||
NSError *error = nil;
|
||||
return [[PersistentCacheManager shared] loadForKey:pageType path:[self getPathForPersistentCachePage:pageType] error:&error];
|
||||
}
|
||||
|
||||
- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName {
|
||||
NSError *error = nil;
|
||||
return [[PersistentCacheManager shared] loadForKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] error:&error];
|
||||
}
|
||||
|
||||
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
|
||||
}
|
||||
|
||||
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
[[PersistentCacheManager shared] removeForKey:moduleType error:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Advanced Fetch
|
||||
|
||||
- (void)fetchJSONForPageType:(nullable NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler {
|
||||
@ -140,6 +204,9 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
|
||||
// First checks the cache by page type.
|
||||
dictionary = [weakSelf.pageTypeCache objectForKey:pageType];
|
||||
if (!dictionary) {
|
||||
dictionary = [self fetchPageFromPersistentCache:pageType];
|
||||
}
|
||||
} else {
|
||||
|
||||
// If no pagetype, return whole cache
|
||||
@ -176,6 +243,11 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
NSDictionary *moduleDictionary = [weakSelf.moduleCache objectForKey:module];
|
||||
if (moduleDictionary) {
|
||||
[modulesDictionary setObject:moduleDictionary forKey:module];
|
||||
} else {
|
||||
moduleDictionary = [self fetchModuleFromPersistentCache:module];
|
||||
if (moduleDictionary) {
|
||||
[modulesDictionary setObject:moduleDictionary forKey:module];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,16 +278,37 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[self addModulesToCache:jsonDictionary queue:nil waitUntilFinished:NO completionBlock:NULL];
|
||||
}
|
||||
|
||||
- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate {
|
||||
NSError *error = nil;
|
||||
[[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:pageType path:[self getPathForPersistentCachePage:pageType] expirationDate:expirationDate error:&error];
|
||||
if (error) {
|
||||
[[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,pageType]]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate {
|
||||
NSError *error = nil;
|
||||
[[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] expirationDate:expirationDate error:&error];
|
||||
if (error) {
|
||||
[[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,moduleName]]];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Advanced Insertion
|
||||
|
||||
- (void)addPageToCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
|
||||
if (![self shouldCachePageJSON:jsonDictionary pageType:pageType]) {
|
||||
if (completionBlock) {
|
||||
completionBlock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSBlockOperation *addOperation = [[NSBlockOperation alloc] init];
|
||||
__weak NSBlockOperation *weakOperation = addOperation;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[addOperation addExecutionBlock:^{
|
||||
|
||||
if (!weakOperation.isCancelled && [[MVMCoreCache sharedCache] shouldCacheJSONWithPageType:pageType]) {
|
||||
if (!weakOperation.isCancelled) {
|
||||
|
||||
// There must be a dictionary and page type to cache.
|
||||
if (jsonDictionary && pageType) {
|
||||
@ -227,10 +320,13 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
}
|
||||
|
||||
// Adds json to cache with page type key.
|
||||
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
|
||||
if (shouldCache == nil || shouldCache.boolValue) {
|
||||
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
|
||||
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
|
||||
|
||||
if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) {
|
||||
[self removePersistentJSONCacheForPageType:pageType pageJSON:jsonDictionary];
|
||||
return;
|
||||
}
|
||||
[self addPageToPersistentCache:jsonDictionary pageType:pageType expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
|
||||
}
|
||||
}
|
||||
if (completionBlock) {
|
||||
@ -241,11 +337,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
}
|
||||
|
||||
- (void)addModuleToCache:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
[self addModulesToCache:@{module:jsonDictionary} queue:queue waitUntilFinished:waitUntilFinished completionBlock:completionBlock];
|
||||
}
|
||||
|
||||
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
|
||||
if (![self shouldCacheModuleJSON:jsonDictionary moduleName:module]) {
|
||||
if (completionBlock) {
|
||||
completionBlock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSBlockOperation *addOperation = [[NSBlockOperation alloc] init];
|
||||
__weak NSBlockOperation *weakOperation = addOperation;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
@ -253,23 +350,19 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
|
||||
if (!weakOperation.isCancelled) {
|
||||
|
||||
[jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
if (!weakSelf.moduleCache) {
|
||||
|
||||
if ([[MVMCoreCache sharedCache] shouldCacheJSONWithModule:key]) {
|
||||
|
||||
if (!weakSelf.moduleCache) {
|
||||
|
||||
// Create the cache if necessary.
|
||||
weakSelf.moduleCache = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
// Adds json to cache with page type key.
|
||||
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
|
||||
if (shouldCache == nil || shouldCache.boolValue) {
|
||||
[weakSelf.moduleCache setObject:obj forKey:key];
|
||||
}
|
||||
}
|
||||
}];
|
||||
// Create the cache if necessary.
|
||||
weakSelf.moduleCache = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
[weakSelf.moduleCache setObject:jsonDictionary forKey:module];
|
||||
|
||||
if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) {
|
||||
[self removePersistentModuleCacheForModule:module moduleJSON:jsonDictionary];
|
||||
return;
|
||||
}
|
||||
[self addModuleToPersistentCache:jsonDictionary moduleName:module expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
|
||||
}
|
||||
if (completionBlock) {
|
||||
[(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished];
|
||||
@ -278,6 +371,28 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[self.moduleQueue addOperations:@[addOperation] waitUntilFinished:waitUntilFinished];
|
||||
}
|
||||
|
||||
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
[jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
if (![obj isKindOfClass:[NSDictionary class]]) {
|
||||
MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc]
|
||||
initWithTitle:nil
|
||||
messageToLog:[NSString stringWithFormat:@"Invalid module format. Cannot cache %@ as it's not an object", key]
|
||||
code:ErrorCodeJSONNotDictionary
|
||||
domain:ErrorDomainSystem
|
||||
location:NSStringFromClass([self class])];
|
||||
[MVMCoreLoggingHandler.sharedLoggingHandler addErrorToLog:error];
|
||||
return;
|
||||
}
|
||||
[self addModuleToCache:obj module:key];
|
||||
}];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.moduleQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{
|
||||
if (completionBlock) {
|
||||
[(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished];
|
||||
}
|
||||
}]] waitUntilFinished:waitUntilFinished];
|
||||
}
|
||||
|
||||
#pragma mark - Simple Deletion
|
||||
|
||||
- (void)removeJSONForPageType:(nonnull NSString *)pageType {
|
||||
@ -292,6 +407,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[self removeJSONForModules:modules queue:nil waitUntilFinished:NO completionBlock:NULL];
|
||||
}
|
||||
|
||||
- (void)clearPersistentJSONCache {
|
||||
[[PersistentCacheManager shared] removeAllAndReturnError:nil];
|
||||
}
|
||||
|
||||
- (void)clearMFCache {
|
||||
[self.pageTypeQueue cancelAllOperations];
|
||||
[self.moduleQueue cancelAllOperations];
|
||||
@ -329,6 +448,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
}
|
||||
}];
|
||||
[self.pageTypeQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished];
|
||||
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
|
||||
}
|
||||
|
||||
- (void)removeJSONForModule:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
@ -352,6 +472,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
}
|
||||
}];
|
||||
[self.moduleQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished];
|
||||
[[PersistentCacheManager shared] removeForKey:module error:nil];
|
||||
}
|
||||
|
||||
- (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
@ -371,6 +492,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
// Removes json from cache with module key.
|
||||
[weakSelf.moduleCache removeObjectForKey:obj];
|
||||
}
|
||||
[[PersistentCacheManager shared] removeForKey:obj error:nil];
|
||||
}
|
||||
}];
|
||||
if (completionBlock) {
|
||||
|
||||
@ -7,9 +7,31 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
@objc public extension MVMCoreLoggingHandler {
|
||||
@objc func print(with message: String) {
|
||||
Swift.print(message)
|
||||
public protocol CoreLogging {
|
||||
static var loggingCategory: String? { get }
|
||||
|
||||
var loggingPrefix: String { get }
|
||||
}
|
||||
|
||||
public extension CoreLogging {
|
||||
|
||||
static var loggingCategory: String? { return nil }
|
||||
|
||||
var loggingPrefix: String {
|
||||
return ""
|
||||
}
|
||||
|
||||
static func debugLog(_ string: String) {
|
||||
#if LOGGING
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage(string, category: loggingCategory)
|
||||
#endif
|
||||
}
|
||||
|
||||
func debugLog(_ string: String) {
|
||||
#if LOGGING
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("\(loggingPrefix)\(string)", category: Self.loggingCategory)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,28 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
@objc open class MVMCoreLoggingHandler: NSObject, MVMCoreLoggingDelegateProtocol {
|
||||
|
||||
public static let standardCategory = "General"
|
||||
|
||||
private let logger = Logger(subsystem: "MVMCoreLogging", category: standardCategory)
|
||||
private var loggerCache = [String: Logger]()
|
||||
|
||||
open func getLogger(category: String?) -> Logger {
|
||||
if let category {
|
||||
if let logger = loggerCache[category] {
|
||||
return logger
|
||||
} else {
|
||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: category)
|
||||
loggerCache[category] = logger
|
||||
return logger
|
||||
}
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
@objc(sharedLoggingHandler)
|
||||
public static func shared() -> Self? {
|
||||
return MVMCoreActionUtility.initializerClassCheck(MVMCoreObject.sharedInstance()?.loggingDelegate as? NSObject, classToVerify: self) as? Self
|
||||
@ -29,13 +48,58 @@ import Foundation
|
||||
// MARK: - logging delegate
|
||||
@objc open func handleDebugMessage(_ message: String?) {
|
||||
#if LOGGING
|
||||
guard let message = message else { return }
|
||||
self.print(with: message)
|
||||
guard let message = message else { return }
|
||||
guard message.count < 1024 else {
|
||||
logger.debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
logger.debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
// TODO: How do we split the messaging by Library and Subsystem?
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc open func handleDebugMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
open func handleWarningMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).warning("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).warning("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
open func handleErrorMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).error("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).error("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc(addErrorToLog:)
|
||||
open func addError(toLog errorObject: MVMCoreErrorObject) {
|
||||
// Subclass to handle.
|
||||
if errorObject.silentError {
|
||||
handleWarningMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
|
||||
} else {
|
||||
handleErrorMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
|
||||
}
|
||||
}
|
||||
|
||||
open func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?) {}
|
||||
}
|
||||
|
||||
@ -8,7 +8,11 @@
|
||||
|
||||
#ifndef MVMCoreLoggingHandlerHelper_h
|
||||
#define MVMCoreLoggingHandlerHelper_h
|
||||
|
||||
#define MVMCoreLog(fmt, ...) \
|
||||
[MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__]];
|
||||
#endif
|
||||
|
||||
#define MVMCoreNetworkLog(fmt, ...) \
|
||||
[MVMCoreLoggingHandler.sharedLoggingHandler handleDebugMessage:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__] category: @"Network"];
|
||||
|
||||
#endif
|
||||
|
||||
@ -189,6 +189,13 @@ public class NavigationHandler {
|
||||
await navigate(with: .pop(navigationController: navigationController, animated: animated), delegateObject: delegateObject)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to go to navigate to a viewcontroller of pageType and controllerType. Returns the view controller if successful
|
||||
@MainActor
|
||||
func navigateToViewController(of pageType: String, controllerType: AnyClass?) async -> UIViewController? {
|
||||
// TODO: Need to manage for present view controllers.
|
||||
return await MVMCoreObject.sharedInstance()?.viewControllerManager?.navigate(toViewControllerOfPageType: pageType, controllerType: controllerType)
|
||||
}
|
||||
}
|
||||
|
||||
extension UINavigationController {
|
||||
|
||||
@ -68,9 +68,9 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
|
||||
let index = navigationController.getIndexOfViewController(with: pageType) else {
|
||||
return false
|
||||
}
|
||||
var viewControllers = navigationController.viewControllers[...index]
|
||||
var viewControllers = Array(navigationController.viewControllers[...index])
|
||||
viewControllers[index] = viewController
|
||||
set(viewControllers: Array(viewControllers), navigationController: navigationController, animated: animated)
|
||||
set(viewControllers: viewControllers, navigationController: navigationController, animated: animated)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -110,7 +110,8 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
|
||||
*/
|
||||
@MainActor
|
||||
open func set(viewControllers: [UIViewController], navigationController: UINavigationController, animated: Bool) {
|
||||
guard viewControllers.count > 0 else {
|
||||
guard viewControllers.count > 0,
|
||||
navigationController.viewControllers != viewControllers else { // If the controller stack is the same, iOS will not call the delegate method of the change, causing the operation to hang.
|
||||
markAsFinished()
|
||||
return
|
||||
}
|
||||
|
||||
@ -38,6 +38,9 @@
|
||||
/// Clears the session singleton. Creates a new session NSURLSession also.
|
||||
- (void)clearSessionObject;
|
||||
|
||||
/// Clears any persistent cache related to the current session.
|
||||
- (void)clearPersistentCache;
|
||||
|
||||
/// Copys string to clipboard and assigns self.clipboardString for validation
|
||||
/// Should only be used when expected string is a secure string
|
||||
-(void)copyStringToClipboard :(nullable NSString *)clipboardString;
|
||||
|
||||
@ -53,4 +53,6 @@
|
||||
self.session = [self createNSURLSession];
|
||||
}
|
||||
|
||||
- (void)clearPersistentCache {}
|
||||
|
||||
@end
|
||||
|
||||
@ -23,6 +23,9 @@ public class MVMCoreObject: NSObject {
|
||||
public var globalLoadDelegate: MVMCoreGlobalLoadProtocol?
|
||||
public var loadingProtocol: MVMCoreLoadingOverlayDelegateProtocol?
|
||||
public var loggingDelegate: MVMCoreLoggingDelegateProtocol?
|
||||
|
||||
/// The main manager of the view controllers in the application.
|
||||
public weak var viewControllerManager: MVMCoreViewManagerProtocol?
|
||||
|
||||
/// A reference to the calling application delegate that should be set. For a normal app, could be the UIApplicationDelegate. For watch, could be WKExtensionDelegate. For iMessage, could be MSMessagesAppViewController. etc, etc. Useful for the framework to call delegate specific functions.
|
||||
public weak var applicationDelegate: AnyObject?
|
||||
|
||||
@ -89,21 +89,21 @@ public extension NavigationHandler {
|
||||
switch requestParameters.loadStyle {
|
||||
case .replaceCurrent:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .onTopOfRoot:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController,
|
||||
let root = navigationController.viewControllers.first else { return nil }
|
||||
let viewControllers = [root, viewController]
|
||||
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .becomeRoot:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .present:
|
||||
guard let viewControllerToPresentOn = getViewControllerToPresentOn() else { return nil }
|
||||
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
default:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,8 +23,10 @@
|
||||
// Initialization code
|
||||
self.title = title;
|
||||
self.messageToDisplay = message;
|
||||
self.messageToLog = message;
|
||||
self.code = code;
|
||||
self.domain = domain;
|
||||
self.systemDomain = nil;
|
||||
self.location = location;
|
||||
self.date = [NSDate date];
|
||||
self.silentError = YES;
|
||||
@ -32,6 +34,11 @@
|
||||
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
|
||||
self.applicationState = [UIApplication sharedApplication].applicationState;
|
||||
}];
|
||||
self.sessionId = nil;
|
||||
self.requestId = nil;
|
||||
self.requestUrl = nil;
|
||||
self.serverResponseInfo = nil;
|
||||
self.crashLog = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -41,9 +48,11 @@
|
||||
if (self = [super init]) {
|
||||
// Initialization code
|
||||
self.title = title;
|
||||
self.messageToDisplay = nil;
|
||||
self.messageToLog = messageToLog;
|
||||
self.code = code;
|
||||
self.domain = domain;
|
||||
self.systemDomain = nil;
|
||||
self.location = location;
|
||||
self.date = [NSDate date];
|
||||
self.silentError = YES;
|
||||
@ -51,6 +60,11 @@
|
||||
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
|
||||
self.applicationState = [UIApplication sharedApplication].applicationState;
|
||||
}];
|
||||
self.sessionId = nil;
|
||||
self.requestId = nil;
|
||||
self.requestUrl = nil;
|
||||
self.serverResponseInfo = nil;
|
||||
self.crashLog = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
69
MVMCore/MVMCore/Utility/ReadableDecodingErrors.swift
Normal file
69
MVMCore/MVMCore/Utility/ReadableDecodingErrors.swift
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// ReadableDecodingErrors.swift
|
||||
// MVMCore
|
||||
//
|
||||
// Created by Kyle Hedden on 10/5/23.
|
||||
// Copyright © 2023 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol HumanReadableDecodingErrorProtocol {
|
||||
var readableDescription: String { get }
|
||||
}
|
||||
|
||||
extension JSONError: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .other(let other):
|
||||
if let other = other as? HumanReadableDecodingErrorProtocol {
|
||||
return other.readableDescription
|
||||
}
|
||||
return description
|
||||
default:
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ModelRegistry.Error: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil:
|
||||
return "Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })"
|
||||
|
||||
case .decoderErrorObjectNotPresent(let codingKey, let codingPath):
|
||||
return "Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .decoderOther(let message):
|
||||
return "An issue occurred while decoding: \(message)"
|
||||
|
||||
case .other(let message):
|
||||
return "Registry error: \(message)"
|
||||
|
||||
default:
|
||||
return "Registry error: \((self as NSError).localizedFailureReason ?? self.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DecodingError: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .keyNotFound(let codingKey, let context):
|
||||
return "Required key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .valueNotFound(_, let context):
|
||||
return "Value not found @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .typeMismatch(_, let context):
|
||||
return "Value type mismatch @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .dataCorrupted(let context):
|
||||
return "Data corrupted @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
@unknown default:
|
||||
return (self as NSError).localizedFailureReason ?? self.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,9 @@
|
||||
// For pages external to the mobile first framework to be added to the view controller mapping.
|
||||
- (void)addToTemplateViewControllerMapping:(nullable NSDictionary <NSString *,NSObject <MVMCoreViewControllerMappingProtocol>*>*)map;
|
||||
|
||||
// Transition function: A mix of new and legacy.
|
||||
- (nullable NSObject <MVMCoreViewControllerMappingProtocol>*)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType;
|
||||
|
||||
// Transition function: A mix of new and legacy.
|
||||
- (nullable UIViewController <MVMCoreViewControllerProtocol> *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType;
|
||||
|
||||
|
||||
@ -46,6 +46,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSObject <MVMCoreViewControllerMappingProtocol>*)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType {
|
||||
if (templateID) {
|
||||
return [self getViewControllerMappingForTemplate:templateID];
|
||||
} else if (pageType) {
|
||||
return [self getViewControllerMappingForPageType:pageType];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Transition function: A mix of new and legacy.
|
||||
- (nullable UIViewController <MVMCoreViewControllerProtocol> *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType {
|
||||
if (templateID) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user