diff --git a/Images/Popup/datepicker_1.png b/Images/Popup/datepicker_1.png new file mode 100644 index 0000000..142ba56 Binary files /dev/null and b/Images/Popup/datepicker_1.png differ diff --git a/Images/Popup/datepicker_2.png b/Images/Popup/datepicker_2.png new file mode 100644 index 0000000..5d126ea Binary files /dev/null and b/Images/Popup/datepicker_2.png differ diff --git a/Notification Agent Alerts/Info.plist b/Notification Agent Alerts/Info.plist index 6a11bc8..411d179 100644 --- a/Notification Agent Alerts/Info.plist +++ b/Notification Agent Alerts/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 96 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -32,7 +32,7 @@ NSHumanReadableCopyright - Copyright © 2021 IBM Inc. All rights reserved. + Copyright © 2021 IBM. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/Notification Agent Banners/Info.plist b/Notification Agent Banners/Info.plist index a76d44f..411d179 100644 --- a/Notification Agent Banners/Info.plist +++ b/Notification Agent Banners/Info.plist @@ -6,8 +6,6 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -19,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 96 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -34,7 +32,7 @@ NSHumanReadableCopyright - Copyright © 2021 IBM Inc. All rights reserved. + Copyright © 2021 IBM. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/Notification Agent Core Tests/NACTriggersTests.swift b/Notification Agent Core Tests/NACTriggersTests.swift index b34e13c..fa4c369 100644 --- a/Notification Agent Core Tests/NACTriggersTests.swift +++ b/Notification Agent Core Tests/NACTriggersTests.swift @@ -16,10 +16,10 @@ class NACTriggersTests: XCTestCase { ["toBeRemoved", "-type", "banner", "-title", "This is a title"], ["toBeRemoved", "-type", "alert", "-title", "This is a title"], ["toBeRemoved", "-type", "popup", "-title", "This is a title", "-silent", "-subtitle", "This is a subtitle"]] - let processerUseCases = [URL(string: "macatibm:shownotification?type=popup&title=This%20is%20a%20title")!, - URL(string: "macatibm:shownotification?type=banner&title=This%20is%20a%20title")!, - URL(string: "macatibm:shownotification?type=alert&title=This%20is%20a%20title")!, - URL(string: "macatibm:shownotification?type=alert&title=title&silent&subtitle=This%20is%20a%20subtitle")!] + let processerUseCases = [URL(string: "ibmnotifier:shownotification?type=popup&title=This%20is%20a%20title")!, + URL(string: "ibmnotifier:shownotification?type=banner&title=This%20is%20a%20title")!, + URL(string: "ibmnotifier:shownotification?type=alert&title=This%20is%20a%20title")!, + URL(string: "ibmnotifier:shownotification?type=alert&title=title&silent&subtitle=This%20is%20a%20subtitle")!] override func setUpWithError() throws { worker.startObservation() diff --git a/Notification Agent Core/Controllers/HelpBuilder.swift b/Notification Agent Core/Controllers/HelpBuilder.swift index 143c9ef..80a26b7 100644 --- a/Notification Agent Core/Controllers/HelpBuilder.swift +++ b/Notification Agent Core/Controllers/HelpBuilder.swift @@ -43,7 +43,9 @@ public final class HelpBuilder { "-force_light_mode".yellow(), "-position".yellow(), "-popup_reminder".yellow(), - "-retain_values".yellow()] + "-retain_values".yellow(), + "-background_panel".yellow(), + "-unmovable".yellow()] static let bannerArguments: [String] = ["-type".green(), "-title".yellow(), "-subtitle".yellow(), @@ -60,7 +62,10 @@ public final class HelpBuilder { static let onboardingArguments: [String] = ["-type".green(), "-payload".green(), "-always_on_top".yellow(), - "-hide_title_bar_buttons".yellow()] + "-hide_title_bar_buttons".yellow(), + "-background_panel".yellow(), + "-unmovable".yellow(), + "-timeout".yellow()] static let systemAlertArguments: [String] = ["-type".green(), "-title".yellow(), "-subtitle".yellow(), @@ -75,26 +80,27 @@ public final class HelpBuilder { "-tertiary_button_cta_type".yellow(), "-tertiary_button_cta_payload".yellow(), "-silent".yellow(), - "-showSuppressionButton".yellow()] + "-show_suppression_button".yellow()] static let popupDescriptions: [String] = ["[ popup ]".red() + "\n The UI type of the notification.\n Example: -type popup", "\n The bar title.\n Example: -bar_title \"Bar Title\"", "\n The title of the notification.\n Suggested length < 120 characters.\n Allowed length < 240 characters.\n Example: -title \"Title\"", "\n The title font size.\n Example: -title_size \"20\"", "\n The subtitle of the notification. It supports MarkDown text.\n Example: -subtitle \"Subtitle\"", - "\n The local or remote URL for a custom icon defined for this notification. Despite the name it does accept also base64 encoded images.\n Example: -icon_path \"~/Icon/Path.png\"", + "\n The custom icon path/URL/SF Symbol name defined for this notification.\n Example: -icon_path \"~/Icon/Path.png\"", "\n The custom icon width defined for this notification. Max. width = 150\n Example: -icon_width \"150\"", "\n The custom icon height defined for this notification. Max. height = 300\n Example: -icon_height \"150\"", - "[ whitebox | timer | image | video | progressbar | input | secureinput | dropdown | html | htmlwhitebox | checklist ]".red() + "\n The UI type for the needed accessory view.\n Example: -accessory_view_type whitebox", + "[ whitebox | timer | image | video | progressbar | input | secureinput | dropdown | html | htmlwhitebox | checklist | datepicker ]".red() + "\n The UI type for the needed accessory view.\n Example: -accessory_view_type whitebox", "\n The payload for the accessory view:\n " + "- Text for " + "[ whitebox ]".red() + " view type;\n " + "- Text for " + "[ timer ]".red() + " view type. This will be timer's label. Use \"%@\" to define timer's position inside the label. Use " + "[ -timeout ]".yellow() + " argument to define timer's duration;\n " + "- File path/link for " + "[ image ]".red() + " view type;\n " + "- Text with the format " + "\"/url TEXT /autoplay /delay INT\" ".green() + "for " + "[ video ]".red() + " view type;\n " + "- Text with the format " + "\"/percent DOUBLE /top_message TEXT /bottom_message TEXT /user_interaction_enabled BOOL /user_interruption_allowed BOOL\" ".green() + "for " + "[ progressbar ]".red() + " view type;\n " + - "- Text with the format " + "\"/placeholder TEXT /title TEXT /value TEXT /required\" ".green() + "for the " + "[ input | secureinput ]".red() + " view type.\n " + + "- Text with the format " + "\"/placeholder TEXT /title TEXT /value TEXT /required\" ".green() + "for the " + "[ input | secureinput ]".red() + " view type;\n " + "- Text with the format " + "\"/list ITEM\\nITEM\\nITEM /selected INT /placeholder TEXT /title TEXT\" ".green() + "for " + "[ dropdown ]".red() + " view type;\n " + "- Text with HTML format for " + "[ html | htmlwhitebox ]".red() + " view type;\n " + - "- Text with the format " + "\"/list ITEM\\nITEM\\nITEM /preselection ITEM_INDEX ITEM_INDEX ITEM_INDEX /required /complete /title TEXT /radio\" ".green() + "for " + "[ checklist ]".red() + " view type. To read more about the usage of /complete and /required look at the project wiki;\n " + + "- Text with the format " + "\"/list ITEM\\nITEM\\nITEM /preselection ITEM_INDEX ITEM_INDEX ITEM_INDEX /required /complete /title TEXT /radio\" ".green() + "for " + "[ checklist ]".red() + " view type. To read more about the usage of /complete and /required look at the project wiki;\n " + + "- Text with the format " + "\"/title TEXT /preselection DATE WITH FORMAT yyyy-MM-dd hh:mm:ss /style TEXT /components TEXT\" ".green() + "for " + "[ datepicker ]".red() + " view type. To read more about the usage of /style and /components look at the project wiki;\n " + "Example 1: -accessory_view_payload \"This is the time left: %@\"\n " + "Example 2: -accessory_view_payload \"/percent 0 /top_message This is the top message /bottom_message This is the bottom message\";\n " + "Example 3: -accessory_view_payload \"/percent indeterminate /top_message This is the top message /bottom_message This is the bottom message\";\n " + @@ -122,7 +128,10 @@ public final class HelpBuilder { "\n Flag that force the UI in light mode.\n Example: -force_light_mode", "[ center | top_right | top_left | bottom_right | bottom_left ]".red() + "\n Tells the app where to place the pop-up window.\n Example: -position center", "\n A text payload to define the behavior of an optional reminder for the pop-up. The reminder is basically a timer at the end of which the pop-up is pushed again on top of the view hierarchy on screen. The payload format is: " + "\"/timeinterval /silent /repeat\" ".green() + "\n Example: -popup_reminder \"/timeinterval 300\"", - "\n Flag that tells the agent to print the available accessory view outputs on any exit (main or secondary button clicked)."] + "\n Flag that tells the agent to print the available accessory view outputs on any exit (main or secondary button clicked).", + "[ opaque | translucent ]".red() + "\n The style for the background panel that will cover all the screens.\n Example: -background_panel opaque", + "\n Flag that make the UI unmovable for the user.\n Example: -unmovable"] + static let bannerDescriptions: [String] = ["[ banner | alert ]".red() + "\n The UI type of the notification.\n Example: -type banner", "\n The title of the notification.\n Example: -title \"Title\"", "\n The subtitle of the notification. It supports MarkDown text.\n Example: -subtitle \"Subtitle\"", @@ -137,13 +146,16 @@ public final class HelpBuilder { "\n An URL if " + "[ link ]".red() + " cta type defined.\n Example: -tertiary_button_cta_payload \"URL\"", "\n The path (local or remote) or the base64 encoded representation of the image attached to this notification.\n Example: -notification_image \"~/Icon/Path.png\""] static let onboardingDescriptions: [String] = ["[ onboarding ]".red() + "\n The UI type of the notification.\n Example: -type onboarding", - "\n The json payload for the \"onboarding\" UI type.\n Example: -payload \"{ \"pages\": [\n {\n \"title\": \"Some title\",\n \"subtitle\": \"Some subtitle\",\n \"body\": \"Some body\",\n \"accessoryViews\": \"[\n [\n {\n \"type\": \"input\"\n \"payload\": \"/placeholder Something /title First\"\n },\n {\n \"type\": \"input\"\n \"payload\": \"/placeholder Something /title Second\"\n }\n ],\n [\n {\n \"type\": \"image\"\n \"payload\": \"/local/or/remote/path/to/image.png\"\n },\n {\n \"type\": \"video\"\n \"payload\": \"/local/or/remote/path/to/video.mov\"\n }\n ]\n ]\n }\n ]\n }\"\n Please see more about this feature on the project wiki.", + "\n The json payload for the \"onboarding\" UI type. Please see more about this feature on the project wiki.", "\n Flag that tells the agent to keep the Onbording UI always on top of the window hierarchy.\n Example: -always_on_top", - "\n Flag that tells the agent to remove the title bar buttons for the Onbording UI.\n Example: -hide_title_bar_buttons"] - static let systemAlertDescriptions: [String] = ["[ systemAlert ]".red() + "\n The UI type of the notification.\n Example: -type popup", + "\n Flag that tells the agent to remove the title bar buttons for the Onbording UI.\n Example: -hide_title_bar_buttons", + "[ opaque | translucent ]".red() + "\n The style for the background panel that will cover all the screens.\n Example: -background_panel opaque", + "\n Flag that make the UI unmovable for the user.\n Example: -unmovable", + "\n The timeout for the onboarding. After this amount of seconds the agent exit with the timeout exit code.\n Example: -timeout 300"] + static let systemAlertDescriptions: [String] = ["[ systemAlert ]".red() + "\n The UI type of the notification.\n Example: -type systemAlert", "\n The title of the notification.\n Example: -title \"Title\"", "\n The subtitle of the notification.\n Example: -subtitle \"Subtitle\"", - "\n The custom icon path defined for this notification.\n Example: -icon_path \"~/Icon/Path.png\"", + "\n The custom icon path defined for this notification or an SF Symbol name.\n Example: -icon_path \"~/Icon/Path.png\"", "\n The label of the main button.\n Example: -main_button_label \"Main button title\"", "[ none | link ]".red() + "\n The call to action type for the main button (default: none -> exit).\n Example: -main_button_cta_type link", "\n An URL if " + "[ link ]".red() + " cta type defined.\n Example: -main_button_cta_payload \"URL\"", @@ -262,7 +274,7 @@ public final class HelpBuilder { print("\nIBM Notifier Help Page".bold().blue() + "\n") print("You can use:\n --help -popup - To show help page about the pop-up UI;\n --help -banner - To show help page about the banner (temporary banner notification) UI;\n --help -alert - To show help page about the alert (persistent banner notification) UI;\n --help -onboarding - To show help page about the onboarding UI;\n --help -systemAlert - To show help page about the system alert UI;\n --help -configuration - To show help page about the configuration mode.\n") } - + static func printPopupHelp() { print("\nIBM Notifier Popup UI".bold().blue() + "\n") var argumentsString = "" @@ -292,7 +304,7 @@ public final class HelpBuilder { print("- Popup UI - ".blue() + examplePopup) print("\n") } - + static func printBannerHelp() { print("\nIBM Notifier Banner and Alert UIs".bold().blue() + "\n") var argumentsString = "" @@ -319,7 +331,7 @@ public final class HelpBuilder { print("- Alert UI - ".blue() + exampleAlert) print("\n") } - + static func printOnboardingHelp() { print("\nIBM Notifier Onboarding UI".bold().blue() + "\n") var argumentsString = "" @@ -385,7 +397,7 @@ public final class HelpBuilder { print("- System Alert UI - ".blue() + exampleSystemAlert) print("\n") } - + static func printNoArgumentsPage() { print("\nIBM Notifier".bold().blue() + "\n") for index in specialArguments.indices { @@ -396,7 +408,7 @@ public final class HelpBuilder { static func printPrivacyPolicy() { guard let url = Bundle.main.url(forResource: "PRIVACY POLICY", withExtension: "rtf") else { return } let opts : [NSAttributedString.DocumentReadingOptionKey : Any] = - [.documentType : NSAttributedString.DocumentType.rtf] + [.documentType : NSAttributedString.DocumentType.rtf] var attributes : NSDictionary? if let attributedString = try? NSAttributedString(url: url, options: opts, documentAttributes: &attributes) { print(attributedString.string) @@ -406,14 +418,16 @@ public final class HelpBuilder { static func printTermsAndCondition() { guard let url = Bundle.main.url(forResource: "TERMS AND CONDITIONS", withExtension: "rtf") else { return } let opts : [NSAttributedString.DocumentReadingOptionKey : Any] = - [.documentType : NSAttributedString.DocumentType.rtf] + [.documentType : NSAttributedString.DocumentType.rtf] var attributes : NSDictionary? if let attributedString = try? NSAttributedString(url: url, options: opts, documentAttributes: &attributes) { print(attributedString.string) } } - + static func printAppVersion() { print("IBM Notifier version: \(Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String ?? "Unknown")".bold()) } } + +// swiftlint:enable type_body_length file_length diff --git a/Notification Agent Core/Controllers/NALogger.swift b/Notification Agent Core/Controllers/NALogger.swift index 06931e8..a4d9272 100644 --- a/Notification Agent Core/Controllers/NALogger.swift +++ b/Notification Agent Core/Controllers/NALogger.swift @@ -13,32 +13,26 @@ import os.log /// A simple class based on Apple os.log that handle normal and verbose logs. public final class NALogger { + // MARK: - Static Variables + static let shared = NALogger() + // MARK: - Methods + func log(_ type: OSLogType, _ message: StaticString, _ args: [String] = []) { - if #available(OSX 11.0, *) { - Logger().log(level: type, - "\(String(format: message.description.replacingOccurrences(of: "{public}", with: ""), arguments: args), privacy: .public)") - } else { - os_log(type, message, args) - } + Logger().log(level: type, + "\(String(format: message.description.replacingOccurrences(of: "{public}", with: ""), arguments: args), privacy: .public)") if Context.main.sharedSettings.isVerboseModeEnabled || type == .error { self.verbose(type, message, args) } } - func log(_ message: StaticString, _ args: [String] = []) { - if #available(OSX 11.0, *) { - Logger().log(level: .default, - "\(String(format: message.description.replacingOccurrences(of: "{public}", with: ""), arguments: args), privacy: .public)") - } else { - os_log(message, args) - } + Logger().log(level: .default, + "\(String(format: message.description.replacingOccurrences(of: "{public}", with: ""), arguments: args), privacy: .public)") if Context.main.sharedSettings.isVerboseModeEnabled { self.verbose(.default, message, args) } } - func deprecationLog(since version: AppVersion, deprecatedArgument: String) { if version.isFinalDeprecatedVersion() { self.log(.error, "The following argument has been deprecated: %{public}@. Please update your workflow.", [deprecatedArgument]) @@ -47,6 +41,8 @@ public final class NALogger { } } + // MARK: - Private Methods + private func verbose(_ type: OSLogType, _ message: StaticString, _ args: [String] = []) { let message = type == .error ? message.description.replacingOccurrences(of: "{public}", with: "").red() : @@ -54,5 +50,4 @@ public final class NALogger { print(String(format: message, arguments: args)) } - } diff --git a/Notification Agent Core/Extensions/EFCLController-Extension.swift b/Notification Agent Core/Extensions/EFCLController-Extension.swift index 7af9922..2d982ba 100644 --- a/Notification Agent Core/Extensions/EFCLController-Extension.swift +++ b/Notification Agent Core/Extensions/EFCLController-Extension.swift @@ -11,7 +11,7 @@ import Foundation extension EFCLController { - // MARK: - Internal methods + // MARK: - Internal Methods /// Exit the app with the related reason code. /// - Parameter reason: reason why the application should exit. @@ -50,7 +50,7 @@ extension EFCLController { } } - // MARK: - Private methods + // MARK: - Private Methods /// Check if the passed arguments are configuration. /// - Parameter arguments: the app's launch arguments. diff --git a/Notification Agent Core/Info.plist b/Notification Agent Core/Info.plist index 06c95d6..1d19076 100644 --- a/Notification Agent Core/Info.plist +++ b/Notification Agent Core/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 96 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/Notification Agent Onboarding Tests/NAOTriggersTests.swift b/Notification Agent Onboarding Tests/NAOTriggersTests.swift index a3d010a..8b79125 100644 --- a/Notification Agent Onboarding Tests/NAOTriggersTests.swift +++ b/Notification Agent Onboarding Tests/NAOTriggersTests.swift @@ -30,7 +30,7 @@ class NAOTriggersTests: XCTestCase { ] ] ] - ], + ] as [String : Any], [ "title": "This is a title", "subtitle": "This is a subtitle", diff --git a/Notification Agent Onboarding UI Tests/NAOUITests.swift b/Notification Agent Onboarding UI Tests/NAOUITests.swift index 96e68a8..add6dfc 100644 --- a/Notification Agent Onboarding UI Tests/NAOUITests.swift +++ b/Notification Agent Onboarding UI Tests/NAOUITests.swift @@ -4,23 +4,44 @@ // // Created by Simone Martorelli on 08/06/22. // Copyright © 2022 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 // import XCTest class NAOUITests: XCTestCase { - /// Testing simple Pop-up with: - /// Title: This is a title - /// Main Button: Ok + /// Testing simple Onboarding UI func test1Onboarding() throws { - let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCIsInJldGFpblZhbHVlcyI6ZmFsc2UsImFjY2Vzc29yeVZpZXdzIjpbXSwicGF5bG9hZCI6eyJwYWdlcyI6W3siYm9keSI6IlNvbWUgYm9keSIsInRpdGxlIjoiVGhpcyBpcyBhIHRpdGxlIiwic3VidGl0bGUiOiJUaGlzIGlzIGEgc3VidGl0bGUiLCJhY2Nlc3NvcnlWaWV3cyI6W1t7InR5cGUiOiJpbnB1dCIsInBheWxvYWQiOiJcL3BsYWNlaG9sZGVyIFNvbWUgXC90aXRsZSBGaXJzdCJ9LHsidHlwZSI6ImlucHV0IiwicGF5bG9hZCI6IlwvcGxhY2Vob2xkZXIgU29tZSBcL3RpdGxlIFNlY29uZCJ9XV19LHsiYm9keSI6IlNvbWUgYm9keSIsInRpdGxlIjoiVGhpcyBpcyBhIHRpdGxlIiwic3VidGl0bGUiOiJUaGlzIGlzIGEgc3VidGl0bGUiLCJhY2Nlc3NvcnlWaWV3cyI6W1t7InR5cGUiOiJpbnB1dCIsInBheWxvYWQiOiJcL3BsYWNlaG9sZGVyIFNvbWUgXC90aXRsZSBGaXJzdCJ9LHsidHlwZSI6ImlucHV0IiwicGF5bG9hZCI6IlwvcGxhY2Vob2xkZXIgU29tZSBcL3RpdGxlIFNlY29uZCJ9XV19XX0sImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6Im9uYm9hcmRpbmciLCJzaWxlbnQiOmZhbHNlLCJtaW5pYXR1cml6YWJsZSI6ZmFsc2UsImJhclRpdGxlIjoiTWFjQElCTSBOb3RpZmljYXRpb25zIiwiZm9yY2VMaWdodE1vZGUiOmZhbHNlLCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZX0sInNldHRpbmdzIjp7ImVudmlyb25tZW50IjoicHJvZCIsImlzUmVzdENsaWVudEVuYWJsZWQiOmZhbHNlLCJpc0FuYWx5dGljc0VuYWJsZWQiOmZhbHNlLCJpc1ZlcmJvc2VNb2RlRW5hYmxlZCI6ZmFsc2V9fQ==" // pragma: allowlist-secret + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCIsInJldGFpblZhbHVlcyI6ZmFsc2UsImFjY2Vzc29yeVZpZXdzIjpbXSwicGF5bG9hZCI6eyJwYWdlcyI6W3siYm9keSI6IkZpcnN0IHBhZ2UncyBib2R5IiwidG9wSWNvbiI6InNxdWFyZS5hbmQuYXJyb3cudXAiLCJ0aXRsZSI6IkZpcnN0IHBhZ2UncyB0aXRsZSIsInN1YnRpdGxlIjoiRmlyc3QgcGFnZSdzIHN1YnRpdGxlIiwiaW5mb1NlY3Rpb24iOnsiZmllbGRzIjpbeyJpZCI6IlNvbWUgRGVzY3JpcHRpb24gU29tZSIsImxhYmVsIjoiU29tZSBEZXNjcmlwdGlvbiBTb21lIn0seyJpZCI6IlNvbWUgRGVzY3JpcHRpb24gU29tZSIsImxhYmVsIjoiU29tZSBEZXNjcmlwdGlvbiBTb21lIn0seyJpZCI6IlNvbWUgRGVzY3JpcHRpb24gU29tZSIsImxhYmVsIjoiU29tZSBEZXNjcmlwdGlvbiBTb21lIn1dfX0seyJzdWJ0aXRsZSI6IlNlY29uZCBwYWdlJ3Mgc3VidGl0bGUiLCJzaW5nbGVDaGFuZ2UiOnRydWUsInByaW1hcnlCdXR0b25MYWJlbCI6IlNvbWUiLCJ0aXRsZSI6IlNlY29uZCBwYWdlJ3MgdGl0bGUiLCJ0ZXJ0aWFyeUJ1dHRvbiI6eyJsYWJlbCI6IlRlcnRpYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6ImxpbmsiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiaHR0cHM6XC9cL3d3dy5nb29nbGUuY29tIn0sImluZm9TZWN0aW9uIjp7ImZpZWxkcyI6W3siaWQiOiJGaXJzdCBsYWJlbCBvbmx5IiwibGFiZWwiOiJGaXJzdCBsYWJlbCBvbmx5In0seyJpZCI6IlNlY29uZCBsYWJlbCBvbmx5IiwibGFiZWwiOiJTZWNvbmQgbGFiZWwgb25seSJ9LHsiaWQiOiJUaGlyZCBsYWJlbCBvbmx5IiwibGFiZWwiOiJUaGlyZCBsYWJlbCBvbmx5In1dfSwiYm9keSI6IlNlY29uZCBwYWdlJ3MgYm9keSJ9LHsiYm9keSI6IlRoaXJkIHBhZ2UncyBib2R5IiwidGl0bGUiOiJUaGlyZCBwYWdlJ3MgdGl0bGUiLCJzdWJ0aXRsZSI6IlRoaXJkIHBhZ2UncyBzdWJ0aXRsZSIsInNpbmdsZUNoYW5nZSI6dHJ1ZX0seyJ0aXRsZSI6IkZvdXJ0aCBwYWdlJ3MgdGl0bGUiLCJzdWJ0aXRsZSI6IkZvdXJ0aCBwYWdlJ3Mgc3VidGl0bGUiLCJib2R5IjoiRm91cnRoIHBhZ2UncyBib2R5In1dLCJwcm9ncmVzc0JhclBheWxvYWQiOiJhdXRvbWF0aWMifSwiaXNNb3ZhYmxlIjp0cnVlLCJhbHdheXNPblRvcCI6ZmFsc2UsInR5cGUiOiJvbmJvYXJkaW5nIiwic2lsZW50IjpmYWxzZSwic2hvd1N1cHByZXNzaW9uQnV0dG9uIjpmYWxzZSwibWluaWF0dXJpemFibGUiOmZhbHNlLCJiYXJUaXRsZSI6Ik1hY0BJQk0gTm90aWZpY2F0aW9ucyIsImZvcmNlTGlnaHRNb2RlIjpmYWxzZSwiaGlkZVRpdGxlQmFyQnV0dG9ucyI6ZmFsc2V9LCJzZXR0aW5ncyI6eyJpc1ZlcmJvc2VNb2RlRW5hYmxlZCI6ZmFsc2UsImVudmlyb25tZW50IjoicHJvZCJ9fQ==" // pragma: allowlist-secret let app = XCUIApplication() app.launchArguments = [useCase] app.launch() - XCTAssert(app.buttons["onboarding_accessibility_button_right"].exists) - app.buttons["onboarding_accessibility_button_right"].tap() - XCTAssert(app.buttons["onboarding_accessibility_button_right"].exists) - XCTAssert(app.buttons["onboarding_accessibility_button_left"].exists) + XCTAssert(app.staticTexts["onboarding_title"].exists) + XCTAssert(app.staticTexts["onboarding_subtitle"].exists) + XCTAssert(app.staticTexts["onboarding_body"].exists) + XCTAssert(app.buttons["main_button"].exists) + XCTAssert(!app.buttons["secondary_button"].exists) + XCTAssert(app.buttons["help_button"].exists) + app.buttons["main_button"].tap() + XCTAssert(app.staticTexts["onboarding_title"].exists) + XCTAssert(app.staticTexts["onboarding_subtitle"].exists) + XCTAssert(app.staticTexts["onboarding_body"].exists) + XCTAssert(app.buttons["main_button"].exists) + XCTAssert(app.buttons["secondary_button"].exists) + XCTAssert(app.buttons["tertiary_button"].exists) + XCTAssert(app.buttons["help_button"].exists) + app.buttons["main_button"].tap() + XCTAssert(app.staticTexts["onboarding_title"].exists) + XCTAssert(app.staticTexts["onboarding_subtitle"].exists) + XCTAssert(app.staticTexts["onboarding_body"].exists) + XCTAssert(app.buttons["main_button"].exists) + XCTAssert(!app.buttons["secondary_button"].exists) + app.buttons["main_button"].tap() + XCTAssert(app.staticTexts["onboarding_title"].exists) + XCTAssert(app.staticTexts["onboarding_subtitle"].exists) + XCTAssert(app.staticTexts["onboarding_body"].exists) + XCTAssert(app.buttons["main_button"].exists) + XCTAssert(!app.buttons["secondary_button"].exists) } } diff --git a/Notification Agent Onboarding/AppDelegate.swift b/Notification Agent Onboarding/AppDelegate.swift index 1ee3f39..6e2667a 100644 --- a/Notification Agent Onboarding/AppDelegate.swift +++ b/Notification Agent Onboarding/AppDelegate.swift @@ -31,6 +31,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationDidFinishLaunching(_ aNotification: Notification) { + // Intercept the command+q shortcut and modify the exit value to reflect a manual user dismission. + NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in + switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { + case [.command] where event.characters == "q": + Utils.applicationExit(withReason: .userDismissedOnboarding) + default: + return event + } + return event + } configureApp() } } diff --git a/Notification Agent Onboarding/Controllers/OnboardingInteractiveEFCLController.swift b/Notification Agent Onboarding/Controllers/OnboardingInteractiveEFCLController.swift index 770ce21..a644714 100644 --- a/Notification Agent Onboarding/Controllers/OnboardingInteractiveEFCLController.swift +++ b/Notification Agent Onboarding/Controllers/OnboardingInteractiveEFCLController.swift @@ -22,7 +22,7 @@ final class OnboardingInteractiveEFCLController: InteractiveEFCLController { value.removeLast() } switch argument { - case "percent", "top_message", "bottom_message", "user_interaction_enabled", "user_interruption_allowed", "exit_on_completion": + case "percent", "top_message", "bottom_message", "user_interaction_enabled", "user_interruption_allowed", "exit_on_completion", "end": NotificationCenter.default.post(name: Notification.Name("progressbar_interactive_updates"), object: nil, userInfo: ["data" : inputData]) default: continue diff --git a/Notification Agent Onboarding/Extensions/NotificationDispatch-Extension.swift b/Notification Agent Onboarding/Extensions/NotificationDispatch-Extension.swift index 985ca7f..bbefbd9 100644 --- a/Notification Agent Onboarding/Extensions/NotificationDispatch-Extension.swift +++ b/Notification Agent Onboarding/Extensions/NotificationDispatch-Extension.swift @@ -9,6 +9,7 @@ import Foundation import Cocoa +import SwiftUI extension NotificationDispatch { /// Handle the received notification and send the notification object to the correct controller. @@ -17,23 +18,42 @@ extension NotificationDispatch { func receivedNotification(_ notification: Notification) { guard let object = notification.userInfo?["object"] as? NotificationObject else { return } guard let onboardingData = object.payload else { return } - let onboardingViewController = OnboardingViewController(with: onboardingData, alwaysOnTop: object.alwaysOnTop ?? false) - let window = NSWindow(contentViewController: onboardingViewController) - window.styleMask.remove(.resizable) - if object.payload?.progressBarPayload != nil { - window.styleMask.remove(.closable) - window.styleMask.remove(.miniaturizable) - } else if object.hideTitleBarButtons ?? false { - window.styleMask.remove(.closable) - window.styleMask.remove(.miniaturizable) - } - if object.forceLightMode ?? false { - window.appearance = NSAppearance(named: .aqua) + DispatchQueue.main.async { + var mainWindow = NSWindow() + mainWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 812, height: 600), styleMask: .titled, backing: .buffered, defer: false) + guard let viewModel = OnboardingViewModel(onboardingData, window: mainWindow, position: object.position, timeout: object.timeout) else { return } + mainWindow.delegate = viewModel + let contentView = OnboardingView(viewModel: viewModel) + mainWindow.contentView = NSHostingView(rootView: contentView) + mainWindow.setWindowPosition(object.position ?? .center) + mainWindow.styleMask.remove(.resizable) + if object.payload?.progressBarPayload != nil { + mainWindow.styleMask.remove(.closable) + mainWindow.styleMask.remove(.miniaturizable) + } else if object.hideTitleBarButtons ?? false { + mainWindow.styleMask.remove(.closable) + mainWindow.styleMask.remove(.miniaturizable) + } + mainWindow.canBecomeVisibleWithoutLogin = true + + if object.forceLightMode ?? false { + NSApp.appearance = NSAppearance(named: .aqua) + } + mainWindow.title = "" + mainWindow.titlebarAppearsTransparent = true + + if let backgroundPanelStyle = object.backgroundPanel { + mainWindow.level = .init(Int(CGWindowLevelForKey(.maximumWindow)) + 2) + mainWindow.isMovable = false + mainWindow.collectionBehavior = [.stationary, .canJoinAllSpaces] + Context.main.backgroundPanelsController = BackPanelController(backgroundPanelStyle) + Context.main.backgroundPanelsController?.showBackgroundWindows() + } else { + mainWindow.isMovable = object.isMovable + mainWindow.level = object.alwaysOnTop ?? false ? .floating : .normal + } + + mainWindow.makeKeyAndOrderFront(self) } - window.title = "" - window.titlebarAppearsTransparent = true - window.center() - window.delegate = onboardingViewController - window.makeKeyAndOrderFront(self) } } diff --git a/Notification Agent Onboarding/Info.plist b/Notification Agent Onboarding/Info.plist index a76d44f..411d179 100644 --- a/Notification Agent Onboarding/Info.plist +++ b/Notification Agent Onboarding/Info.plist @@ -6,8 +6,6 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -19,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 96 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -34,7 +32,7 @@ NSHumanReadableCopyright - Copyright © 2021 IBM Inc. All rights reserved. + Copyright © 2021 IBM. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/Notification Agent Onboarding/Views/OnboardingPageViewController.swift b/Notification Agent Onboarding/Views/OnboardingPageViewController.swift deleted file mode 100644 index 346fd0d..0000000 --- a/Notification Agent Onboarding/Views/OnboardingPageViewController.swift +++ /dev/null @@ -1,464 +0,0 @@ -// -// OnboardingPageViewController.swift -// Notification Agent -// -// Created by Simone Martorelli on 21/01/2021. -// Copyright © 2021 IBM Inc. All rights reserved. -// SPDX-License-Identifier: Apache2.0 -// -// swiftlint:disable function_body_length type_body_length file_length - -import Cocoa - -final class OnboardingPageViewController: NSViewController { - - // MARK: - Enums - - /// The position of the page in the onboarding process. - enum PagePosition { - case first - case relativeFirst - case last - case middle - case singlePage - var rightButtonTitle: String { - switch self { - case .first, .relativeFirst: - return "onboarding_page_continue_button".localized - case .middle: - return "onboarding_page_continue_button".localized - case .last, .singlePage: - return "onboarding_page_close_button".localized - } - } - var leftButtonTitle: String { - return "onboarding_page_back_button".localized - } - var isRightButtonHidden: Bool { - switch self { - case .first, .relativeFirst, .middle, .last, .singlePage: - return false - } - } - var isLeftButtonHidden: Bool { - switch self { - case .first, .relativeFirst, .singlePage: - return true - case .middle, .last: - return false - } - } - } - - // MARK: - Outlets - - @IBOutlet weak var topIconImageView: NSImageView! - @IBOutlet weak var bodyStackView: NSStackView! - @IBOutlet weak var rightButton: NSButton! - @IBOutlet weak var leftButton: NSButton! - @IBOutlet weak var helpButton: NSButton! - - // MARK: - Variables - - weak var navigationDelegate: OnboardingNavigationDelegate? - var titleLabel: NSTextField! - var subtitleLabel: NSTextField! - var bodyTextView: MarkdownTextView! - var mediaView: NSView! - var accessoryViews: [AccessoryView] = [] - var store: [String] - let page: OnboardingPage - let position: PagePosition - - // MARK: - Initializers - - init(with page: OnboardingPage, position: PagePosition, store: [String]? = nil) { - self.page = page - self.position = position - self.store = store ?? [] - super.init(nibName: nil, bundle: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(checkButtonVisibility), - name: .onboardingParentStatusDidChange, - object: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Instance methods - - override func viewDidLoad() { - super.viewDidLoad() - self.setupStackViewLayout() - self.setupButtonsLayout() - self.configureAccessibilityElements() - self.setIconIfNeeded() - self.displayStoredData() - } - - // MARK: - Private methods - - /// Set up the stackview components and layout. - private func setupStackViewLayout() { - self.bodyStackView.distribution = .gravityAreas - self.bodyStackView.alignment = .centerX - self.bodyStackView.spacing = 12 - - var remainingSpace = bodyStackView.bounds.height - var topGravityAreaIndex = 0 - if let title = page.title { - titleLabel = NSTextField(wrappingLabelWithString: title) - titleLabel.font = NSFont.boldSystemFont(ofSize: 26) - titleLabel.alignment = .center - titleLabel.setAccessibilityIdentifier("onboarding_accessibility_title") - bodyStackView.insertView(titleLabel, at: topGravityAreaIndex, in: .top) - topGravityAreaIndex += 1 - remainingSpace -= titleLabel.intrinsicContentSize.height+12 - } - if let subtitle = page.subtitle { - subtitleLabel = NSTextField(wrappingLabelWithString: subtitle) - subtitleLabel.font = NSFont.systemFont(ofSize: 16, weight: .semibold) - subtitleLabel.alignment = .center - subtitleLabel.setAccessibilityIdentifier("onboarding_accessibility_subtitle") - bodyStackView.insertView(subtitleLabel, at: topGravityAreaIndex, in: .top) - topGravityAreaIndex += 1 - remainingSpace -= subtitleLabel.intrinsicContentSize.height+12 - } - if let pageMedia = (page as? LegacyOnboardingPage)?.pageMedia { - if let body = page.body { - bodyTextView = MarkdownTextView(withText: body, maxViewHeight: remainingSpace, alignment: .center) - bodyStackView.insertView(bodyTextView, at: 0, in: .center) - remainingSpace -= bodyTextView.fittingSize.height+12 - } - switch pageMedia.mediaType { - case .image: - guard pageMedia.image != nil else { return } - mediaView = ImageAccessoryView(with: pageMedia, preferredSize: CGSize(width: bodyStackView.bounds.width, height: remainingSpace), needsFullWidth: false) - bodyStackView.insertView(mediaView, at: 0, in: .bottom) - case .video: - guard pageMedia.player != nil else { return } - mediaView = VideoAccessoryView(with: pageMedia, preferredSize: CGSize(width: bodyStackView.bounds.width, height: remainingSpace), needsFullWidth: false) - bodyStackView.insertView(mediaView, at: 0, in: .bottom) - } - } else if let accessoryViews = (page as? InteractiveOnboardingPage)?.accessoryViews { - if let body = page.body { - bodyTextView = MarkdownTextView(withText: body, maxViewHeight: remainingSpace, alignment: .center) - bodyStackView.insertView(bodyTextView, at: topGravityAreaIndex, in: .top) - remainingSpace -= bodyTextView.fittingSize.height+12 - } - self.setupAccessoryViews(accessoryViews, in: remainingSpace) - self.checkButtonVisibility() - } else { - if let body = page.body { - bodyTextView = MarkdownTextView(withText: body, maxViewHeight: remainingSpace, alignment: .center) - bodyStackView.insertView(bodyTextView, at: topGravityAreaIndex, in: .top) - } - } - } - - /// Setup the accessory views on the current page. - /// - Parameter accessoryViews: the accessory views related to this page. - private func setupAccessoryViews(_ accessoryViewsMatrix: [[NotificationAccessoryElement]], in remainingSpace: CGFloat) { - var avSpaceLeft = remainingSpace - for row in accessoryViewsMatrix { - if row.count > 1 { - let rowBodyStackView = NSStackView() - rowBodyStackView.alignment = .centerY - rowBodyStackView.orientation = .horizontal - rowBodyStackView.spacing = 12 - rowBodyStackView.distribution = .fillEqually - bodyStackView.addArrangedSubview(rowBodyStackView) - rowBodyStackView.translatesAutoresizingMaskIntoConstraints = false - rowBodyStackView.widthAnchor.constraint(equalTo: bodyStackView.widthAnchor, multiplier: 1).isActive = true - rowBodyStackView.setClippingResistancePriority(.defaultHigh, for: .horizontal) - let originalSpaceLeft = avSpaceLeft - for accessoryView in row { - var currentSpaceLeft = originalSpaceLeft-6 - self.addAccessoryView(accessoryView, - in: rowBodyStackView, - dedicatedHeight: ¤tSpaceLeft, - dedicatedWidth: bodyStackView.bounds.width/CGFloat(row.count)-6) - avSpaceLeft = min(avSpaceLeft, currentSpaceLeft) - } - rowBodyStackView.layout() - } else { - if let accessoryView = row.first { - self.addAccessoryView(accessoryView, in: self.bodyStackView, - at: .center, - withIndex: 0, - dedicatedHeight: &avSpaceLeft, - dedicatedWidth: bodyStackView.bounds.width) - } - } - } - } - - /// Configure and insert the related accessory view. - /// - Parameter accessoryView: the defined accessory view. - private func addAccessoryView(_ accessoryView: NotificationAccessoryElement, - in stackView: NSStackView, - at gravity: NSStackView.Gravity? = nil, - withIndex index: Int = 0, - dedicatedHeight: inout CGFloat, - dedicatedWidth: CGFloat) { - func add(_ view: NSView) { - if let gravity = gravity { - stackView.insertView(view, at: index, in: gravity) - } else { - stackView.addArrangedSubview(view) - } - } - switch accessoryView.type { - case .whitebox: - let markdownTextView = MarkdownTextView(withText: accessoryView.payload ?? "", drawsBackground: true, containerWidth: dedicatedWidth) - add(markdownTextView) - case .image: - guard let media = accessoryView.media, media.image != nil else { return } - let imageAccessoryView = ImageAccessoryView(with: media, preferredSize: CGSize(width: dedicatedWidth, height: dedicatedHeight), needsFullWidth: false, containerWidth: dedicatedWidth) - add(imageAccessoryView) - case .video: - guard let media = accessoryView.media, media.player != nil else { return } - let videoAccessoryView = VideoAccessoryView(with: media, preferredSize: CGSize(width: dedicatedWidth, height: dedicatedHeight), needsFullWidth: false, containerWidth: dedicatedWidth) - videoAccessoryView.delegate = self - /// Embed the AV inside a container view avoid to expose the black video player background if the video resolution - /// doesn't match the available space for the video accessory view. - let containerView = NSView() - containerView.addSubview(videoAccessoryView) - videoAccessoryView.translatesAutoresizingMaskIntoConstraints = false - videoAccessoryView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true - videoAccessoryView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true - videoAccessoryView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true - add(containerView) - case .input, .securedinput, .secureinput: - do { - let inputAccessoryView = try InputAccessoryView(with: accessoryView.payload ?? "", - isSecure: accessoryView.type == .securedinput || accessoryView.type == .secureinput, - containerWidth: dedicatedWidth, - preventResize: true) - inputAccessoryView.delegate = self - add(inputAccessoryView) - self.accessoryViews.append(inputAccessoryView) - dedicatedHeight -= inputAccessoryView.hasTitle ? 52 : 32 - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - case .dropdown: - do { - let dropDownAccessoryView = try DropDownAccessoryView(with: accessoryView.payload ?? "", containerWidth: dedicatedWidth) - dropDownAccessoryView.delegate = self - add(dropDownAccessoryView) - self.accessoryViews.append(dropDownAccessoryView) - dedicatedHeight -= dropDownAccessoryView.hasTitle ? 52 : 32 - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - case .html: - let htmlAccessoryView = HTMLAccessoryView(withText: accessoryView.payload ?? "", drawsBackground: false, containerWidth: dedicatedWidth) - add(htmlAccessoryView) - case .htmlwhitebox: - let htmlAccessoryView = HTMLAccessoryView(withText: accessoryView.payload ?? "", drawsBackground: true, containerWidth: dedicatedWidth) - add(htmlAccessoryView) - case .checklist: - do { - let checklistAccessoryView = try CheckListAccessoryView(with: accessoryView.payload ?? "", containerWidth: dedicatedWidth) - add(checklistAccessoryView) - checklistAccessoryView.delegate = self - self.accessoryViews.append(checklistAccessoryView) - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - default: - return - } - } - - /// Set up buttons appearence. - private func setupButtonsLayout() { - rightButton.isHidden = position.isRightButtonHidden - leftButton.isHidden = position.isLeftButtonHidden - rightButton.title = position.rightButtonTitle - leftButton.title = position.leftButtonTitle - helpButton.isHidden = !(page.infoSection != nil) - } - - /// This method load and set the icon if a custom one was defined. - private func setIconIfNeeded() { - if let iconPath = page.topIcon { - if FileManager.default.fileExists(atPath: iconPath), - let data = try? Data(contentsOf: URL(fileURLWithPath: iconPath)) { - let image = NSImage(data: data) - topIconImageView.image = image - } else if iconPath.isValidURL, - let url = URL(string: iconPath), - let data = try? Data(contentsOf: url) { - let image = NSImage(data: data) - topIconImageView.image = image - } else if let imageData = Data(base64Encoded: iconPath, options: .ignoreUnknownCharacters), - let image = NSImage(data: imageData) { - topIconImageView.image = image - } else { - NALogger.shared.log("Unable to load image from %{public}@", [iconPath]) - } - } else { - topIconImageView.image = NSImage(named: NSImage.Name("default_icon")) - } - } - - private func goToNextPage() { - self.navigationDelegate?.didSelectNextButton(self) - } - - private func goToPreviousPage() { - self.navigationDelegate?.didSelectBackButton(self) - } - - /// Exit the completed onboarding. - private func closeOnboarding() { - self.navigationDelegate?.shouldCloseOnboardingWindow(self) - } - - private func configureAccessibilityElements() { - self.rightButton.setAccessibilityLabel(position == .last ? "onboarding_accessibility_button_right_close".localized : "onboarding_accessibility_button_right_continue".localized) - self.rightButton.setAccessibilityIdentifier("onboarding_accessibility_button_right") - self.leftButton.setAccessibilityLabel("onboarding_accessibility_button_left".localized) - self.leftButton.setAccessibilityIdentifier("onboarding_accessibility_button_left") - self.helpButton.setAccessibilityLabel("onboarding_accessibility_button_center".localized) - self.helpButton.setAccessibilityIdentifier("onboarding_accessibility_button_center") - self.bodyStackView.setAccessibilityLabel("onboarding_accessibility_stackview_body".localized) - self.bodyStackView.setAccessibilityElement(false) - self.topIconImageView.setAccessibilityLabel("onboarding_accessibility_image_top".localized) - self.topIconImageView.setAccessibilityIdentifier("onboarding_accessibility_image_top") - } - - @objc - private func checkButtonVisibility() { - var mainButtonState: AccessoryView.ButtonState = .enabled - var secondaryButtonState: AccessoryView.ButtonState = .enabled - - for accessoryView in accessoryViews { - switch accessoryView.mainButtonState { - case .disabled, .hidden: - guard mainButtonState != .hidden else { continue } - mainButtonState = accessoryView.mainButtonState - case .enabled: - continue - } - } - switch mainButtonState { - case .disabled: - self.rightButton.isEnabled = false - case .hidden: - self.rightButton.isEnabled = false - case .enabled: - if let parent = self.parent as? OnboardingViewController, !parent.isClosable && (self.position == .last || self.position == .singlePage) { - self.rightButton.isEnabled = false - } else { - self.rightButton.isEnabled = true - } - } - guard position != .first else { return } - for accessoryView in accessoryViews { - switch accessoryView.secondaryButtonState { - case .disabled, .hidden: - guard secondaryButtonState != .hidden else { continue } - secondaryButtonState = accessoryView.secondaryButtonState - case .enabled: - break - } - } - switch secondaryButtonState { - case .disabled: - self.leftButton.isEnabled = false - case .hidden: - self.leftButton.isEnabled = false - case .enabled: - self.leftButton.isEnabled = true - } - } - - /// Display the data previously stored for this page - private func displayStoredData() { - guard !store.isEmpty else { return } - guard store.count == accessoryViews.count else { return } - for (index, accessoryView) in accessoryViews.enumerated() { - guard store[index] != "" else { continue } - accessoryView.displayStoredData(store[index]) - } - } - - // MARK: - Public methods - - func collectData() -> [String] { - var data: [String] = [] - guard !accessoryViews.isEmpty else { return data } - for accessoryView in accessoryViews { - switch accessoryView.self { - case is InputAccessoryView: - if let value = (accessoryView as? InputAccessoryView)?.inputValue { - data.append(value) - } - case is DropDownAccessoryView: - if let value = (accessoryView as? DropDownAccessoryView)?.selectedItem { - data.append("\(value.description)") - } - case is CheckListAccessoryView: - if let needsCompletion = (accessoryView as? CheckListAccessoryView)?.needCompletion, - !needsCompletion, - let values = (accessoryView as? CheckListAccessoryView)?.selectedIndexes { - var entry = "" - values.forEach({ entry.append("\($0.description) ") }) - if !entry.isEmpty { - entry.removeLast() - } - data.append(entry) - } - default: - break - } - } - return data - } - - // MARK: - Actions - - @IBAction func didPressRightButton(_ sender: NSButton) { - switch position { - case .first, .relativeFirst: - goToNextPage() - case .middle: - goToNextPage() - case .last, .singlePage: - closeOnboarding() - } - } - - @IBAction func didPressLeftButton(_ sender: NSButton) { - switch position { - case .first, .singlePage, .relativeFirst: - return - case .middle: - goToPreviousPage() - case .last: - goToPreviousPage() - } - } - - @IBAction func didPressHelpButton(_ sender: NSButton) { - guard let infos = page.infoSection else { return } - let infoPopupViewController = InfoPopOverViewController(with: infos) - self.present(infoPopupViewController, - asPopoverRelativeTo: sender.convert(sender.bounds, to: self.view), - of: self.view, - preferredEdge: .maxX, - behavior: .semitransient) - } -} - -// MARK: - AccessoryViewDelegate methods implementation. -extension OnboardingPageViewController: AccessoryViewDelegate { - func accessoryViewStatusDidChange(_ sender: AccessoryView) { - checkButtonVisibility() - } -} diff --git a/Notification Agent Onboarding/Views/OnboardingPageViewController.xib b/Notification Agent Onboarding/Views/OnboardingPageViewController.xib deleted file mode 100644 index 61e39e4..0000000 --- a/Notification Agent Onboarding/Views/OnboardingPageViewController.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Notification Agent Onboarding/Views/OnboardingView.swift b/Notification Agent Onboarding/Views/OnboardingView.swift new file mode 100644 index 0000000..b044da8 --- /dev/null +++ b/Notification Agent Onboarding/Views/OnboardingView.swift @@ -0,0 +1,113 @@ +// +// OnboardingView.swift +// Notification Agent +// +// Created by Simone Martorelli on 03/04/2023. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI + +/// OnboardingView struct define the main view for the Onboarding UI +struct OnboardingView: View { + + // MARK: - State Variables + + @State var helpButtonPopoverVisible: Bool = false + + // MARK: - Observed Variables + + @ObservedObject var viewModel: OnboardingViewModel + + // MARK: - Views + + var body: some View { + VStack(alignment: .center, spacing: 0) { + currentPage + .accessibilityElement(children: .contain) + Spacer() + Rectangle() + .frame(height: 0.5) + .foregroundColor(Color(.darkGray)) + HStack { + HStack { + if let infoSection = viewModel.currentPage.infoSection { + CircleButton(action: { + helpButtonPopoverVisible = true + viewModel.didClickButton(of: .help) + }, infoSection: infoSection, type: .help, buttonState: $viewModel.helpButtonState, showPopover: $helpButtonPopoverVisible) + .accessibilityLabel("help_button_label".localized) + .accessibilityHint("button_hint_text".localized) + .accessibilityIdentifier("help_button") + } + if let tertiaryButton = viewModel.currentPage.tertiaryButton { + StandardButton(action: { + viewModel.didClickButton(of: .tertiary) + }, label: tertiaryButton.label, buttonState: $viewModel.tertiaryButtonState) + .fixedSize() + .accessibilityIdentifier("tertiary_button") + .accessibilityHint(viewModel.currentPage.tertiaryButton?.callToActionType.accessibilityHint ?? "") + } + Spacer() + } + try? ProgressBarView(viewModel: viewModel.progressBarViewModel) + .frame(height: 20) + .padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)) + .frame(alignment: .center) + HStack { + Spacer() + if !viewModel.hideBackButton { + StandardButton(action: { + viewModel.didClickButton(of: .secondary) + }, label: viewModel.secondaryButtonLabel, + buttonState: $viewModel.secondaryButtonState) + .fixedSize() + .accessibilityIdentifier("secondary_button") + .accessibilityHint("onboarding_button_secondary_hint".localized) + } + if viewModel.isLastPage { + StandardButton(action: { + viewModel.didClickButton(of: .main) + }, label: viewModel.primaryButtonLabel, + buttonState: $viewModel.closeButtonState) + .fixedSize() + .accessibilityIdentifier("main_button") + .accessibilityHint("button_hint_destructive".localized) + } else { + StandardButton(action: { + viewModel.didClickButton(of: .main) + }, label: viewModel.primaryButtonLabel, + buttonState: $viewModel.primaryButtonState) + .fixedSize() + .accessibilityIdentifier("main_button") + .accessibilityHint("onboarding_button_primary_hint".localized) + } + } + } + .padding() + } + } + + var currentPage: some View { + PageView(viewModel: PageViewModel(page: viewModel.currentPage, + inp: $viewModel.pageInputs, + outp: $viewModel.pageOutputs, + primaryButtonState: $viewModel.primaryButtonState, + secondaryButtonState: $viewModel.secondaryButtonState)) + .padding() + } +} + +// swiftlint:disable force_try + +struct OnboardingView_Previews: PreviewProvider { + static var previews: some View { + OnboardingView(viewModel: OnboardingViewModel(try! OnboardingData(from: testJson), window: NSWindow())!) + .previewLayout(PreviewLayout.fixed(width: 812, height: 600)) + } +} + +// swiftlint:enable force_try + +private let testJson: String = "{\"progressBarPayload\":\"/percent 0 /top_message Top Message /bottom_message Bottom Message\",\"pages\":[{\"title\":\"First page's title First page's title First page's title First page's title First page's title First page's title First page's title\",\"subtitle\":\"First page's subtitle\",\"body\":\"First page's body\", \"accessoryViews\":[[{\"type\":\"input\",\"payload\":\"/placeholder Something /title First\"},{\"type\":\"input\",\"payload\":\"/placeholder Something /title Second\"}]]}]}" diff --git a/Notification Agent Onboarding/Views/OnboardingViewController.swift b/Notification Agent Onboarding/Views/OnboardingViewController.swift deleted file mode 100644 index 6e52064..0000000 --- a/Notification Agent Onboarding/Views/OnboardingViewController.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// OnboardingViewController.swift -// Notification Agent -// -// Created by Simone Martorelli on 01/02/2021. -// Copyright © 2021 IBM Inc. All rights reserved. -// SPDX-License-Identifier: Apache2.0 -// - -import Cocoa - -protocol OnboardingNavigationDelegate: AnyObject { - func didSelectNextButton(_ sender: OnboardingPageViewController) - func didSelectBackButton(_ sender: OnboardingPageViewController) - func shouldCloseOnboardingWindow(_ sender: OnboardingPageViewController) -} - -class OnboardingViewController: NSViewController { - - // MARK: - Variables - - private var pages: [OnboardingPage] - private var store: [[String]] - private var alwaysOnTop: Bool - private var presentedVC: NSViewController? - private var presentedPageIndex: Int = 0 - private var commonProgressBar: ProgressBarAccessoryView! - private var interactiveUpdatesObserver: OnboardingInteractiveEFCLController? - let context = Context.main - let logger = NALogger.shared - var isClosable: Bool = true - - // MARK: - Initializers - - init(with onboardingData: OnboardingData, alwaysOnTop: Bool = false) { - self.pages = onboardingData.pages - if let progressBarPayload = onboardingData.progressBarPayload { - self.commonProgressBar = ProgressBarAccessoryView(progressBarPayload) - if !commonProgressBar.progressCompleted { - self.isClosable = false - } - } - self.alwaysOnTop = alwaysOnTop - var defaultStore: [[String]] = [] - pages.forEach({ _ in defaultStore.append([]) }) - self.store = defaultStore - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - return nil - } - - // MARK: - Instance methods - - override func viewDidLoad() { - super.viewDidLoad() - checkPagesConsistency() - setupLayout() - } - - override func viewWillAppear() { - super.viewWillAppear() - self.view.window?.level = alwaysOnTop ? .floating : .normal - } - - override func viewDidAppear() { - super.viewDidAppear() - NotificationCenter.default.post(name: .onboardingParentStatusDidChange, - object: self, - userInfo: nil) - } - - // MARK: - Private methods - - /// Check if all the provided pages are valid. - private func checkPagesConsistency() { - for page in self.pages { - guard page.isValidPage() else { - logger.log("One or more of the provided onboarding pages doesnt provide any information.") - Utils.applicationExit(withReason: .internalError) - return - } - } - } - - /// Define and present the target VC. - private func setupLayout() { - let sourceViewController = OnboardingPageViewController(with: pages.first!, - position: pages.count > 1 ? .first : .singlePage) - sourceViewController.navigationDelegate = self - self.insertChild(sourceViewController, at: 0) - self.view.addSubview(sourceViewController.view) - self.view.frame = sourceViewController.view.frame - guard commonProgressBar != nil else { return } - commonProgressBar.frame = NSRect(x: 208, y: 8, width: 400, height: 40) - commonProgressBar.delegate = self - isClosable = false - self.view.addSubview(commonProgressBar) - interactiveUpdatesObserver = OnboardingInteractiveEFCLController() - interactiveUpdatesObserver?.startObservingStandardInput() - } - - /// Write the saved store on a file on the user device. - private func writeStoreOnDevice() { - guard !store.isEmpty else { return } - var plistDictionary: [String : [String: Any]] = [:] - for (index, page) in store.enumerated() { - if page != [] { - var pageDictionary: [String : Any] = [:] - page.enumerated().forEach({ element in - pageDictionary[element.offset.description] = element.element - }) - plistDictionary[index.description] = pageDictionary - } - } - let dictionaryResult = NSDictionary(dictionary: plistDictionary) - Utils.write(dictionaryResult, to: Constants.storeFileName) - } -} - -// MARK: - OnboardingNavigationDelegate implementation -extension OnboardingViewController: OnboardingNavigationDelegate { - func didSelectNextButton(_ sender: OnboardingPageViewController) { - guard presentedPageIndex < pages.count-1, let sourceVC = self.children.first else { return } - store[presentedPageIndex] = sender.collectData() - let nextPageRelativePosition: OnboardingPageViewController.PagePosition = (pages[presentedPageIndex] as? InteractiveOnboardingPage)?.singleChange ?? false ? (pages.count-1 == presentedPageIndex+1 ? .singlePage : .relativeFirst) : (pages.count-1 == presentedPageIndex+1 ? .last : .middle) - presentedPageIndex += 1 - let destinationVC = OnboardingPageViewController(with: pages[presentedPageIndex], - position: nextPageRelativePosition, - store: store[presentedPageIndex]) - destinationVC.navigationDelegate = self - writeStoreOnDevice() - let segue = NASegue(identifier: "goToNextPage", source: sourceVC, destination: destinationVC) - segue.perform() - } - - func didSelectBackButton(_ sender: OnboardingPageViewController) { - guard presentedPageIndex > 0, let sourceVC = self.children.first else { return } - store[presentedPageIndex] = sender.collectData() - presentedPageIndex -= 1 - let nextPageRelativePosition: OnboardingPageViewController.PagePosition = presentedPageIndex == 0 ? .first : (pages[presentedPageIndex-1] as? InteractiveOnboardingPage)?.singleChange ?? false ? .relativeFirst : .middle - let destinationVC = OnboardingPageViewController(with: pages[presentedPageIndex], - position: nextPageRelativePosition, - store: store[presentedPageIndex]) - destinationVC.navigationDelegate = self - let segue = NASegue(identifier: "backToPreviousPage", source: sourceVC, destination: destinationVC) - segue.perform() - } - - func shouldCloseOnboardingWindow(_ sender: OnboardingPageViewController) { - store[presentedPageIndex] = sender.collectData() - writeStoreOnDevice() - Utils.applicationExit(withReason: .userFinishedOnboarding) - } -} - -// MARK: - NSWindowDelegate implementation -extension OnboardingViewController: NSWindowDelegate { - func windowWillClose(_ notification: Notification) { - Utils.applicationExit(withReason: .userDismissedOnboarding) - } -} - -// MARK: - AccessoryViewDelegate implementation -extension OnboardingViewController: AccessoryViewDelegate { - func accessoryViewStatusDidChange(_ sender: AccessoryView) { - self.isClosable = commonProgressBar?.progressCompleted ?? true - NotificationCenter.default.post(name: .onboardingParentStatusDidChange, - object: self, - userInfo: nil) - } -} diff --git a/Notification Agent Onboarding/Views/OnboardingViewController.xib b/Notification Agent Onboarding/Views/OnboardingViewController.xib deleted file mode 100644 index 83833db..0000000 --- a/Notification Agent Onboarding/Views/OnboardingViewController.xib +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/Notification Agent Onboarding/Views/OnboardingViewModel.swift b/Notification Agent Onboarding/Views/OnboardingViewModel.swift new file mode 100644 index 0000000..fc59c41 --- /dev/null +++ b/Notification Agent Onboarding/Views/OnboardingViewModel.swift @@ -0,0 +1,237 @@ +// +// OnboardingViewModel.swift +// Notification Agent +// +// Created by Simone Martorelli on 03/04/2023. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// +// swiftlint:disable function_body_length + +import Foundation +import Combine +import Cocoa +import SwiftUI + +/// OnboardingViewModel define a view model for the OnboardingView struct. +class OnboardingViewModel: NSObject, ObservableObject { + + // MARK: - Variables + + var onboardingData: OnboardingData + var window: NSWindow + var position: NSWindow.WindowPosition + var progressBarViewModel: ProgressBarViewModel? + + // MARK: - Private Variables + + private var outputsStore: [[[String]]] + private var inputsStore: [[[String]]] + private var interactiveUpdatesObserver: OnboardingInteractiveEFCLController? + private var automaticProgressBar: Bool = false + private var timeoutTimer: Timer? + + // MARK: - Private Variables + + private(set) var currentIndex: Int = 0 { + willSet { + guard currentIndex < inputsStore.count else { return } + if inputsStore[currentIndex] != self.pageInputs { + inputsStore[currentIndex] = self.pageInputs + } + guard currentIndex < outputsStore.count else { return } + if outputsStore[currentIndex] != self.pageOutputs { + outputsStore[currentIndex] = self.pageOutputs + } + } + didSet { + isLastPage = currentIndex == (onboardingData.pages.count - 1) + hideBackButton = currentIndex == 0 || (onboardingData.pages[safe: currentIndex-1]?.singleChange ?? false) + guard currentIndex < onboardingData.pages.count else { return } + if let page = onboardingData.pages[safe: currentIndex] { + primaryButtonState = .enabled + secondaryButtonState = .enabled + primaryButtonLabel = page.primaryButtonLabel ?? (isLastPage ? "onboarding_page_close_button".localized : "onboarding_page_continue_button".localized) + secondaryButtonLabel = page.secondaryButtonLabel ?? "onboarding_page_back_button".localized + currentPage = page + guard currentIndex < outputsStore.count && currentIndex < inputsStore.count else { return } + pageInputs = inputsStore[currentIndex] + pageOutputs = outputsStore[currentIndex] + } + } + } + + // MARK: - Published Variables + + @Published var pageInputs: [[String]] + @Published var pageOutputs: [[String]] + @Published var helpButtonState: SwiftUIButtonState = .enabled + @Published var closeButtonState: SwiftUIButtonState = .enabled + @Published var primaryButtonState: SwiftUIButtonState = .enabled + @Published var secondaryButtonState: SwiftUIButtonState = .enabled + @Published var tertiaryButtonState: SwiftUIButtonState = .enabled + @Published var currentPage: InteractiveOnboardingPage + @Published var isLastPage: Bool + @Published var hideBackButton: Bool + @Published var primaryButtonLabel: String + @Published var secondaryButtonLabel: String + + // MARK: - Initializers + + init?(_ onboardingData: OnboardingData, window: NSWindow, position: NSWindow.WindowPosition? = .center, timeout: String? = nil) { + self.onboardingData = onboardingData + self.window = window + self.position = position ?? .center + guard let firstPage = onboardingData.pages[safe: currentIndex] else { return nil } + self.currentPage = firstPage + self.isLastPage = onboardingData.pages.count == 1 + self.hideBackButton = true + self.primaryButtonLabel = firstPage.primaryButtonLabel ?? "onboarding_page_continue_button".localized + self.secondaryButtonLabel = firstPage.secondaryButtonLabel ?? "onboarding_page_back_button".localized + let tempMatrixArray: [[[String]]] = onboardingData.pages.map { page in + guard page.isValidPage() else { + NALogger.shared.log("One or more of the provided onboarding pages doesnt provide any information.") + Utils.applicationExit(withReason: .internalError) + return [] + } + return page.accessoryViews.map { matrix in + return matrix.map { row in + return row.map { _ in + return "" + } + } + } ?? [] + } + self.pageInputs = tempMatrixArray.first ?? [] + self.pageOutputs = tempMatrixArray.first ?? [] + self.outputsStore = tempMatrixArray + self.inputsStore = tempMatrixArray + super.init() + if let timeout = timeout { + self.setTimeout(timeout) + } + NotificationCenter.default.addObserver(self, selector: #selector(repositionWindow), name: NSApplication.didChangeScreenParametersNotification, object: nil) + if let progressBarPayload = onboardingData.progressBarPayload { + var payload: String = "/percent 0 /user_interaction_enabled true" + if progressBarPayload.lowercased() == "automatic" { + automaticProgressBar = true + } else { + payload = progressBarPayload + interactiveUpdatesObserver = OnboardingInteractiveEFCLController() + interactiveUpdatesObserver?.startObservingStandardInput() + } + self.progressBarViewModel = ProgressBarViewModel(progressState: ProgressState(payload), + mainButtonState: Binding(get: { + return self.closeButtonState + }, set: { newValue, _ in + guard newValue != self.closeButtonState else { return } + // Map .hidden state to .disabled + self.closeButtonState = (newValue == .hidden || newValue == .cancel) ? .disabled : newValue + }), secondaryButtonState: Binding(get: { + return .enabled + }, set: { _, _ in })) + } + } + + // MARK: - Public Methods + + /// React to the user action on the dialog's buttons. + /// - Parameter type: the user action. + func didClickButton(of type: UserReplyType) { + resetTimers() + switch type { + case .main: + writeStoreOnDevice() + if currentIndex >= (onboardingData.pages.count - 1) { + Utils.applicationExit(withReason: .userFinishedOnboarding) + } else { + currentIndex += 1 + } + updateProgressBarIfNeeded() + case .secondary: + guard currentIndex > 0 else { return } + currentIndex -= 1 + updateProgressBarIfNeeded() + case .tertiary: + if let tertiaryButton = currentPage.tertiaryButton { + switch tertiaryButton.callToActionType { + case .link: + open(tertiaryButton.callToActionPayload) + case .exitlink: + open(tertiaryButton.callToActionPayload) + Utils.applicationExit(withReason: .tertiaryButtonClicked) + default: + break + } + } + case .timeout: + break + default: + break + } + } + + func open(_ link: String) { + guard let url = URL(string: link) else { + NALogger.shared.log("Failed to create a valid URL or App path from payload: %{public}@", [link]) + return + } + if ProcessInfo.processInfo.environment["--isRunningTest"] == nil { + NSWorkspace.shared.open(url) + } + } + + // MARK: - Private Methods + + /// Write the saved store on a file on the user device. + private func writeStoreOnDevice() { + guard !outputsStore.isEmpty else { return } + var plistDictionary: [String : [String: Any]] = [:] + for (index, page) in outputsStore.enumerated() where page != [] { + var pageDictionary: [String : Any] = [:] + page.enumerated().forEach({ element in + pageDictionary[element.offset.description] = element.element + }) + plistDictionary[index.description] = pageDictionary + } + let dictionaryResult = NSDictionary(dictionary: plistDictionary) + Utils.write(dictionaryResult, to: Constants.storeFileName) + } + + /// Update the state of the progress bar if it's set to automatic. + private func updateProgressBarIfNeeded() { + guard automaticProgressBar else { return } + let newPercent = ceil(100 / Double(onboardingData.pages.count-1) * Double(currentIndex)) + progressBarViewModel?.progressState = ProgressState("/percent \(min(newPercent, 100))") + } + + /// If needed to set a timeout for the popup this method set the related actions and fire a timer. + private func setTimeout(_ timeoutString: String) { + if let timeout = Int(timeoutString) { + timeoutTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(timeout), + repeats: false, block: { _ in + Utils.applicationExit(withReason: .timeout) + }) + } + } + + private func resetTimers() { + timeoutTimer?.invalidate() + timeoutTimer = nil + } + + @objc + private func repositionWindow() { + self.window.setWindowPosition(position) + } +} + +// swiftlint:enable function_body_length + +// MARK: - NSWindowDelegate implementation + +extension OnboardingViewModel: NSWindowDelegate { + func windowWillClose(_ notification: Notification) { + Utils.applicationExit(withReason: .userDismissedOnboarding) + } +} diff --git a/Notification Agent Onboarding/Views/PageView.swift b/Notification Agent Onboarding/Views/PageView.swift new file mode 100644 index 0000000..f7a4091 --- /dev/null +++ b/Notification Agent Onboarding/Views/PageView.swift @@ -0,0 +1,126 @@ +// +// PageView.swift +// Notification Agent +// +// Created by Simone Martorelli on 05/04/2023. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI +import SwiftyMarkdown + +/// PageView struct define a view for the Page inside an Onboarding UI. +struct PageView: View { + + // MARK: - Variables + + @ObservedObject var viewModel: PageViewModel + + // MARK: - Computed Variables + + var customPopupIcon: NSImage? { + if let iconPath = viewModel.page.topIcon { + if FileManager.default.fileExists(atPath: iconPath), + let data = try? Data(contentsOf: URL(fileURLWithPath: iconPath)) { + let image = NSImage(data: data) + return image + } else if iconPath.isValidURL, + let url = URL(string: iconPath), + let data = try? Data(contentsOf: url) { + let image = NSImage(data: data) + return image + } else if let imageData = Data(base64Encoded: iconPath, options: .ignoreUnknownCharacters), + let image = NSImage(data: imageData) { + return image + } else if let image = NSImage(systemSymbolName: iconPath, accessibilityDescription: nil) { + return image + } else { + NALogger.shared.log("Unable to load image from %{public}@", [iconPath]) + } + } + return nil + } + + // MARK: - Views + + var body: some View { + VStack(alignment: .center, spacing: 0) { + Icon(icon: customPopupIcon, iconSize: CGSize(width: 86, height: 86)) + .padding(.top, 16) + .accessibilityHidden(true) + if let title = viewModel.page.title { + Text(title) + .font(.bold(.title)()) + .multilineTextAlignment(.center) + .padding(.top, 6) + .padding(.bottom, 4) + .accessibilityAddTraits(.isHeader) + .accessibilityIdentifier("onboarding_title") + } + if #available(macOS 12, *) { + if let subtitle = viewModel.page.subtitle { + Text(AttributedString(markdownText(subtitle).attributedString())) + .multilineTextAlignment(.center) + .font(.title3) + .accessibilityAddTraits(.isHeader) + .accessibilityIdentifier("onboarding_subtitle") + } + if let body = viewModel.page.body { + Text(AttributedString(markdownText(body).attributedString())) + .multilineTextAlignment(.leading) + .padding(.top, 8) + .accessibilityIdentifier("onboarding_body") + } + } else { + if let subtitle = viewModel.page.subtitle { + Text(subtitle) + .multilineTextAlignment(.center) + .font(.title3) + .accessibilityAddTraits(.isHeader) + .accessibilityIdentifier("onboarding_subtitle") + } + if let body = viewModel.page.body { + Text(body) + .font(.body) + .multilineTextAlignment(.center) + .padding(.top, 8) + .accessibilityIdentifier("onboarding_body") + } + } + + if !viewModel.accessoryViewsMatrix.isEmpty { + VStack { + ForEach(viewModel.accessoryViewsMatrix, id: \.hashValue) { row in + HStack { + ForEach(row, id: \.hashValue) { accessoryView in + accessoryView + } + } + } + } + .padding([.top, .leading, .trailing], 16) + } + Spacer() + } + .onAppear { + viewModel.evaluateBindings() + } + } + + func markdownText(_ string: String) -> SwiftyMarkdown { + let markdownText = SwiftyMarkdown(string: string) + markdownText.h1.fontSize = 20 + markdownText.h1.fontStyle = .bold + markdownText.h2.fontSize = 18 + markdownText.h2.fontStyle = .bold + markdownText.h3.fontSize = 16 + markdownText.h3.fontStyle = .bold + markdownText.link.color = .linkColor + markdownText.code.fontName = "Courier New" + markdownText.blockquotes.color = .gray + markdownText.blockquotes.fontStyle = .italic + markdownText.bullet = "•" + return markdownText + } +} diff --git a/Notification Agent Onboarding/Views/PageViewModel.swift b/Notification Agent Onboarding/Views/PageViewModel.swift new file mode 100644 index 0000000..6fce1b9 --- /dev/null +++ b/Notification Agent Onboarding/Views/PageViewModel.swift @@ -0,0 +1,120 @@ +// +// PageViewModel.swift +// Notification Agent +// +// Created by Simone Martorelli on 26/04/2023. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import Foundation +import SwiftUI + +/// PageViewModel class provides a view model for the PageView view. +final class PageViewModel: ObservableObject { + + // MARK: - Variables + + var page: InteractiveOnboardingPage + var primaryButtonStates: [[SwiftUIButtonState]] + var secondaryButtonStates: [[SwiftUIButtonState]] + var accessoryViewsMatrix: [[AccessoryViewWrapper]] + + // MARK: - Binded Variables + + @Binding var inputs: [[String]] + @Binding var outputs: [[String]] + @Binding var primaryButtonState: SwiftUIButtonState + @Binding var secondaryButtonState: SwiftUIButtonState + + // MARK: - Initiliaziers + + init(page: InteractiveOnboardingPage, + inp: Binding<[[String]]>, + outp: Binding<[[String]]>, + primaryButtonState: Binding, + secondaryButtonState: Binding) { + self.page = page + self._inputs = inp + self._outputs = outp + self._primaryButtonState = primaryButtonState + self._secondaryButtonState = secondaryButtonState + + primaryButtonStates = [] + secondaryButtonStates = [] + accessoryViewsMatrix = [] + + guard let accessoryViews = page.accessoryViews else { return } + for (row, accessoryViewsRow) in accessoryViews.enumerated() { + var primaryButtonStatesRow: [SwiftUIButtonState] = [] + var secondaryButtonStatesRow: [SwiftUIButtonState] = [] + var wrappedAccessoryViewsRow: [AccessoryViewWrapper] = [] + for (column, accessoryView) in accessoryViewsRow.enumerated() { + var primaryButtonState: SwiftUIButtonState = .enabled + primaryButtonStatesRow.append(primaryButtonState) + var secondaryButtonState: SwiftUIButtonState = .enabled + secondaryButtonStatesRow.append(secondaryButtonState) + wrappedAccessoryViewsRow.append(AccessoryViewWrapper(source: AccessoryViewSource(output: Binding(get: { + return self.$outputs[row][column].wrappedValue + }, set: { newValue, _ in + guard newValue != self.$outputs[row][column].wrappedValue else { return } + self.$outputs[row][column].wrappedValue = newValue + self.evaluateBindings() + }), mainButtonState: Binding(get: { + return primaryButtonState + }, set: { newValue, _ in + guard newValue != primaryButtonState else { return } + primaryButtonState = newValue + self.evaluateBindings() + }), secondaryButtonState: Binding(get: { + return secondaryButtonState + }, set: { newValue, _ in + guard newValue != secondaryButtonState else { return } + secondaryButtonState = newValue + self.evaluateBindings() + }), accessoryView: accessoryView), contentMode: .fit)) + } + primaryButtonStates.append(primaryButtonStatesRow) + secondaryButtonStates.append(secondaryButtonStatesRow) + accessoryViewsMatrix.append(wrappedAccessoryViewsRow) + } + } + + // MARK: - Public Methods + + /// Evaluate the current state of the binded variables. + func evaluateBindings() { + var localPrimaryButtonState: SwiftUIButtonState = .enabled + var localSecondaryButtonState: SwiftUIButtonState = .enabled + for row in accessoryViewsMatrix { + for acv in row { + switch acv.source.mainButtonState { + case .enabled: + break + case .disabled: + localPrimaryButtonState = .disabled + case .hidden: + localPrimaryButtonState = .hidden + case .cancel: + break + } + switch acv.source.secondaryButtonState { + case .enabled: + break + case .disabled: + localSecondaryButtonState = .disabled + case .hidden: + localSecondaryButtonState = .hidden + case .cancel: + break + } + } + } + if self.primaryButtonState != localPrimaryButtonState { + self.primaryButtonState = localPrimaryButtonState + } + if self.secondaryButtonState != localSecondaryButtonState { + self.secondaryButtonState = localSecondaryButtonState + } + } +} diff --git a/Notification Agent Popup UI Tests/NAPUITests.swift b/Notification Agent Popup UI Tests/NAPUITests.swift index 0f3f4e9..6e918b5 100644 --- a/Notification Agent Popup UI Tests/NAPUITests.swift +++ b/Notification Agent Popup UI Tests/NAPUITests.swift @@ -13,31 +13,38 @@ class NAPUITests: XCTestCase { /// Testing simple Pop-up with: /// Title: This is a title - /// Main Button: Ok + /// Main Button: Label --> Ok + /// BarTitle: Some Title + /// Icon: "default_icon" func test1Popup() throws { - let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJhbHdheXNPblRvcCI6ZmFsc2UsInR5cGUiOiJwb3B1cCIsInRpdGxlIjoiVGhpcyBpcyBhIHRpdGxlIiwic2lsZW50IjpmYWxzZSwibWluaWF0dXJpemFibGUiOmZhbHNlLCJiYXJUaXRsZSI6Ik1hY0BJQk0gTm90aWZpY2F0aW9ucyIsImZvcmNlTGlnaHRNb2RlIjpmYWxzZSwibm90aWZpY2F0aW9uSUQiOiJ1bnRyYWNrZWQifSwic2V0dGluZ3MiOnsiZW52aXJvbm1lbnQiOiJwcm9kIiwiaXNSZXN0Q2xpZW50RW5hYmxlZCI6ZmFsc2UsImlzQW5hbHl0aWNzRW5hYmxlZCI6ZmFsc2UsImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZX19" // pragma: allowlist-secret + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJzaWxlbnQiOmZhbHNlLCJzaG93U3VwcHJlc3Npb25CdXR0b24iOmZhbHNlLCJtaW5pYXR1cml6YWJsZSI6ZmFsc2UsImJhclRpdGxlIjoiU29tZSBUaXRsZSIsImZvcmNlTGlnaHRNb2RlIjpmYWxzZSwibm90aWZpY2F0aW9uSUQiOiJ1bnRyYWNrZWQifSwic2V0dGluZ3MiOnsiaXNWZXJib3NlTW9kZUVuYWJsZWQiOmZhbHNlLCJlbnZpcm9ubWVudCI6InByb2QifX0=" // pragma: allowlist-secret let app = XCUIApplication() app.launchArguments = [useCase] app.launch() + XCTAssert(app.buttons["main_button"].exists) XCTAssertEqual(app.buttons["main_button"].title, "OK") - XCTAssert(app.otherElements["title_textfield"].exists) - XCTAssertEqual(app.otherElements["title_textfield"].value as? String ?? "", "This is a title") + XCTAssertEqual(app.images["popup_icon"].label, "default_icon") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssertEqual(app.windows["main_window"].title, "Some Title") app.terminate() } /// Testing simple Pop-up with: /// Subtitle: This is a subtitle - /// Main Button: Ok + /// BarTitle: IBM Notifier + /// Main Button: Label --> Ok func test2Popup() throws { - let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCIsInN1YnRpdGxlIjoiVGhpcyBpcyBhIHN1YnRpdGxlIiwiYWNjZXNzb3J5Vmlld3MiOltdLCJyZXRhaW5WYWx1ZXMiOmZhbHNlLCJhbHdheXNPblRvcCI6ZmFsc2UsInR5cGUiOiJwb3B1cCIsInNpbGVudCI6ZmFsc2UsIm1pbmlhdHVyaXphYmxlIjpmYWxzZSwiYmFyVGl0bGUiOiJNYWNASUJNIE5vdGlmaWNhdGlvbnMiLCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsImhpZGVUaXRsZUJhckJ1dHRvbnMiOmZhbHNlfSwic2V0dGluZ3MiOnsiZW52aXJvbm1lbnQiOiJwcm9kIiwiaXNSZXN0Q2xpZW50RW5hYmxlZCI6ZmFsc2UsImlzQW5hbHl0aWNzRW5hYmxlZCI6ZmFsc2UsImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZX19" // pragma: allowlist-secret + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwic3VidGl0bGUiOiJUaGlzIGlzIGEgc3VidGl0bGUiLCJzaWxlbnQiOmZhbHNlLCJzaG93U3VwcHJlc3Npb25CdXR0b24iOmZhbHNlLCJtaW5pYXR1cml6YWJsZSI6ZmFsc2UsImZvcmNlTGlnaHRNb2RlIjpmYWxzZSwibm90aWZpY2F0aW9uSUQiOiJ1bnRyYWNrZWQifSwic2V0dGluZ3MiOnsiaXNWZXJib3NlTW9kZUVuYWJsZWQiOmZhbHNlLCJlbnZpcm9ubWVudCI6InByb2QifX0=" // pragma: allowlist-secret let app = XCUIApplication() app.launchArguments = [useCase] app.launch() XCTAssert(app.buttons["main_button"].exists) XCTAssertEqual(app.buttons["main_button"].title, "OK") - XCTAssert(app.textViews["accessory_view_accessibility_markdown_textview"].exists) - XCTAssertEqual(app.textViews["accessory_view_accessibility_markdown_textview"].value as? String ?? "", "This is a subtitle") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") + XCTAssertEqual(app.windows["main_window"].title, "IBM Notifier") app.terminate() } @@ -45,18 +52,18 @@ class NAPUITests: XCTestCase { /// Title: This is a title /// Subtitle: This is a subtitle /// Position: top_left - /// Main Button: Ok + /// Main Button: Label --> Ok func test3Popup() throws { - let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsInN1YnRpdGxlIjoiVGhpcyBpcyBhIHN1YnRpdGxlIiwiYWNjZXNzb3J5Vmlld3MiOltdLCJyZXRhaW5WYWx1ZXMiOmZhbHNlLCJwb3NpdGlvbiI6InRvcF9sZWZ0IiwiYWx3YXlzT25Ub3AiOmZhbHNlLCJ0eXBlIjoicG9wdXAiLCJ0aXRsZSI6IlRoaXMgaXMgYSB0aXRsZSIsInNpbGVudCI6ZmFsc2UsIm1pbmlhdHVyaXphYmxlIjpmYWxzZSwiYmFyVGl0bGUiOiJNYWNASUJNIE5vdGlmaWNhdGlvbnMiLCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCIsImhpZGVUaXRsZUJhckJ1dHRvbnMiOmZhbHNlfSwic2V0dGluZ3MiOnsiZW52aXJvbm1lbnQiOiJwcm9kIiwiaXNSZXN0Q2xpZW50RW5hYmxlZCI6ZmFsc2UsImlzQW5hbHl0aWNzRW5hYmxlZCI6ZmFsc2UsImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZX19" // pragma: allowlist-secret + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJwb3NpdGlvbiI6InRvcF9sZWZ0Iiwic3VidGl0bGUiOiJUaGlzIGlzIGEgc3VidGl0bGUiLCJzaWxlbnQiOmZhbHNlLCJzaG93U3VwcHJlc3Npb25CdXR0b24iOmZhbHNlLCJtaW5pYXR1cml6YWJsZSI6ZmFsc2UsImJhclRpdGxlIjoiTWFjQElCTSBOb3RpZmljYXRpb25zIiwiZm9yY2VMaWdodE1vZGUiOmZhbHNlLCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCJ9LCJzZXR0aW5ncyI6eyJpc1ZlcmJvc2VNb2RlRW5hYmxlZCI6ZmFsc2UsImVudmlyb25tZW50IjoicHJvZCJ9fQ==" // pragma: allowlist-secret let app = XCUIApplication() app.launchArguments = [useCase] app.launch() XCTAssert(app.buttons["main_button"].exists) XCTAssertEqual(app.buttons["main_button"].title, "OK") - XCTAssert(app.otherElements["title_textfield"].exists) - XCTAssertEqual(app.otherElements["title_textfield"].value as? String ?? "", "This is a title") - XCTAssert(app.textViews["accessory_view_accessibility_markdown_textview"].exists) - XCTAssertEqual(app.textViews["accessory_view_accessibility_markdown_textview"].value as? String ?? "", "This is a subtitle") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") app.terminate() } @@ -64,18 +71,109 @@ class NAPUITests: XCTestCase { /// Title: This is a title /// Subtitle: This is a subtitle /// Silent: true - /// Main Button: Ok + /// Main Button: Label --> Ok func test4Popup() throws { - let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsInN1YnRpdGxlIjoiVGhpcyBpcyBhIHN1YnRpdGxlIiwiYWNjZXNzb3J5Vmlld3MiOltdLCJyZXRhaW5WYWx1ZXMiOmZhbHNlLCJhbHdheXNPblRvcCI6ZmFsc2UsInR5cGUiOiJwb3B1cCIsInRpdGxlIjoiVGhpcyBpcyBhIHRpdGxlIiwic2lsZW50Ijp0cnVlLCJtaW5pYXR1cml6YWJsZSI6ZmFsc2UsImJhclRpdGxlIjoiTWFjQElCTSBOb3RpZmljYXRpb25zIiwibm90aWZpY2F0aW9uSUQiOiJ1bnRyYWNrZWQiLCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZX0sInNldHRpbmdzIjp7ImVudmlyb25tZW50IjoicHJvZCIsImlzUmVzdENsaWVudEVuYWJsZWQiOmZhbHNlLCJpc0FuYWx5dGljc0VuYWJsZWQiOmZhbHNlLCJpc1ZlcmJvc2VNb2RlRW5hYmxlZCI6ZmFsc2V9fQ==" // pragma: allowlist-secret + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJPSyIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJzdWJ0aXRsZSI6IlRoaXMgaXMgYSBzdWJ0aXRsZSIsInNpbGVudCI6dHJ1ZSwic2hvd1N1cHByZXNzaW9uQnV0dG9uIjpmYWxzZSwibWluaWF0dXJpemFibGUiOmZhbHNlLCJiYXJUaXRsZSI6Ik1hY0BJQk0gTm90aWZpY2F0aW9ucyIsImZvcmNlTGlnaHRNb2RlIjpmYWxzZSwibm90aWZpY2F0aW9uSUQiOiJ1bnRyYWNrZWQifSwic2V0dGluZ3MiOnsiaXNWZXJib3NlTW9kZUVuYWJsZWQiOmZhbHNlLCJlbnZpcm9ubWVudCI6InByb2QifX0=" // pragma: allowlist-secret let app = XCUIApplication() app.launchArguments = [useCase] app.launch() XCTAssert(app.buttons["main_button"].exists) XCTAssertEqual(app.buttons["main_button"].title, "OK") - XCTAssert(app.otherElements["title_textfield"].exists) - XCTAssertEqual(app.otherElements["title_textfield"].value as? String ?? "", "This is a title") - XCTAssert(app.textViews["accessory_view_accessibility_markdown_textview"].exists) - XCTAssertEqual(app.textViews["accessory_view_accessibility_markdown_textview"].value as? String ?? "", "This is a subtitle") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") + app.terminate() + } + + /// Testing Pop-up with: + /// Title: This is a title + /// Subtitle: This is a subtitle + /// Main Button: Label --> Primary + /// Secondary Button: Label --> Secondary + func test5Popup() throws { + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJQcmltYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6Im5vbmUiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiIn0sInNlY29uZGFyeUJ1dHRvbiI6eyJsYWJlbCI6IlNlY29uZGFyeSIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJzdWJ0aXRsZSI6IlRoaXMgaXMgYSBzdWJ0aXRsZSIsInNpbGVudCI6ZmFsc2UsInNob3dTdXBwcmVzc2lvbkJ1dHRvbiI6ZmFsc2UsIm1pbmlhdHVyaXphYmxlIjpmYWxzZSwiYmFyVGl0bGUiOiJNYWNASUJNIE5vdGlmaWNhdGlvbnMiLCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsIm5vdGlmaWNhdGlvbklEIjoidW50cmFja2VkIn0sInNldHRpbmdzIjp7ImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZSwiZW52aXJvbm1lbnQiOiJwcm9kIn19" // pragma: allowlist-secret + let app = XCUIApplication() + app.launchArguments = [useCase] + app.launch() + XCTAssert(app.buttons["main_button"].exists) + XCTAssertEqual(app.buttons["main_button"].title, "Primary") + XCTAssert(app.buttons["secondary_button"].exists) + XCTAssertEqual(app.buttons["secondary_button"].label, "Secondary") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") + app.terminate() + } + + /// Testing Pop-up with: + /// Title: This is a title + /// Subtitle: This is a subtitle + /// Main Button: Label --> Primary + /// Secondary Button: Label --> Secondary + /// Tertiary Button: Label --> Tertiary + func test6Popup() throws { + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJQcmltYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6Im5vbmUiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiIn0sInNlY29uZGFyeUJ1dHRvbiI6eyJsYWJlbCI6IlNlY29uZGFyeSIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJ0ZXJ0aWFyeUJ1dHRvbiI6eyJsYWJlbCI6IlRlcnRpYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6ImxpbmsiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbSJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJzdWJ0aXRsZSI6IlRoaXMgaXMgYSBzdWJ0aXRsZSIsInNpbGVudCI6ZmFsc2UsInNob3dTdXBwcmVzc2lvbkJ1dHRvbiI6ZmFsc2UsIm1pbmlhdHVyaXphYmxlIjpmYWxzZSwiYmFyVGl0bGUiOiJNYWNASUJNIE5vdGlmaWNhdGlvbnMiLCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsIm5vdGlmaWNhdGlvbklEIjoidW50cmFja2VkIn0sInNldHRpbmdzIjp7ImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZSwiZW52aXJvbm1lbnQiOiJwcm9kIn19" // pragma: allowlist-secret + let app = XCUIApplication() + app.launchArguments = [useCase] + app.launch() + XCTAssert(app.buttons["main_button"].exists) + XCTAssertEqual(app.buttons["main_button"].title, "Primary") + XCTAssert(app.buttons["secondary_button"].exists) + XCTAssertEqual(app.buttons["secondary_button"].label, "Secondary") + XCTAssert(app.buttons["tertiary_button"].exists) + XCTAssertEqual(app.buttons["tertiary_button"].label, "Tertiary") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") + app.terminate() + } + + /// Testing Pop-up with: + /// Title: This is a title + /// Subtitle: This is a subtitle + /// Main Button: Primary + /// Secondary Button: Secondary + /// BarTitle: Some + func test7Popup() throws { + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJQcmltYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6Im5vbmUiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiIn0sInNlY29uZGFyeUJ1dHRvbiI6eyJsYWJlbCI6IlNlY29uZGFyeSIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJoaWRlVGl0bGVCYXJCdXR0b25zIjpmYWxzZSwicmV0YWluVmFsdWVzIjpmYWxzZSwiYWNjZXNzb3J5Vmlld3MiOltdLCJpc01vdmFibGUiOnRydWUsImFsd2F5c09uVG9wIjpmYWxzZSwidHlwZSI6InBvcHVwIiwidGl0bGUiOiJUaGlzIGlzIGEgdGl0bGUiLCJzdWJ0aXRsZSI6IlRoaXMgaXMgYSBzdWJ0aXRsZSIsInNpbGVudCI6ZmFsc2UsInNob3dTdXBwcmVzc2lvbkJ1dHRvbiI6ZmFsc2UsIm1pbmlhdHVyaXphYmxlIjpmYWxzZSwiYmFyVGl0bGUiOiJTb21lIiwiZm9yY2VMaWdodE1vZGUiOmZhbHNlLCJub3RpZmljYXRpb25JRCI6InVudHJhY2tlZCJ9LCJzZXR0aW5ncyI6eyJpc1ZlcmJvc2VNb2RlRW5hYmxlZCI6ZmFsc2UsImVudmlyb25tZW50IjoicHJvZCJ9fQ==" // pragma: allowlist-secret + let app = XCUIApplication() + app.launchArguments = [useCase] + app.launch() + XCTAssert(app.buttons["main_button"].exists) + XCTAssertEqual(app.buttons["main_button"].title, "Primary") + XCTAssert(app.buttons["secondary_button"].exists) + XCTAssertEqual(app.buttons["secondary_button"].label, "Secondary") + XCTAssertEqual(app.windows["main_window"].title, "Some") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") + app.terminate() + } + + /// Testing Pop-up with: + /// Title: This is a title + /// Subtitle: This is a subtitle + /// Main Button: Primary + /// Secondary Button: Secondary + /// Icon: Circle SFSymbol + func test8Popup() throws { + let useCase = "eyJub3RpZmljYXRpb24iOnsidG9waWNJRCI6InVudHJhY2tlZCIsIm1haW5CdXR0b24iOnsibGFiZWwiOiJQcmltYXJ5IiwiY2FsbFRvQWN0aW9uVHlwZSI6Im5vbmUiLCJjYWxsVG9BY3Rpb25QYXlsb2FkIjoiIn0sInNlY29uZGFyeUJ1dHRvbiI6eyJsYWJlbCI6IlNlY29uZGFyeSIsImNhbGxUb0FjdGlvblR5cGUiOiJub25lIiwiY2FsbFRvQWN0aW9uUGF5bG9hZCI6IiJ9LCJpY29uUGF0aCI6ImNpcmNsZSIsImhpZGVUaXRsZUJhckJ1dHRvbnMiOmZhbHNlLCJyZXRhaW5WYWx1ZXMiOmZhbHNlLCJhY2Nlc3NvcnlWaWV3cyI6W10sImlzTW92YWJsZSI6dHJ1ZSwiYWx3YXlzT25Ub3AiOmZhbHNlLCJ0eXBlIjoicG9wdXAiLCJ0aXRsZSI6IlRoaXMgaXMgYSB0aXRsZSIsInN1YnRpdGxlIjoiVGhpcyBpcyBhIHN1YnRpdGxlIiwic2lsZW50IjpmYWxzZSwic2hvd1N1cHByZXNzaW9uQnV0dG9uIjpmYWxzZSwibWluaWF0dXJpemFibGUiOmZhbHNlLCJiYXJUaXRsZSI6IlNvbWUiLCJmb3JjZUxpZ2h0TW9kZSI6ZmFsc2UsIm5vdGlmaWNhdGlvbklEIjoidW50cmFja2VkIn0sInNldHRpbmdzIjp7ImlzVmVyYm9zZU1vZGVFbmFibGVkIjpmYWxzZSwiZW52aXJvbm1lbnQiOiJwcm9kIn19" // pragma: allowlist-secret + let app = XCUIApplication() + app.launchArguments = [useCase] + app.launch() + XCTAssert(app.buttons["main_button"].exists) + XCTAssertEqual(app.buttons["main_button"].title, "Primary") + XCTAssert(app.buttons["secondary_button"].exists) + XCTAssertEqual(app.buttons["secondary_button"].label, "Secondary") + XCTAssertEqual(app.images["popup_icon"].label, "circle") + XCTAssert(app.staticTexts["popup_title"].exists) + XCTAssertEqual(app.staticTexts["popup_title"].value as? String ?? "", "This is a title") + XCTAssert(app.staticTexts["popup_subtitle"].exists) + XCTAssertEqual(app.staticTexts["popup_subtitle"].value as? String ?? "", "This is a subtitle") app.terminate() } } diff --git a/Notification Agent Popups/AppDelegate.swift b/Notification Agent Popups/AppDelegate.swift index ef50aa8..fb2f4bd 100644 --- a/Notification Agent Popups/AppDelegate.swift +++ b/Notification Agent Popups/AppDelegate.swift @@ -26,10 +26,21 @@ class AppDelegate: NSObject, NSApplicationDelegate { NSApplication.shared.activate(ignoringOtherApps: true) notificationDispatch.startObservingForNotifications() efclController.parseArguments() + completion() } func applicationDidFinishLaunching(_ aNotification: Notification) { + // Intercept the command+q shortcut and modify the exit value to reflect a manual user dismission. + NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in + switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { + case [.command] where event.characters == "q": + Utils.applicationExit(withReason: .userDismissedPopup) + default: + return event + } + return event + } configureApp() } } diff --git a/Notification Agent Popups/Base.lproj/Main.storyboard b/Notification Agent Popups/Base.lproj/Main.storyboard index e57864c..f8f7dd0 100644 --- a/Notification Agent Popups/Base.lproj/Main.storyboard +++ b/Notification Agent Popups/Base.lproj/Main.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -680,147 +679,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Notification Agent Popups/PopupInteractiveEFCLController.swift b/Notification Agent Popups/Controllers/PopupInteractiveEFCLController.swift similarity index 98% rename from Notification Agent Popups/PopupInteractiveEFCLController.swift rename to Notification Agent Popups/Controllers/PopupInteractiveEFCLController.swift index 1d41e55..fc9db53 100644 --- a/Notification Agent Popups/PopupInteractiveEFCLController.swift +++ b/Notification Agent Popups/Controllers/PopupInteractiveEFCLController.swift @@ -24,7 +24,7 @@ final class PopupInteractiveEFCLController: InteractiveEFCLController { switch argument { case "warning_button_visibility": NotificationCenter.default.post(name: Notification.Name("dynamic_button_updates"), object: nil, userInfo: ["data" : inputData]) - case "percent", "top_message", "bottom_message", "user_interaction_enabled", "user_interruption_allowed", "exit_on_completion": + case "percent", "top_message", "bottom_message", "user_interaction_enabled", "user_interruption_allowed", "exit_on_completion", "end": NotificationCenter.default.post(name: Notification.Name("progressbar_interactive_updates"), object: nil, userInfo: ["data" : inputData]) default: continue diff --git a/Notification Agent Popups/Controllers/SystemAlertController.swift b/Notification Agent Popups/Controllers/SystemAlertController.swift index 504a71a..58a0ca4 100644 --- a/Notification Agent Popups/Controllers/SystemAlertController.swift +++ b/Notification Agent Popups/Controllers/SystemAlertController.swift @@ -52,6 +52,8 @@ final class SystemAlertController { } else if let imageData = Data(base64Encoded: iconPath, options: .ignoreUnknownCharacters), let image = NSImage(data: imageData) { alert.icon = image + } else if let image = NSImage(systemSymbolName: iconPath, accessibilityDescription: nil) { + alert.icon = image } else { NALogger.shared.log("Unable to load image from %{public}@", [iconPath]) } diff --git a/Notification Agent Popups/Extensions/NotificationDispatch-Extension.swift b/Notification Agent Popups/Extensions/NotificationDispatch-Extension.swift index 54d0025..3d2d053 100644 --- a/Notification Agent Popups/Extensions/NotificationDispatch-Extension.swift +++ b/Notification Agent Popups/Extensions/NotificationDispatch-Extension.swift @@ -9,6 +9,7 @@ import Foundation import Cocoa +import SwiftUI extension NotificationDispatch { /// Handle the received notification and send the notification object to the correct controller. @@ -24,20 +25,40 @@ extension NotificationDispatch { } case .popup: DispatchQueue.main.async { - let storyboard = NSStoryboard(name: "Main", bundle: nil) - guard let popUpViewController = storyboard.instantiateController(withIdentifier: PopUpViewController.identifier) as? PopUpViewController else { return } - popUpViewController.notificationObject = object - let window = NSWindow(contentViewController: popUpViewController) + let mainWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 520, height: 130), styleMask: .titled, backing: .buffered, defer: false) + let viewModel = PopUpViewModel(object, window: mainWindow) + let contentView = PopUpView(viewModel: viewModel) + let hostingView = NSHostingView(rootView: contentView) + mainWindow.contentView = hostingView + mainWindow.title = object.barTitle ?? ConfigurableParameters.defaultPopupBarTitle + mainWindow.setWindowPosition(object.position ?? .center) + mainWindow.styleMask.remove(.resizable) + mainWindow.styleMask.remove(.closable) + mainWindow.canBecomeVisibleWithoutLogin = true + mainWindow.setAccessibilityIdentifier("main_window") + + if let backgroundPanelStyle = object.backgroundPanel { + mainWindow.level = .init(Int(CGWindowLevelForKey(.maximumWindow)) + 2) + mainWindow.isMovable = false + mainWindow.collectionBehavior = [.stationary, .canJoinAllSpaces] + Context.main.backgroundPanelsController = BackPanelController(backgroundPanelStyle) + Context.main.backgroundPanelsController?.showBackgroundWindows() + } else { + mainWindow.isMovable = object.isMovable + mainWindow.level = object.alwaysOnTop ?? false ? .floating : .normal + } + if object.forceLightMode ?? false { - window.appearance = NSAppearance(named: .aqua) + NSApp.appearance = NSAppearance(named: .aqua) } - window.styleMask.remove(.resizable) if !(object.isMiniaturizable ?? false) { - window.styleMask.remove(.miniaturizable) + mainWindow.styleMask.remove(.miniaturizable) } - window.styleMask.remove(.closable) - window.makeKeyAndOrderFront(self) + + mainWindow.makeKeyAndOrderFront(self) + guard object.silent == false else { return } + guard Utils.UISoundEffectStatusEnable else { return } NSSound(named: .init("Funk"))?.play() } default: diff --git a/Notification Agent Popups/Info.plist b/Notification Agent Popups/Info.plist index a76d44f..411d179 100644 --- a/Notification Agent Popups/Info.plist +++ b/Notification Agent Popups/Info.plist @@ -6,8 +6,6 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -19,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 96 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -34,7 +32,7 @@ NSHumanReadableCopyright - Copyright © 2021 IBM Inc. All rights reserved. + Copyright © 2021 IBM. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/Notification Agent Popups/Views/BodyLabels.swift b/Notification Agent Popups/Views/BodyLabels.swift new file mode 100644 index 0000000..e73af4e --- /dev/null +++ b/Notification Agent Popups/Views/BodyLabels.swift @@ -0,0 +1,47 @@ +// +// BodyLabels.swift +// Notification Agent +// +// Created by Simone Martorelli on 22/11/22. +// Copyright © 2022 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI +import SwiftyMarkdown + +/// BodyLabels is a struct that defines a view with the title and the subtitle for the PupUpView. +struct BodyLabels: View { + + // MARK: - Variables + + var title: String? + var titleFont: Font? + var subtitle: String? + + // MARK: - Views + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let title = title, !title.isEmpty { + Text(title) + .font(titleFont) + .fixedSize(horizontal: false, vertical: true) + .accessibilityIdentifier("popup_title") + } + if let subtitle = subtitle { + MarkdownView(text: subtitle.localized, maxViewHeight: 450) + .accessibilityElement() + .accessibilityValue(SwiftyMarkdown(string: subtitle).attributedString().string) + .accessibilityAddTraits(.isStaticText) + .accessibilityIdentifier("popup_subtitle") + } + } + } +} + +struct BodyLabels_Previews: PreviewProvider { + static var previews: some View { + BodyLabels(title: "Some Title", subtitle: "Some Subtitle") + } +} diff --git a/Notification Agent Popups/Views/PopUpView.swift b/Notification Agent Popups/Views/PopUpView.swift new file mode 100644 index 0000000..a059d80 --- /dev/null +++ b/Notification Agent Popups/Views/PopUpView.swift @@ -0,0 +1,128 @@ +// +// PopUpView.swift +// Notification Agent +// +// Created by Simone Martorelli on 04/11/22. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI + +/// PopUpView struct define the main view for the popup UI. +struct PopUpView: View { + + // MARK: - Observed Variables + + /// This variable define the observed view model on which is based the PopUpView + @ObservedObject var viewModel: PopUpViewModel + + // MARK: - Views + + var body: some View { + HStack(alignment: .top, spacing: 8) { + VStack(alignment: .leading) { + Icon(icon: viewModel.customPopupIcon, iconSize: viewModel.iconSize) + .accessibilityIdentifier("popup_icon") + Spacer(minLength: 8) + HStack(spacing: 4) { + CircleButton(action: { + self.viewModel.didClickButton(of: .help) + }, + popoverText: self.viewModel.notificationObject.helpButton?.callToActionPayload.localized, + type: .help, + buttonState: $viewModel.helpButtonState, + showPopover: $viewModel.showHelpButtonPopover) + .accessibilityLabel("help_button_label".localized) + .accessibilityHint(viewModel.notificationObject.helpButton?.callToActionType.accessibilityHint ?? "") + .accessibilityIdentifier("help_button") + CircleButton(action: { + self.viewModel.didClickButton(of: .warning) + }, + popoverText: self.viewModel.notificationObject.warningButton?.callToActionPayload.localized, + type: .warning, + buttonState: $viewModel.warningButtonState, + showPopover: $viewModel.showWarningButtonPopover) + .accessibilityLabel("warning_button_label".localized) + .accessibilityHint(viewModel.notificationObject.warningButton?.callToActionType.accessibilityHint ?? "") + .accessibilityIdentifier("warning_button") + } + } + VStack(alignment: .leading) { + BodyLabels(title: viewModel.notificationObject.title, + titleFont: viewModel.titleFont, + subtitle: viewModel.notificationObject.subtitle) + Spacer(minLength: 8) + if let primaryAV = viewModel.primaryAccessoryView { + switch primaryAV.accessoryView.type { + case .timer: + Text($viewModel.primaryAVInput.wrappedValue.localized) + .fixedSize(horizontal: false, vertical: true) + .accessibilityIdentifier("timer_accessory_view") + default: + AccessoryViewWrapper(source: primaryAV) + .accessibilityIdentifier("primary_accessory_view") + } + } + if let secondaryAV = viewModel.secondaryAccessoryView { + switch secondaryAV.accessoryView.type { + case .timer: + Text($viewModel.secondaryAVInput.wrappedValue.localized) + .fixedSize(horizontal: false, vertical: true) + .accessibilityIdentifier("timer_accessory_view") + default: + AccessoryViewWrapper(source: secondaryAV) + .accessibilityIdentifier("secondary_accessory_view") + } + } + Spacer(minLength: 12) + HStack { + StandardButton(action: { + self.viewModel.didClickButton(of: .tertiary) + }, label: viewModel.notificationObject.tertiaryButton?.label ?? "", buttonState: $viewModel.tertiaryButtonState) + .accessibilityHint(viewModel.notificationObject.tertiaryButton?.callToActionType.accessibilityHint ?? "") + .accessibilityIdentifier("tertiary_button") + Spacer(minLength: 8) + StandardButton(action: { + self.viewModel.didClickButton(of: .secondary) + }, label: viewModel.notificationObject.secondaryButton?.label ?? "", buttonState: $viewModel.secondaryButtonState) + .accessibilityHint(viewModel.notificationObject.secondaryButton?.callToActionType.accessibilityHint ?? "") + .accessibilityIdentifier("secondary_button") + StandardButton(keyboardShortcut: .return, action: { + self.viewModel.didClickButton(of: .main) + }, label: viewModel.notificationObject.mainButton.label, buttonState: $viewModel.mainButtonState) + .accessibilityHint(viewModel.notificationObject.mainButton.callToActionType.accessibilityHint) + .accessibilityIdentifier("main_button") + } + } + } + .padding(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)) + .frame(width: 520) + } +} + +struct PopUpView_Previews: PreviewProvider { + static var previews: some View { + PopUpView(viewModel: PopUpViewModel(notificationObject, window: NSWindow())) + .previewLayout(.fixed(width: 520, height: 150)) + } +} + +// swiftlint:disable force_try + +private let notificationObject = try! NotificationObject(from: ["type":"popup", + "title":"This is a title", + "subtitle":"**This is a subtitle** [A Link](https://www.google.com) \n Something", + "main_button_label":"Main", + "secondary_button_label":"Secondary", + "tertiary_button_label":"Tertiary", + "tertiary_button_cta_type":"link", + "tertiary_button_cta_payload":"https://www.google.com", + "help_button_cta_type":"link", + "help_button_cta_payload":"https://www.ibm.com", + "accessory_view_type":"image", + "accessory_view_payload":"/Users/simonemartorelli.max/Desktop/test.png", + "secondary_accessory_view_type":"input", + "secondary_accessory_view_payload":"/title title /value some"]) + +// swiftlint:enable force_try diff --git a/Notification Agent Popups/Views/PopUpViewController.swift b/Notification Agent Popups/Views/PopUpViewController.swift deleted file mode 100644 index 777ff51..0000000 --- a/Notification Agent Popups/Views/PopUpViewController.swift +++ /dev/null @@ -1,531 +0,0 @@ -// -// PopUpViewController.swift -// Notification Agent -// -// Created by Jan Valentik on 18/06/2021. -// Copyright © 2021 IBM Inc. All rights reserved -// SPDX-License-Identifier: Apache2.0 -// -// swiftlint:disable function_body_length type_body_length file_length - -import Cocoa -import os.log -import Foundation -import SwiftyMarkdown - -class PopUpViewController: NSViewController { - - // MARK: - Static variables - - static var identifier: NSStoryboard.SceneIdentifier = .init(stringLiteral: "popUpViewController") - - // MARK: - Outlets - - @IBOutlet weak var iconView: NSImageView! - @IBOutlet weak var helpButton: NSButton! - @IBOutlet weak var warningButton: NSButton! - @IBOutlet weak var mainButton: NSButton! - @IBOutlet weak var secondaryButton: NSButton! - @IBOutlet weak var tertiaryButton: NSButton! - @IBOutlet weak var popupElementsStackView: NSStackView! - @IBOutlet weak var iconViewHeight: NSLayoutConstraint! - @IBOutlet weak var iconViewWidth: NSLayoutConstraint! - - // MARK: - Variables - - var notificationObject: NotificationObject! - var timeoutTimer: Timer? - var reminderTimer: Timer? - var replyHandler = ReplyHandler.shared - let context = Context.main - var shouldAllowCancel: Bool = false { - didSet { - DispatchQueue.main.async { - self.mainButton.title = self.shouldAllowCancel ? "cancel_label".localized : self.notificationObject.mainButton.label - } - } - } - var accessoryViews: [AccessoryView] = [] - var interactiveUpdatesObserver: PopupInteractiveEFCLController? - var warningPopoverViewController: InfoPopOverViewController! - - // MARK: - Instance methods - - override func viewWillAppear() { - super.viewWillAppear() - view.window?.level = (notificationObject?.alwaysOnTop ?? false) ? .floating : .normal - } - - override func viewDidAppear() { - super.viewDidAppear() - view.window?.setWindowPosition(notificationObject.position ?? .center) - } - - override func viewDidLoad() { - super.viewDidLoad() - configureView() - } - - // MARK: - Private methods - - /// Configure the popup's window. - private func configureView() { - configureWindow() - configureMainLabels() - setIconIfNeeded() - configureButtons() - - for accessoryView in notificationObject?.accessoryViews?.reversed() ?? [] { - configureAccessoryView(accessoryView) - } - - checkStackViewLayout() - setTimeoutIfNeeded() - setRemindTimerIfNeeded() - setInteractiveUpdatesIfNeeded() - checkButtonVisibility() - configureAccessibilityElements() - } - - /// Configure the bar title and the level for the popup's window. - private func configureWindow() { - self.title = notificationObject?.barTitle - } - - /// Set the title and the description of the popup if defined. - private func configureMainLabels() { - if let subtitle = notificationObject?.subtitle { - let maxSubtitleHeight: CGFloat = !(notificationObject.accessoryViews?.isEmpty ?? true) ? 200 : 450 - let textView = MarkdownTextView(withText: subtitle.localized, maxViewHeight: maxSubtitleHeight) - textView.setAccessibilityElement(true) - textView.setAccessibilityIdentifier("subtitle_textfield") - textView.setAccessibilityLabel("popup_accessibility_label_subtitle".localized) - self.popupElementsStackView.insertView(textView, at: 0, in: .top) - } - if let title = notificationObject?.title { - let titleLabel = NSTextField(wrappingLabelWithString: title.localized) - titleLabel.translatesAutoresizingMaskIntoConstraints = false - titleLabel.setAccessibilityElement(true) - titleLabel.setAccessibilityIdentifier("title_textfield") - titleLabel.setAccessibilityLabel("popup_accessibility_label_title".localized) - // Check to see if a custom title font size has been defined - if let requestedFontSize = notificationObject.titleFontSize, - let customFontSize = NumberFormatter().number(from: requestedFontSize) { - let titleFontSize = CGFloat(truncating: customFontSize) - titleLabel.font = .boldSystemFont(ofSize: titleFontSize) - } else if let fontSize = titleLabel.font?.pointSize { - titleLabel.font = .boldSystemFont(ofSize: fontSize) - } - self.popupElementsStackView.insertView(titleLabel, at: 0, in: .top) - let fitHeight: CGFloat = titleLabel.sizeThatFits(NSSize(width: popupElementsStackView.bounds.width, height: 0)).height - titleLabel.heightAnchor.constraint(equalToConstant: fitHeight).isActive = true - } - } - - /// This method load and set the icon if a custom one was defined. - private func setIconIfNeeded() { - if let iconPath = notificationObject.iconPath { - if FileManager.default.fileExists(atPath: iconPath), - let data = try? Data(contentsOf: URL(fileURLWithPath: iconPath)) { - let image = NSImage(data: data) - iconView.image = image - } else if iconPath.isValidURL, - let url = URL(string: iconPath), - let data = try? Data(contentsOf: url) { - let image = NSImage(data: data) - iconView.image = image - } else if let imageData = Data(base64Encoded: iconPath, options: .ignoreUnknownCharacters), - let image = NSImage(data: imageData) { - iconView.image = image - } else { - NALogger.shared.log("Unable to load image from %{public}@", [iconPath]) - } - } else { - iconView.image = NSImage(named: NSImage.Name("default_icon")) - } - // Set icon width and height if specified - if let iconWidthAsString = notificationObject.iconWidth, - let customWidth = NumberFormatter().number(from: iconWidthAsString) { - iconViewWidth.isActive = false - iconViewWidth.constant = CGFloat(truncating: customWidth) - iconViewWidth.isActive = true - } - if let iconHeightAsString = notificationObject.iconHeight, - let customHeight = NumberFormatter().number(from: iconHeightAsString) { - iconViewHeight.isActive = false - iconViewHeight.constant = CGFloat(truncating: customHeight) - iconViewHeight.isActive = true - } - if iconViewHeight.constant != iconViewWidth.constant { - iconView.imageScaling = .scaleAxesIndependently - iconView.image?.resizingMode = .stretch - } - iconView.layout() - } - - /// Set the needed buttons in the popup's window. - private func configureButtons() { - self.helpButton.isHidden = notificationObject?.helpButton == nil - - if let warningButton = notificationObject.warningButton { - warningButton.startObservingForUpdates() - warningButton.delegate = self - self.warningButton.isHidden = !warningButton.isVisible - } - - let defaultTitle = ConfigurableParameters.defaultMainButtonLabel - self.mainButton.title = notificationObject?.mainButton.label.localized ?? defaultTitle - - if let secondaryButtonLabel = notificationObject?.secondaryButton?.label { - self.secondaryButton.isHidden = false - self.secondaryButton.title = secondaryButtonLabel.localized - } - - if let tertiaryButtonLabel = notificationObject?.tertiaryButton?.label { - self.tertiaryButton.isHidden = false - self.tertiaryButton.title = tertiaryButtonLabel.localized - } - } - - /// Configure and insert the related accessory view. - /// - Parameter accessoryView: the defined accessory view. - private func configureAccessoryView(_ accessoryView: NotificationAccessoryElement) { - switch accessoryView.type { - case .timer: - guard let rawTime = notificationObject.timeout, - let time = Int(rawTime) else { return } - let timerAccessoryView = TimerAccessoryView(withTimeInSeconds: time, label: accessoryView.payload ?? "") - timerAccessoryView.translatesAutoresizingMaskIntoConstraints = false - timerAccessoryView.timerDelegate = self - self.popupElementsStackView.insertView(timerAccessoryView, at: 0, in: .center) - case .whitebox: - let markdownTextView = MarkdownTextView(withText: accessoryView.payload ?? "", drawsBackground: true) - self.popupElementsStackView.insertView(markdownTextView, at: 0, in: .center) - case .progressbar: - let progressBarAccessoryView = ProgressBarAccessoryView(accessoryView.payload) - self.popupElementsStackView.insertView(progressBarAccessoryView, at: 0, in: .center) - progressBarAccessoryView.progressBarDelegate = self - progressBarAccessoryView.delegate = self - self.accessoryViews.append(progressBarAccessoryView) - self.shouldAllowCancel = progressBarAccessoryView.isUserInterruptionAllowed - case .image: - guard let media = accessoryView.media, media.image != nil else { return } - let imageAccessoryView = ImageAccessoryView(with: media) - self.popupElementsStackView.insertView(imageAccessoryView, at: 0, in: .center) - case .video: - guard let media = accessoryView.media, media.player != nil else { return } - let videoAccessoryView = VideoAccessoryView(with: media) - videoAccessoryView.delegate = self - self.popupElementsStackView.insertView(videoAccessoryView, at: 0, in: .center) - case .input, .securedinput, .secureinput: - do { - let inputAccessoryView = try InputAccessoryView(with: accessoryView.payload ?? "", isSecure: accessoryView.type == .securedinput || accessoryView.type == .secureinput) - inputAccessoryView.delegate = self - self.popupElementsStackView.insertView(inputAccessoryView, at: 0, in: .center) - self.accessoryViews.append(inputAccessoryView) - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - case .dropdown: - do { - let dropDownAccessoryView = try DropDownAccessoryView(with: accessoryView.payload ?? "") - self.popupElementsStackView.insertView(dropDownAccessoryView, at: 0, in: .center) - dropDownAccessoryView.delegate = self - self.accessoryViews.append(dropDownAccessoryView) - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - case .html: - let htmlAccessoryView = HTMLAccessoryView(withText: accessoryView.payload ?? "", drawsBackground: false) - self.popupElementsStackView.insertView(htmlAccessoryView, at: 0, in: .center) - case .htmlwhitebox: - let htmlAccessoryView = HTMLAccessoryView(withText: accessoryView.payload ?? "", drawsBackground: true) - self.popupElementsStackView.insertView(htmlAccessoryView, at: 0, in: .center) - case .checklist: - do { - let checklistAccessoryView = try CheckListAccessoryView(with: accessoryView.payload ?? "") - self.popupElementsStackView.insertView(checklistAccessoryView, at: 0, in: .center) - checklistAccessoryView.delegate = self - self.accessoryViews.append(checklistAccessoryView) - } catch { - NALogger.shared.log("Error while creating accessory view: %{public}@", [error.localizedDescription]) - } - } - } - - /// Check the stack view distribution based on the number of the arrangedSubviews. - private func checkStackViewLayout() { - if self.accessoryViews.isEmpty { - self.popupElementsStackView.distribution = .fillEqually - } - } - - /// If needed to set a timeout for the popup this method set the related actions and fire a timer. - private func setTimeoutIfNeeded() { - for accessoryView in notificationObject.accessoryViews ?? [] { - guard accessoryView.type != .timer else { return } - } - if let timeoutString = notificationObject?.timeout, let timeout = Int(timeoutString) { - timeoutTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(timeout), - repeats: false, block: { [weak self] _ in - self?.triggerAction(ofType: .timeout) - }) - } - } - - /// If needed to set a pop-up reminder timeout for the user this method set the related actions and fire a timer. - private func setRemindTimerIfNeeded(_ repeated: Bool = false) { - guard let popupReminder = notificationObject.popupReminder, - notificationObject.alwaysOnTop == false else { return } - if repeated { - guard popupReminder.repeatReminder else { return } - } - reminderTimer = Timer.scheduledTimer(withTimeInterval: popupReminder.timeInterval, - repeats: false, block: { [weak self] _ in - self?.view.window?.orderFrontRegardless() - self?.view.window?.setWindowPosition(self?.notificationObject.position ?? .center) - if self?.notificationObject.silent == false && !popupReminder.silent { - NSSound(named: .init("Funk"))?.play() - } - self?.setRemindTimerIfNeeded(true) - }) - } - - private func setInteractiveUpdatesIfNeeded() { - guard notificationObject.accessoryViews?.contains(where: { $0.type == .progressbar }) ?? false || notificationObject.warningButton != nil else { return } - self.interactiveUpdatesObserver = PopupInteractiveEFCLController() - self.interactiveUpdatesObserver?.startObservingStandardInput() - } - - private func checkButtonVisibility() { - var mainButtonState: AccessoryView.ButtonState = .enabled - var secondaryButtonState: AccessoryView.ButtonState = .enabled - - for accessoryView in accessoryViews { - switch accessoryView.mainButtonState { - case .disabled, .hidden: - guard mainButtonState != .hidden else { continue } - mainButtonState = accessoryView.mainButtonState - case .enabled: - continue - } - } - switch mainButtonState { - case .disabled: - self.mainButton.isHidden = false - self.mainButton.isEnabled = false - case .hidden: - self.mainButton.isHidden = true - case .enabled: - self.mainButton.isHidden = false - self.mainButton.isEnabled = true - } - guard notificationObject.secondaryButton != nil else { return } - for accessoryView in accessoryViews { - switch accessoryView.secondaryButtonState { - case .disabled, .hidden: - guard secondaryButtonState != .hidden else { continue } - secondaryButtonState = accessoryView.secondaryButtonState - case .enabled: - break - } - } - switch secondaryButtonState { - case .disabled: - self.secondaryButton.isHidden = false - self.secondaryButton.isEnabled = false - case .hidden: - self.secondaryButton.isHidden = true - case .enabled: - self.secondaryButton.isHidden = false - self.secondaryButton.isEnabled = true - } - } - - /// Invalidate and delete the existing timer. - private func resetTimer() { - timeoutTimer?.invalidate() - timeoutTimer = nil - } - - /// Close the popup window. - private func closeWindow() { - resetTimer() - view.window?.close() - } - - private func triggerAction(ofType type: UserReplyType) { - defer { - DispatchQueue.global(qos: .background).async { - self.replyHandler.handleResponse(ofType: type, for: self.notificationObject) - } - } - switch type { - case .main, .secondary, .timeout: - DispatchQueue.main.async { - self.closeWindow() - } - default: - break - } - } - - private func configureAccessibilityElements() { - self.mainButton.setAccessibilityLabel("\("popup_accessibility_button_main".localized). \(self.mainButton.isEnabled ? "" : "popup_accessibility_button_disabled".localized)") - self.mainButton.setAccessibilityIdentifier("main_button") - self.secondaryButton.setAccessibilityLabel("popup_accessibility_button_secondary".localized) - self.secondaryButton.setAccessibilityIdentifier("secondary_button") - self.tertiaryButton.setAccessibilityLabel("popup_accessibility_button_tertiary".localized) - self.tertiaryButton.setAccessibilityIdentifier("tertiary_button") - self.helpButton.setAccessibilityLabel("popup_accessibility_button_info".localized) - self.helpButton.setAccessibilityIdentifier("help_button") - self.iconView.setAccessibilityLabel("popup_accessibility_image_left".localized) - self.iconView.setAccessibilityIdentifier("popup_icon") - self.popupElementsStackView.setAccessibilityElement(false) - } - - private func printOutputIfAvailable() { - for accessoryView in accessoryViews.reversed() { - switch accessoryView.self { - case is InputAccessoryView: - if let value = (accessoryView as? InputAccessoryView)?.inputValue { - print(value) - } - case is DropDownAccessoryView: - if let value = (accessoryView as? DropDownAccessoryView)?.selectedItem { - print(value) - } - case is CheckListAccessoryView: - if let needsCompletion = (accessoryView as? CheckListAccessoryView)?.needCompletion, - !needsCompletion, - let value = (accessoryView as? CheckListAccessoryView)?.selectedIndexes { - var output = "" - value.forEach({ output += "\($0.description) "}) - print(output.trimmingCharacters(in: .whitespaces)) - } - default: - break - } - } - } - - // MARK: - Actions - - /// User clicked the main button. - @IBAction func didClickedMainButton(_ sender: NSButton) { - self.printOutputIfAvailable() - self.triggerAction(ofType: shouldAllowCancel ? .cancel : .main) - } - - /// User clicked the secondary button. - @IBAction func didClickedSecondaryButton(_ sender: NSButton) { - if self.notificationObject.retainValues ?? false { - self.printOutputIfAvailable() - } - self.triggerAction(ofType: .secondary) - } - - /// User clicked the tertiary button. - @IBAction func didClickedTertiaryButton(_ sender: NSButton) { - self.triggerAction(ofType: .tertiary) - } - - /// User clicked the help button. - @IBAction func didClickedHelpButton(_ sender: NSButton) { - guard let helpButtonObject = notificationObject?.helpButton else { return } - switch helpButtonObject.callToActionType { - case .infopopup: - let infos = InfoSection(fields: [InfoField(label: helpButtonObject.callToActionPayload)]) - let infoPopupViewController = InfoPopOverViewController(with: infos) - self.present(infoPopupViewController, - asPopoverRelativeTo: sender.convert(sender.bounds, to: self.view), - of: self.view, - preferredEdge: .minY, - behavior: .transient) - default: - self.triggerAction(ofType: .help) - } - } - - /// User clicked the help button. - @IBAction func didClickedWarningButton(_ sender: NSButton) { - guard let warningButtonObject = notificationObject?.warningButton else { return } - switch warningButtonObject.callToActionType { - case .infopopup: - if warningPopoverViewController != nil { - guard warningPopoverViewController.presentingViewController == nil else { - return - } - } - let infos = InfoSection(fields: [InfoField(label: warningButtonObject.callToActionPayload)]) - warningPopoverViewController = InfoPopOverViewController(with: infos) - self.present(warningPopoverViewController, - asPopoverRelativeTo: sender.convert(sender.bounds, to: self.view), - of: self.view, - preferredEdge: .minY, - behavior: .transient) - default: - self.triggerAction(ofType: .warning) - } - } -} - -// MARK: - TimerAccessoryViewDelegate methods implementation. -extension PopUpViewController: TimerAccessoryViewDelegate { - func timerDidFinished(_ sender: TimerAccessoryView) { - self.triggerAction(ofType: .timeout) - } -} - -// MARK: - ProgressBarAccessoryViewDelegate methods implementation. -extension PopUpViewController: ProgressBarAccessoryViewDelegate { - func didChangeEstimation(_ isIndeterminated: Bool) { - if isIndeterminated { - self.mainButton.title = notificationObject.mainButton.label - self.secondaryButton.isHidden = notificationObject.secondaryButton != nil ? false : true - } else { - self.mainButton.title = "cancel_label".localized - self.secondaryButton.isHidden = true - } - } -} - -// MARK: - AccessoryViewDelegate methods implementation. -extension PopUpViewController: AccessoryViewDelegate { - func accessoryViewStatusDidChange(_ sender: AccessoryView) { - self.timeoutTimer?.invalidate() - self.setTimeoutIfNeeded() - if (sender as? ProgressBarAccessoryView)?.progressCompleted ?? false, shouldAllowCancel { - self.shouldAllowCancel = false - } else { - self.shouldAllowCancel = (sender as? ProgressBarAccessoryView)?.isUserInterruptionAllowed ?? false - } - checkButtonVisibility() - } -} - -// MARK: - DynamicNotificationButtonDelegate methods implementation. -extension PopUpViewController: DynamicNotificationButtonDelegate { - func didReceivedNewStateForWarningButton(_ isVisible: Bool, isExpanded: Bool) { - let queue = OperationQueue.main - let visibilityOperation = BlockOperation { - self.warningButton.isHidden = !isVisible - } - queue.addOperation(visibilityOperation) - if isExpanded { - let expandOperation = BlockOperation { - self.didClickedWarningButton(self.warningButton) - } - queue.addOperation(expandOperation) - } else { - if self.warningPopoverViewController?.presentingViewController != nil { - guard !isVisible else { return } - let dismissOperation = BlockOperation { - self.warningPopoverViewController.dismiss(nil) - } - queue.addOperation(dismissOperation) - } - } - - } -} diff --git a/Notification Agent Popups/Views/PopUpViewModel.swift b/Notification Agent Popups/Views/PopUpViewModel.swift new file mode 100644 index 0000000..3f48086 --- /dev/null +++ b/Notification Agent Popups/Views/PopUpViewModel.swift @@ -0,0 +1,401 @@ +// +// PopUpViewModel.swift +// Notification Agent +// +// Created by Simone Martorelli on 04/11/22. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// +// swiftlint:disable type_body_length file_length + +import SwiftUI +import Combine +import SwiftyMarkdown + +/// The PopUpViewModel class define a view model for the popup UI view. +/// It include all the methods and the logics to handle the popup UI workflows. +class PopUpViewModel: ObservableObject { + + // MARK: - Constants + + let notificationObject: NotificationObject + let window: NSWindow + + // MARK: - Computed properties + + var bodyLabelsAccessibilityValue: String { + var value = "" + if let title = notificationObject.title { + value.append("Title of the window: \(title)\n") + } + if let subtitle = notificationObject.subtitle { + value.append("Subtitle of the window: \(SwiftyMarkdown(string: subtitle).attributedString().string)") + } + return value + } + var titleFont: Font { + if let fontSizeString = notificationObject.titleFontSize, + let fontSize = NumberFormatter().number(from: fontSizeString) { + return .system(size: CGFloat(truncating: fontSize), weight: .bold) + } + return .system(size: NSFont.systemFontSize, weight: .bold) + } + var customPopupIcon: NSImage? { + if let iconPath = notificationObject.iconPath { + if FileManager.default.fileExists(atPath: iconPath), + let data = try? Data(contentsOf: URL(fileURLWithPath: iconPath)) { + let image = NSImage(data: data) + return image + } else if iconPath.isValidURL, + let url = URL(string: iconPath), + let data = try? Data(contentsOf: url) { + let image = NSImage(data: data) + return image + } else if let imageData = Data(base64Encoded: iconPath, options: .ignoreUnknownCharacters), + let image = NSImage(data: imageData) { + return image + } else if let image = NSImage(systemSymbolName: iconPath, accessibilityDescription: iconPath) { + return image + } else { + NALogger.shared.log("Unable to load image from %{public}@", [iconPath]) + } + } + return nil + } + var iconSize: CGSize { + if let widthString = notificationObject.iconWidth, + let width = NumberFormatter().number(from: widthString), + let heightString = notificationObject.iconHeight, + let height = NumberFormatter().number(from: heightString) { + return CGSize(width: CGFloat(truncating: width), height: CGFloat(truncating: height)) + } else { + return CGSize(width: 60, height: 60) + } + } + + // MARK: - Variables + + var warningButton: DynamicNotificationButton? + var primaryAccessoryView: AccessoryViewSource? + var secondaryAccessoryView: AccessoryViewSource? + var replyHandler = ReplyHandler.shared + var helpButtonState: SwiftUIButtonState + var tertiaryButtonState: SwiftUIButtonState + var interactiveUpdatesObserver: PopupInteractiveEFCLController? + var mainButtonStatuses: [Binding] = [] + var secondaryButtonStatuses: [Binding] = [] + var reminderTimer: Timer? + var timeoutTimer: Timer? + var countDown: Int = 0 + + // MARK: - Published Variables + + @Published var mainButtonState: SwiftUIButtonState + @Published var secondaryButtonState: SwiftUIButtonState + @Published var warningButtonState: SwiftUIButtonState + + @Published var primaryAVOutput: String = "" + @Published var primaryAVInput: String = "" + @Published var secondaryAVOutput: String = "" + @Published var secondaryAVInput: String = "" + @Published var primaryAVMainButtonState: SwiftUIButtonState = .enabled + @Published var primaryAVSecButtonState: SwiftUIButtonState = .enabled + @Published var secondaryAVMainButtonState: SwiftUIButtonState = .enabled + @Published var secondaryAVSecButtonState: SwiftUIButtonState = .enabled + + @Published var showHelpButtonPopover: Bool = false + @Published var showWarningButtonPopover: Bool = false + + @Published var mainButton: NotificationButton + + // MARK: - Initializers + + init(_ notificationObject: NotificationObject, window: NSWindow) { + self.notificationObject = notificationObject + self.window = window + mainButton = notificationObject.mainButton + mainButtonState = .enabled + secondaryButtonState = notificationObject.secondaryButton != nil ? .enabled : .hidden + tertiaryButtonState = notificationObject.tertiaryButton != nil ? .enabled : .hidden + helpButtonState = notificationObject.helpButton != nil ? .enabled : .hidden + warningButtonState = notificationObject.warningButton?.isVisible ?? false ? .enabled : .hidden + + NotificationCenter.default.addObserver(self, selector: #selector(repositionWindow), name: NSApplication.didChangeScreenParametersNotification, object: nil) + + if let warningButton = notificationObject.warningButton { + warningButton.delegate = self + warningButton.startObservingForUpdates() + self.warningButton = warningButton + } + setupAccessoryViews() + setRemindTimerIfNeeded() + setInteractiveUpdatesIfNeeded() + } + + // MARK: - Public Methods + + /// React to the user action on the dialog's buttons. + /// - Parameter type: the user action. + @MainActor + func didClickButton(of type: UserReplyType) { + switch type { + case .main: + printOutputIfAvailable() + triggerAction(ofType: mainButtonState == .cancel ? .cancel : .main) + case .secondary: + if let retainValues = notificationObject.retainValues, retainValues { + printOutputIfAvailable() + } + triggerAction(ofType: .secondary) + case .tertiary: + triggerAction(ofType: .tertiary) + case .help: + switch notificationObject.helpButton?.callToActionType ?? .infopopup { + case .infopopup: + showHelpButtonPopover.toggle() + case .link: + triggerAction(ofType: .help) + default: + break + } + case .warning: + switch notificationObject.warningButton?.callToActionType ?? .infopopup { + case .infopopup: + showWarningButtonPopover.toggle() + case .link: + triggerAction(ofType: .warning) + default: + break + } + default: + break + } + } + + /// Validate the destructive buttons appearance on the dialog. + func checkDestructiveButtonVisibility() { + mainButtonState = { + guard primaryAccessoryView != nil else { return .enabled } + guard secondaryAccessoryView != nil else { return primaryAVMainButtonState } + switch primaryAVMainButtonState { + case .enabled: + return secondaryAVMainButtonState + case .disabled: + return secondaryAVMainButtonState != .hidden ? .disabled : .hidden + case .hidden: + return .hidden + case .cancel: + return .cancel + } + }() + guard notificationObject.secondaryButton != nil else { return } + secondaryButtonState = { + guard primaryAccessoryView != nil else { return .enabled } + guard secondaryAccessoryView != nil else { return primaryAVSecButtonState } + switch primaryAVSecButtonState { + case .enabled: + return secondaryAVSecButtonState + case .disabled: + return secondaryAVSecButtonState != .hidden ? .disabled : .hidden + case .hidden: + return .hidden + case .cancel: + return .hidden + } + }() + } + + // MARK: - Private Methods + + /// This method wrap and bind the accessory views - if present - with the view model + /// in order to be able to react to changes occurred in those accessory views. + private func setupAccessoryViews() { + if let avs = notificationObject.accessoryViews, avs.count > 0 { + if avs[0].type == .dropdown { + primaryAVOutput = "-1" + } + primaryAccessoryView = AccessoryViewSource(output: Binding(get: { + return self.primaryAVOutput + }, set: { newValue, _ in + self.primaryAVOutput = newValue + self.checkDestructiveButtonVisibility() + }), mainButtonState: Binding(get: { + return self.primaryAVMainButtonState + }, set: { newValue, _ in + self.primaryAVMainButtonState = newValue + self.checkDestructiveButtonVisibility() + }), secondaryButtonState: Binding(get: { + return self.primaryAVSecButtonState + }, set: { newValue, _ in + self.primaryAVSecButtonState = newValue + self.checkDestructiveButtonVisibility() + }), accessoryView: avs[0]) + if avs.count > 1 { + if avs[1].type == .dropdown { + secondaryAVOutput = "-1" + } + secondaryAccessoryView = AccessoryViewSource(output: Binding(get: { + return self.secondaryAVOutput + }, set: { newValue, _ in + self.secondaryAVOutput = newValue + self.checkDestructiveButtonVisibility() + }), mainButtonState: Binding(get: { + return self.secondaryAVMainButtonState + }, set: { newValue, _ in + self.secondaryAVMainButtonState = newValue + self.checkDestructiveButtonVisibility() + }), secondaryButtonState: Binding(get: { + return self.secondaryAVSecButtonState + }, set: { newValue, _ in + self.secondaryAVSecButtonState = newValue + self.checkDestructiveButtonVisibility() + }), accessoryView: avs[1]) + } + } + setTimeoutIfNeeded() + } + + /// Send the relative user action to the replyHandler. + /// - Parameter type: the user action. + private func triggerAction(ofType type: UserReplyType) { + self.replyHandler.handleResponse(ofType: type, for: self.notificationObject) + switch type { + case .main, .secondary, .timeout: + DispatchQueue.main.async { + self.closeWindow() + } + default: + break + } + } + + /// Close the popup window. + private func closeWindow() { + resetTimers() + window.close() + } + + /// Invalidate and delete the existing timer. + private func resetTimers() { + reminderTimer?.invalidate() + reminderTimer = nil + timeoutTimer?.invalidate() + timeoutTimer = nil + } + + /// If needed to set a timeout for the popup this method set the related actions and fire a timer. + private func setTimeoutIfNeeded() { + if let timeoutString = notificationObject.timeout, let timeout = Int(timeoutString) { + countDown = timeout + if let accv = notificationObject.accessoryViews?[safe: 0], let payload = accv.payload, accv.type == .timer { + primaryAVInput = String.init(format: payload, arguments: [timeout.timeFormattedString]) + timeoutTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in + self.updateCountdown() + self.primaryAVInput = String(format: payload, arguments: [self.countDown.timeFormattedString]) + }) + return + } + if let accv = notificationObject.accessoryViews?[safe: 1], let payload = accv.payload, accv.type == .timer { + secondaryAVInput = String.init(format: payload, arguments: [timeout.timeFormattedString]) + timeoutTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in + self.updateCountdown() + self.secondaryAVInput = String(format: payload, arguments: [self.countDown.timeFormattedString]) + }) + return + } + timeoutTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(timeout), + repeats: false, block: { [weak self] _ in + self?.printOutputIfAvailable() + self?.triggerAction(ofType: .timeout) + }) + } + } + + /// Update the countdown value. + private func updateCountdown() { + guard countDown >= 2 else { + timeoutTimer?.invalidate() + timeoutTimer = nil + countDown = 0 + printOutputIfAvailable() + triggerAction(ofType: .timeout) + return + } + countDown -= 1 + } + + /// If needed to set a pop-up reminder timeout for the user this method set the related actions and fire a timer. + private func setRemindTimerIfNeeded(_ repeated: Bool = false) { + guard let popupReminder = notificationObject.popupReminder, + notificationObject.alwaysOnTop == false else { return } + if repeated { + guard popupReminder.repeatReminder else { return } + } + reminderTimer = Timer.scheduledTimer(withTimeInterval: popupReminder.timeInterval, + repeats: false, block: { [weak self] _ in + self?.window.orderFrontRegardless() + self?.window.setWindowPosition(self?.notificationObject.position ?? .center) + if self?.notificationObject.silent == false && !popupReminder.silent { + NSSound(named: .init("Funk"))?.play() + } + self?.setRemindTimerIfNeeded(true) + }) + } + + private func setInteractiveUpdatesIfNeeded() { + guard notificationObject.accessoryViews?.contains(where: { $0.type == .progressbar }) ?? false || notificationObject.warningButton != nil else { return } + interactiveUpdatesObserver = PopupInteractiveEFCLController() + interactiveUpdatesObserver?.startObservingStandardInput() + } + + private func printOutputIfAvailable() { + guard let primaryAccessoryView = self.primaryAccessoryView else { return } + switch primaryAccessoryView.accessoryView.type { + case .checklist: + if let payload = primaryAccessoryView.accessoryView.payload, + !payload.localizedStandardContains("/complete") && !primaryAVOutput.isEmpty && !(primaryAVOutput == "-1") { + print(primaryAVOutput) + } + case .input, .secureinput, .securedinput, .dropdown, .datepicker : + if !primaryAVOutput.isEmpty { + print(primaryAVOutput) + } + default: + break + } + guard let secondaryAccessoryView = self.secondaryAccessoryView else { return } + switch secondaryAccessoryView.accessoryView.type { + case .checklist: + if let payload = secondaryAccessoryView.accessoryView.payload, + !payload.localizedStandardContains("/complete") && !secondaryAVOutput.isEmpty && !(secondaryAVOutput == "-1") { + print(secondaryAVOutput) + } + case .input, .secureinput, .securedinput, .dropdown, .datepicker : + if !secondaryAVOutput.isEmpty { + print(secondaryAVOutput) + } + default: + break + } + } + + @objc + private func repositionWindow() { + self.window.setWindowPosition(notificationObject.position ?? .center) + } +} + +// MARK: - DynamicNotificationButtonDelegate methods implementation. +extension PopUpViewModel: DynamicNotificationButtonDelegate { + @MainActor + func didReceivedNewStateForWarningButton(_ isVisible: Bool, isExpanded: Bool) { + if self.warningButtonState != (isVisible ? .enabled : .hidden) { + self.warningButtonState = isVisible ? .enabled : .hidden + } + if self.showWarningButtonPopover != isExpanded { + self.showWarningButtonPopover = isExpanded + } + } +} + +// swiftlint:enable type_body_length file_length diff --git a/Notification Agent.xcodeproj/project.pbxproj b/Notification Agent.xcodeproj/project.pbxproj index ac56674..1fb892d 100644 --- a/Notification Agent.xcodeproj/project.pbxproj +++ b/Notification Agent.xcodeproj/project.pbxproj @@ -7,6 +7,170 @@ objects = { /* Begin PBXBuildFile section */ + 1B5AFAEF2A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF02A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF12A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF22A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF32A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF42A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */; }; + 1B5AFAF62A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAF72A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAF82A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAF92A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAFA2A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAFB2A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAFC2A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFAFD2A52BD2F00F777E1 /* BackPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */; }; + 1B5AFB012A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */; }; + 1B5AFB022A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */; }; + 1B5AFB052A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */; }; + 1B5AFB062A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */; }; + 1B5AFB082A52ED8900F777E1 /* ACVDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */; }; + 1B5AFB092A52ED8900F777E1 /* ACVDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */; }; + 1B5AFB0A2A52ED8900F777E1 /* ACVDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */; }; + 1B5AFB0B2A52ED8900F777E1 /* ACVDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */; }; + 1B5AFB0D2A52EDB300F777E1 /* PickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */; }; + 1B5AFB0E2A52EDB300F777E1 /* PickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */; }; + 1B5AFB0F2A52EDB300F777E1 /* PickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */; }; + 1B5AFB102A52EDB300F777E1 /* PickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */; }; + 1B5AFB2E2A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */; }; + 1B5AFB2F2A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */; }; + 1B5AFB302A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */; }; + 1B5AFB312A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */; }; + 1B5AFB322A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */; }; + 1B5AFB332A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */; }; + 1B5AFB342A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */; }; + 1B5AFB352A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */; }; + 1B5AFB362A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */; }; + 1B5AFB372A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */; }; + 1B5AFB382A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */; }; + 1B5AFB392A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */; }; + 1B5AFB3A2A52EE3C00F777E1 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */; }; + 1B5AFB3B2A52EE3C00F777E1 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */; }; + 1B5AFB3C2A52EE3C00F777E1 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */; }; + 1B5AFB3D2A52EE3C00F777E1 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */; }; + 1B5AFB3E2A52EE3C00F777E1 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */; }; + 1B5AFB3F2A52EE3C00F777E1 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */; }; + 1B5AFB402A52EE3C00F777E1 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */; }; + 1B5AFB412A52EE3C00F777E1 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */; }; + 1B5AFB422A52EE3C00F777E1 /* HTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */; }; + 1B5AFB432A52EE3C00F777E1 /* HTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */; }; + 1B5AFB442A52EE3C00F777E1 /* HTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */; }; + 1B5AFB452A52EE3C00F777E1 /* HTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */; }; + 1B5AFB462A52EE3C00F777E1 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */; }; + 1B5AFB472A52EE3C00F777E1 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */; }; + 1B5AFB482A52EE3C00F777E1 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */; }; + 1B5AFB492A52EE3C00F777E1 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */; }; + 1B5AFB4A2A52EE3C00F777E1 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */; }; + 1B5AFB4B2A52EE3C00F777E1 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */; }; + 1B5AFB4C2A52EE3C00F777E1 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */; }; + 1B5AFB4D2A52EE3C00F777E1 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */; }; + 1B5AFB4E2A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */; }; + 1B5AFB4F2A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */; }; + 1B5AFB502A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */; }; + 1B5AFB512A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */; }; + 1B5AFB522A52EE3C00F777E1 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */; }; + 1B5AFB532A52EE3C00F777E1 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */; }; + 1B5AFB542A52EE3C00F777E1 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */; }; + 1B5AFB552A52EE3C00F777E1 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */; }; + 1B5AFB562A52EE3C00F777E1 /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */; }; + 1B5AFB572A52EE3C00F777E1 /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */; }; + 1B5AFB582A52EE3C00F777E1 /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */; }; + 1B5AFB592A52EE3C00F777E1 /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */; }; + 1B5AFB5A2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */; }; + 1B5AFB5B2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */; }; + 1B5AFB5C2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */; }; + 1B5AFB5D2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */; }; + 1B5AFB5E2A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */; }; + 1B5AFB5F2A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */; }; + 1B5AFB602A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */; }; + 1B5AFB612A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */; }; + 1B5AFB622A52EE3C00F777E1 /* DatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */; }; + 1B5AFB632A52EE3C00F777E1 /* DatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */; }; + 1B5AFB642A52EE3C00F777E1 /* DatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */; }; + 1B5AFB652A52EE3C00F777E1 /* DatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */; }; + 1B5AFB662A52EE3C00F777E1 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */; }; + 1B5AFB672A52EE3C00F777E1 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */; }; + 1B5AFB682A52EE3C00F777E1 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */; }; + 1B5AFB692A52EE3C00F777E1 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */; }; + 1B5AFB6B2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB6C2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB6D2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB6E2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB6F2A52EEBC00F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB702A52EEBD00F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB712A52EEBE00F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB722A52EEBE00F777E1 /* BackPanelWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */; }; + 1B5AFB7A2A52EEDF00F777E1 /* CircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */; }; + 1B5AFB7B2A52EEDF00F777E1 /* CircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */; }; + 1B5AFB7C2A52EEDF00F777E1 /* CircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */; }; + 1B5AFB7D2A52EEDF00F777E1 /* CircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */; }; + 1B5AFB7E2A52EEDF00F777E1 /* StandardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */; }; + 1B5AFB7F2A52EEDF00F777E1 /* StandardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */; }; + 1B5AFB802A52EEDF00F777E1 /* StandardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */; }; + 1B5AFB812A52EEDF00F777E1 /* StandardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */; }; + 1B5AFB822A52EEDF00F777E1 /* NativeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */; }; + 1B5AFB832A52EEDF00F777E1 /* NativeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */; }; + 1B5AFB842A52EEDF00F777E1 /* NativeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */; }; + 1B5AFB852A52EEDF00F777E1 /* NativeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */; }; + 1B5AFB862A52EEDF00F777E1 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB782A52EEDF00F777E1 /* Icon.swift */; }; + 1B5AFB872A52EEDF00F777E1 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB782A52EEDF00F777E1 /* Icon.swift */; }; + 1B5AFB882A52EEDF00F777E1 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB782A52EEDF00F777E1 /* Icon.swift */; }; + 1B5AFB892A52EEDF00F777E1 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB782A52EEDF00F777E1 /* Icon.swift */; }; + 1B5AFB8A2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */; }; + 1B5AFB8B2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */; }; + 1B5AFB8C2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */; }; + 1B5AFB8D2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */; }; + 1B5AFB932A52EF9500F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB942A52EF9500F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB952A52EF9500F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB962A52EF9500F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB972A52EF9A00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB982A52EF9B00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB992A52EF9B00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB9A2A52EF9C00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB9B2A52EF9C00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB9C2A52EF9C00F777E1 /* View-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */; }; + 1B5AFB9E2A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFB9F2A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA02A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA12A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA22A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA32A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA42A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA52A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA62A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA72A52EFC200F777E1 /* Collection-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */; }; + 1B5AFBA92A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAA2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAB2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAC2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAD2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAE2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBAF2A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBB02A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBB12A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBB22A52EFE600F777E1 /* Binding-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */; }; + 1B5AFBB52A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBB62A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBB72A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBB82A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBB92A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBBA2A52F03F00F777E1 /* ActionTrampoline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */; }; + 1B5AFBBC2A52FBA700F777E1 /* BodyLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBBB2A52FBA700F777E1 /* BodyLabels.swift */; }; + 1B5AFBBE2A52FBA700F777E1 /* BodyLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBBB2A52FBA700F777E1 /* BodyLabels.swift */; }; + 1B5AFBC02A52FBB900F777E1 /* PopUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBBF2A52FBB900F777E1 /* PopUpView.swift */; }; + 1B5AFBC22A52FBB900F777E1 /* PopUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBBF2A52FBB900F777E1 /* PopUpView.swift */; }; + 1B5AFBC42A52FBD800F777E1 /* PopUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC32A52FBD800F777E1 /* PopUpViewModel.swift */; }; + 1B5AFBC52A52FBD800F777E1 /* PopUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC32A52FBD800F777E1 /* PopUpViewModel.swift */; }; + 1B5AFBC72A52FC5C00F777E1 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC62A52FC5C00F777E1 /* OnboardingView.swift */; }; + 1B5AFBC82A52FC5C00F777E1 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC62A52FC5C00F777E1 /* OnboardingView.swift */; }; + 1B5AFBCA2A52FC7100F777E1 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC92A52FC7100F777E1 /* OnboardingViewModel.swift */; }; + 1B5AFBCB2A52FC7100F777E1 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBC92A52FC7100F777E1 /* OnboardingViewModel.swift */; }; + 1B5AFBCD2A52FC8400F777E1 /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBCC2A52FC8400F777E1 /* PageView.swift */; }; + 1B5AFBCE2A52FC8400F777E1 /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBCC2A52FC8400F777E1 /* PageView.swift */; }; + 1B5AFBD02A52FC9800F777E1 /* PageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBCF2A52FC9800F777E1 /* PageViewModel.swift */; }; + 1B5AFBD12A52FC9800F777E1 /* PageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5AFBCF2A52FC9800F777E1 /* PageViewModel.swift */; }; B9013A8D24F80C0F009A4554 /* HelpBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9013A8C24F80C0F009A4554 /* HelpBuilder.swift */; }; B9064C0D276BAF240085FA31 /* PopupReminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9064C0C276BAF240085FA31 /* PopupReminder.swift */; }; B9064C0E276BAF240085FA31 /* PopupReminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9064C0C276BAF240085FA31 /* PopupReminder.swift */; }; @@ -27,7 +191,6 @@ B920D4DE265F90D800437BE6 /* NotificationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A03AA224B779F5005BB90E /* NotificationButton.swift */; }; B920D4E0265F90E200437BE6 /* NAMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92D021B25ED50900076C452 /* NAMedia.swift */; }; B920D4E1265F90E200437BE6 /* LoadableNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */; }; - B920D4E2265F90E200437BE6 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; B920D4E5265F90E200437BE6 /* NAError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E98E8A24BCA983002FAABD /* NAError.swift */; }; B920D4E6265F90E200437BE6 /* InfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98D24EC0D91004F6108 /* InfoSection.swift */; }; B920D4E7265F90EB00437BE6 /* Notification-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E98E8424B887A2002FAABD /* Notification-Extension.swift */; }; @@ -88,7 +251,6 @@ B961A4AF2673676000657930 /* NAMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92D021B25ED50900076C452 /* NAMedia.swift */; }; B961A4B02673676000657930 /* LoadableNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */; }; B961A4B22673676000657930 /* ProgressState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92E6707253893D900F39949 /* ProgressState.swift */; }; - B961A4B32673676000657930 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; B961A4B42673676000657930 /* NALogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9013A8A24F7E5E5009A4554 /* NALogger.swift */; }; B961A4B52673676000657930 /* ReplyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96AE19524F52A2300C99A5E /* ReplyHandler.swift */; }; B961A4B62673677C00657930 /* NSScreen-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B985DAC9263854EA004F3470 /* NSScreen-Extension.swift */; }; @@ -121,22 +283,7 @@ B961A51D267A4D7D00657930 /* NAMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92D021B25ED50900076C452 /* NAMedia.swift */; }; B961A51E267A4D7F00657930 /* LoadableNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */; }; B961A51F267A4D8200657930 /* InfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98D24EC0D91004F6108 /* InfoSection.swift */; }; - B961A522267A4D8F00657930 /* CheckListAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */; }; - B961A523267A4D8F00657930 /* ImageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */; }; - B961A524267A4D8F00657930 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96172E92518E9370042F0CF /* MarkdownTextView.swift */; }; - B961A525267A4D8F00657930 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */; }; - B961A526267A4D8F00657930 /* InputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CA46A125516523001AA69B /* InputAccessoryView.swift */; }; - B961A527267A4D8F00657930 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389EC26284472008E314A /* HTMLAccessoryView.swift */; }; - B961A528267A4D8F00657930 /* ProgressBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */; }; - B961A529267A4D8F00657930 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E4926403443006BB5C6 /* AccessoryView.swift */; }; - B961A52A267A4D8F00657930 /* TimerAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */; }; - B961A52B267A4D8F00657930 /* DropDownAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */; }; - B961A52C267A4D9B00657930 /* InfoPopOverViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */; }; - B961A52D267A4D9B00657930 /* InfoPopOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */; }; - B961A52E267A4D9C00657930 /* InfoPopOverStackItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */; }; - B961A52F267A4D9C00657930 /* InfoPopOverStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */; }; B961A531267A4DC700657930 /* FlippedStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E46263DAEE5006BB5C6 /* FlippedStackView.swift */; }; - B961A532267A4DC700657930 /* PopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47F553B249B8C1B006A0754 /* PopUpViewController.swift */; }; B961A533267A4DC700657930 /* HorizontalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98F24EC0DAD004F6108 /* HorizontalLine.swift */; }; B961A534267A4DC700657930 /* NoBackgroundScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E43263C858E006BB5C6 /* NoBackgroundScroller.swift */; }; B961A535267A4DD600657930 /* NSScreen-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B985DAC9263854EA004F3470 /* NSScreen-Extension.swift */; }; @@ -159,17 +306,12 @@ B961A5A7267B44B400657930 /* Decodable-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8855525BB404000081C8D /* Decodable-Extensions.swift */; }; B961A5A8267B44B400657930 /* NSColor-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389F1262889FB008E314A /* NSColor-Extension.swift */; }; B961A5A9267B44B400657930 /* NSScreen-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B985DAC9263854EA004F3470 /* NSScreen-Extension.swift */; }; - B961A5B0267B44E000657930 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C42525C83D11008EDCFD /* OnboardingViewController.swift */; }; - B961A5B1267B44E000657930 /* OnboardingPageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9A8854825B9C68200081C8D /* OnboardingPageViewController.xib */; }; - B961A5B2267B44E000657930 /* OnboardingPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8854725B9C68200081C8D /* OnboardingPageViewController.swift */; }; - B961A5B3267B44E000657930 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9C8C42625C83D11008EDCFD /* OnboardingViewController.xib */; }; B961A5B6267B451F00657930 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B961A5B4267B451400657930 /* Main.storyboard */; }; B961A5B7267B457D00657930 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DC84602639C26B00D2BBA6 /* Environment.swift */; }; B961A5B9267B458C00657930 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BC85FA2626D802000C9DBD /* Context.swift */; }; B961A5CF267B84C800657930 /* ReplyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96AE19524F52A2300C99A5E /* ReplyHandler.swift */; }; B961A5D1267B89AC00657930 /* NALogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9013A8A24F7E5E5009A4554 /* NALogger.swift */; }; B961A5D3267B89CE00657930 /* OnboardingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8852125B9B10600081C8D /* OnboardingData.swift */; }; - B961A5D4267B89CE00657930 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; B961A5D6267B89CE00657930 /* NAError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E98E8A24BCA983002FAABD /* NAError.swift */; }; B961A5D7267B89CE00657930 /* NotificationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A03A9A24B76511005BB90E /* NotificationObject.swift */; }; B961A5D8267B89CE00657930 /* ProgressState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92E6707253893D900F39949 /* ProgressState.swift */; }; @@ -180,15 +322,6 @@ B961A5DE267B89CE00657930 /* ConfigurableParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D58830250A1410009F93C0 /* ConfigurableParameters.swift */; }; B961A5DF267B89CE00657930 /* NotificationAccessoryElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A03A9E24B779B8005BB90E /* NotificationAccessoryElement.swift */; }; B961A5E1267B89F900657930 /* ReplyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96AE19524F52A2300C99A5E /* ReplyHandler.swift */; }; - B961A5E2267B8A1500657930 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */; }; - B961A5E4267B8A1500657930 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E4926403443006BB5C6 /* AccessoryView.swift */; }; - B961A5E6267B8A1500657930 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389EC26284472008E314A /* HTMLAccessoryView.swift */; }; - B961A5E7267B8A1500657930 /* ImageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */; }; - B961A5EB267B8A1500657930 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96172E92518E9370042F0CF /* MarkdownTextView.swift */; }; - B961A5EC267B8A1F00657930 /* InfoPopOverStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */; }; - B961A5ED267B8A1F00657930 /* InfoPopOverStackItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */; }; - B961A5EE267B8A1F00657930 /* InfoPopOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */; }; - B961A5EF267B8A1F00657930 /* InfoPopOverViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */; }; B961A5F0267B8A2600657930 /* FlippedStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E46263DAEE5006BB5C6 /* FlippedStackView.swift */; }; B961A5F1267B8A2600657930 /* HorizontalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98F24EC0DAD004F6108 /* HorizontalLine.swift */; }; B961A5F2267B8A2600657930 /* NoBackgroundScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E43263C858E006BB5C6 /* NoBackgroundScroller.swift */; }; @@ -237,7 +370,6 @@ B9C7118C2681DC2800A3E898 /* SharedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C711882681DC2800A3E898 /* SharedSettings.swift */; }; B9C7118D2681DC2800A3E898 /* SharedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C711882681DC2800A3E898 /* SharedSettings.swift */; }; B9C7A9CA24D16F790038D4A7 /* NotificationDispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C7A9C924D16F790038D4A7 /* NotificationDispatch.swift */; }; - B9C8C41C25C828A7008EDCFD /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; B9D58831250A1410009F93C0 /* ConfigurableParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D58830250A1410009F93C0 /* ConfigurableParameters.swift */; }; B9DA1199267CA08000A38B3B /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DA1198267CA08000A38B3B /* Claims.swift */; }; B9DC84612639C26B00D2BBA6 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DC84602639C26B00D2BBA6 /* Environment.swift */; }; @@ -273,11 +405,6 @@ FD17301C2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1730142875B9A000781A69 /* InteractiveObjectProtocol.swift */; }; FD17301D2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1730142875B9A000781A69 /* InteractiveObjectProtocol.swift */; }; FD17301E2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1730142875B9A000781A69 /* InteractiveObjectProtocol.swift */; }; - FD18021B27E1DA8C00FC995A /* CheckListAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */; }; - FD18021C27E1DA8E00FC995A /* TimerAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */; }; - FD18021D27E1DA9300FC995A /* ProgressBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */; }; - FD18021E27E1DA9A00FC995A /* DropDownAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */; }; - FD18021F27E1DA9A00FC995A /* InputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CA46A125516523001AA69B /* InputAccessoryView.swift */; }; FD18022027E1DF3E00FC995A /* InteractiveEFCLContoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A624267C7DFB00657930 /* InteractiveEFCLContoller.swift */; }; FD8A353D2863582600DA72CC /* EFCLController-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94BDAF026863C7E00663D21 /* EFCLController-Extension.swift */; }; FD8A353E2863582700DA72CC /* EFCLController-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94BDAF026863C7E00663D21 /* EFCLController-Extension.swift */; }; @@ -379,37 +506,6 @@ FDC346EC2858C459001F2212 /* NAMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92D021B25ED50900076C452 /* NAMedia.swift */; }; FDC346ED2858C459001F2212 /* AppComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9360EBF268A08FE00217247 /* AppComponent.swift */; }; FDC346EE2858C459001F2212 /* NAError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E98E8A24BCA983002FAABD /* NAError.swift */; }; - FDC346EF2858C463001F2212 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; - FDC346F02858C464001F2212 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; - FDC346F12858C465001F2212 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; - FDC346F22858C472001F2212 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */; }; - FDC346F32858C472001F2212 /* ProgressBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */; }; - FDC346F42858C472001F2212 /* ImageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */; }; - FDC346F52858C472001F2212 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96172E92518E9370042F0CF /* MarkdownTextView.swift */; }; - FDC346F62858C472001F2212 /* TimerAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */; }; - FDC346F72858C472001F2212 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389EC26284472008E314A /* HTMLAccessoryView.swift */; }; - FDC346F82858C472001F2212 /* CheckListAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */; }; - FDC346F92858C472001F2212 /* DropDownAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */; }; - FDC346FA2858C472001F2212 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E4926403443006BB5C6 /* AccessoryView.swift */; }; - FDC346FB2858C472001F2212 /* InputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CA46A125516523001AA69B /* InputAccessoryView.swift */; }; - FDC346FC2858C472001F2212 /* VideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */; }; - FDC346FD2858C472001F2212 /* ProgressBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */; }; - FDC346FE2858C472001F2212 /* ImageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */; }; - FDC346FF2858C472001F2212 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96172E92518E9370042F0CF /* MarkdownTextView.swift */; }; - FDC347002858C472001F2212 /* TimerAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */; }; - FDC347012858C472001F2212 /* HTMLAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389EC26284472008E314A /* HTMLAccessoryView.swift */; }; - FDC347022858C472001F2212 /* CheckListAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */; }; - FDC347032858C472001F2212 /* DropDownAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */; }; - FDC347042858C472001F2212 /* AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E4926403443006BB5C6 /* AccessoryView.swift */; }; - FDC347052858C472001F2212 /* InputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CA46A125516523001AA69B /* InputAccessoryView.swift */; }; - FDC347062858C476001F2212 /* InfoPopOverStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */; }; - FDC347072858C476001F2212 /* InfoPopOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */; }; - FDC347082858C476001F2212 /* InfoPopOverStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */; }; - FDC347092858C476001F2212 /* InfoPopOverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */; }; - FDC3470A2858C47E001F2212 /* InfoPopOverStackItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */; }; - FDC3470B2858C47E001F2212 /* InfoPopOverViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */; }; - FDC3470C2858C47F001F2212 /* InfoPopOverStackItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */; }; - FDC3470D2858C47F001F2212 /* InfoPopOverViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */; }; FDC3470E2858C484001F2212 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F47F553F249B8C1B006A0754 /* Main.storyboard */; }; FDC3470F2858C48A001F2212 /* NoBackgroundScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E43263C858E006BB5C6 /* NoBackgroundScroller.swift */; }; FDC347102858C48A001F2212 /* FlippedStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94D3E46263DAEE5006BB5C6 /* FlippedStackView.swift */; }; @@ -466,14 +562,9 @@ FDC347432858C4C4001F2212 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A4FA267A48AD00657930 /* AppDelegate.swift */; }; FDC347442858C4C4001F2212 /* NotificationDispatch-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97583D0267364CE0038A99F /* NotificationDispatch-Extension.swift */; }; FDC3474A2858C4E8001F2212 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A4FC267A48B100657930 /* AppDelegate.swift */; }; - FDC3474B2858C4E8001F2212 /* PopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47F553B249B8C1B006A0754 /* PopUpViewController.swift */; }; FDC3474D2858C4E8001F2212 /* NotificationDispatch-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A4F5267A483500657930 /* NotificationDispatch-Extension.swift */; }; FDC3474E2858C4EB001F2212 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B961A5B4267B451400657930 /* Main.storyboard */; }; - FDC3474F2858C4F3001F2212 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9C8C42625C83D11008EDCFD /* OnboardingViewController.xib */; }; FDC347502858C4F3001F2212 /* NotificationDispatch-Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A55B267A4F2300657930 /* NotificationDispatch-Extension.swift */; }; - FDC347522858C4F3001F2212 /* OnboardingPageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9A8854825B9C68200081C8D /* OnboardingPageViewController.xib */; }; - FDC347532858C4F3001F2212 /* OnboardingPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8854725B9C68200081C8D /* OnboardingPageViewController.swift */; }; - FDC347542858C4F3001F2212 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C42525C83D11008EDCFD /* OnboardingViewController.swift */; }; FDC347552858C4F3001F2212 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B961A57E267A594300657930 /* AppDelegate.swift */; }; FDFCA53B2857CE3D009C1880 /* NACTriggersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFCA53A2857CE3D009C1880 /* NACTriggersTests.swift */; }; FDFCA5422857CE80009C1880 /* NACInteractiveEFCLControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFCA5412857CE80009C1880 /* NACInteractiveEFCLControllerTests.swift */; }; @@ -482,7 +573,6 @@ FDFCA54D2857CED7009C1880 /* SharedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C711882681DC2800A3E898 /* SharedSettings.swift */; }; FDFCA54E2857CED7009C1880 /* ConfigurableParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D58830250A1410009F93C0 /* ConfigurableParameters.swift */; }; FDFCA54F2857CED7009C1880 /* PopupReminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9064C0C276BAF240085FA31 /* PopupReminder.swift */; }; - FDFCA5502857CED7009C1880 /* NASegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C8C41B25C828A7008EDCFD /* NASegue.swift */; }; FDFCA5512857CED7009C1880 /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9DA1198267CA08000A38B3B /* Claims.swift */; }; FDFCA5522857CED7009C1880 /* LoadableNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */; }; FDFCA5532857CED7009C1880 /* NotificationDispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C7A9C924D16F790038D4A7 /* NotificationDispatch.swift */; }; @@ -648,6 +738,43 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlActionClosureProtocol.swift; sourceTree = ""; }; + 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackPanelController.swift; sourceTree = ""; }; + 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIButtonState.swift; sourceTree = ""; }; + 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACVDecoder.swift; sourceTree = ""; }; + 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerItem.swift; sourceTree = ""; }; + 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLAccessoryView.swift; sourceTree = ""; }; + 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTextView.swift; sourceTree = ""; }; + 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoAccessoryView.swift; sourceTree = ""; }; + 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryView.swift; sourceTree = ""; }; + 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownView.swift; sourceTree = ""; }; + 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLView.swift; sourceTree = ""; }; + 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; + 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = ""; }; + 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryViewSource.swift; sourceTree = ""; }; + 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = ""; }; + 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBarView.swift; sourceTree = ""; }; + 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBarViewModel.swift; sourceTree = ""; }; + 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryViewWrapper.swift; sourceTree = ""; }; + 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerView.swift; sourceTree = ""; }; + 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; + 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackPanelWindow.swift; sourceTree = ""; }; + 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleButton.swift; sourceTree = ""; }; + 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardButton.swift; sourceTree = ""; }; + 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeButton.swift; sourceTree = ""; }; + 1B5AFB782A52EEDF00F777E1 /* Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = ""; }; + 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoSectionView.swift; sourceTree = ""; }; + 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View-Extension.swift"; sourceTree = ""; }; + 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection-Extension.swift"; sourceTree = ""; }; + 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding-Extension.swift"; sourceTree = ""; }; + 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTrampoline.swift; sourceTree = ""; }; + 1B5AFBBB2A52FBA700F777E1 /* BodyLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyLabels.swift; sourceTree = ""; }; + 1B5AFBBF2A52FBB900F777E1 /* PopUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpView.swift; sourceTree = ""; }; + 1B5AFBC32A52FBD800F777E1 /* PopUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpViewModel.swift; sourceTree = ""; }; + 1B5AFBC62A52FC5C00F777E1 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 1B5AFBC92A52FC7100F777E1 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; + 1B5AFBCC2A52FC8400F777E1 /* PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = ""; }; + 1B5AFBCF2A52FC9800F777E1 /* PageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewModel.swift; sourceTree = ""; }; B9013A8A24F7E5E5009A4554 /* NALogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NALogger.swift; sourceTree = ""; }; B9013A8C24F80C0F009A4554 /* HelpBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpBuilder.swift; sourceTree = ""; }; B9064C0C276BAF240085FA31 /* PopupReminder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupReminder.swift; sourceTree = ""; }; @@ -661,23 +788,15 @@ B9360EA5268A005C00217247 /* EFCLController-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EFCLController-Extension.swift"; sourceTree = ""; }; B9360EBF268A08FE00217247 /* AppComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppComponent.swift; sourceTree = ""; }; B9360EC5268A0AE800217247 /* NotificationDispatch-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationDispatch-Extension.swift"; sourceTree = ""; }; - B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownAccessoryView.swift; sourceTree = ""; }; - B94389EC26284472008E314A /* HTMLAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLAccessoryView.swift; sourceTree = ""; }; B94389F1262889FB008E314A /* NSColor-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor-Extension.swift"; sourceTree = ""; }; B94BDAD92685FA7E00663D21 /* NALogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NALogger.swift; sourceTree = ""; }; B94BDAF026863C7E00663D21 /* EFCLController-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EFCLController-Extension.swift"; sourceTree = ""; }; - B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAccessoryView.swift; sourceTree = ""; }; - B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAccessoryView.swift; sourceTree = ""; }; - B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckListAccessoryView.swift; sourceTree = ""; }; B94D3E43263C858E006BB5C6 /* NoBackgroundScroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoBackgroundScroller.swift; sourceTree = ""; }; B94D3E46263DAEE5006BB5C6 /* FlippedStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlippedStackView.swift; sourceTree = ""; }; - B94D3E4926403443006BB5C6 /* AccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryView.swift; sourceTree = ""; }; B9504D23275EA00B00DD78C5 /* UNNotificationAttachment-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotificationAttachment-Extension.swift"; sourceTree = ""; }; - B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerAccessoryView.swift; sourceTree = ""; }; B9524D2D24E15B1F007662C8 /* String-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String-Extension.swift"; sourceTree = ""; }; B9524D3224E15DA9007662C8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; B9524D3724E17E7F007662C8 /* Int-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int-Extension.swift"; sourceTree = ""; }; - B96172E92518E9370042F0CF /* MarkdownTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTextView.swift; sourceTree = ""; }; B96172FA251D10420042F0CF /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; B961A4C926736D3200657930 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B961A4D32677AFB000657930 /* EFCLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EFCLController.swift; sourceTree = ""; }; @@ -709,7 +828,6 @@ B97583D0267364CE0038A99F /* NotificationDispatch-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationDispatch-Extension.swift"; sourceTree = ""; }; B985DAC9263854EA004F3470 /* NSScreen-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen-Extension.swift"; sourceTree = ""; }; B99C0D9A25E7CF4B00DB2168 /* Notification_Agent_Core.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Notification_Agent_Core.entitlements; sourceTree = ""; }; - B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarAccessoryView.swift; sourceTree = ""; }; B9A03A9A24B76511005BB90E /* NotificationObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationObject.swift; sourceTree = ""; }; B9A03A9E24B779B8005BB90E /* NotificationAccessoryElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAccessoryElement.swift; sourceTree = ""; }; B9A03AA224B779F5005BB90E /* NotificationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButton.swift; sourceTree = ""; }; @@ -717,8 +835,6 @@ B9A4707324F430DF001FCFB4 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; B9A4707524F430E6001FCFB4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; B9A8852125B9B10600081C8D /* OnboardingData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingData.swift; sourceTree = ""; }; - B9A8854725B9C68200081C8D /* OnboardingPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageViewController.swift; sourceTree = ""; }; - B9A8854825B9C68200081C8D /* OnboardingPageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingPageViewController.xib; sourceTree = ""; }; B9A8855525BB404000081C8D /* Decodable-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decodable-Extensions.swift"; sourceTree = ""; }; B9B4E20C26E8AEA1001D2D89 /* NOTICES.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = NOTICES.rtf; sourceTree = ""; }; B9B4E20D26E8AEA2001D2D89 /* PRIVACY POLICY.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = "PRIVACY POLICY.rtf"; sourceTree = ""; }; @@ -731,24 +847,15 @@ B9C711882681DC2800A3E898 /* SharedSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedSettings.swift; sourceTree = ""; }; B9C7A9C724D16A690038D4A7 /* UserNotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationController.swift; sourceTree = ""; }; B9C7A9C924D16F790038D4A7 /* NotificationDispatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDispatch.swift; sourceTree = ""; }; - B9C8C41B25C828A7008EDCFD /* NASegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NASegue.swift; sourceTree = ""; }; - B9C8C42525C83D11008EDCFD /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; - B9C8C42625C83D11008EDCFD /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = ""; }; B9D58830250A1410009F93C0 /* ConfigurableParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableParameters.swift; sourceTree = ""; }; B9DA1198267CA08000A38B3B /* Claims.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Claims.swift; sourceTree = ""; }; B9DC84602639C26B00D2BBA6 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; B9E98E8424B887A2002FAABD /* Notification-Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification-Extension.swift"; sourceTree = ""; }; B9E98E8A24BCA983002FAABD /* NAError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NAError.swift; sourceTree = ""; }; - B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoPopOverViewController.swift; sourceTree = ""; }; - B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoPopOverStackItem.swift; sourceTree = ""; }; - B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InfoPopOverViewController.xib; sourceTree = ""; }; - B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InfoPopOverStackItem.xib; sourceTree = ""; }; B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadableNib.swift; sourceTree = ""; }; B9EDC98D24EC0D91004F6108 /* InfoSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSection.swift; sourceTree = ""; }; B9EDC98F24EC0DAD004F6108 /* HorizontalLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalLine.swift; sourceTree = ""; }; - D3CA46A125516523001AA69B /* InputAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputAccessoryView.swift; sourceTree = ""; }; F47F5536249B8C1B006A0754 /* IBM Notifier.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "IBM Notifier.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - F47F553B249B8C1B006A0754 /* PopUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpViewController.swift; sourceTree = ""; }; F47F553D249B8C1B006A0754 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F47F5540249B8C1B006A0754 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; F47F5542249B8C1B006A0754 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -868,6 +975,79 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1B5AFB1B2A52EE3C00F777E1 /* AppKit */ = { + isa = PBXGroup; + children = ( + 1B5AFB1C2A52EE3C00F777E1 /* HTMLAccessoryView.swift */, + 1B5AFB1D2A52EE3C00F777E1 /* MarkdownTextView.swift */, + 1B5AFB1E2A52EE3C00F777E1 /* VideoAccessoryView.swift */, + 1B5AFB1F2A52EE3C00F777E1 /* AccessoryView.swift */, + ); + path = AppKit; + sourceTree = ""; + }; + 1B5AFB202A52EE3C00F777E1 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + 1B5AFB282A52EE3C00F777E1 /* ProgressBar */, + 1B5AFB212A52EE3C00F777E1 /* ViewRepresentable */, + 1B5AFB252A52EE3C00F777E1 /* PickerView.swift */, + 1B5AFB262A52EE3C00F777E1 /* AccessoryViewSource.swift */, + 1B5AFB272A52EE3C00F777E1 /* MediaView.swift */, + 1B5AFB2B2A52EE3C00F777E1 /* AccessoryViewWrapper.swift */, + 1B5AFB2C2A52EE3C00F777E1 /* DatePickerView.swift */, + 1B5AFB2D2A52EE3C00F777E1 /* InputView.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; + 1B5AFB212A52EE3C00F777E1 /* ViewRepresentable */ = { + isa = PBXGroup; + children = ( + 1B5AFB222A52EE3C00F777E1 /* MarkdownView.swift */, + 1B5AFB232A52EE3C00F777E1 /* HTMLView.swift */, + 1B5AFB242A52EE3C00F777E1 /* PlayerView.swift */, + ); + path = ViewRepresentable; + sourceTree = ""; + }; + 1B5AFB282A52EE3C00F777E1 /* ProgressBar */ = { + isa = PBXGroup; + children = ( + 1B5AFB292A52EE3C00F777E1 /* ProgressBarView.swift */, + 1B5AFB2A2A52EE3C00F777E1 /* ProgressBarViewModel.swift */, + ); + path = ProgressBar; + sourceTree = ""; + }; + 1B5AFB732A52EEDF00F777E1 /* Buttons */ = { + isa = PBXGroup; + children = ( + 1B5AFB742A52EEDF00F777E1 /* CircleButton.swift */, + 1B5AFB752A52EEDF00F777E1 /* StandardButton.swift */, + 1B5AFB762A52EEDF00F777E1 /* NativeButton.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + 1B5AFB772A52EEDF00F777E1 /* Components */ = { + isa = PBXGroup; + children = ( + 1B5AFB782A52EEDF00F777E1 /* Icon.swift */, + 1B5AFB792A52EEDF00F777E1 /* InfoSectionView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 1B5AFBB32A52F01D00F777E1 /* Utils */ = { + isa = PBXGroup; + children = ( + B96172FA251D10420042F0CF /* Utils.swift */, + 1B5AFBB42A52F03F00F777E1 /* ActionTrampoline.swift */, + ); + path = Utils; + sourceTree = ""; + }; B920D4BA265F8FC600437BE6 /* Notification Agent Alerts */ = { isa = PBXGroup; children = ( @@ -925,16 +1105,8 @@ B9524D2A24E158FF007662C8 /* AccessoryViews */ = { isa = PBXGroup; children = ( - B96172E92518E9370042F0CF /* MarkdownTextView.swift */, - B94389EC26284472008E314A /* HTMLAccessoryView.swift */, - B94D3E40263C56AE006BB5C6 /* CheckListAccessoryView.swift */, - B9524D2B24E1591E007662C8 /* TimerAccessoryView.swift */, - B94CF9C125222F0000B11CF1 /* ImageAccessoryView.swift */, - B94CF9D225249E4700B11CF1 /* VideoAccessoryView.swift */, - B99D1E8D2535F68900661954 /* ProgressBarAccessoryView.swift */, - D3CA46A125516523001AA69B /* InputAccessoryView.swift */, - B94389D92626F2CC008E314A /* DropDownAccessoryView.swift */, - B94D3E4926403443006BB5C6 /* AccessoryView.swift */, + 1B5AFB202A52EE3C00F777E1 /* SwiftUI */, + 1B5AFB1B2A52EE3C00F777E1 /* AppKit */, ); path = AccessoryViews; sourceTree = ""; @@ -948,6 +1120,7 @@ B9A03A9624B764BE005BB90E /* Views */, B9E98E8324B88782002FAABD /* Extensions */, B9A03AA724B87378005BB90E /* Resources */, + 1B5AFBB32A52F01D00F777E1 /* Utils */, ); path = Shared; sourceTree = ""; @@ -972,7 +1145,6 @@ B961A4FC267A48B100657930 /* AppDelegate.swift */, B961A5B4267B451400657930 /* Main.storyboard */, B961A62B267C9DA200657930 /* Assets.xcassets */, - FD1730062875B0D200781A69 /* PopupInteractiveEFCLController.swift */, FD0D02D728D9F8B500A167B7 /* Controllers */, B9360EBA268A04A900217247 /* Extensions */, B961A5F8267B8B7800657930 /* Views */, @@ -997,7 +1169,9 @@ B961A5F8267B8B7800657930 /* Views */ = { isa = PBXGroup; children = ( - F47F553B249B8C1B006A0754 /* PopUpViewController.swift */, + 1B5AFBBB2A52FBA700F777E1 /* BodyLabels.swift */, + 1B5AFBBF2A52FBB900F777E1 /* PopUpView.swift */, + 1B5AFBC32A52FBD800F777E1 /* PopUpViewModel.swift */, ); path = Views; sourceTree = ""; @@ -1005,10 +1179,10 @@ B961A5F9267B8B9E00657930 /* Views */ = { isa = PBXGroup; children = ( - B9A8854725B9C68200081C8D /* OnboardingPageViewController.swift */, - B9A8854825B9C68200081C8D /* OnboardingPageViewController.xib */, - B9C8C42525C83D11008EDCFD /* OnboardingViewController.swift */, - B9C8C42625C83D11008EDCFD /* OnboardingViewController.xib */, + 1B5AFBC62A52FC5C00F777E1 /* OnboardingView.swift */, + 1B5AFBC92A52FC7100F777E1 /* OnboardingViewModel.swift */, + 1B5AFBCC2A52FC8400F777E1 /* PageView.swift */, + 1B5AFBCF2A52FC9800F777E1 /* PageViewModel.swift */, ); path = Views; sourceTree = ""; @@ -1029,10 +1203,11 @@ B9A03A9624B764BE005BB90E /* Views */ = { isa = PBXGroup; children = ( - B9524D2A24E158FF007662C8 /* AccessoryViews */, - B9EDC98224EC0D04004F6108 /* InfoPopOver */, F47F553F249B8C1B006A0754 /* Main.storyboard */, + B9524D2A24E158FF007662C8 /* AccessoryViews */, + 1B5AFB732A52EEDF00F777E1 /* Buttons */, B9CB20F925C1958A00465E4E /* Common */, + 1B5AFB772A52EEDF00F777E1 /* Components */, ); path = Views; sourceTree = ""; @@ -1050,6 +1225,7 @@ isa = PBXGroup; children = ( B9C7A9C924D16F790038D4A7 /* NotificationDispatch.swift */, + 1B5AFAF52A52BD2F00F777E1 /* BackPanelController.swift */, B961A4D32677AFB000657930 /* EFCLController.swift */, B9DC84602639C26B00D2BBA6 /* Environment.swift */, B961A626267C80BA00657930 /* TaskManager.swift */, @@ -1065,7 +1241,6 @@ B9A03AA724B87378005BB90E /* Resources */ = { isa = PBXGroup; children = ( - B96172FA251D10420042F0CF /* Utils.swift */, F47F553D249B8C1B006A0754 /* Assets.xcassets */, B9524D3324E15DA9007662C8 /* Localizable.strings */, ); @@ -1087,10 +1262,11 @@ B9A03A9A24B76511005BB90E /* NotificationObject.swift */, B92E6707253893D900F39949 /* ProgressState.swift */, B9A03A9E24B779B8005BB90E /* NotificationAccessoryElement.swift */, - B9064C0C276BAF240085FA31 /* PopupReminder.swift */, B9A8852125B9B10600081C8D /* OnboardingData.swift */, B9A03AA224B779F5005BB90E /* NotificationButton.swift */, FD1730092875B82000781A69 /* DynamicNotificationButton.swift */, + B9064C0C276BAF240085FA31 /* PopupReminder.swift */, + 1B5AFAFE2A52EC4400F777E1 /* SwiftUIButtonState.swift */, B9D58830250A1410009F93C0 /* ConfigurableParameters.swift */, ); path = UIObjects; @@ -1099,17 +1275,18 @@ B9A8853625B9B18400081C8D /* Common */ = { isa = PBXGroup; children = ( - B9A1424E25D68D6600E87AD6 /* Token.swift */, B9C711812681DBC200A3E898 /* TaskObject.swift */, B9360EBF268A08FE00217247 /* AppComponent.swift */, + B9A1424E25D68D6600E87AD6 /* Token.swift */, B961A619267B913C00657930 /* UserReplyType.swift */, - B9C711882681DC2800A3E898 /* SharedSettings.swift */, B9DA1198267CA08000A38B3B /* Claims.swift */, + B9EDC98D24EC0D91004F6108 /* InfoSection.swift */, B9E98E8A24BCA983002FAABD /* NAError.swift */, B92D021B25ED50900076C452 /* NAMedia.swift */, B9EDC98B24EC0D3E004F6108 /* LoadableNib.swift */, - B9EDC98D24EC0D91004F6108 /* InfoSection.swift */, - B9C8C41B25C828A7008EDCFD /* NASegue.swift */, + B9C711882681DC2800A3E898 /* SharedSettings.swift */, + 1B5AFB072A52ED8900F777E1 /* ACVDecoder.swift */, + 1B5AFB0C2A52EDB300F777E1 /* PickerItem.swift */, ); path = Common; sourceTree = ""; @@ -1128,6 +1305,7 @@ isa = PBXGroup; children = ( B9EDC98F24EC0DAD004F6108 /* HorizontalLine.swift */, + 1B5AFB6A2A52EEB700F777E1 /* BackPanelWindow.swift */, B94D3E43263C858E006BB5C6 /* NoBackgroundScroller.swift */, B94D3E46263DAEE5006BB5C6 /* FlippedStackView.swift */, ); @@ -1145,21 +1323,13 @@ B985DAC9263854EA004F3470 /* NSScreen-Extension.swift */, B9524D3724E17E7F007662C8 /* Int-Extension.swift */, B94389F1262889FB008E314A /* NSColor-Extension.swift */, + 1B5AFB922A52EF9500F777E1 /* View-Extension.swift */, + 1B5AFB9D2A52EFC200F777E1 /* Collection-Extension.swift */, + 1B5AFBA82A52EFE600F777E1 /* Binding-Extension.swift */, ); path = Extensions; sourceTree = ""; }; - B9EDC98224EC0D04004F6108 /* InfoPopOver */ = { - isa = PBXGroup; - children = ( - B9EDC98324EC0D04004F6108 /* InfoPopOverViewController.swift */, - B9EDC98424EC0D04004F6108 /* InfoPopOverStackItem.swift */, - B9EDC98524EC0D04004F6108 /* InfoPopOverViewController.xib */, - B9EDC98624EC0D04004F6108 /* InfoPopOverStackItem.xib */, - ); - path = InfoPopOver; - sourceTree = ""; - }; F47F552D249B8C1B006A0754 = { isa = PBXGroup; children = ( @@ -1217,6 +1387,7 @@ FD0D02D728D9F8B500A167B7 /* Controllers */ = { isa = PBXGroup; children = ( + FD1730062875B0D200781A69 /* PopupInteractiveEFCLController.swift */, FD0D02D428D9F8AE00A167B7 /* SystemAlertController.swift */, ); path = Controllers; @@ -1234,6 +1405,7 @@ isa = PBXGroup; children = ( FD1730142875B9A000781A69 /* InteractiveObjectProtocol.swift */, + 1B5AFAEE2A52BCE700F777E1 /* ControlActionClosureProtocol.swift */, ); path = Protocols; sourceTree = ""; @@ -1539,8 +1711,9 @@ F47F552E249B8C1B006A0754 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = IBM; TargetAttributes = { B920D4B8265F8FC600437BE6 = { @@ -1639,9 +1812,7 @@ buildActionMask = 2147483647; files = ( B961A53D267A4DE100657930 /* Localizable.strings in Resources */, - B961A52C267A4D9B00657930 /* InfoPopOverViewController.xib in Resources */, B961A5B6267B451F00657930 /* Main.storyboard in Resources */, - B961A52E267A4D9C00657930 /* InfoPopOverStackItem.xib in Resources */, B9B4E22C26E918AB001D2D89 /* Assets.xcassets in Resources */, B961A62D267C9DC900657930 /* Assets.xcassets in Resources */, ); @@ -1651,10 +1822,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B961A5B1267B44E000657930 /* OnboardingPageViewController.xib in Resources */, - B961A5ED267B8A1F00657930 /* InfoPopOverStackItem.xib in Resources */, - B961A5B3267B44E000657930 /* OnboardingViewController.xib in Resources */, - B961A5EF267B8A1F00657930 /* InfoPopOverViewController.xib in Resources */, B961A584267A5CE100657930 /* Localizable.strings in Resources */, B9B4E22D26E918B2001D2D89 /* Assets.xcassets in Resources */, B9B4E22E26E918C6001D2D89 /* Assets.xcassets in Resources */, @@ -1717,11 +1884,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FDC3470D2858C47F001F2212 /* InfoPopOverViewController.xib in Resources */, FDC3474E2858C4EB001F2212 /* Main.storyboard in Resources */, FDC3473B2858C4A3001F2212 /* Localizable.strings in Resources */, FDC347382858C4A0001F2212 /* Assets.xcassets in Resources */, - FDC3470C2858C47F001F2212 /* InfoPopOverStackItem.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1736,12 +1901,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FDC347522858C4F3001F2212 /* OnboardingPageViewController.xib in Resources */, - FDC3470B2858C47E001F2212 /* InfoPopOverViewController.xib in Resources */, FDC3473D2858C4A5001F2212 /* Localizable.strings in Resources */, - FDC3474F2858C4F3001F2212 /* OnboardingViewController.xib in Resources */, FDC347392858C4A0001F2212 /* Assets.xcassets in Resources */, - FDC3470A2858C47E001F2212 /* InfoPopOverStackItem.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1784,7 +1945,6 @@ B9360EC7268A0B2B00217247 /* NotificationDispatch.swift in Sources */, B920D4DC265F90D800437BE6 /* ConfigurableParameters.swift in Sources */, B920D4DA265F90D800437BE6 /* ProgressState.swift in Sources */, - B920D4E2265F90E200437BE6 /* NASegue.swift in Sources */, B920D4D2265F90D200437BE6 /* NALogger.swift in Sources */, B920D4DB265F90D800437BE6 /* OnboardingData.swift in Sources */, B920D4DE265F90D800437BE6 /* NotificationButton.swift in Sources */, @@ -1793,9 +1953,11 @@ B961A4F9267A48AA00657930 /* AppDelegate.swift in Sources */, B961A61B267B913C00657930 /* UserReplyType.swift in Sources */, B920D4EA265F90EB00437BE6 /* NSColor-Extension.swift in Sources */, + 1B5AFBAA2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, B920D4E5265F90E200437BE6 /* NAError.swift in Sources */, FD1730162875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, B920D4CE265F90C700437BE6 /* Environment.swift in Sources */, + 1B5AFAF62A52BD2F00F777E1 /* BackPanelController.swift in Sources */, B920D4F1265F90F900437BE6 /* Utils.swift in Sources */, B920D4DD265F90D800437BE6 /* NotificationAccessoryElement.swift in Sources */, B94BDAF126863C7E00663D21 /* EFCLController-Extension.swift in Sources */, @@ -1817,8 +1979,11 @@ B9504D25275EA00B00DD78C5 /* UNNotificationAttachment-Extension.swift in Sources */, B920D4E7265F90EB00437BE6 /* Notification-Extension.swift in Sources */, B920D4E9265F90EB00437BE6 /* Int-Extension.swift in Sources */, + 1B5AFB982A52EF9B00F777E1 /* View-Extension.swift in Sources */, + 1B5AFB6F2A52EEBC00F777E1 /* BackPanelWindow.swift in Sources */, B920D4D9265F90D800437BE6 /* NotificationObject.swift in Sources */, B920D4E0265F90E200437BE6 /* NAMedia.swift in Sources */, + 1B5AFB9F2A52EFC200F777E1 /* Collection-Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1826,61 +1991,81 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B5AFBA12A52EFC200F777E1 /* Collection-Extension.swift in Sources */, + 1B5AFAF82A52BD2F00F777E1 /* BackPanelController.swift in Sources */, B961A536267A4DD600657930 /* Notification-Extension.swift in Sources */, - B961A522267A4D8F00657930 /* CheckListAccessoryView.swift in Sources */, - B961A532267A4DC700657930 /* PopUpViewController.swift in Sources */, FD8A35412863582A00DA72CC /* EFCLController-Extension.swift in Sources */, + 1B5AFB322A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */, + 1B5AFB7A2A52EEDF00F777E1 /* CircleButton.swift in Sources */, B961A518267A4D6A00657930 /* ConfigurableParameters.swift in Sources */, B9C711852681DBC200A3E898 /* TaskObject.swift in Sources */, + 1B5AFB7E2A52EEDF00F777E1 /* StandardButton.swift in Sources */, B961A534267A4DC700657930 /* NoBackgroundScroller.swift in Sources */, + 1B5AFBC42A52FBD800F777E1 /* PopUpViewModel.swift in Sources */, + 1B5AFAF02A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, + 1B5AFBBC2A52FBA700F777E1 /* BodyLabels.swift in Sources */, + 1B5AFB6B2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */, B961A51E267A4D7F00657930 /* LoadableNib.swift in Sources */, + 1B5AFBC02A52FBB900F777E1 /* PopUpView.swift in Sources */, B961A516267A4D6600657930 /* OnboardingData.swift in Sources */, - B961A524267A4D8F00657930 /* MarkdownTextView.swift in Sources */, B961A512267A4D5900657930 /* NALogger.swift in Sources */, - B961A525267A4D8F00657930 /* VideoAccessoryView.swift in Sources */, B961A539267A4DD600657930 /* Decodable-Extensions.swift in Sources */, + 1B5AFB3A2A52EE3C00F777E1 /* AccessoryView.swift in Sources */, B94BDAD72684C1BB00663D21 /* Token.swift in Sources */, + 1B5AFB8A2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */, + 1B5AFB862A52EEDF00F777E1 /* Icon.swift in Sources */, B961A4FD267A48B100657930 /* AppDelegate.swift in Sources */, B961A51C267A4D7B00657930 /* NAError.swift in Sources */, + 1B5AFBAC2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, + 1B5AFB822A52EEDF00F777E1 /* NativeButton.swift in Sources */, + 1B5AFB422A52EE3C00F777E1 /* HTMLView.swift in Sources */, + 1B5AFB082A52ED8900F777E1 /* ACVDecoder.swift in Sources */, B9360EC3268A08FE00217247 /* AppComponent.swift in Sources */, B961A5CF267B84C800657930 /* ReplyHandler.swift in Sources */, - B961A529267A4D8F00657930 /* AccessoryView.swift in Sources */, + 1B5AFB5A2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */, B961A53A267A4DD600657930 /* NSColor-Extension.swift in Sources */, FD17300D2875B82000781A69 /* DynamicNotificationButton.swift in Sources */, B961A61D267B913C00657930 /* UserReplyType.swift in Sources */, - B961A52F267A4D9C00657930 /* InfoPopOverStackItem.swift in Sources */, B961A515267A4D6400657930 /* NotificationAccessoryElement.swift in Sources */, + 1B5AFB4A2A52EE3C00F777E1 /* PickerView.swift in Sources */, B9360EC9268A0B2C00217247 /* NotificationDispatch.swift in Sources */, B961A51F267A4D8200657930 /* InfoSection.swift in Sources */, B9504D27275EA00B00DD78C5 /* UNNotificationAttachment-Extension.swift in Sources */, B961A535267A4DD600657930 /* NSScreen-Extension.swift in Sources */, - B961A523267A4D8F00657930 /* ImageAccessoryView.swift in Sources */, B961A513267A4D5F00657930 /* NotificationObject.swift in Sources */, B9C7118C2681DC2800A3E898 /* SharedSettings.swift in Sources */, + 1B5AFB562A52EE3C00F777E1 /* ProgressBarView.swift in Sources */, + 1B5AFB362A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */, + 1B5AFBB62A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, B961A537267A4DD600657930 /* String-Extension.swift in Sources */, B961A514267A4D6100657930 /* ProgressState.swift in Sources */, + 1B5AFB622A52EE3C00F777E1 /* DatePickerView.swift in Sources */, B961A510267A4D4400657930 /* Context.swift in Sources */, + 1B5AFB462A52EE3C00F777E1 /* PlayerView.swift in Sources */, B961A538267A4DD600657930 /* Int-Extension.swift in Sources */, - B961A526267A4D8F00657930 /* InputAccessoryView.swift in Sources */, B961A531267A4DC700657930 /* FlippedStackView.swift in Sources */, - B961A528267A4D8F00657930 /* ProgressBarAccessoryView.swift in Sources */, B9360EA8268A00E100217247 /* EFCLController.swift in Sources */, B961A4F6267A483900657930 /* NotificationDispatch-Extension.swift in Sources */, - B961A52A267A4D8F00657930 /* TimerAccessoryView.swift in Sources */, - B961A52D267A4D9B00657930 /* InfoPopOverViewController.swift in Sources */, B961A51D267A4D7D00657930 /* NAMedia.swift in Sources */, B961A517267A4D6800657930 /* NotificationButton.swift in Sources */, B9064C10276BAF240085FA31 /* PopupReminder.swift in Sources */, B92C37772701D933006114A3 /* InteractiveEFCLContoller.swift in Sources */, + 1B5AFB4E2A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */, + 1B5AFB5E2A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */, + 1B5AFB662A52EE3C00F777E1 /* InputView.swift in Sources */, B961A629267C80C000657930 /* TaskManager.swift in Sources */, FD1730072875B0D200781A69 /* PopupInteractiveEFCLController.swift in Sources */, B961A53C267A4DDF00657930 /* Utils.swift in Sources */, - B961A52B267A4D8F00657930 /* DropDownAccessoryView.swift in Sources */, + 1B5AFB0D2A52EDB300F777E1 /* PickerItem.swift in Sources */, B961A533267A4DC700657930 /* HorizontalLine.swift in Sources */, + 1B5AFB522A52EE3C00F777E1 /* MediaView.swift in Sources */, B961A50F267A4D3C00657930 /* Environment.swift in Sources */, FD0D02D528D9F8AE00A167B7 /* SystemAlertController.swift in Sources */, - B961A527267A4D8F00657930 /* HTMLAccessoryView.swift in Sources */, FD1730182875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, + 1B5AFB932A52EF9500F777E1 /* View-Extension.swift in Sources */, + 1B5AFB012A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */, + 1B5AFB3E2A52EE3C00F777E1 /* MarkdownView.swift in Sources */, + 1B5AFB2E2A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1888,62 +2073,81 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B5AFBA22A52EFC200F777E1 /* Collection-Extension.swift in Sources */, B961A5DE267B89CE00657930 /* ConfigurableParameters.swift in Sources */, B961A57F267A594300657930 /* AppDelegate.swift in Sources */, B961A5F0267B8A2600657930 /* FlippedStackView.swift in Sources */, B961A61E267B913C00657930 /* UserReplyType.swift in Sources */, + 1B5AFB332A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */, + 1B5AFB7B2A52EEDF00F777E1 /* CircleButton.swift in Sources */, B961A5A5267B44B400657930 /* Int-Extension.swift in Sources */, B961A5D3267B89CE00657930 /* OnboardingData.swift in Sources */, + 1B5AFBCD2A52FC8400F777E1 /* PageView.swift in Sources */, + 1B5AFB7F2A52EEDF00F777E1 /* StandardButton.swift in Sources */, B961A5DF267B89CE00657930 /* NotificationAccessoryElement.swift in Sources */, - B961A5EC267B8A1F00657930 /* InfoPopOverStackItem.swift in Sources */, B961A5A9267B44B400657930 /* NSScreen-Extension.swift in Sources */, + 1B5AFB6C2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */, B961A5B9267B458C00657930 /* Context.swift in Sources */, FD8A35422863582A00DA72CC /* EFCLController-Extension.swift in Sources */, - B961A5EB267B8A1500657930 /* MarkdownTextView.swift in Sources */, B961A62A267C80C100657930 /* TaskManager.swift in Sources */, + 1B5AFB3B2A52EE3C00F777E1 /* AccessoryView.swift in Sources */, B961A55C267A4F2700657930 /* NotificationDispatch-Extension.swift in Sources */, - B961A5E4267B8A1500657930 /* AccessoryView.swift in Sources */, - FD18021D27E1DA9300FC995A /* ProgressBarAccessoryView.swift in Sources */, - B961A5B2267B44E000657930 /* OnboardingPageViewController.swift in Sources */, + 1B5AFB8B2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */, + 1B5AFB872A52EEDF00F777E1 /* Icon.swift in Sources */, B9C7118D2681DC2800A3E898 /* SharedSettings.swift in Sources */, - FD18021F27E1DA9A00FC995A /* InputAccessoryView.swift in Sources */, + 1B5AFBC72A52FC5C00F777E1 /* OnboardingView.swift in Sources */, + 1B5AFB432A52EE3C00F777E1 /* HTMLView.swift in Sources */, + 1B5AFBAD2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, + 1B5AFBD02A52FC9800F777E1 /* PageViewModel.swift in Sources */, + 1B5AFB832A52EEDF00F777E1 /* NativeButton.swift in Sources */, + 1B5AFB092A52ED8900F777E1 /* ACVDecoder.swift in Sources */, B9360EC4268A08FE00217247 /* AppComponent.swift in Sources */, B961A5A8267B44B400657930 /* NSColor-Extension.swift in Sources */, + 1B5AFB5B2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */, B94BDAD82684C1BC00663D21 /* Token.swift in Sources */, - FD18021B27E1DA8C00FC995A /* CheckListAccessoryView.swift in Sources */, B961A5D8267B89CE00657930 /* ProgressState.swift in Sources */, B961A5A4267B44B400657930 /* Notification-Extension.swift in Sources */, FD17300E2875B82000781A69 /* DynamicNotificationButton.swift in Sources */, B9360EA9268A00E100217247 /* EFCLController.swift in Sources */, + 1B5AFB4B2A52EE3C00F777E1 /* PickerView.swift in Sources */, B9360ECA268A0B2D00217247 /* NotificationDispatch.swift in Sources */, B961A5F2267B8A2600657930 /* NoBackgroundScroller.swift in Sources */, B961A5D1267B89AC00657930 /* NALogger.swift in Sources */, B961A5DA267B89CE00657930 /* InfoSection.swift in Sources */, FD18022027E1DF3E00FC995A /* InteractiveEFCLContoller.swift in Sources */, B9064C11276BAF240085FA31 /* PopupReminder.swift in Sources */, - B961A5E6267B8A1500657930 /* HTMLAccessoryView.swift in Sources */, + 1B5AFB572A52EE3C00F777E1 /* ProgressBarView.swift in Sources */, + 1B5AFB372A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */, + 1B5AFBB72A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, B961A5B7267B457D00657930 /* Environment.swift in Sources */, - B961A5E2267B8A1500657930 /* VideoAccessoryView.swift in Sources */, - FD18021E27E1DA9A00FC995A /* DropDownAccessoryView.swift in Sources */, + 1B5AFAF12A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, + 1B5AFBCA2A52FC7100F777E1 /* OnboardingViewModel.swift in Sources */, + 1B5AFB632A52EE3C00F777E1 /* DatePickerView.swift in Sources */, B9C711862681DBC200A3E898 /* TaskObject.swift in Sources */, + 1B5AFB472A52EE3C00F777E1 /* PlayerView.swift in Sources */, B961A5D9267B89CE00657930 /* LoadableNib.swift in Sources */, B961A5DC267B89CE00657930 /* NAMedia.swift in Sources */, - B961A5EE267B8A1F00657930 /* InfoPopOverViewController.swift in Sources */, FD1730192875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, - B961A5E7267B8A1500657930 /* ImageAccessoryView.swift in Sources */, B961A5A7267B44B400657930 /* Decodable-Extensions.swift in Sources */, + 1B5AFAF92A52BD2F00F777E1 /* BackPanelController.swift in Sources */, B961A5A6267B44B400657930 /* String-Extension.swift in Sources */, + 1B5AFB022A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */, B961A5F1267B8A2600657930 /* HorizontalLine.swift in Sources */, + 1B5AFB4F2A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */, + 1B5AFB5F2A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */, + 1B5AFB672A52EE3C00F777E1 /* InputView.swift in Sources */, FD1730032875B02F00781A69 /* OnboardingInteractiveEFCLController.swift in Sources */, - FD18021C27E1DA8E00FC995A /* TimerAccessoryView.swift in Sources */, + 1B5AFB0E2A52EDB300F777E1 /* PickerItem.swift in Sources */, B9504D28275EA00B00DD78C5 /* UNNotificationAttachment-Extension.swift in Sources */, - B961A5B0267B44E000657930 /* OnboardingViewController.swift in Sources */, - B961A5D4267B89CE00657930 /* NASegue.swift in Sources */, B961A5E1267B89F900657930 /* ReplyHandler.swift in Sources */, + 1B5AFB532A52EE3C00F777E1 /* MediaView.swift in Sources */, B961A5D7267B89CE00657930 /* NotificationObject.swift in Sources */, B961A582267A5CDA00657930 /* Utils.swift in Sources */, B961A5DD267B89CE00657930 /* NotificationButton.swift in Sources */, + 1B5AFB942A52EF9500F777E1 /* View-Extension.swift in Sources */, B961A5D6267B89CE00657930 /* NAError.swift in Sources */, + 1B5AFB3F2A52EE3C00F777E1 /* MarkdownView.swift in Sources */, + 1B5AFB2F2A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1962,10 +2166,12 @@ B961A4FB267A48AD00657930 /* AppDelegate.swift in Sources */, B961A61C267B913C00657930 /* UserReplyType.swift in Sources */, B961A4B52673676000657930 /* ReplyHandler.swift in Sources */, + 1B5AFBAB2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, FD8A35402863582900DA72CC /* EFCLController-Extension.swift in Sources */, B961A4B02673676000657930 /* LoadableNib.swift in Sources */, FD1730172875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, B961A49F2673673C00657930 /* UserNotificationController.swift in Sources */, + 1B5AFAF72A52BD2F00F777E1 /* BackPanelController.swift in Sources */, B961A4C52673679500657930 /* Utils.swift in Sources */, B961A4AE2673676000657930 /* OnboardingData.swift in Sources */, B961A4A02673673F00657930 /* Context.swift in Sources */, @@ -1979,7 +2185,6 @@ B9064C0F276BAF240085FA31 /* PopupReminder.swift in Sources */, B961A628267C80C000657930 /* TaskManager.swift in Sources */, B961A4B22673676000657930 /* ProgressState.swift in Sources */, - B961A4B32673676000657930 /* NASegue.swift in Sources */, B961A4B42673676000657930 /* NALogger.swift in Sources */, B961A4A72673676000657930 /* NotificationObject.swift in Sources */, FD17300C2875B82000781A69 /* DynamicNotificationButton.swift in Sources */, @@ -1987,8 +2192,11 @@ B9504D26275EA00B00DD78C5 /* UNNotificationAttachment-Extension.swift in Sources */, B961A4B92673677C00657930 /* Int-Extension.swift in Sources */, B961A4A52673676000657930 /* NotificationButton.swift in Sources */, + 1B5AFB992A52EF9B00F777E1 /* View-Extension.swift in Sources */, + 1B5AFB702A52EEBD00F777E1 /* BackPanelWindow.swift in Sources */, B961A49D2673673800657930 /* Environment.swift in Sources */, B961A4AF2673676000657930 /* NAMedia.swift in Sources */, + 1B5AFBA02A52EFC200F777E1 /* Collection-Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1996,6 +2204,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B5AFAEF2A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, B9A03AA324B779F5005BB90E /* NotificationButton.swift in Sources */, B9360EA7268A006300217247 /* EFCLController-Extension.swift in Sources */, B9064C0D276BAF240085FA31 /* PopupReminder.swift in Sources */, @@ -2016,6 +2225,7 @@ B9C711892681DC2800A3E898 /* SharedSettings.swift in Sources */, B9DA1199267CA08000A38B3B /* Claims.swift in Sources */, B9A03A9B24B76511005BB90E /* NotificationObject.swift in Sources */, + 1B5AFB972A52EF9A00F777E1 /* View-Extension.swift in Sources */, B9DC84612639C26B00D2BBA6 /* Environment.swift in Sources */, B9EDC98E24EC0D91004F6108 /* InfoSection.swift in Sources */, B9360EC0268A08FE00217247 /* AppComponent.swift in Sources */, @@ -2024,17 +2234,19 @@ B9A8855625BB404000081C8D /* Decodable-Extensions.swift in Sources */, B94BDADA2685FA7E00663D21 /* NALogger.swift in Sources */, B9710AB624A0F184000B5A67 /* DeepLinkEngine.swift in Sources */, + 1B5AFBB52A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, B9D58831250A1410009F93C0 /* ConfigurableParameters.swift in Sources */, B961A625267C7DFE00657930 /* InteractiveEFCLContoller.swift in Sources */, B9C711822681DBC200A3E898 /* TaskObject.swift in Sources */, + 1B5AFBA92A52EFE600F777E1 /* Binding-Extension.swift in Sources */, B9360EC6268A0AE800217247 /* NotificationDispatch-Extension.swift in Sources */, B9C7A9CA24D16F790038D4A7 /* NotificationDispatch.swift in Sources */, FD1730152875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, FD17300A2875B82000781A69 /* DynamicNotificationButton.swift in Sources */, + 1B5AFB9E2A52EFC200F777E1 /* Collection-Extension.swift in Sources */, B961A4D42677AFB000657930 /* EFCLController.swift in Sources */, B961A4F42679F50E00657930 /* TaskManager.swift in Sources */, B9524D2E24E15B1F007662C8 /* String-Extension.swift in Sources */, - B9C8C41C25C828A7008EDCFD /* NASegue.swift in Sources */, B9A03A9F24B779B8005BB90E /* NotificationAccessoryElement.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2043,9 +2255,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B5AFBB82A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, FDFCA5712857CF71009C1880 /* NALogger.swift in Sources */, FD17300F2875B82000781A69 /* DynamicNotificationButton.swift in Sources */, - FDFCA5502857CED7009C1880 /* NASegue.swift in Sources */, FDFCA5542857CED7009C1880 /* NAError.swift in Sources */, FDFCA5602857CEF3009C1880 /* String-Extension.swift in Sources */, FDFCA5702857CF71009C1880 /* TaskManager.swift in Sources */, @@ -2054,6 +2266,7 @@ FDFCA5522857CED7009C1880 /* LoadableNib.swift in Sources */, FDFCA54F2857CED7009C1880 /* PopupReminder.swift in Sources */, FDFCA5662857CEF3009C1880 /* Int-Extension.swift in Sources */, + 1B5AFAF22A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, FDFCA5592857CED7009C1880 /* TaskObject.swift in Sources */, FDFCA5642857CEF3009C1880 /* Notification-Extension.swift in Sources */, FDFCA55D2857CED7009C1880 /* OnboardingData.swift in Sources */, @@ -2069,6 +2282,7 @@ FDFCA5612857CEF3009C1880 /* NSScreen-Extension.swift in Sources */, FDFCA5572857CED7009C1880 /* InfoSection.swift in Sources */, FDFCA56E2857CF71009C1880 /* InteractiveEFCLContoller.swift in Sources */, + 1B5AFB9A2A52EF9C00F777E1 /* View-Extension.swift in Sources */, FDFCA5582857CED7009C1880 /* Token.swift in Sources */, FDFCA55A2857CED7009C1880 /* EFCLController.swift in Sources */, FDFCA5672857CEF6009C1880 /* Utils.swift in Sources */, @@ -2077,11 +2291,13 @@ FDFCA54B2857CED7009C1880 /* Environment.swift in Sources */, FDFCA5422857CE80009C1880 /* NACInteractiveEFCLControllerTests.swift in Sources */, FDFCA5552857CED7009C1880 /* NotificationObject.swift in Sources */, + 1B5AFBA32A52EFC200F777E1 /* Collection-Extension.swift in Sources */, FDFCA55B2857CED7009C1880 /* NotificationAccessoryElement.swift in Sources */, FDFCA5692857CF71009C1880 /* HelpBuilder.swift in Sources */, FDFCA5532857CED7009C1880 /* NotificationDispatch.swift in Sources */, FDFCA54D2857CED7009C1880 /* SharedSettings.swift in Sources */, FDFCA56D2857CF71009C1880 /* Context.swift in Sources */, + 1B5AFBAE2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, FDFCA56B2857CF71009C1880 /* AppDelegate.swift in Sources */, FDFCA54C2857CED7009C1880 /* NAMedia.swift in Sources */, FD17301A2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, @@ -2093,6 +2309,7 @@ buildActionMask = 2147483647; files = ( FDC347412858C4B9001F2212 /* NotificationDispatch-Extension.swift in Sources */, + 1B5AFAFA2A52BD2F00F777E1 /* BackPanelController.swift in Sources */, FDC346B32858C43F001F2212 /* PopupReminder.swift in Sources */, FD17301B2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, FDC346D02858C458001F2212 /* InfoSection.swift in Sources */, @@ -2108,10 +2325,11 @@ FDC347162858C494001F2212 /* Notification-Extension.swift in Sources */, FDC347422858C4B9001F2212 /* AppDelegate.swift in Sources */, FDC346CF2858C458001F2212 /* UserReplyType.swift in Sources */, + 1B5AFB712A52EEBE00F777E1 /* BackPanelWindow.swift in Sources */, FDC3469F2858C421001F2212 /* UserNotificationController.swift in Sources */, FDC3471B2858C494001F2212 /* Decodable-Extensions.swift in Sources */, - FDC346EF2858C463001F2212 /* NASegue.swift in Sources */, FDC347152858C494001F2212 /* UNNotificationAttachment-Extension.swift in Sources */, + 1B5AFB9B2A52EF9C00F777E1 /* View-Extension.swift in Sources */, FDC346A92858C42D001F2212 /* NALogger.swift in Sources */, FDC346A12858C425001F2212 /* Context.swift in Sources */, FDC346B02858C43F001F2212 /* NotificationAccessoryElement.swift in Sources */, @@ -2123,10 +2341,12 @@ FDC3468F2858C404001F2212 /* NotificationDispatch.swift in Sources */, FDC347402858C4B9001F2212 /* EFCLController-Extension.swift in Sources */, FDC346D22858C458001F2212 /* AppComponent.swift in Sources */, + 1B5AFBAF2A52EFE600F777E1 /* Binding-Extension.swift in Sources */, FDC346C42858C445001F2212 /* ConfigurableParameters.swift in Sources */, FDC346CD2858C458001F2212 /* Token.swift in Sources */, FDC346C32858C445001F2212 /* NotificationButton.swift in Sources */, FDC347172858C494001F2212 /* NSColor-Extension.swift in Sources */, + 1B5AFBA42A52EFC200F777E1 /* Collection-Extension.swift in Sources */, FDFCA57A2857D720009C1880 /* NAATriggersTests.swift in Sources */, FDC346D12858C458001F2212 /* NAMedia.swift in Sources */, FD1730102875B82000781A69 /* DynamicNotificationButton.swift in Sources */, @@ -2139,6 +2359,7 @@ buildActionMask = 2147483647; files = ( FDC347442858C4C4001F2212 /* NotificationDispatch-Extension.swift in Sources */, + 1B5AFAFB2A52BD2F00F777E1 /* BackPanelController.swift in Sources */, FDC346B82858C43F001F2212 /* PopupReminder.swift in Sources */, FD17301C2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, FDC346D92858C458001F2212 /* InfoSection.swift in Sources */, @@ -2154,10 +2375,11 @@ FDC3471D2858C494001F2212 /* Notification-Extension.swift in Sources */, FDC346D82858C458001F2212 /* UserReplyType.swift in Sources */, FDC346A02858C421001F2212 /* UserNotificationController.swift in Sources */, + 1B5AFB722A52EEBE00F777E1 /* BackPanelWindow.swift in Sources */, FDC347222858C494001F2212 /* Decodable-Extensions.swift in Sources */, - FDC346F02858C464001F2212 /* NASegue.swift in Sources */, FDC3471C2858C494001F2212 /* UNNotificationAttachment-Extension.swift in Sources */, FDC346AA2858C42E001F2212 /* NALogger.swift in Sources */, + 1B5AFB9C2A52EF9C00F777E1 /* View-Extension.swift in Sources */, FDC347432858C4C4001F2212 /* AppDelegate.swift in Sources */, FDC346A22858C425001F2212 /* Context.swift in Sources */, FDC346B52858C43F001F2212 /* NotificationAccessoryElement.swift in Sources */, @@ -2169,10 +2391,12 @@ FDC346902858C405001F2212 /* NotificationDispatch.swift in Sources */, FD8A353D2863582600DA72CC /* EFCLController-Extension.swift in Sources */, FDC346DB2858C458001F2212 /* AppComponent.swift in Sources */, + 1B5AFBB02A52EFE600F777E1 /* Binding-Extension.swift in Sources */, FDC346C62858C445001F2212 /* ConfigurableParameters.swift in Sources */, FDC346D62858C458001F2212 /* Token.swift in Sources */, FDC346C52858C445001F2212 /* NotificationButton.swift in Sources */, FDC3471E2858C494001F2212 /* NSColor-Extension.swift in Sources */, + 1B5AFBA52A52EFC200F777E1 /* Collection-Extension.swift in Sources */, FDFCA5872857D798009C1880 /* NABTriggersTests.swift in Sources */, FDC346DA2858C458001F2212 /* NAMedia.swift in Sources */, FD1730112875B82000781A69 /* DynamicNotificationButton.swift in Sources */, @@ -2184,60 +2408,80 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B5AFB4C2A52EE3C00F777E1 /* PickerView.swift in Sources */, FDC346BD2858C440001F2212 /* PopupReminder.swift in Sources */, FDC346E22858C459001F2212 /* InfoSection.swift in Sources */, FDC346E02858C459001F2212 /* TaskObject.swift in Sources */, FD17301D2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, - FDC347072858C476001F2212 /* InfoPopOverViewController.swift in Sources */, FD8A353E2863582700DA72CC /* EFCLController-Extension.swift in Sources */, + 1B5AFB952A52EF9500F777E1 /* View-Extension.swift in Sources */, FDC346DD2858C459001F2212 /* SharedSettings.swift in Sources */, + 1B5AFB382A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */, FDC346BB2858C440001F2212 /* OnboardingData.swift in Sources */, + 1B5AFB482A52EE3C00F777E1 /* PlayerView.swift in Sources */, FDC346E52858C459001F2212 /* NAError.swift in Sources */, FDC346AB2858C42E001F2212 /* NALogger.swift in Sources */, + 1B5AFB8C2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */, FDC347102858C48A001F2212 /* FlippedStackView.swift in Sources */, FDC347112858C48A001F2212 /* HorizontalLine.swift in Sources */, - FDC3474B2858C4E8001F2212 /* PopUpViewController.swift in Sources */, FDC346A72858C42A001F2212 /* ReplyHandler.swift in Sources */, FDC346E12858C459001F2212 /* UserReplyType.swift in Sources */, FD0D02D628D9F8AE00A167B7 /* SystemAlertController.swift in Sources */, - FDC347062858C476001F2212 /* InfoPopOverStackItem.swift in Sources */, FDC347292858C494001F2212 /* Decodable-Extensions.swift in Sources */, + 1B5AFB402A52EE3C00F777E1 /* MarkdownView.swift in Sources */, FDC3473E2858C4AF001F2212 /* InteractiveEFCLContoller.swift in Sources */, + 1B5AFBC52A52FBD800F777E1 /* PopUpViewModel.swift in Sources */, FDC3474D2858C4E8001F2212 /* NotificationDispatch-Extension.swift in Sources */, FDC346972858C413001F2212 /* EFCLController.swift in Sources */, FDC347262858C494001F2212 /* NSScreen-Extension.swift in Sources */, FDC346982858C413001F2212 /* Environment.swift in Sources */, - FDC346F62858C472001F2212 /* TimerAccessoryView.swift in Sources */, FDC3469D2858C41B001F2212 /* TaskManager.swift in Sources */, FDC347232858C494001F2212 /* UNNotificationAttachment-Extension.swift in Sources */, + 1B5AFB882A52EEDF00F777E1 /* Icon.swift in Sources */, + 1B5AFB602A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */, + 1B5AFB3C2A52EE3C00F777E1 /* AccessoryView.swift in Sources */, FDC346BA2858C440001F2212 /* NotificationAccessoryElement.swift in Sources */, FDC346DE2858C459001F2212 /* LoadableNib.swift in Sources */, + 1B5AFB542A52EE3C00F777E1 /* MediaView.swift in Sources */, + 1B5AFB342A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */, FDC346A32858C425001F2212 /* Context.swift in Sources */, FDC347332858C49C001F2212 /* Utils.swift in Sources */, FDC346B92858C440001F2212 /* ProgressState.swift in Sources */, + 1B5AFB802A52EEDF00F777E1 /* StandardButton.swift in Sources */, + 1B5AFB302A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */, FDC346BC2858C440001F2212 /* NotificationObject.swift in Sources */, - FDC346F22858C472001F2212 /* VideoAccessoryView.swift in Sources */, - FDC346F32858C472001F2212 /* ProgressBarAccessoryView.swift in Sources */, FDC346E42858C459001F2212 /* AppComponent.swift in Sources */, - FDC346F42858C472001F2212 /* ImageAccessoryView.swift in Sources */, + 1B5AFB842A52EEDF00F777E1 /* NativeButton.swift in Sources */, + 1B5AFB7C2A52EEDF00F777E1 /* CircleButton.swift in Sources */, + 1B5AFB0F2A52EDB300F777E1 /* PickerItem.swift in Sources */, + 1B5AFB582A52EE3C00F777E1 /* ProgressBarView.swift in Sources */, + 1B5AFAF32A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, FDC346C82858C446001F2212 /* ConfigurableParameters.swift in Sources */, - FDC346F82858C472001F2212 /* CheckListAccessoryView.swift in Sources */, FDC347282858C494001F2212 /* Int-Extension.swift in Sources */, FDC346DF2858C459001F2212 /* Token.swift in Sources */, - FDC346F52858C472001F2212 /* MarkdownTextView.swift in Sources */, - FDC346FA2858C472001F2212 /* AccessoryView.swift in Sources */, FDC3474A2858C4E8001F2212 /* AppDelegate.swift in Sources */, FDC3470F2858C48A001F2212 /* NoBackgroundScroller.swift in Sources */, + 1B5AFBC22A52FBB900F777E1 /* PopUpView.swift in Sources */, + 1B5AFAFC2A52BD2F00F777E1 /* BackPanelController.swift in Sources */, + 1B5AFBB92A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, + 1B5AFB5C2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */, FD1730122875B82000781A69 /* DynamicNotificationButton.swift in Sources */, - FDC346F72858C472001F2212 /* HTMLAccessoryView.swift in Sources */, - FDC346FB2858C472001F2212 /* InputAccessoryView.swift in Sources */, + 1B5AFBBE2A52FBA700F777E1 /* BodyLabels.swift in Sources */, + 1B5AFB502A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */, + 1B5AFB0A2A52ED8900F777E1 /* ACVDecoder.swift in Sources */, + 1B5AFB052A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */, FDC347252858C494001F2212 /* NSColor-Extension.swift in Sources */, FDC347242858C494001F2212 /* Notification-Extension.swift in Sources */, FDC346C72858C446001F2212 /* NotificationButton.swift in Sources */, + 1B5AFB442A52EE3C00F777E1 /* HTMLView.swift in Sources */, FDC346912858C405001F2212 /* NotificationDispatch.swift in Sources */, FD1730082875B0D200781A69 /* PopupInteractiveEFCLController.swift in Sources */, - FDC346F92858C472001F2212 /* DropDownAccessoryView.swift in Sources */, + 1B5AFB642A52EE3C00F777E1 /* DatePickerView.swift in Sources */, FDC346E32858C459001F2212 /* NAMedia.swift in Sources */, + 1B5AFBA62A52EFC200F777E1 /* Collection-Extension.swift in Sources */, + 1B5AFB682A52EE3C00F777E1 /* InputView.swift in Sources */, + 1B5AFBB12A52EFE600F777E1 /* Binding-Extension.swift in Sources */, + 1B5AFB6D2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */, FDFCA5942857DC81009C1880 /* NAPTriggersTests.swift in Sources */, FDC347272858C494001F2212 /* String-Extension.swift in Sources */, ); @@ -2255,61 +2499,80 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FDC346FF2858C472001F2212 /* MarkdownTextView.swift in Sources */, + 1B5AFBC82A52FC5C00F777E1 /* OnboardingView.swift in Sources */, + 1B5AFB4D2A52EE3C00F777E1 /* PickerView.swift in Sources */, FDC346C22858C440001F2212 /* PopupReminder.swift in Sources */, FDC3472A2858C495001F2212 /* UNNotificationAttachment-Extension.swift in Sources */, FDC346EB2858C459001F2212 /* InfoSection.swift in Sources */, FDC347122858C48A001F2212 /* NoBackgroundScroller.swift in Sources */, FDC346E92858C459001F2212 /* TaskObject.swift in Sources */, + 1B5AFB962A52EF9500F777E1 /* View-Extension.swift in Sources */, + 1B5AFAFD2A52BD2F00F777E1 /* BackPanelController.swift in Sources */, FDC346E62858C459001F2212 /* SharedSettings.swift in Sources */, + 1B5AFB392A52EE3C00F777E1 /* VideoAccessoryView.swift in Sources */, FDC346C02858C440001F2212 /* OnboardingData.swift in Sources */, - FDC347092858C476001F2212 /* InfoPopOverViewController.swift in Sources */, + 1B5AFB492A52EE3C00F777E1 /* PlayerView.swift in Sources */, FDC346EE2858C459001F2212 /* NAError.swift in Sources */, + 1B5AFB8D2A52EEDF00F777E1 /* InfoSectionView.swift in Sources */, FDC346AC2858C42F001F2212 /* NALogger.swift in Sources */, - FDC347032858C472001F2212 /* DropDownAccessoryView.swift in Sources */, - FDC346FC2858C472001F2212 /* VideoAccessoryView.swift in Sources */, FDC346A82858C42A001F2212 /* ReplyHandler.swift in Sources */, - FDC347042858C472001F2212 /* AccessoryView.swift in Sources */, FDC347132858C48A001F2212 /* FlippedStackView.swift in Sources */, FDC346EA2858C459001F2212 /* UserReplyType.swift in Sources */, - FDC347532858C4F3001F2212 /* OnboardingPageViewController.swift in Sources */, FD1730132875B82000781A69 /* DynamicNotificationButton.swift in Sources */, - FDC346FE2858C472001F2212 /* ImageAccessoryView.swift in Sources */, FDC347142858C48A001F2212 /* HorizontalLine.swift in Sources */, FDC346992858C414001F2212 /* EFCLController.swift in Sources */, - FDC346F12858C465001F2212 /* NASegue.swift in Sources */, - FDC347012858C472001F2212 /* HTMLAccessoryView.swift in Sources */, - FDC347022858C472001F2212 /* CheckListAccessoryView.swift in Sources */, + 1B5AFB412A52EE3C00F777E1 /* MarkdownView.swift in Sources */, FDC3472B2858C495001F2212 /* Notification-Extension.swift in Sources */, + 1B5AFB062A52EC4400F777E1 /* SwiftUIButtonState.swift in Sources */, FDC347342858C49C001F2212 /* Utils.swift in Sources */, FDC3469A2858C414001F2212 /* Environment.swift in Sources */, FDC3469E2858C41C001F2212 /* TaskManager.swift in Sources */, FDC346BF2858C440001F2212 /* NotificationAccessoryElement.swift in Sources */, + 1B5AFB892A52EEDF00F777E1 /* Icon.swift in Sources */, FDC346E72858C459001F2212 /* LoadableNib.swift in Sources */, - FDC347052858C472001F2212 /* InputAccessoryView.swift in Sources */, + 1B5AFB612A52EE3C00F777E1 /* AccessoryViewWrapper.swift in Sources */, + 1B5AFB3D2A52EE3C00F777E1 /* AccessoryView.swift in Sources */, FD17301E2875B9A000781A69 /* InteractiveObjectProtocol.swift in Sources */, FDC346A42858C426001F2212 /* Context.swift in Sources */, + 1B5AFB552A52EE3C00F777E1 /* MediaView.swift in Sources */, + 1B5AFB352A52EE3C00F777E1 /* MarkdownTextView.swift in Sources */, + 1B5AFB102A52EDB300F777E1 /* PickerItem.swift in Sources */, FD8A353F2863582700DA72CC /* EFCLController-Extension.swift in Sources */, + 1B5AFBD12A52FC9800F777E1 /* PageViewModel.swift in Sources */, + 1B5AFB812A52EEDF00F777E1 /* StandardButton.swift in Sources */, FDC347552858C4F3001F2212 /* AppDelegate.swift in Sources */, + 1B5AFB312A52EE3C00F777E1 /* HTMLAccessoryView.swift in Sources */, FDC346BE2858C440001F2212 /* ProgressState.swift in Sources */, + 1B5AFBCB2A52FC7100F777E1 /* OnboardingViewModel.swift in Sources */, + 1B5AFB852A52EEDF00F777E1 /* NativeButton.swift in Sources */, + 1B5AFB7D2A52EEDF00F777E1 /* CircleButton.swift in Sources */, + 1B5AFAF42A52BCE700F777E1 /* ControlActionClosureProtocol.swift in Sources */, FDC346C12858C440001F2212 /* NotificationObject.swift in Sources */, + 1B5AFB592A52EE3C00F777E1 /* ProgressBarView.swift in Sources */, FDC3472E2858C495001F2212 /* String-Extension.swift in Sources */, FDC3472D2858C495001F2212 /* NSScreen-Extension.swift in Sources */, FDC347502858C4F3001F2212 /* NotificationDispatch-Extension.swift in Sources */, - FDC347082858C476001F2212 /* InfoPopOverStackItem.swift in Sources */, FDC346ED2858C459001F2212 /* AppComponent.swift in Sources */, FDC347302858C495001F2212 /* Decodable-Extensions.swift in Sources */, FD1730042875B02F00781A69 /* OnboardingInteractiveEFCLController.swift in Sources */, + 1B5AFBBA2A52F03F00F777E1 /* ActionTrampoline.swift in Sources */, + 1B5AFB5D2A52EE3C00F777E1 /* ProgressBarViewModel.swift in Sources */, + 1B5AFB0B2A52ED8900F777E1 /* ACVDecoder.swift in Sources */, + 1B5AFB512A52EE3C00F777E1 /* AccessoryViewSource.swift in Sources */, FDC346CA2858C446001F2212 /* ConfigurableParameters.swift in Sources */, - FDC347542858C4F3001F2212 /* OnboardingViewController.swift in Sources */, - FDC347002858C472001F2212 /* TimerAccessoryView.swift in Sources */, FDC346E82858C459001F2212 /* Token.swift in Sources */, FDC346C92858C446001F2212 /* NotificationButton.swift in Sources */, FDC3473F2858C4AF001F2212 /* InteractiveEFCLContoller.swift in Sources */, + 1B5AFB452A52EE3C00F777E1 /* HTMLView.swift in Sources */, FDC346922858C408001F2212 /* NotificationDispatch.swift in Sources */, FDC3472C2858C495001F2212 /* NSColor-Extension.swift in Sources */, + 1B5AFB652A52EE3C00F777E1 /* DatePickerView.swift in Sources */, FDC346EC2858C459001F2212 /* NAMedia.swift in Sources */, - FDC346FD2858C472001F2212 /* ProgressBarAccessoryView.swift in Sources */, + 1B5AFBA72A52EFC200F777E1 /* Collection-Extension.swift in Sources */, + 1B5AFB692A52EE3C00F777E1 /* InputView.swift in Sources */, + 1B5AFBCE2A52FC8400F777E1 /* PageView.swift in Sources */, + 1B5AFBB22A52EFE600F777E1 /* Binding-Extension.swift in Sources */, + 1B5AFB6E2A52EEB700F777E1 /* BackPanelWindow.swift in Sources */, FDFCA5B02857DD10009C1880 /* NAOTriggersTests.swift in Sources */, FDC3472F2858C495001F2212 /* Int-Extension.swift in Sources */, ); @@ -2444,7 +2707,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Alerts/Info.plist"; @@ -2452,8 +2716,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.alert; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2473,7 +2737,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Alerts/Info.plist"; @@ -2481,8 +2746,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.alert; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2502,7 +2767,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Popups/Info.plist"; @@ -2510,8 +2776,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.popup; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2531,7 +2797,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Popups/Info.plist"; @@ -2539,8 +2806,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.popup; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2560,7 +2827,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Onboarding/Info.plist"; @@ -2568,8 +2836,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.onboarding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2588,7 +2856,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Onboarding/Info.plist"; @@ -2596,8 +2865,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.onboarding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2616,7 +2885,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Banners/Info.plist"; @@ -2624,8 +2894,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.banner; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2645,7 +2915,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Notification Agent Banners/Info.plist"; @@ -2653,8 +2924,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier.banner; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2699,7 +2970,8 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2718,8 +2990,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 3.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2763,7 +3035,8 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2776,8 +3049,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 3.0.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -2795,7 +3068,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; GCC_GENERATE_TEST_COVERAGE_FILES = YES; @@ -2804,8 +3078,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2824,7 +3098,8 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 97; + CURRENT_PROJECT_VERSION = 104; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; GCC_GENERATE_TEST_COVERAGE_FILES = YES; @@ -2833,8 +3108,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 2.9.1; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 3.0.0; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.ibm.cio.notifier; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2851,10 +3126,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Core-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2872,10 +3148,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Core-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2893,10 +3170,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Alert-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2914,10 +3192,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Alert-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2935,10 +3214,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Banner-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2956,10 +3236,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Banner-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2977,10 +3258,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Popup-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2998,10 +3280,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Popup-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3018,10 +3301,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Popup-UI-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3038,10 +3322,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Popup-UI-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3059,10 +3344,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Onboarding-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3080,10 +3366,11 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Onboarding-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3100,9 +3387,10 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Onboarding-UI-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3118,9 +3406,10 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 96; + DEAD_CODE_STRIPPING = YES; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ibm.cio.be.Notification-Agent-Onboarding-UI-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Notification Agent.xcodeproj/xcshareddata/xcschemes/IBM Notifier Alert.xcscheme b/Notification Agent.xcodeproj/xcshareddata/xcschemes/IBM Notifier Alert.xcscheme index dbdd64b..e5df50f 100644 --- a/Notification Agent.xcodeproj/xcshareddata/xcschemes/IBM Notifier Alert.xcscheme +++ b/Notification Agent.xcodeproj/xcshareddata/xcschemes/IBM Notifier Alert.xcscheme @@ -1,6 +1,6 @@ Void) { - completionHandler([.badge, .sound, .alert]) + completionHandler([.badge, .sound, .banner]) } } diff --git a/Shared/Extensions/Binding-Extension.swift b/Shared/Extensions/Binding-Extension.swift new file mode 100644 index 0000000..8cbd9ce --- /dev/null +++ b/Shared/Extensions/Binding-Extension.swift @@ -0,0 +1,25 @@ +// +// Binding-Extension.swift +// Notification Agent +// +// Created by Simone Martorelli on 11/01/23. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI + +extension Binding { + + /// When the `Binding`'s `wrappedValue` changes, the given closure is executed. + /// - Parameter closure: Chunk of code to execute whenever the value changes. + /// - Returns: New `Binding`. + func onUpdate(_ closure: @escaping () -> Void) -> Binding { + Binding(get: { + wrappedValue + }, set: { newValue in + wrappedValue = newValue + closure() + }) + } +} diff --git a/Shared/Extensions/Collection-Extension.swift b/Shared/Extensions/Collection-Extension.swift new file mode 100644 index 0000000..700b969 --- /dev/null +++ b/Shared/Extensions/Collection-Extension.swift @@ -0,0 +1,16 @@ +// +// Collection-Extension.swift +// Notification Agent +// +// Created by Simone Martorelli on 10/05/2023. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import Foundation + +extension Collection where Indices.Iterator.Element == Index { + subscript (safe index: Index) -> Iterator.Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Shared/Extensions/String-Extension.swift b/Shared/Extensions/String-Extension.swift index 7418229..d42903d 100644 --- a/Shared/Extensions/String-Extension.swift +++ b/Shared/Extensions/String-Extension.swift @@ -156,3 +156,13 @@ extension String { return result } } + +extension String { + func heightThatFitsWidth(_ width: CGFloat) -> CGFloat { + return NSTextField(wrappingLabelWithString: self).sizeThatFits(NSSize(width: width, height: 0)).height + } + + func widthThatFitsHeight(_ height: CGFloat) -> CGFloat { + return NSTextField(wrappingLabelWithString: self).sizeThatFits(NSSize(width: 0, height: height)).width + } +} diff --git a/Shared/Extensions/UNNotificationAttachment-Extension.swift b/Shared/Extensions/UNNotificationAttachment-Extension.swift index 682a60b..dffb8e5 100644 --- a/Shared/Extensions/UNNotificationAttachment-Extension.swift +++ b/Shared/Extensions/UNNotificationAttachment-Extension.swift @@ -7,7 +7,6 @@ // SPDX-License-Identifier: Apache2.0 // -import Foundation import UserNotifications extension UNNotificationAttachment { diff --git a/Shared/Extensions/View-Extension.swift b/Shared/Extensions/View-Extension.swift new file mode 100644 index 0000000..771fc84 --- /dev/null +++ b/Shared/Extensions/View-Extension.swift @@ -0,0 +1,16 @@ +// +// View-Extension.swift +// Notification Agent +// +// Created by Simone Martorelli on 06/02/23. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import SwiftUI + +extension View { + func compatibleAccessibilityLabel(label: String) -> some View { + return self.accessibilityLabel(label) + } +} diff --git a/Shared/Model/Common/ACVDecoder.swift b/Shared/Model/Common/ACVDecoder.swift new file mode 100644 index 0000000..a4e2132 --- /dev/null +++ b/Shared/Model/Common/ACVDecoder.swift @@ -0,0 +1,80 @@ +// +// ACVDecoder.swift +// Notification Agent +// +// Created by Simone Martorelli on 15/12/22. +// Copyright © 2023 IBM. All rights reserved. +// SPDX-License-Identifier: Apache2.0 +// + +import Foundation + +protocol ACVDecodable { + init(stringLiteral: String) +} + +protocol AVCIterable: CaseIterable, CodingKey {} + +struct ACVDecoder { + + var codingKeys: any Collection + + // swiftlint:disable force_cast + + init(codingKeys: T.Type) where T : AVCIterable { + self.codingKeys = codingKeys.allCases as! [any CodingKey] + } + + // swiftlint:enable force_cast + + // MARK: - Private Methods + + func decode(key: CodingKey, ofType type: T.Type, from payload: String) throws -> T where T : ACVDecodable { + guard codingKeys.contains(where: { $0.stringValue == key.stringValue }) else { + throw NAError.efclController(type: .invalidAccessoryViewPayload) + } + + var splittedStrings = payload.split(separator: "/") + guard splittedStrings.count > 0 else { throw NAError.efclController(type: .invalidAccessoryViewPayload) } + splittedStrings.reverse() + + for index in 0.. OnboardingData? { + static func loadOnboardingPayload(_ payload: String) throws -> OnboardingData { if payload.isValidURL, let url = URL(string: payload) { return try OnboardingData(from: url) } else if FileManager.default.fileExists(atPath: payload) { @@ -318,6 +339,8 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { case retainValues case showSuppressionButton case workflow + case backgroundPanel + case isMovable } required public init(from decoder: Decoder) throws { @@ -333,9 +356,9 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { self.titleFontSize = try container.decodeIfPresent(String.self, forKey: .titleFontSize) self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) self.iconPath = try container.decodeIfPresent(String.self, forKey: .iconPath) - self.notificationImage = try container.decodeIfPresent(String.self, forKey: .notificationAttachment) self.iconWidth = try container.decodeIfPresent( String.self, forKey: .iconWidth) self.iconHeight = try container.decodeIfPresent( String.self, forKey: .iconHeight) + self.notificationImage = try container.decodeIfPresent(String.self, forKey: .notificationAttachment) self.accessoryViews = try container.decodeIfPresent([NotificationAccessoryElement].self, forKey: .accessoryViews) self.mainButton = try container.decode(NotificationButton.self, forKey: .mainButton) self.secondaryButton = try container.decodeIfPresent(NotificationButton.self, forKey: .secondaryButton) @@ -358,6 +381,10 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { if let workflowRawValue = try container.decodeIfPresent(String.self, forKey: .workflow) { self.workflow = PredefinedWorkflow(rawValue: workflowRawValue) } + if let backgroundPanelRawValue = try container.decodeIfPresent(String.self, forKey: .backgroundPanel) { + self.backgroundPanel = BackgroundPanelStyle(rawValue: backgroundPanelRawValue) + } + self.isMovable = try container.decode(Bool.self, forKey: .isMovable) } public func encode(to encoder: Encoder) throws { @@ -392,6 +419,9 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { try container.encodeIfPresent(self.position?.rawValue, forKey: .position) try container.encodeIfPresent(self.popupReminder, forKey: .popupReminder) try container.encodeIfPresent(self.workflow?.rawValue, forKey: .workflow) + try container.encodeIfPresent(self.backgroundPanel?.rawValue, forKey: .backgroundPanel) + try container.encodeIfPresent(self.isMovable, forKey: .isMovable) + } // MARK: Codable protocol conformity - END @@ -399,6 +429,8 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { public static var supportsSecureCoding: Bool = true + // swiftlint:disable function_body_length + public func encode(with coder: NSCoder) { coder.encode(self.type.rawValue, forKey: NOCodingKeys.type.rawValue) coder.encode(self.notificationID, forKey: NOCodingKeys.notificationID.rawValue) @@ -483,8 +515,15 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { if let workflowRawValue = self.workflow?.rawValue { coder.encode(workflowRawValue, forKey: NOCodingKeys.workflow.rawValue) } + if let backgroundPanelRawValue = self.backgroundPanel?.rawValue { + coder.encode(backgroundPanelRawValue, forKey: NOCodingKeys.backgroundPanel.rawValue) + } + let number = NSNumber(booleanLiteral: isMovable) + coder.encode(number, forKey: NOCodingKeys.isMovable.rawValue) } + // swiftlint:enable function_body_length + public required init?(coder: NSCoder) { self.identifier = UUID() self.type = UIType(rawValue: coder.decodeObject(of: NSString.self, forKey: NOCodingKeys.type.rawValue) as String? ?? "none") ?? .popup @@ -519,7 +558,13 @@ public final class NotificationObject: NSObject, Codable, NSSecureCoding { if let workflowRawValue = coder.decodeObject(of: NSString.self, forKey: NOCodingKeys.workflow.rawValue) { self.workflow = PredefinedWorkflow(rawValue: workflowRawValue as String) } + if let backgroundPanelRawValue = coder.decodeObject(of: NSString.self, forKey: NOCodingKeys.backgroundPanel.rawValue) { + self.backgroundPanel = BackgroundPanelStyle(rawValue: backgroundPanelRawValue as String) + } + self.isMovable = coder.decodeObject(of: NSNumber.self, forKey: NOCodingKeys.isMovable.rawValue) as? Bool ?? true } // MARK: - NSSecureCoding protocol conformity - END } + +// swiftlint:enable type_body_length file_length diff --git a/Shared/Model/UIObjects/OnboardingData.swift b/Shared/Model/UIObjects/OnboardingData.swift index c894430..bc5f43b 100644 --- a/Shared/Model/UIObjects/OnboardingData.swift +++ b/Shared/Model/UIObjects/OnboardingData.swift @@ -14,7 +14,7 @@ import AVFoundation /// This object describe the data defined for the onboarding. public final class OnboardingData: Codable { /// An array of pages. - var pages: [OnboardingPage] + var pages: [InteractiveOnboardingPage] var progressBarPayload: String? // MARK: - Codable protocol conformity - START @@ -26,13 +26,8 @@ public final class OnboardingData: Codable { required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: ODCodingKeys.self) - if let interactiveOnboardingPages = try? container.decode([InteractiveOnboardingPage].self, forKey: .pages), - let legacyOnboardingPages = try? container.decode([LegacyOnboardingPage].self, forKey: .pages) { - if interactiveOnboardingPages.contains(where: { $0.accessoryViews != nil }) { - self.pages = interactiveOnboardingPages - } else { - self.pages = legacyOnboardingPages - } + if let interactiveOnboardingPages = try? container.decode([InteractiveOnboardingPage].self, forKey: .pages) { + self.pages = interactiveOnboardingPages } else { throw NAError.dataFormat(type: .invalidJSONPayload) } @@ -45,101 +40,14 @@ public final class OnboardingData: Codable { var container = encoder.container(keyedBy: ODCodingKeys.self) try container.encodeIfPresent(progressBarPayload, forKey: .progressBarPayload) - guard let pages = self.pages as? [LegacyOnboardingPage] else { - guard let pages = self.pages as? [InteractiveOnboardingPage] else { - return - } - try container.encode(pages, forKey: .pages) - return - } try container.encode(pages, forKey: .pages) } // MARK: Codable protocol conformity - END } -protocol OnboardingPage: Codable { - // MARK: - Variables - - /// The title of the page. - var title: String? { get } - /// The subtitle of the page. - var subtitle: String? { get } - /// The body of the page. - var body: String? { get } - /// The info section showed with the click on the info button. - var infoSection: InfoSection? { get } - /// The path for a custom icon on top of the page - var topIcon: String? { get } - - func isValidPage() -> Bool -} - -/// This object describe a legacy onboarding page deprecated starting from v2.6.0. -final class LegacyOnboardingPage: OnboardingPage { - - // MARK: - Variables - - /// The title of the page. - var title: String? - /// The subtitle of the page. - var subtitle: String? - /// The body of the page. - var body: String? - /// The info section showed with the click on the info button. - var infoSection: InfoSection? - /// The path for a custom icon on top of the page - var topIcon: String? - /// The media type the desired media. - var pageMedia: NAMedia? - - public func isValidPage() -> Bool { - return title != nil || subtitle != nil || body != nil || pageMedia != nil - } - - // MARK: - Codable protocol conformity - START - - enum NOCodingKeys: String, CodingKey { - case title - case subtitle - case body - case infoSection - case topIcon - case mediaType - case mediaPayload - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: NOCodingKeys.self) - - self.title = try container.decodeIfPresent(String.self, forKey: .title) - self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) - self.body = try container.decodeIfPresent(String.self, forKey: .body) - self.infoSection = try container.decodeIfPresent(InfoSection.self, forKey: .infoSection) - self.topIcon = try container.decodeIfPresent(String.self, forKey: .topIcon) - if let mediaType = try container.decodeIfPresent(String.self, forKey: .mediaType), - let mediaPayload = try container.decodeIfPresent(String.self, forKey: .mediaPayload) { - self.pageMedia = NAMedia(type: mediaType, from: mediaPayload) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: NOCodingKeys.self) - - try container.encodeIfPresent(self.title, forKey: .title) - try container.encodeIfPresent(self.subtitle, forKey: .subtitle) - try container.encodeIfPresent(self.body, forKey: .body) - try container.encodeIfPresent(self.infoSection, forKey: .infoSection) - try container.encodeIfPresent(self.topIcon, forKey: .topIcon) - try container.encodeIfPresent(self.pageMedia?.mediaType.rawValue, forKey: .mediaType) - try container.encodeIfPresent(self.pageMedia?.mediaPayload, forKey: .mediaPayload) - } - - // MARK: Codable protocol conformity - END -} - /// This object describe an interactive onboarding page. -final class InteractiveOnboardingPage: OnboardingPage { +final class InteractiveOnboardingPage: Codable { // MARK: - Variables @@ -157,6 +65,12 @@ final class InteractiveOnboardingPage: OnboardingPage { var singleChange: Bool? /// The list of accessory views for the page. var accessoryViews: [[NotificationAccessoryElement]]? + /// A tertiary button available on the page. + var tertiaryButton: NotificationButton? + /// Custom label for the primary button. + var primaryButtonLabel: String? + /// Custom label for the secondary button. + var secondaryButtonLabel: String? public func isValidPage() -> Bool { return title != nil || subtitle != nil || body != nil || (accessoryViews != nil && !(accessoryViews?.isEmpty ?? true)) diff --git a/Shared/Model/UIObjects/ProgressState.swift b/Shared/Model/UIObjects/ProgressState.swift index d6f557b..a03232f 100644 --- a/Shared/Model/UIObjects/ProgressState.swift +++ b/Shared/Model/UIObjects/ProgressState.swift @@ -35,34 +35,36 @@ struct ProgressState: Equatable { self.isUserInterruptionAllowed = currentState?.isUserInterruptionAllowed ?? false self.exitOnCompletion = currentState?.exitOnCompletion ?? false guard let payload = payload else { return } - let splittedStrings = payload.split(separator: "/") - for string in splittedStrings { - guard let argument = string.split(separator: " ", maxSplits: 1).first?.lowercased(), - var value = string.split(separator: " ", maxSplits: 1).last else { continue } - if value.last == " " { - value.removeLast() - } + guard payload.lowercased() != "end" else { + self.percent = 100 + return + } + var splittedStrings = payload.split(separator: "/") + splittedStrings.reverse() + for index in 0..