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.. Void) {
+ let trampoline = ActionTrampoline(action: action)
+ self.target = trampoline
+ self.action = #selector(ActionTrampoline.action(sender:))
+ objc_setAssociatedObject(self, &CACProtocolAssociatedObjectKey, trampoline, .OBJC_ASSOCIATION_RETAIN)
+ }
+}
+
+extension NSControl: ControlActionClosureProtocol {}
diff --git a/Shared/Resources/en.lproj/Localizable.strings b/Shared/Resources/en.lproj/Localizable.strings
index 0fd95b5..4d87af4 100644
--- a/Shared/Resources/en.lproj/Localizable.strings
+++ b/Shared/Resources/en.lproj/Localizable.strings
@@ -1,4 +1,4 @@
-/*
+/*
Localizable.strings
Notification Agent
@@ -23,37 +23,12 @@
"onboarding_page_close_button" = "Close";
// MARK: - Accessibility
-"popup_accessibility_label_title" = "Pop-up title.";
-"popup_accessibility_label_subtitle" = "Pop-up subtitle.";
-"popup_accessibility_button_main" = "Main button of the pop-up window. It is destructive for the pop-up.";
-"popup_accessibility_button_disabled" = "Disabled.";
-"popup_accessibility_button_secondary" = "Secondary button of the pop-up window. It is destructive for the pop-up.";
-"popup_accessibility_button_tertiary" = "Tertiary button of the pop-up window.";
-"popup_accessibility_button_info" = "Info button of the pop-up window.";
-"popup_accessibility_image_left" = "Icon of the pop-up window.";
-"popup_accessibility_stackview_body" = "This is the body of the pop-up window.";
-"onboarding_accessibility_button_right_continue" = "Go to the next onboarding page.";
-"onboarding_accessibility_button_right_close" = "Close the onboarding window.";
-"onboarding_accessibility_button_left" = "Go to the previous onboarding page.";
-"onboarding_accessibility_button_center" = "Info button of the onboarding page.";
-"onboarding_accessibility_image_top" = "Top icon of the onboarding page.";
-"onboarding_accessibility_stackview_body" = "This is the body of the onboarding page.";
-"accessory_view_accessibility_markdown_textview" = "";
-"accessory_view_accessibility_html_textview" = "";
-"accessory_view_accessibility_checklist_title" = "Title of the checklist.";
-"accessory_view_accessibility_checklist_liststackview" = "Body of the checklist.";
-"accessory_view_accessibility_checklist_liststackview_element" = "Element of the checklist.";
-"accessory_view_accessibility_timer_label" = "";
-"accessory_view_accessibility_image_imageview" = "";
-"accessory_view_accessibility_video_playerView" = "";
-"accessory_view_accessibility_progressbar_toplabel" = "Top label of the progress bar.";
-"accessory_view_accessibility_progressbar_bar" = "Progress bar.";
-"accessory_view_accessibility_progressbar_bottomlabel" = "Bottom label of the progress bar.";
-"accessory_view_accessibility_input_secured_required" = "Require an input value.";
-"accessory_view_accessibility_input_secured" = "";
-"accessory_view_accessibility_input_required" = "Require an input value.";
-"accessory_view_accessibility_input" = "";
-"accessory_view_accessibility_input_title" = "Input field title.";
-"accessory_view_accessibility_dropdown" = "Dropdown selector.";
-"accessory_view_accessibility_dropdown_title" = "Dropdown selector title.";
+"help_button_label" = "Help";
+"warning_button_label" = "Warning";
+"button_hint_link" = "A click on this button will open a link";
+"button_hint_text" = "A click on this button will show additional text";
+"button_hint_link_destructive" = "A click on this button will open a link and close the window";
+"button_hint_destructive" = "A click on this button will close the window";
+"onboarding_button_primary_hint" = "A click on this button will show a new page";
+"onboarding_button_secondary_hint" = "A click on this button will show the previous page";
diff --git a/Shared/Utils/ActionTrampoline.swift b/Shared/Utils/ActionTrampoline.swift
new file mode 100644
index 0000000..ecd2585
--- /dev/null
+++ b/Shared/Utils/ActionTrampoline.swift
@@ -0,0 +1,28 @@
+//
+// ActionTrampoline.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 09/02/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+// Credits: Sindre Sorhus (sindresorhus)
+//
+
+import SwiftUI
+
+final class ActionTrampoline: NSObject {
+ let action: (T) -> Void
+
+ init(action: @escaping (T) -> Void) {
+ self.action = action
+ }
+
+ // swiftlint:disable force_cast
+
+ @objc
+ func action(sender: AnyObject) {
+ action(sender as! T)
+ }
+
+ // swiftlint:enable force_cast
+}
diff --git a/Shared/Resources/Utils.swift b/Shared/Utils/Utils.swift
similarity index 88%
rename from Shared/Resources/Utils.swift
rename to Shared/Utils/Utils.swift
index d8eeb50..a3108c1 100644
--- a/Shared/Resources/Utils.swift
+++ b/Shared/Utils/Utils.swift
@@ -12,6 +12,7 @@ import Foundation
struct Utils {
// MARK: - Enums
+
enum InterfaceStyle: String {
case dark = "Dark"
case light = "Light"
@@ -31,6 +32,7 @@ struct Utils {
case userDismissedNotification
case userDismissedOnboarding
case userFinishedOnboarding
+ case userDismissedPopup
case invalidArgumentsSyntax
case invalidArgumentFormat
case internalError
@@ -45,6 +47,15 @@ struct Utils {
static var currentInterfaceStyle: InterfaceStyle {
return InterfaceStyle()
}
+ static var UISoundEffectStatusEnable: Bool {
+ let command = "defaults read -g com.apple.sound.uiaudio.enabled"
+ if let result = Self.runCommand(command, needAuthorize: false) {
+ let output = result.trimmingCharacters(in: .newlines)
+ return Int(output) != 0
+ } else {
+ return true
+ }
+ }
// MARK: - Static Methods
@@ -61,7 +72,10 @@ struct Utils {
let appleScript = NSAppleScript(source: script)
var error: NSDictionary?
+ let group = DispatchGroup()
+ group.enter()
let result = appleScript!.executeAndReturnError(&error)
+ group.leave()
if let error = error {
NALogger.shared.log("Apple script returned error: %{public}@", [error.description])
return nil
@@ -76,7 +90,7 @@ struct Utils {
}
static func write(_ dictionary: NSDictionary, to fileName: String) {
- let mainDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".ibmnotifier")
+ let mainDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".mainotifications")
if !FileManager.default.fileExists(atPath: mainDirectory.path) {
do {
try FileManager.default.createDirectory(at: mainDirectory, withIntermediateDirectories: false, attributes: nil)
@@ -93,7 +107,7 @@ struct Utils {
}
static func delete(_ fileName: String) {
- let mainDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".ibmnotifier")
+ let mainDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".mainotifications")
if FileManager.default.fileExists(atPath: mainDirectory.path) {
let filePath = mainDirectory.appendingPathComponent(fileName)
if FileManager.default.fileExists(atPath: filePath.path) && FileManager.default.isDeletableFile(atPath: filePath.path) {
@@ -120,7 +134,7 @@ struct Utils {
exit(2)
case .tertiaryButtonClicked:
exit(3)
- case .userDismissedNotification, .userDismissedOnboarding:
+ case .userDismissedNotification, .userDismissedOnboarding, .userDismissedPopup:
exit(239)
case .invalidArgumentsSyntax:
exit(250)
diff --git a/Shared/Views/AccessoryViews/AccessoryView.swift b/Shared/Views/AccessoryViews/AppKit/AccessoryView.swift
similarity index 92%
rename from Shared/Views/AccessoryViews/AccessoryView.swift
rename to Shared/Views/AccessoryViews/AppKit/AccessoryView.swift
index 2a4dd96..1073ec5 100644
--- a/Shared/Views/AccessoryViews/AccessoryView.swift
+++ b/Shared/Views/AccessoryViews/AppKit/AccessoryView.swift
@@ -24,20 +24,20 @@ class AccessoryView: NSView {
var secondaryButtonState: ButtonState = .enabled
weak var delegate: AccessoryViewDelegate?
- // MARK: - Instance methods
+ // MARK: - Instance Methods
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
adjustViewSize()
configureAccessibilityElements()
}
-
- // MARK: - Public methods
+
+ // MARK: - Public Methods
/// Adjust the view size based on the superview width and on the video height.
func adjustViewSize() {}
-
+
func configureAccessibilityElements() {}
-
+
func displayStoredData(_ data: String) {}
}
diff --git a/Shared/Views/AccessoryViews/HTMLAccessoryView.swift b/Shared/Views/AccessoryViews/AppKit/HTMLAccessoryView.swift
similarity index 95%
rename from Shared/Views/AccessoryViews/HTMLAccessoryView.swift
rename to Shared/Views/AccessoryViews/AppKit/HTMLAccessoryView.swift
index 0df9f4a..6cbe6e0 100644
--- a/Shared/Views/AccessoryViews/HTMLAccessoryView.swift
+++ b/Shared/Views/AccessoryViews/AppKit/HTMLAccessoryView.swift
@@ -12,7 +12,7 @@ import Cocoa
/// This view show and handle hyperlinks inside the input text.
final class HTMLAccessoryView: AccessoryView {
- // MARK: - Private variables
+ // MARK: - Private Variables
private var scrollView: NSScrollView!
private var textView: NSTextView!
@@ -107,14 +107,14 @@ final class HTMLAccessoryView: AccessoryView {
textColor = .labelColor
}
self.setText(text)
- self.identifier = NSUserInterfaceItemIdentifier("html_accessoryview")
+ self.identifier = NSUserInterfaceItemIdentifier("html_accessory_view")
}
required init?(coder: NSCoder) {
return nil
}
- // MARK: - Instance methods
+ // MARK: - Instance Methods
override func adjustViewSize() {
let textField = NSTextField(labelWithAttributedString: self.textView.attributedString())
@@ -139,12 +139,10 @@ final class HTMLAccessoryView: AccessoryView {
}
override func configureAccessibilityElements() {
- self.setAccessibilityElement(false)
- textView.setAccessibilityLabel("accessory_view_accessibility_html_textview".localized)
- textView.setAccessibilityIdentifier("accessory_view_accessibility_html_textview")
+
}
- // MARK: - Private methods
+ // MARK: - Private Methods
/// Translate the html string in a Swift attributed string.
/// - Parameter text: the text that needs to be displayed.
diff --git a/Shared/Views/AccessoryViews/MarkdownTextView.swift b/Shared/Views/AccessoryViews/AppKit/MarkdownTextView.swift
similarity index 86%
rename from Shared/Views/AccessoryViews/MarkdownTextView.swift
rename to Shared/Views/AccessoryViews/AppKit/MarkdownTextView.swift
index 9f62966..448df76 100644
--- a/Shared/Views/AccessoryViews/MarkdownTextView.swift
+++ b/Shared/Views/AccessoryViews/AppKit/MarkdownTextView.swift
@@ -9,26 +9,28 @@
import Cocoa
import SwiftyMarkdown
+import SwiftUI
/// This view show and handle hyperlinks inside the input text.
final class MarkdownTextView: AccessoryView {
- // MARK: - Private variables
+ // MARK: - Private Variables
- private var scrollView: NSScrollView!
- var textView: NSTextView!
- private var _containerWidth: CGFloat?
- private var containerWidth: CGFloat {
- return _containerWidth ?? (self.superview?.bounds.width ?? 0)
+ private var _containerWidth: CGFloat {
+ return containerWidth ?? (self.superview?.bounds.width ?? 0)
}
private var scrollViewHeightAnchor: NSLayoutConstraint!
private var textViewWidthAnchor: NSLayoutConstraint!
private var textViewHeightAnchor: NSLayoutConstraint!
+ private var _textColor: NSColor?
+ private var _textViewBackgroundColor: NSColor?
// MARK: - Variables
+ var textView: NSTextView!
+ var containerWidth: CGFloat?
+ var scrollView: NSScrollView!
var maxViewHeight: CGFloat
- private var _textColor: NSColor?
var textColor: NSColor? {
get {
return _textColor
@@ -45,7 +47,6 @@ final class MarkdownTextView: AccessoryView {
self.textView.textStorage?.setAttributedString(attributedString)
}
}
- private var _textViewBackgroundColor: NSColor?
var textViewBackgroundColor: NSColor? {
get {
return _textViewBackgroundColor
@@ -71,7 +72,7 @@ final class MarkdownTextView: AccessoryView {
alignment: NSTextAlignment = .left,
containerWidth: CGFloat? = nil) {
self.maxViewHeight = maxViewHeight
- self._containerWidth = containerWidth
+ self.containerWidth = containerWidth
super.init(frame: .zero)
let textStorage = NSTextStorage()
@@ -86,6 +87,7 @@ final class MarkdownTextView: AccessoryView {
textView.isEditable = false
textView.isSelectable = true
textView.translatesAutoresizingMaskIntoConstraints = false
+ textView.setAccessibilityHidden(true)
scrollView = NSScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
@@ -95,6 +97,7 @@ final class MarkdownTextView: AccessoryView {
scrollView.verticalScrollElasticity = .none
scrollView.documentView = textView
scrollView.drawsBackground = false
+ scrollView.setAccessibilityHidden(true)
self.addSubview(scrollView)
@@ -111,28 +114,28 @@ final class MarkdownTextView: AccessoryView {
textColor = .labelColor
}
self.setText(text)
- self.identifier = NSUserInterfaceItemIdentifier("markdown_accessoryview")
+ self.identifier = NSUserInterfaceItemIdentifier("markdown_accessory_view")
}
required init?(coder: NSCoder) {
return nil
}
- // MARK: - Instance methods
+ // MARK: - Instance Methods
override func adjustViewSize() {
let textField = NSTextField(labelWithAttributedString: self.textView.attributedString())
textField.lineBreakMode = .byCharWrapping
textField.sizeToFit()
- let textViewHeight = textField.sizeThatFits(.init(width: max(containerWidth-18, 0),
+ let textViewHeight = textField.sizeThatFits(.init(width: max(_containerWidth-18, 0),
height: 0)).height
- let textViewSize = CGSize(width: containerWidth, height: textViewHeight)
+ let textViewSize = CGSize(width: _containerWidth, height: textViewHeight)
let scrollViewHeight = min(textViewSize.height, maxViewHeight)
scrollViewHeightAnchor?.isActive = false
scrollViewHeightAnchor = scrollView.heightAnchor.constraint(equalToConstant: scrollViewHeight)
scrollViewHeightAnchor?.isActive = true
textViewWidthAnchor?.isActive = false
- textViewWidthAnchor = textView.widthAnchor.constraint(equalToConstant: max(containerWidth-12, 0))
+ textViewWidthAnchor = textView.widthAnchor.constraint(equalToConstant: max(_containerWidth-12, 0))
textViewWidthAnchor?.isActive = true
textViewHeightAnchor?.isActive = false
textViewHeightAnchor = textView.heightAnchor.constraint(equalToConstant: textViewSize.height)
@@ -141,30 +144,34 @@ final class MarkdownTextView: AccessoryView {
textView.textContainer?.size = CGSize(width: textViewSize.width-12, height: textViewSize.height)
}
- override func configureAccessibilityElements() {
- self.setAccessibilityElement(false)
- textView.setAccessibilityLabel("accessory_view_accessibility_markdown_textview".localized)
- textView.setAccessibilityIdentifier("accessory_view_accessibility_markdown_textview")
- }
-
- // MARK: - Private methods
-
/// Set the text and eventually handle hyperlinks.
/// - Parameter text: the text that needs to be displayed.
- private func setText(_ text: String) {
+ func setText(_ text: String) {
let markdownText = SwiftyMarkdown(string: text)
markdownText.setFontColorForAllStyles(with: textView.drawsBackground ? .black : .labelColor)
+
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.code.color = .gray
markdownText.code.fontName = "CourierNewPSMT"
+ markdownText.link.color = .linkColor
+
+ markdownText.blockquotes.color = .gray
+ markdownText.blockquotes.fontStyle = .italic
+
+ markdownText.bullet = "•"
+
let attributedString = markdownText.attributedString()
self.textView.textStorage?.setAttributedString(attributedString)
+ self.setAccessibilityValue(attributedString.string)
+ self.setAccessibilityRole(.staticText)
}
}
diff --git a/Shared/Views/AccessoryViews/VideoAccessoryView.swift b/Shared/Views/AccessoryViews/AppKit/VideoAccessoryView.swift
similarity index 95%
rename from Shared/Views/AccessoryViews/VideoAccessoryView.swift
rename to Shared/Views/AccessoryViews/AppKit/VideoAccessoryView.swift
index 70c9e02..ee17a4f 100644
--- a/Shared/Views/AccessoryViews/VideoAccessoryView.swift
+++ b/Shared/Views/AccessoryViews/AppKit/VideoAccessoryView.swift
@@ -9,11 +9,12 @@
import Cocoa
import AVKit
+import SwiftUI
/// This view show a video loaded from a file or an URL.
final class VideoAccessoryView: AccessoryView {
- // MARK: - Private variables
+ // MARK: - Private Variables
private var playerView: AVPlayerView!
private var videoResolution: CGSize!
@@ -39,6 +40,7 @@ final class VideoAccessoryView: AccessoryView {
self.media = media
self._containerWidth = containerWidth
super.init(frame: .zero)
+
playerView = AVPlayerView()
playerView.translatesAutoresizingMaskIntoConstraints = false
playerView.videoGravity = .resizeAspect
@@ -48,6 +50,7 @@ final class VideoAccessoryView: AccessoryView {
playerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
playerView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
playerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
+
playerView.player = media.player!
playerView.player?.allowsExternalPlayback = false
playerView.allowsPictureInPicturePlayback = true
@@ -57,14 +60,14 @@ final class VideoAccessoryView: AccessoryView {
self.playerView.controlsStyle = .inline
})
videoResolution = media.videoResolution ?? .zero
- self.identifier = NSUserInterfaceItemIdentifier("video_accessoryview")
+ self.identifier = NSUserInterfaceItemIdentifier("video_accessory_view")
}
required init?(coder: NSCoder) {
return nil
}
- // MARK: - Instance methods
+ // MARK: - Instance Methods
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
@@ -99,6 +102,6 @@ final class VideoAccessoryView: AccessoryView {
}
override func configureAccessibilityElements() {
- playerView.setAccessibilityLabel("accessory_view_accessibility_video_playerView".localized)
+
}
}
diff --git a/Shared/Views/AccessoryViews/CheckListAccessoryView.swift b/Shared/Views/AccessoryViews/CheckListAccessoryView.swift
deleted file mode 100644
index 465f3a7..0000000
--- a/Shared/Views/AccessoryViews/CheckListAccessoryView.swift
+++ /dev/null
@@ -1,223 +0,0 @@
-//
-// CheckListAccessoryView.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 30/04/2021.
-// Copyright © 2021 IBM Inc. All rights reserved.
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This view represents a list of elements with checkboxes
-final class CheckListAccessoryView: AccessoryView {
-
- // MARK: Variables
-
- private var scrollView: NSScrollView!
- private var _containerWidth: CGFloat?
- private var containerWidth: CGFloat {
- return _containerWidth ?? (self.superview?.bounds.width ?? 0)
- }
- private var scrollViewHeightAnchor: NSLayoutConstraint!
- private var stackViewHeightAnchor: NSLayoutConstraint!
- private var stackViewWidthAnchor: NSLayoutConstraint!
- private var maxViewHeight: CGFloat
- private var title: NSTextField?
-
- var listStackView: FlippedStackView
- var elements: [NSButton] = []
- var isRequired: Bool = false
- var needCompletion: Bool = false
- var useRadioButtons: Bool = false
- var selectedIndexes: [Int] = []
-
- // MARK: - Initializers
-
- required init?(coder: NSCoder) {
- return nil
- }
-
- init(with payload: String, maxViewHeight: CGFloat = 300, containerWidth: CGFloat? = nil) throws {
- self.maxViewHeight = maxViewHeight
- self.listStackView = FlippedStackView()
- self._containerWidth = containerWidth
- super.init(frame: .zero)
- try configureView(with: payload)
- self.identifier = NSUserInterfaceItemIdentifier("checklist_accessoryview")
- }
-
- // MARK: - Instance methods
-
- override func adjustViewSize() {
- let tempStackView = NSStackView()
- tempStackView.distribution = .fill
- tempStackView.orientation = .vertical
- tempStackView.alignment = .leading
- for element in elements {
- tempStackView.addArrangedSubview(element)
- }
- if let title = title {
- tempStackView.insertArrangedSubview(title, at: 0)
- }
- let stackViewHeight = tempStackView.fittingSize.height
- let scrollViewHeight = min(stackViewHeight, maxViewHeight)
- scrollViewHeightAnchor?.isActive = false
- scrollViewHeightAnchor = scrollView.heightAnchor.constraint(equalToConstant: scrollViewHeight)
- scrollViewHeightAnchor?.isActive = true
- stackViewWidthAnchor?.isActive = false
- stackViewWidthAnchor = listStackView.widthAnchor.constraint(equalToConstant: max(containerWidth-12, 0))
- stackViewWidthAnchor?.isActive = true
- stackViewHeightAnchor?.isActive = false
- stackViewHeightAnchor = listStackView.heightAnchor.constraint(equalToConstant: stackViewHeight)
- stackViewHeightAnchor?.isActive = true
- for element in elements {
- listStackView.addArrangedSubview(element)
- }
- if let title = title {
- listStackView.insertArrangedSubview(title, at: 0)
- }
- }
-
- override func configureAccessibilityElements() {
- self.setAccessibilityElement(false)
- title?.setAccessibilityLabel("accessory_view_accessibility_checklist_title".localized)
- title?.setAccessibilityIdentifier("accessory_view_accessibility_checklist_title")
- listStackView.setAccessibilityLabel("accessory_view_accessibility_checklist_liststackview".localized)
- listStackView.setAccessibilityIdentifier("accessory_view_accessibility_checklist_liststackview")
- scrollView.setAccessibilityLabel("accessory_view_accessibility_checklist_liststackview".localized)
- scrollView.setAccessibilityIdentifier("accessory_view_accessibility_checklist_liststackview")
- }
-
- override func displayStoredData(_ data: String) {
- let arrayOfStrings = data.split(separator: " ")
- var arrayOfIndexes: [Int] = []
- for string in arrayOfStrings {
- guard !string.isEmpty, let index = Int(string) else {
- continue
- }
- arrayOfIndexes.append(index)
- }
- for (index, element) in elements.enumerated() {
- if arrayOfIndexes.contains(index) {
- element.state = .on
- selectedIndexes.append(index)
- }
- }
- }
-
- // MARK: - Private methods
-
- private func configureView(with payload: String) throws {
- try parsePayload(payload) { (elements, predefinedvalues) in
- self.elements = elements
- guard !predefinedvalues.isEmpty else { return }
- self.displayStoredData(predefinedvalues)
- }
- self.mainButtonState = !(self.isRequired || self.needCompletion || self.useRadioButtons) ? .enabled : .disabled
- listStackView.distribution = .fill
- listStackView.orientation = .vertical
- listStackView.translatesAutoresizingMaskIntoConstraints = false
- listStackView.alignment = .leading
- scrollView = NSScrollView()
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.autohidesScrollers = true
- scrollView.verticalScroller = NoBackgroundScroller()
- scrollView.hasVerticalScroller = true
- scrollView.verticalScrollElasticity = .none
- scrollView.documentView = listStackView
- scrollView.drawsBackground = false
- self.addSubview(scrollView)
- scrollView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
- scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
- scrollView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
- scrollView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
- }
-
- private func parsePayload(_ payload: String, completion: @escaping (_ elements: [NSButton], _ predefinedValues: String) -> Void) throws {
- var buttons: [NSButton] = []
- var predefinedvalues: String = ""
- var splittedStrings = payload.split(separator: "/")
- guard splittedStrings.count > 0 else { throw NAError.efclController(type: .invalidAccessoryViewPayload) }
- splittedStrings.reverse()
- for index in 0.. NSButton in
- let radioButton = NSButton(radioButtonWithTitle: checkboxButton.title, target: self, action: #selector(didChangeCheckBoxSelection(_:)))
- radioButton.setAccessibilityLabel("accessory_view_accessibility_checklist_liststackview_element".localized)
- return radioButton
- })
- completion(radioButtons, predefinedvalues)
- return
- }
- completion(buttons, predefinedvalues)
- }
-
- // MARK: - Actions
-
- @objc
- private func didChangeCheckBoxSelection(_ sender: NSButton) {
- defer {
- delegate?.accessoryViewStatusDidChange(self)
- }
- if needCompletion {
- var flag: Bool = true
- selectedIndexes = []
- for index in elements.indices {
- guard elements[index].state == .on else {
- flag = false
- continue
- }
- selectedIndexes.append(index)
- }
- mainButtonState = flag ? .enabled : .disabled
- } else if isRequired || useRadioButtons {
- var flag: Bool = false
- selectedIndexes = []
- for index in elements.indices {
- guard elements[index].state == .on else {
- continue
- }
- flag = true
- selectedIndexes.append(index)
- }
- mainButtonState = flag ? .enabled : .disabled
- } else {
- selectedIndexes = []
- for index in elements.indices {
- guard elements[index].state == .on else {
- continue
- }
- selectedIndexes.append(index)
- }
- }
- }
-}
diff --git a/Shared/Views/AccessoryViews/DropDownAccessoryView.swift b/Shared/Views/AccessoryViews/DropDownAccessoryView.swift
deleted file mode 100644
index b7385c8..0000000
--- a/Shared/Views/AccessoryViews/DropDownAccessoryView.swift
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-// DropDownAccessoryView.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 14/04/2021.
-// Copyright © 2021 IBM Inc. All rights reserved.
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This view represents a drop down combo box
-class DropDownAccessoryView: AccessoryView {
-
- // MARK: - Private variables
-
- private var dropDown: NSComboBox
- private var defaultIndex: Int?
- private var _containerWidth: CGFloat?
- private var containerWidth: CGFloat {
- return _containerWidth ?? (self.superview?.bounds.width ?? 0)
- }
- private var dropDownTopAnchor: NSLayoutConstraint!
-
- // MARK: - Variables
-
- var selectedItem: Int {
- return dropDown.indexOfSelectedItem
- }
- var hasTitle: Bool = false
-
- // MARK: - Initializers
-
- init(with payload: String, containerWidth: CGFloat? = nil) throws {
- dropDown = NSComboBox()
- dropDown.isSelectable = false
- _containerWidth = containerWidth
- super.init(frame: .zero)
- dropDown.delegate = self
- dropDown.translatesAutoresizingMaskIntoConstraints = false
- self.addSubview(dropDown)
- dropDownTopAnchor = dropDown.topAnchor.constraint(equalTo: self.topAnchor)
- dropDownTopAnchor.isActive = true
- dropDown.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
- dropDown.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
- dropDown.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
- try configureDropDown(with: payload)
- self.identifier = NSUserInterfaceItemIdentifier("dropdown_accessoryview")
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- // MARK: - Instance methods
-
- override func adjustViewSize() {
- self.widthAnchor.constraint(equalToConstant: containerWidth).isActive = true
- }
-
- override func configureAccessibilityElements() {
- dropDown.setAccessibilityLabel("\(dropDown.placeholderString ?? ""). \("accessory_view_accessibility_dropdown".localized)")
- }
-
- override func displayStoredData(_ data: String) {
- guard let index = Int(data) else { return }
- self.dropDown.selectItem(at: index)
- }
-
- // MARK: - Private methods
-
- private func configureDropDown(with payload: String) throws {
- var splittedStrings = payload.split(separator: "/")
- guard splittedStrings.count > 0 else {
- throw NAError.efclController(type: .invalidAccessoryViewPayload)
- }
- splittedStrings.reverse()
- for index in 0..= 0 ? .enabled : .disabled
- }
-}
-
-// MARK: - NSComboBoxDelegate methods implementation.
-extension DropDownAccessoryView: NSComboBoxDelegate {
- func comboBoxSelectionDidChange(_ notification: Notification) {
- defer {
- delegate?.accessoryViewStatusDidChange(self)
- }
- guard self.dropDown.indexOfSelectedItem >= 0 else { return }
- mainButtonState = .enabled
- secondaryButtonState = .enabled
- }
-}
diff --git a/Shared/Views/AccessoryViews/ImageAccessoryView.swift b/Shared/Views/AccessoryViews/ImageAccessoryView.swift
deleted file mode 100644
index 8789677..0000000
--- a/Shared/Views/AccessoryViews/ImageAccessoryView.swift
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-// ImageAccessoryView.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 9/28/20.
-// Copyright © 2021 IBM Inc. All rights reserved
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This view show an image loaded from a file or an URL.
-final class ImageAccessoryView: AccessoryView {
-
- // MARK: - Private variables
-
- private var imageView: NSImageView!
- private var _containerWidth: CGFloat?
- private var containerWidth: CGFloat {
- return _containerWidth ?? (self.superview?.bounds.width ?? 0)
- }
- private var imageViewWidthAnchor: NSLayoutConstraint!
- private var imageViewHeightAnchor: NSLayoutConstraint!
- private let needsFullWidth: Bool
- private let maxImageHeight: CGFloat = 300
- private let preferredSize: CGSize
-
- // MARK: - Initializers
-
- init(with media: NAMedia,
- preferredSize: CGSize = .zero,
- needsFullWidth: Bool = true,
- containerWidth: CGFloat? = nil) {
- self.needsFullWidth = needsFullWidth
- self.preferredSize = preferredSize
- self._containerWidth = containerWidth
- super.init(frame: .zero)
- imageView = NSImageView()
- imageView.imageScaling = .scaleProportionallyUpOrDown
- imageView.translatesAutoresizingMaskIntoConstraints = false
- self.addSubview(imageView)
- imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
- imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
- imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
- imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
- imageView.image = media.image!
- self.identifier = NSUserInterfaceItemIdentifier("image_accessoryview")
- }
-
- required init?(coder: NSCoder) {
- return nil
- }
-
- // MARK: - Instance methods
-
- override func adjustViewSize() {
- guard needsFullWidth else {
- if preferredSize != .zero {
- imageView.heightAnchor.constraint(equalToConstant: preferredSize.height).isActive = true
- imageView.widthAnchor.constraint(equalToConstant: preferredSize.width).isActive = true
- }
- return
- }
- let imageSize = imageView.intrinsicContentSize
- guard containerWidth != 0 && imageSize != .zero else { return }
- imageViewWidthAnchor?.isActive = false
- imageViewWidthAnchor = imageView.widthAnchor.constraint(equalToConstant: containerWidth)
- imageViewWidthAnchor.isActive = true
- guard imageSize.width > imageSize.height else { return }
- let scaleFactor = containerWidth/imageSize.width
- imageViewHeightAnchor?.isActive = false
- imageViewHeightAnchor = imageView.heightAnchor.constraint(equalToConstant: min(imageSize.height*scaleFactor, maxImageHeight))
- imageViewHeightAnchor.isActive = true
- }
-
- override func configureAccessibilityElements() {
- imageView.setAccessibilityLabel("accessory_view_accessibility_image_imageview".localized)
- }
-}
diff --git a/Shared/Views/AccessoryViews/InputAccessoryView.swift b/Shared/Views/AccessoryViews/InputAccessoryView.swift
deleted file mode 100644
index 3bce9c1..0000000
--- a/Shared/Views/AccessoryViews/InputAccessoryView.swift
+++ /dev/null
@@ -1,148 +0,0 @@
-//
-// InputAccessoryView.swift
-// Notification Agent
-//
-// Created by Jan Valentik on 03/11/2021.
-// Copyright © 2021 IBM Inc. All rights reserved
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This view represents view with input box and secured input box
-class InputAccessoryView: AccessoryView {
-
- // MARK: - Private variables
-
- private var inputTextField: NSTextField
- private var fieldTopAnchor: NSLayoutConstraint!
- private var textFieldHeightAnchor: NSLayoutConstraint!
- private var isRequired: Bool = false
- private var _containerWidth: CGFloat?
- private var containerWidth: CGFloat {
- return _containerWidth ?? (self.superview?.bounds.width ?? 0)
- }
-
- // MARK: - Variables
-
- var inputValue: String {
- return inputTextField.stringValue
- }
- var isSecure: Bool
- var preventResize: Bool
- var hasTitle: Bool = false
-
- // MARK: - Initializers
-
- init(with payload: String? = nil, isSecure: Bool = false, containerWidth: CGFloat? = nil, preventResize: Bool = false) throws {
- if isSecure {
- inputTextField = NSSecureTextField()
- } else {
- inputTextField = NSTextField()
- }
- self.isSecure = isSecure
- self.preventResize = preventResize
- _containerWidth = containerWidth
- super.init(frame: .zero)
- self.addSubview(inputTextField)
- inputTextField.translatesAutoresizingMaskIntoConstraints = false
- fieldTopAnchor = inputTextField.topAnchor.constraint(equalTo: self.topAnchor)
- fieldTopAnchor.isActive = true
- inputTextField.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
- inputTextField.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
- inputTextField.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
- inputTextField.delegate = self
- inputTextField.lineBreakMode = .byCharWrapping
- inputTextField.usesSingleLineMode = false
- try configureView(with: payload)
- self.identifier = NSUserInterfaceItemIdentifier("input_accessoryview")
- }
-
- required init?(coder: NSCoder) {
- return nil
- }
-
- // MARK: - Instance methods
-
- override func adjustViewSize() {
- inputTextField.widthAnchor.constraint(equalToConstant: containerWidth).isActive = true
- adjustTextAreaHeight()
- }
-
- override func configureAccessibilityElements() {
- let accessibilityLabel = isSecure ?
- isRequired ? "accessory_view_accessibility_input_secured_required".localized : "accessory_view_accessibility_input_secured".localized :
- isRequired ? "accessory_view_accessibility_input_required".localized : "accessory_view_accessibility_input".localized
- inputTextField.setAccessibilityLabel("\(inputTextField.placeholderString ?? ""). \(accessibilityLabel)")
- }
-
- override func displayStoredData(_ data: String) {
- inputTextField.stringValue = data
- }
-
- // MARK: - Private methods
-
- private func configureView(with payload: String?) throws {
- guard let payload = payload else { return }
- guard payload.contains("/placeholder") || payload.contains("/required") || payload.contains("/title") || payload.contains("/value") else {
- self.inputTextField.placeholderString = payload
- NALogger.shared.deprecationLog(since: AppVersion(major: 3, release: 0, fix: 0), deprecatedArgument: "input/secured input accessory view payload as placeholder without keys ex. '/placeholder'")
- return
- }
- var splittedStrings = payload.split(separator: "/")
- guard splittedStrings.count > 0 else {
- throw NAError.efclController(type: .invalidAccessoryViewPayload)
- }
- splittedStrings.reverse()
- for index in 0..= 100 {
- self.didFinishedInteractiveUpdates()
- } else if !self.viewState.isIndeterminate {
- self.progressBar.animator().doubleValue = self.viewState.percent
- self.delegate?.accessoryViewStatusDidChange(self)
- } else {
- self.delegate?.accessoryViewStatusDidChange(self)
- }
- }
- }
- }
-
- /// Interactive updates ended.
- private func didFinishedInteractiveUpdates() {
- DispatchQueue.main.async {
- self.progressBar.isIndeterminate = false
- self.progressBar.doubleValue = 100
- self.progressBar.stopAnimation(nil)
- self.progressCompleted = true
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
- guard !self.viewState.exitOnCompletion else {
- Utils.applicationExit(withReason: .mainButtonClicked)
- return
- }
- self.mainButtonState = .enabled
- self.secondaryButtonState = .enabled
- self.delegate?.accessoryViewStatusDidChange(self)
- }
- }
-
- // MARK: InteractiveObjectProtocol protocol conformity - START
-
- var objectIdentifier: String = "progressbar_interactive_updates"
-
- func processInput(_ notification: Notification) {
- guard let inputData = notification.userInfo?["data"] as? Data else { return }
- if !inputData.isEmpty {
- guard let strData = String(data: inputData, encoding: String.Encoding.utf8),
- strData.trimmingCharacters(in: CharacterSet.newlines).lowercased() != "end" else {
- self.didFinishedInteractiveUpdates()
- return
- }
- let newState = ProgressState(strData.trimmingCharacters(in: CharacterSet.newlines), currentState: viewState)
- guard newState != viewState else { return }
- self.didReceivedNewStateforProgressBar(newState)
- }
- }
-
- // MARK: InteractiveObjectProtocol protocol conformity - END
-}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewSource.swift b/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewSource.swift
new file mode 100644
index 0000000..aed7d03
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewSource.swift
@@ -0,0 +1,48 @@
+//
+// AccessoryViewSource.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 11/05/2023.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+struct AccessoryViewSource {
+ // A binding to a String that is used to keep track of the accessory view output.
+ @Binding var output: String
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ var accessoryView: NotificationAccessoryElement
+ var viewModel: AccessoryViewController?
+
+ init(output: Binding, mainButtonState: Binding, secondaryButtonState: Binding, accessoryView: NotificationAccessoryElement, viewModel: AccessoryViewController? = nil) {
+ self._output = output
+ self._mainButtonState = mainButtonState
+ self._secondaryButtonState = secondaryButtonState
+ self.accessoryView = accessoryView
+ if accessoryView.type == .progressbar {
+ self.viewModel = ProgressBarViewModel(progressState: ProgressState(accessoryView.payload ?? ""),
+ mainButtonState: mainButtonState,
+ secondaryButtonState: secondaryButtonState)
+ } else {
+ self.viewModel = viewModel
+ }
+ }
+}
+
+extension AccessoryViewSource: Equatable {
+ static func == (lhs: AccessoryViewSource, rhs: AccessoryViewSource) -> Bool {
+ return lhs.accessoryView == rhs.accessoryView
+ }
+}
+
+extension AccessoryViewSource: Hashable {
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(self.accessoryView)
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewWrapper.swift b/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewWrapper.swift
new file mode 100644
index 0000000..a0d3ef2
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/AccessoryViewWrapper.swift
@@ -0,0 +1,60 @@
+//
+// AccessoryViewWrapper.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 09/02/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import Combine
+
+/// AccessoryViewWrapper is a struct that define a view that works as generic wrapper for all the available Accessory Views.
+struct AccessoryViewWrapper: View {
+
+ // MARK: - Variables
+
+ var source: AccessoryViewSource
+ var contentMode: ContentMode = .fill
+
+ // MARK: - Views
+
+ var body: some View {
+ switch source.accessoryView.type {
+ case .secureinput, .securedinput, .input:
+ try? InputView(source.accessoryView.payload ?? "", output: source.$output, mainButtonState: source.$mainButtonState, secondaryButtonState: source.$secondaryButtonState, legacyType: source.accessoryView.type)
+ .accessibilityIdentifier(source.accessoryView.type == .input ? "input_accessory_view" : "secure_input_accessory_view")
+ case .checklist, .dropdown:
+ try? PickerView(source.accessoryView.payload ?? "", output: source.$output, mainButtonState: source.$mainButtonState, secondaryButtonState: source.$secondaryButtonState, legacyType: source.accessoryView.type)
+ .accessibilityIdentifier(source.accessoryView.type == .checklist ? "checklist_accessory_view" : "dropdown_whitebox_accessory_view")
+ case .image, .video:
+ try? MediaView(source.accessoryView.payload ?? "", output: source.$output, mainButtonState: source.$mainButtonState, secondaryButtonState: source.$secondaryButtonState, legacyType: source.accessoryView.type, contentMode: contentMode)
+ .accessibilityIdentifier(source.accessoryView.type == .image ? "image_accessory_view" : "video_whitebox_accessory_view")
+ case .html, .htmlwhitebox:
+ HTMLView(text: source.accessoryView.payload ?? "", drawsBackground: source.accessoryView.type == .htmlwhitebox || source.accessoryView.type == .whitebox)
+ case .whitebox:
+ MarkdownView(text: source.accessoryView.payload?.localized ?? "", drawsBackground: true)
+ case .progressbar:
+ try? ProgressBarView(viewModel: source.viewModel as? ProgressBarViewModel)
+ .accessibilityIdentifier("progressbar_accessory_view")
+ case .datepicker:
+ try? DatePickerView(source.accessoryView.payload ?? "", output: source.$output, mainButtonState: source.$mainButtonState, secondaryButtonState: source.$secondaryButtonState)
+ .accessibilityIdentifier("datepicker_accessory_view")
+ default:
+ EmptyView()
+ }
+ }
+}
+
+extension AccessoryViewWrapper: Equatable {
+ static func == (lhs: AccessoryViewWrapper, rhs: AccessoryViewWrapper) -> Bool {
+ return lhs.source == rhs.source
+ }
+}
+
+extension AccessoryViewWrapper: Hashable {
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(self.source)
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/DatePickerView.swift b/Shared/Views/AccessoryViews/SwiftUI/DatePickerView.swift
new file mode 100644
index 0000000..3473c8f
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/DatePickerView.swift
@@ -0,0 +1,177 @@
+//
+// DatePickerView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 24/05/2023.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import Combine
+
+/// DatePickerView is a struct that define a view and logic to handle Date input.
+struct DatePickerView: View {
+
+ // MARK: - Support Enums
+
+ /// This enum lists the different coding keys for the DatePicker
+ enum DatePickerCodingKeys: String, AVCIterable {
+ case title
+ case preselection
+ case components
+ case style
+ }
+
+ // MARK: - Private Variables
+
+ /// Properties to keep track of the title, placeholder, completion status, etc.
+ private var title: String
+ private var style: any DatePickerStyle
+ private var components: DatePickerComponents
+ private var initialDate: Date?
+ private var dateFormatter: DateFormatter {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+ dateFormatter.locale = Locale.current
+ return dateFormatter
+ }
+
+ // MARK: - Binded Variables
+
+ /// A binding to a String that is used to keep track of the accessory view output.
+ @Binding var output: String
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ // MARK: - State Variables
+
+ @State var selectionValue: Date = Date()
+
+ // MARK: - Initializers
+
+ init(_ payload: String,
+ output: Binding,
+ mainButtonState: Binding,
+ secondaryButtonState: Binding) throws {
+
+ /// Initialize the binded variables.
+ _output = output
+ _mainButtonState = mainButtonState
+ _secondaryButtonState = secondaryButtonState
+
+ let decoder = ACVDecoder(codingKeys: DatePickerCodingKeys.self)
+ title = try decoder.decode(key: DatePickerCodingKeys.title, ofType: String.self, from: payload)
+ let rawStyle = try decoder.decode(key: DatePickerCodingKeys.style, ofType: String.self, from: payload)
+ switch rawStyle.lowercased() {
+ case "graphical":
+ style = GraphicalDatePickerStyle()
+ case "field":
+ style = FieldDatePickerStyle()
+ case "compact":
+ style = CompactDatePickerStyle()
+ case "stepperfield":
+ style = StepperFieldDatePickerStyle()
+ default:
+ style = DefaultDatePickerStyle()
+ }
+ let rawComponentsField = try decoder.decode(key: DatePickerCodingKeys.components, ofType: String.self, from: payload)
+ switch rawComponentsField.lowercased() {
+ case "date":
+ components = [.date]
+ case "time":
+ components = [.hourAndMinute]
+ default:
+ components = [.date, .hourAndMinute]
+ }
+ let somevalues = try decoder.decode(key: DatePickerCodingKeys.preselection, ofType: String.self, from: payload)
+ initialDate = dateFormatter.date(from: output.wrappedValue.isEmpty ? somevalues : output.wrappedValue) ?? Date()
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ if !title.isEmpty {
+ Text(title)
+ .fixedSize()
+ .accessibilityIdentifier("datepicker_accessory_view_title")
+ }
+ HStack(spacing: 0) {
+ DatePicker("", selection: $selectionValue.onUpdate(evaluateButtonState), displayedComponents: components)
+ .customDatePickerStyle(style)
+ .labelsHidden()
+ .onAppear {
+ if let initialDate = initialDate {
+ self.selectionValue = initialDate
+ }
+ evaluateButtonState()
+ }
+ .accessibilityIdentifier("datepicker_accessory_view_picker")
+ Spacer()
+ }
+ .padding(0)
+ }
+ }
+
+ // MARK: - Private Methods
+
+ private func evaluateButtonState() {
+ $output.wrappedValue = dateFormatter.string(from: selectionValue)
+ }
+}
+
+struct DatePickerView_Previews: PreviewProvider {
+ static var previews: some View {
+ try? DatePickerView("/title This is a title", output: Binding(get: {
+ return ""
+ }, set: { _, _ in
+
+ }), mainButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), secondaryButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }))
+ .previewLayout(.fixed(width: 400, height: 100))
+ }
+}
+
+/// Custom modifier to provide a generic way to define DatePicker style.
+struct CustomDatePickerStyleModifier: ViewModifier {
+ var style: any DatePickerStyle
+
+ func body(content: Content) -> some View {
+ switch style {
+ case is FieldDatePickerStyle:
+ content
+ .datePickerStyle(.field)
+ case is GraphicalDatePickerStyle:
+ content
+ .datePickerStyle(.graphical)
+ case is CompactDatePickerStyle:
+ content
+ .datePickerStyle(.compact)
+ case is StepperFieldDatePickerStyle:
+ content
+ .datePickerStyle(.stepperField)
+ case is DefaultDatePickerStyle:
+ content
+ .datePickerStyle(.automatic)
+ default:
+ content
+ .datePickerStyle(.automatic)
+ }
+ }
+}
+
+extension View {
+ func customDatePickerStyle(_ style: any DatePickerStyle) -> some View {
+ self.modifier(CustomDatePickerStyleModifier(style: style))
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/InputView.swift b/Shared/Views/AccessoryViews/SwiftUI/InputView.swift
new file mode 100644
index 0000000..e4ce30c
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/InputView.swift
@@ -0,0 +1,123 @@
+//
+// InputView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 10/01/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import Combine
+
+/// InputView is a struct that defines a view to display an input - secure or not - text field.
+struct InputView: View {
+
+ // MARK: - Support Enums
+
+ enum InputCodingKeys: String, AVCIterable {
+ case title
+ case placeholder
+ case value
+ case required
+ }
+
+ // MARK: - Variables
+
+ var title: String
+ var placeholder: String
+ var required: Bool
+ var sub: AnyCancellable?
+ var initialValue: String
+ var subscribers: [AnyCancellable] = []
+
+ // MARK: - Binded Variables
+
+ // A binding to a String that is used to keep track of the accessory view output.
+ @Binding var output: String
+ // A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ // A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ // MARK: - State Variables
+
+ @State var isSecure: Bool
+ @State var inputValue: String
+
+ // MARK: - Initializers
+
+ init(_ payload: String,
+ output: Binding,
+ mainButtonState: Binding,
+ secondaryButtonState: Binding,
+ legacyType: NotificationAccessoryElement.ViewType) throws {
+
+ /// Initialize the binded variables
+ _output = output
+ _mainButtonState = mainButtonState
+ _secondaryButtonState = secondaryButtonState
+
+ /// Determine if the input field must be secure or not.
+ isSecure = legacyType == .secureinput || legacyType == .securedinput
+
+ /// Decode the payload using ACVDecoder.
+ let decoder = ACVDecoder(codingKeys: InputCodingKeys.self)
+ title = try decoder.decode(key: InputCodingKeys.title, ofType: String.self, from: payload)
+ placeholder = try decoder.decode(key: InputCodingKeys.placeholder, ofType: String.self, from: payload)
+ let somevalue = try decoder.decode(key: InputCodingKeys.value, ofType: String.self, from: payload)
+ initialValue = !output.wrappedValue.isEmpty ? output.wrappedValue : somevalue
+ inputValue = initialValue
+ required = try decoder.decode(key: InputCodingKeys.required, ofType: Bool.self, from: payload)
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ if !title.isEmpty {
+ Text(title)
+ .fixedSize()
+ .accessibilityIdentifier("input_accessory_view_title")
+ }
+ /// Shows a standard TextField or a SecureField based on the isSecure value.
+ if !isSecure {
+ TextField(placeholder, text: $inputValue.onUpdate(updateReceived))
+ .accessibilityIdentifier("input_accessory_view_textfield")
+ } else {
+ SecureField(placeholder, text: $inputValue.onUpdate(updateReceived))
+ .accessibilityIdentifier("input_accessory_view_secure_textfield")
+ }
+ }
+ .onAppear {
+ updateReceived()
+ }
+ }
+
+ // MARK: - Private Methods
+
+ /// Evaluate the new state for the mainButtonState.
+ private func updateReceived() {
+ self.$output.wrappedValue = inputValue
+ self.$mainButtonState.wrappedValue = self.required ? (!self.output.trimmingCharacters(in: .whitespaces).isEmpty ? .enabled : .disabled) : .enabled
+ }
+}
+
+struct InputView_Previews: PreviewProvider {
+ static var previews: some View {
+ try? InputView("/title This is a title /placeholder Some Placeholder /value Some", output: Binding(get: {
+ return ""
+ }, set: { _, _ in
+
+ }), mainButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), secondaryButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), legacyType: .input)
+ .previewLayout(.fixed(width: 400, height: 100))
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/MediaView.swift b/Shared/Views/AccessoryViews/SwiftUI/MediaView.swift
new file mode 100644
index 0000000..4337a6a
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/MediaView.swift
@@ -0,0 +1,134 @@
+//
+// MediaView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 27/01/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import AVKit
+import AppKit
+
+/// MediaView is a struct that defines a view to display media such as image or video.
+struct MediaView: View {
+
+ // MARK: - Variables
+
+ /// Defines the type of media view to be displayed, either image or video.
+ var type: NotificationAccessoryElement.ViewType
+ /// An object that holds the actual media content.
+ var media: NAMedia
+ /// The desired content mode for the media.
+ var contentMode: ContentMode
+
+ // MARK: - Binded Variables
+
+ /// A binding to a String that is used to keep track of the accessory view output.
+ @Binding var output: String
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ // MARK: - Intializers
+
+ init(_ payload: String,
+ output: Binding,
+ mainButtonState: Binding,
+ secondaryButtonState: Binding,
+ legacyType: NotificationAccessoryElement.ViewType,
+ contentMode: ContentMode = .fill) throws {
+
+ /// Initialize the binded variables.
+ _output = output
+ _mainButtonState = mainButtonState
+ _secondaryButtonState = secondaryButtonState
+ self.contentMode = contentMode
+
+ /// Set the media view type.
+ type = legacyType
+
+ /// Determine the type of media to be displayed and create the NAMedia instance accordingly.
+ switch legacyType {
+ case .image:
+ guard let media = NAMedia(type: .image, from: payload) else {
+ throw NAError.efclController(type: .invalidAccessoryViewPayload)
+ }
+ self.media = media
+ case .video:
+ guard let media = NAMedia(type: .video, from: payload) else {
+ throw NAError.efclController(type: .invalidAccessoryViewPayload)
+ }
+ self.media = media
+ default:
+ throw NAError.efclController(type: .invalidAccessoryViewPayload)
+ }
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ switch type {
+ case .image:
+ /// If the media is an image, display the image using an Image view.
+ if let image = media.image {
+ Image(nsImage: image)
+ .resizable()
+ .aspectRatio(aspectRatioForMedia(media), contentMode: contentMode)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ } else {
+ EmptyView()
+ }
+ case .video:
+ /// If the media is a video, display the video using a PlayerView.
+ if media.player != nil {
+ PlayerView(media: media)
+ .aspectRatio(aspectRatioForMedia(media), contentMode: contentMode)
+ } else {
+ EmptyView()
+ }
+ default:
+ EmptyView()
+ }
+ }
+
+ // MARK: - Private Methods
+
+ /// Returns the resolution size of the media
+ private func resolutionForMedia(_ media: NAMedia) -> CGSize {
+ switch media.mediaType {
+ case .image:
+ guard let image = media.image else { return .zero }
+ return image.size
+ case .video:
+ guard let size = media.videoResolution else { return .zero }
+ return size
+ }
+ }
+
+ /// Returns the aspect ratio of the media
+ private func aspectRatioForMedia(_ media: NAMedia) -> Double {
+ let resolution = resolutionForMedia(media)
+ return resolution != .zero ? resolution.width/resolution.height : 16/9
+ }
+}
+
+struct MediaView_Previews: PreviewProvider {
+ static var previews: some View {
+ try? MediaView("/Users/simonemartorelli.max/Desktop/test.png", output: Binding(get: {
+ return ""
+ }, set: { _, _ in
+
+ }), mainButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), secondaryButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), legacyType: .image)
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/PickerView.swift b/Shared/Views/AccessoryViews/SwiftUI/PickerView.swift
new file mode 100644
index 0000000..60d85f0
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/PickerView.swift
@@ -0,0 +1,221 @@
+//
+// ChecklistView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 24/11/22.
+// Copyright © 2022 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import Combine
+
+/// PickerView is a struct that defines a view for different kind of pickers.
+struct PickerView: View {
+
+ // MARK: - Support Enums
+
+ /// This enum lists the different types of pickers available
+ enum PickerType {
+ case radiobuttons
+ case checkboxlist
+ case dropdown
+ }
+
+ // This enum lists the different coding keys for the checkbox list picker
+ enum CheckListCodingKeys: String, AVCIterable {
+ case title
+ case list
+ case needCompletion = "complete"
+ case useRadioButtons = "radio"
+ case preselection
+ case required
+ }
+
+ /// This enum lists the different coding keys for the dropdown picker
+ enum DropdownCodingKeys: String, AVCIterable {
+ case title
+ case list
+ case placeholder
+ case selected
+ }
+
+ // MARK: - Private Variables
+
+ /// Properties to keep track of the title, placeholder, completion status, etc.
+ private var title: String
+ private var placeholder: String
+ private var needCompletion: Bool
+ private var useRadioButtons: Bool
+ private var required: Bool
+ private var type: PickerType
+ private var initialValues: String
+
+ // MARK: - Binded Variables
+
+ /// A binding to a String that is used to keep track of the accessory view output.
+ @Binding var output: String
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ // MARK: - State Variables
+
+ /// State variable to keep track of the picker items
+ @State var values: [PickerItem]
+ @State var selectionValue: String
+
+ // MARK: - Initializers
+
+ init(_ payload: String,
+ output: Binding,
+ mainButtonState: Binding,
+ secondaryButtonState: Binding,
+ legacyType: NotificationAccessoryElement.ViewType) throws {
+
+ /// Initialize the binded variables.
+ _output = output
+ _mainButtonState = mainButtonState
+ _secondaryButtonState = secondaryButtonState
+
+ /// Initialize an empty PickerItem support array.
+ var someArray: [PickerItem] = []
+
+ /// Determine the type of picker to be displayed and decode the payload accordingly using ACVDecoder.
+ switch legacyType {
+ case .dropdown:
+ let decoder = ACVDecoder(codingKeys: DropdownCodingKeys.self)
+ title = try decoder.decode(key: DropdownCodingKeys.title, ofType: String.self, from: payload)
+ placeholder = try decoder.decode(key: DropdownCodingKeys.placeholder, ofType: String.self, from: payload)
+ someArray = try decoder.decode(key: DropdownCodingKeys.list, ofType: [PickerItem].self, from: payload)
+ required = !(try decoder.decode(key: DropdownCodingKeys.selected, ofType: String.self, from: payload) as String).isEmpty
+ needCompletion = false
+ useRadioButtons = false
+ let somevalues = try decoder.decode(key: DropdownCodingKeys.selected, ofType: String.self, from: payload)
+ initialValues = !output.wrappedValue.isEmpty && output.wrappedValue != "-1" ? output.wrappedValue : (!somevalues.isEmpty ? somevalues : "-1")
+ selectionValue = initialValues
+ type = .dropdown
+ values = someArray
+ case .checklist:
+ let decoder = ACVDecoder(codingKeys: CheckListCodingKeys.self)
+ title = try decoder.decode(key: CheckListCodingKeys.title, ofType: String.self, from: payload)
+ placeholder = ""
+ someArray = try decoder.decode(key: CheckListCodingKeys.list, ofType: [PickerItem].self, from: payload)
+ required = try decoder.decode(key: CheckListCodingKeys.required, ofType: Bool.self, from: payload)
+ needCompletion = try decoder.decode(key: CheckListCodingKeys.needCompletion, ofType: Bool.self, from: payload)
+ useRadioButtons = try decoder.decode(key: CheckListCodingKeys.useRadioButtons, ofType: Bool.self, from: payload)
+ let somevalues = try decoder.decode(key: CheckListCodingKeys.preselection, ofType: String.self, from: payload)
+ initialValues = !output.wrappedValue.isEmpty ? output.wrappedValue : somevalues
+ selectionValue = initialValues
+ type = useRadioButtons ? .radiobuttons : .checkboxlist
+ values = someArray
+ default:
+ throw NAError.efclController(type: .invalidAccessoryViewPayload)
+ }
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ if !title.isEmpty {
+ Text(title)
+ .fixedSize()
+ .accessibilityIdentifier("picker_accessory_view_secure_title")
+ }
+ switch self.type {
+ case .radiobuttons:
+ Picker("", selection: self.$selectionValue.onUpdate(evaluateButtonState)) {
+ ForEach(self.$values) { element in
+ Text(element.label.wrappedValue).tag(element.id.wrappedValue.description).fixedSize(horizontal: false, vertical: true)
+ }
+ }
+ .pickerStyle(.radioGroup)
+ .labelsHidden()
+ .onAppear {
+ evaluateButtonState()
+ }
+ .accessibilityIdentifier("picker_accessory_view_radio_buttons")
+ case .checkboxlist:
+ VStack(alignment: .leading) {
+ ForEach(self.$values.onUpdate(evaluateButtonState)) { element in
+ Toggle(isOn: element.isSelected) {
+ Text(element.label.wrappedValue).tag(element.id.wrappedValue.description).fixedSize(horizontal: false, vertical: true)
+ }
+ .toggleStyle(.checkbox)
+ }
+ }
+ .onAppear {
+ if !initialValues.isEmpty {
+ let selectedIndexes = initialValues.split(separator: " ")
+ selectedIndexes.forEach { indexString in
+ guard let index = Int(indexString), index < values.count else { return }
+ values[index].isSelected = true
+ }
+ }
+ evaluateButtonState()
+ }
+ .accessibilityIdentifier("picker_accessory_view_checkboxes")
+ case .dropdown:
+ Picker("", selection: self.$selectionValue.onUpdate(evaluateButtonState)) {
+ Text(placeholder).tag("-1")
+ ForEach(self.$values) { element in
+ Text(element.label.wrappedValue).tag(element.id.wrappedValue.description)
+ }
+ }
+ .labelsHidden()
+ .onAppear {
+ evaluateButtonState()
+ }
+ .accessibilityIdentifier("picker_accessory_view_dropdown")
+ }
+ }
+ }
+
+ // MARK: - Private Methods
+
+ private func evaluateButtonState() {
+ switch type {
+ case .dropdown:
+ $output.wrappedValue = selectionValue != "-1" ? selectionValue : ""
+ $mainButtonState.wrappedValue = output.isEmpty || output == "-1" ? .disabled : .enabled
+ case .checkboxlist:
+ var selectedIndexes: String = ""
+ for (index, item) in self.values.enumerated() where item.isSelected {
+ selectedIndexes.append("\(index) ")
+ }
+ if !selectedIndexes.isEmpty {
+ _ = selectedIndexes.removeLast()
+ }
+ $output.wrappedValue = selectedIndexes
+ if needCompletion {
+ $mainButtonState.wrappedValue = output.split(separator: " ").count == values.count ? .enabled : .disabled
+ } else {
+ $mainButtonState.wrappedValue = output.isEmpty && required ? .disabled : .enabled
+ }
+ case .radiobuttons:
+ $output.wrappedValue = selectionValue != "-1" ? selectionValue : ""
+ $mainButtonState.wrappedValue = output.isEmpty && required ? .disabled : .enabled
+ }
+ }
+}
+
+struct ChecklistView_Previews: PreviewProvider {
+ static var previews: some View {
+ try? PickerView("/title This is a title /list one\ntwo\nthree /radio /preselection 1", output: Binding(get: {
+ return ""
+ }, set: { _, _ in
+
+ }), mainButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), secondaryButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), legacyType: .checklist)
+ .previewLayout(.fixed(width: 400, height: 100))
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarView.swift b/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarView.swift
new file mode 100644
index 0000000..4e29343
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarView.swift
@@ -0,0 +1,70 @@
+//
+// ProgressBarView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 17/03/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+import Combine
+
+struct ProgressBarView: View {
+
+ // MARK: - Observed Variables
+
+ @ObservedObject var viewModel: ProgressBarViewModel
+
+ init(viewModel: ProgressBarViewModel?) throws {
+ guard let viewModel = viewModel else {
+ throw NAError.dataFormat(type: .invalidJSONPayload)
+ }
+ self.viewModel = viewModel
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 0) {
+ Text(viewModel.progressState.topMessage)
+ .padding(.bottom, 0)
+ .accessibilityIdentifier("progressbar_accessory_view_top_message")
+ if viewModel.progressState.percent != -1 {
+ ProgressView(value: viewModel.progressState.percent/100)
+ .progressViewStyle(.linear)
+ .padding(.top, -1)
+ .padding(.bottom, -3)
+ .accessibilityIdentifier("progressbar_accessory_view_progressview_determined")
+ } else {
+ ProgressView()
+ .progressViewStyle(.linear)
+ .padding(.top, 0)
+ .padding(.bottom, 0)
+ .accessibilityIdentifier("progressbar_accessory_view_progressview_indetermined")
+ }
+ Text(viewModel.progressState.bottomMessage)
+ .font(.system(.footnote))
+ .opacity(0.7225)
+ .padding(.top, 0)
+ .accessibilityIdentifier("progressbar_accessory_view_bottom_message")
+ }
+ .onAppear {
+ viewModel.setButtonsState(for: viewModel.progressState)
+ }
+ }
+}
+
+struct ProgressBarView_Previews: PreviewProvider {
+ static var previews: some View {
+ try? ProgressBarView(viewModel: ProgressBarViewModel(progressState: ProgressState("/percent 50 /top_message Top /bottom_message Bottom"), mainButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), secondaryButtonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ })))
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarViewModel.swift b/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarViewModel.swift
new file mode 100644
index 0000000..d1b2ccf
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/ProgressBar/ProgressBarViewModel.swift
@@ -0,0 +1,76 @@
+//
+// ProgressBarViewModel.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 20/03/23.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import Foundation
+import SwiftUI
+
+class ProgressBarViewModel: ObservableObject, AccessoryViewController, InteractiveObjectProtocol {
+
+ // MARK: - Binded Viariables
+
+ /// Published variable that represents the current state for the progress bar.
+ @Published var progressState: ProgressState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ @Binding var mainButtonState: SwiftUIButtonState
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ @Binding var secondaryButtonState: SwiftUIButtonState
+
+ // MARK: Initializers
+
+ init(progressState: ProgressState,
+ mainButtonState: Binding,
+ secondaryButtonState: Binding) {
+ self.progressState = progressState
+ self._mainButtonState = mainButtonState
+ self._secondaryButtonState = secondaryButtonState
+ self.startObservingForUpdates()
+ }
+
+ /// Define the main/secondary buttons state based on the new progress bar state.
+ /// - Parameter progressState: new progress bar state
+ func setButtonsState(for progressState: ProgressState) {
+ if progressState.percent == 100 {
+ mainButtonState = .enabled
+ secondaryButtonState = .enabled
+ } else {
+ mainButtonState = progressState.isUserInteractionEnabled ? .enabled : progressState.isUserInterruptionAllowed ? .cancel : .hidden
+ secondaryButtonState = progressState.isUserInteractionEnabled ? .enabled : .hidden
+ }
+ }
+
+ // MARK: - InteractiveObjectProtocol protocol conformity - START
+
+ var objectIdentifier: String = "progressbar_interactive_updates"
+
+ func processInput(_ notification: Notification) {
+ guard let inputData = notification.userInfo?["data"] as? Data else { return }
+ if !inputData.isEmpty {
+ guard let strData = String(data: inputData, encoding: String.Encoding.utf8) else { return }
+ let newState = ProgressState(strData.trimmingCharacters(in: CharacterSet.newlines), currentState: progressState)
+ guard newState != progressState else { return }
+ DispatchQueue.main.async {
+ self.progressState = newState
+ if newState.percent == 100 && newState.exitOnCompletion {
+ Utils.applicationExit(withReason: .mainButtonClicked)
+ return
+ }
+ self.setButtonsState(for: newState)
+ }
+ }
+ }
+
+ // MARK: - InteractiveObjectProtocol protocol conformity - END
+}
+
+protocol AccessoryViewController {
+ /// A binding to a SwiftUIButtonState that is used to keep track of the main button state.
+ var mainButtonState: SwiftUIButtonState { get set }
+ /// A binding to a SwiftUIButtonState that is used to keep track of the secondary button state.
+ var secondaryButtonState: SwiftUIButtonState { get set }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/HTMLView.swift b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/HTMLView.swift
new file mode 100644
index 0000000..dd20d2a
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/HTMLView.swift
@@ -0,0 +1,45 @@
+//
+// HTMLView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 24/11/22.
+// Copyright © 2022 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// HTMLView is a struct that defines a SwiftUI view to display the AppKit HTMLAccessoryView view.
+struct HTMLView: NSViewRepresentable {
+
+ // MARK: - Variables
+
+ /// The raw text to be displayed in the HTMLAccessoryView.
+ var text: String
+ /// Define if it's needed to draw a whit background for the view.
+ var drawsBackground: Bool?
+ /// Maximum height for the view.
+ var maxViewHeight: CGFloat?
+ /// Allignement for the text in the view.
+ var alignment: NSTextAlignment?
+ /// The view container width. Used to calculate the size based on the font.
+ var containerWidth: CGFloat?
+
+ // MARK: - Protocol Methods
+
+ func makeNSView(context: NSViewRepresentableContext) -> HTMLAccessoryView {
+ let markdownTextView = HTMLAccessoryView(withText: text,
+ drawsBackground: drawsBackground ?? false,
+ maxViewHeight: maxViewHeight ?? 300,
+ containerWidth: containerWidth ?? 400)
+ return markdownTextView
+ }
+
+ func updateNSView(_ nsView: HTMLAccessoryView, context: NSViewRepresentableContext) {}
+}
+
+struct HTMLView_Previews: PreviewProvider {
+ static var previews: some View {
+ HTMLView(text: "**This is a subtitle** [A Link](https://www.google.com) \n Something")
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/MarkdownView.swift b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/MarkdownView.swift
new file mode 100644
index 0000000..72cd854
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/MarkdownView.swift
@@ -0,0 +1,60 @@
+//
+// MarkdownView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 22/11/22.
+// Copyright © 2022 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// MarkdownView is a struct that defines a SwiftUI view to display the AppKit MarkdownTextView view.
+struct MarkdownView: NSViewRepresentable {
+
+ // MARK: - Variables
+
+ /// The raw text to be displayed in the MarkdownTextView.
+ var text: String
+ /// Define if it's needed to draw a whit background for the view.
+ var drawsBackground: Bool?
+ /// Maximum height for the view.
+ var maxViewHeight: CGFloat?
+ /// Allignement for the text in the view.
+ var alignment: NSTextAlignment?
+ /// The view container width. Used to calculate the size based on the font.
+ var containerWidth: CGFloat?
+
+ // MARK: - Protocol Methods
+
+ func makeNSView(context: NSViewRepresentableContext) -> MarkdownTextView {
+ let markdownTextView = MarkdownTextView(withText: text,
+ drawsBackground: drawsBackground ?? false,
+ maxViewHeight: maxViewHeight ?? 300,
+ alignment: alignment ?? .left,
+ containerWidth: containerWidth ?? 420)
+ return markdownTextView
+ }
+
+ func updateNSView(_ nsView: MarkdownTextView, context: NSViewRepresentableContext) {
+ nsView.textView.alignment = self.alignment ?? .left
+ nsView.maxViewHeight = self.maxViewHeight ?? 300
+ nsView.containerWidth = self.containerWidth ?? 420
+ if self.drawsBackground ?? false {
+ nsView.textViewBackgroundColor = .white
+ nsView.textColor = .black
+ } else {
+ nsView.textViewBackgroundColor = nil
+ nsView.textColor = .labelColor
+ }
+ nsView.needsLayout = true
+ nsView.scrollView.needsLayout = true
+ nsView.setText(self.text)
+ }
+}
+
+struct MarkdownView_Previews: PreviewProvider {
+ static var previews: some View {
+ MarkdownView(text: "**This is a subtitle** [A Link](https://www.google.com) \n Something")
+ }
+}
diff --git a/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/PlayerView.swift b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/PlayerView.swift
new file mode 100644
index 0000000..c7cc607
--- /dev/null
+++ b/Shared/Views/AccessoryViews/SwiftUI/ViewRepresentable/PlayerView.swift
@@ -0,0 +1,23 @@
+//
+// PlayerView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 10/05/2023.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+struct PlayerView: NSViewRepresentable {
+
+ var media: NAMedia
+
+ func makeNSView(context: NSViewRepresentableContext) -> VideoAccessoryView {
+ let accessoryView = VideoAccessoryView(with: media)
+
+ return accessoryView
+ }
+
+ func updateNSView(_ nsView: VideoAccessoryView, context: NSViewRepresentableContext) {}
+}
diff --git a/Shared/Views/AccessoryViews/TimerAccessoryView.swift b/Shared/Views/AccessoryViews/TimerAccessoryView.swift
deleted file mode 100644
index ab1a417..0000000
--- a/Shared/Views/AccessoryViews/TimerAccessoryView.swift
+++ /dev/null
@@ -1,97 +0,0 @@
-//
-// TimerAccessoryView.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 8/10/20.
-// Copyright © 2021 IBM Inc. All rights reserved
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-protocol TimerAccessoryViewDelegate: AnyObject {
- func timerDidFinished(_ sender: TimerAccessoryView)
-}
-
-/// This view present a label with a built in timer.
-final class TimerAccessoryView: AccessoryView {
-
- // MARK: - Variables
- weak var timerDelegate: TimerAccessoryViewDelegate?
- var timerLabel: NSTextField!
- var timer: Timer?
- var label: String
- var countDown: Int {
- didSet {
- DispatchQueue.main.async {
- self.timerLabel.stringValue = String(format: self.label,
- self.countDown.timeFormattedString)
- }
- }
- }
- private var containerWidth: CGFloat {
- return self.superview?.bounds.width ?? 0
- }
-
- // MARK: - Initializers
-
- init(withTimeInSeconds time: Int, label: String) {
- self.timerLabel = NSTextField(labelWithString: String(format: label,
- time.timeFormattedString))
- self.countDown = time
- self.label = label
- super.init(frame: .zero)
- self.startTimer()
- self.buildView()
- self.identifier = NSUserInterfaceItemIdentifier("timer_accessoryview")
- }
-
- required init?(coder: NSCoder) {
- return nil
- }
-
- // MARK: - Instance methods
-
- override func configureAccessibilityElements() {
- timerLabel.setAccessibilityLabel("accessory_view_accessibility_timer_label".localized)
- }
-
- /// Adjust the view size based on the superview width
- override func adjustViewSize() {
- self.widthAnchor.constraint(equalToConstant: containerWidth).isActive = true
- }
-
- // MARK: - Private methods
-
- private func buildView() {
- timerLabel.lineBreakMode = .byWordWrapping
- timerLabel.translatesAutoresizingMaskIntoConstraints = false
- self.addSubview(timerLabel)
-
- timerLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
- timerLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
- timerLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
- timerLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
- configureAccessibilityElements()
- }
-
- /// Create and fire the timer for the countdown.
- private func startTimer() {
- guard timer == nil else { return }
- self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
- self.updateCountdown()
- })
- }
-
- /// Update the countdown value.
- private func updateCountdown() {
- guard countDown >= 1 else {
- self.timer?.invalidate()
- self.timer = nil
- self.countDown = 0
- self.timerDelegate?.timerDidFinished(self)
- return
- }
- self.countDown -= 1
- }
-}
diff --git a/Shared/Views/Buttons/CircleButton.swift b/Shared/Views/Buttons/CircleButton.swift
new file mode 100644
index 0000000..acebc89
--- /dev/null
+++ b/Shared/Views/Buttons/CircleButton.swift
@@ -0,0 +1,119 @@
+//
+// CircleButton.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 22/11/22.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// CircleButton is a struct that defines a view with a circle shaped button.
+struct CircleButton: View {
+
+ // MARK: - Support Enum
+
+ enum CircleButtonType {
+ case help
+ case warning
+
+ /// Returns the label to display in the button
+ var label: some View {
+ switch self {
+ case .help:
+ return AnyView(Image(systemName: "questionmark"))
+ case .warning:
+ return AnyView(Image(systemName: "exclamationmark"))
+ }
+ }
+ }
+
+ // MARK: - Variables
+
+ let action: () -> Void
+ let popoverText: String?
+ let infoSection: InfoSection?
+ let type: CircleButtonType
+
+ // MARK: - State Variables
+
+ @Binding var buttonState: SwiftUIButtonState
+ @Binding var showPopover: Bool
+
+ // MARK: - Initializers
+
+ init(action: @escaping () -> Void, popoverText: String? = nil, infoSection: InfoSection? = nil, type: CircleButtonType, buttonState: Binding, showPopover: Binding) {
+ self.action = action
+ self.popoverText = popoverText
+ self.infoSection = infoSection
+ self.type = type
+ self._buttonState = buttonState
+ self._showPopover = showPopover
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ buttonWithAppearance
+ .popover(isPresented: $showPopover, arrowEdge: popoverText != nil ? .bottom : .trailing) {
+ if let plainText = popoverText {
+ Text(plainText)
+ .padding()
+ } else if let info = infoSection {
+ InfoSectionView(section: info)
+ }
+ }
+ }
+
+ var buttonWithAppearance: some View {
+ switch buttonState {
+ case .enabled:
+ return AnyView(baseButton)
+ case .disabled:
+ return AnyView(baseButton.disabled(true))
+ case .hidden:
+ return AnyView(EmptyView())
+ case .cancel:
+ return AnyView(EmptyView()) /// A circle button should never have the cancel state.
+ }
+ }
+
+ var baseButton: some View {
+ Button {
+ action()
+ } label: {
+ type.label
+ }
+ .clipShape(Circle())
+ }
+
+ func idealHeight(for infoSection: InfoSection) -> CGFloat {
+ var height: CGFloat = 0
+
+ for item in infoSection.fields {
+ let itemLabelHeight = NSTextField(wrappingLabelWithString: item.label).sizeThatFits(NSSize(width: 150, height: 0)).height
+ let itemDescriptionHeight = NSTextField(wrappingLabelWithString: item.description ?? "").sizeThatFits(NSSize(width: 450, height: 0)).height
+ let itemHeight = max(itemLabelHeight, itemDescriptionHeight)
+ height += itemHeight
+ }
+
+ return height
+ }
+}
+
+struct CircleButton_Previews: PreviewProvider {
+ static var previews: some View {
+ CircleButton(action: {
+ return
+ }, type: .help, buttonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in
+
+ }), showPopover: Binding(get: {
+ return true
+ }, set: { _, _ in
+
+ }))
+ }
+}
diff --git a/Shared/Views/Buttons/NativeButton.swift b/Shared/Views/Buttons/NativeButton.swift
new file mode 100644
index 0000000..06deaad
--- /dev/null
+++ b/Shared/Views/Buttons/NativeButton.swift
@@ -0,0 +1,80 @@
+//
+// NativeButton.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 21/11/22.
+// Copyright © 2022 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+// Credits: Sindre Sorhus (sindresorhus)
+//
+
+import SwiftUI
+
+/// NativeButton struct define an NSViewRepresentable view that wrap a standard NSButton.
+struct NativeButton: NSViewRepresentable {
+
+ // MARK: - Support enums
+
+ /// this enum map all the currently tracked keyboard keys.
+ enum KeyEquivalent: String {
+ case escape = "\u{1b}"
+ case `return` = "\r"
+ }
+
+ // MARK: - Variables
+
+ /// The button's title.
+ var title: String?
+ /// The title with attributed format.
+ var attributedTitle: NSAttributedString?
+ /// The key equivalent for this button.
+ var keyEquivalent: KeyEquivalent?
+
+ // MARK: - Constants
+
+ /// This constant define the action tu run when the key pressure is recognized.
+ let action: () -> Void
+
+ // MARK: - Initializers
+
+ init(_ title: String,
+ keyEquivalent: KeyEquivalent? = nil,
+ action: @escaping () -> Void) {
+ self.title = title
+ self.keyEquivalent = keyEquivalent
+ self.action = action
+ }
+
+ init(_ attributedTitle: NSAttributedString,
+ keyEquivalent: KeyEquivalent? = nil,
+ action: @escaping () -> Void) {
+ self.attributedTitle = attributedTitle
+ self.keyEquivalent = keyEquivalent
+ self.action = action
+ }
+
+ // MARK: - NSViewRepresentable protocol methods
+
+ func makeNSView(context: NSViewRepresentableContext) -> NSButton {
+ let button = NSButton(title: "", target: nil, action: nil)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.setContentHuggingPriority(.defaultHigh, for: .vertical)
+ button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.widthAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
+ return button
+ }
+
+ func updateNSView(_ nsView: NSButton, context: NSViewRepresentableContext) {
+ if attributedTitle == nil {
+ nsView.title = title ?? ""
+ }
+ if title == nil {
+ nsView.attributedTitle = attributedTitle ?? NSAttributedString(string: "")
+ }
+ nsView.keyEquivalent = keyEquivalent?.rawValue ?? ""
+ nsView.onAction { _ in
+ self.action()
+ }
+ }
+}
diff --git a/Shared/Views/Buttons/StandardButton.swift b/Shared/Views/Buttons/StandardButton.swift
new file mode 100644
index 0000000..6f99e56
--- /dev/null
+++ b/Shared/Views/Buttons/StandardButton.swift
@@ -0,0 +1,96 @@
+//
+// StandardButton.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 22/11/22.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// StandardButton struct define a view with a standard SwiftUI button that accept keyboard key equivalent.
+struct StandardButton: View {
+
+ // MARK: - Variables
+
+ var keyboardShortcut: NativeButton.KeyEquivalent?
+
+ // MARK: - Constants
+
+ let action: () -> Void
+
+ // MARK: - State Variables
+
+ var label: String
+
+ // MARK: - Binded Variables
+
+ @Binding var buttonState: SwiftUIButtonState
+
+ // MARK: - Views
+
+ var body: some View {
+ switch buttonState {
+ case .enabled:
+ if let key = keyboardShortcut {
+ NativeButton(label, keyEquivalent: key) {
+ action()
+ }
+ } else {
+ Button {
+ action()
+ } label: {
+ Text(label)
+ .frame(minWidth: 44)
+ }
+ }
+ case .disabled:
+ if let key = keyboardShortcut {
+ NativeButton(label, keyEquivalent: key) {
+ return
+ }
+ .disabled(true)
+ } else {
+ Button {
+ return
+ } label: {
+ Text(label)
+ .frame(minWidth: 44)
+ }
+ .disabled(true)
+ }
+ case .hidden:
+ Button {
+ return
+ } label: {
+ Text(label)
+ .frame(minWidth: 44)
+ }
+ .hidden()
+ case .cancel:
+ if let key = keyboardShortcut {
+ NativeButton("cancel_label".localized, keyEquivalent: key) {
+ action()
+ }
+ } else {
+ Button {
+ action()
+ } label: {
+ Text("cancel_label".localized)
+ .frame(minWidth: 44)
+ }
+ }
+ }
+ }
+}
+
+struct StandardButton_Previews: PreviewProvider {
+ static var previews: some View {
+ StandardButton(keyboardShortcut: .return, action: {
+ return
+ }, label: "Main", buttonState: Binding(get: {
+ return .enabled
+ }, set: { _, _ in }))
+ }
+}
diff --git a/Shared/Views/Common/BackPanelWindow.swift b/Shared/Views/Common/BackPanelWindow.swift
new file mode 100644
index 0000000..817137b
--- /dev/null
+++ b/Shared/Views/Common/BackPanelWindow.swift
@@ -0,0 +1,40 @@
+//
+// BackPanelWindow.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 19/05/2023.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import AppKit
+
+/// BackPanelWindow window provide a fullscreen background panel for the UI if needed.
+final class BackPanelWindow: NSWindow {
+
+ // MARK: - Initializers
+
+ convenience init(_ target: NSScreen, _ customWindowStyle: NotificationObject.BackgroundPanelStyle) {
+ self.init(contentRect: NSRect(x: 0, y: 0, width: target.frame.width, height: target.frame.height), styleMask: .fullSizeContentView, backing: .buffered, defer: true, screen: target)
+ self.isOpaque = false
+ let contentView = NSVisualEffectView()
+ contentView.material = .fullScreenUI
+ contentView.blendingMode = .behindWindow
+ contentView.state = customWindowStyle == .opaque ? .inactive : .active
+ self.contentView = contentView
+ self.isMovable = false
+ self.canBecomeVisibleWithoutLogin = true
+ self.collectionBehavior = [.stationary, .canJoinAllSpaces]
+ NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(spaceChange), name: NSWorkspace.activeSpaceDidChangeNotification, object: nil)
+ }
+
+ // MARK: - Private Methods
+
+ @objc
+ private func spaceChange() {
+ DispatchQueue.main.async {
+ (self.contentView as? NSVisualEffectView)?.state = .inactive
+ (self.contentView as? NSVisualEffectView)?.state = .active
+ }
+ }
+}
diff --git a/Shared/Views/Components/Icon.swift b/Shared/Views/Components/Icon.swift
new file mode 100644
index 0000000..46e974c
--- /dev/null
+++ b/Shared/Views/Components/Icon.swift
@@ -0,0 +1,39 @@
+//
+// Icon.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 22/11/22.
+// Copyright © 2022 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// Icon is a struct that defines a view with an image and a give image size.
+struct Icon: View {
+
+ // MARK: - Variables
+
+ var icon: NSImage?
+ var iconSize: CGSize
+
+ // MARK: - Views
+
+ var body: some View {
+ if let image = icon {
+ Image(nsImage: image)
+ .resizable()
+ .frame(width: iconSize.width, height: iconSize.height)
+ } else {
+ Image("default_icon")
+ .resizable()
+ .frame(width: iconSize.width, height: iconSize.height)
+ }
+ }
+}
+
+struct PopupIcon_Previews: PreviewProvider {
+ static var previews: some View {
+ Icon(iconSize: CGSize(width: 60, height: 60))
+ }
+}
diff --git a/Shared/Views/Components/InfoSectionView.swift b/Shared/Views/Components/InfoSectionView.swift
new file mode 100644
index 0000000..a1a7198
--- /dev/null
+++ b/Shared/Views/Components/InfoSectionView.swift
@@ -0,0 +1,115 @@
+//
+// InfoSectionView.swift
+// Notification Agent
+//
+// Created by Simone Martorelli on 04/04/2023.
+// Copyright © 2023 IBM. All rights reserved.
+// SPDX-License-Identifier: Apache2.0
+//
+
+import SwiftUI
+
+/// InfoSectionView is a view used to display and InfoSection object.
+struct InfoSectionView: View {
+
+ // MARK: - Variables
+
+ var section: InfoSection
+ var idealSizeForSection: CGSize {
+ var size: CGSize = .zero
+ if section.fields.count == 1 {
+ if let description = section.fields[0].description {
+ let text = section.fields[0].label + description
+ size.height = max(70, (text.heightThatFitsWidth(400)+30))
+ size.width = 400
+ } else {
+ size.height = max(50, section.fields[0].label.heightThatFitsWidth(400))
+ size.width = 400
+ }
+ }
+ return size
+ }
+
+ // MARK: - Views
+
+ var body: some View {
+ ScrollView {
+ VStack(alignment: .leading, spacing: 10) {
+ ForEach(section.fields) { field in
+ HStack {
+ if let description = field.description {
+ Text(field.label)
+ .font(.body)
+ .frame(maxWidth: 80, maxHeight: .infinity, alignment: .leading)
+ Divider()
+ .padding(.horizontal)
+ Text(description)
+ .font(.body)
+ } else {
+ Text(field.label)
+ .font(.body)
+ .frame(alignment: .leading)
+ }
+ }
+ }
+ }
+ .padding()
+ }
+ .frame(width: calculateIdealSize().width, height: calculateIdealSize().height)
+ }
+
+ func calculateIdealSize() -> CGSize {
+ let maxWidth: CGFloat = 450
+ let maxHeight: CGFloat = 450
+ let dividerWidth: CGFloat = 30
+ let fixedLabelWidth: CGFloat = 80
+
+ let font = NSFont.preferredFont(forTextStyle: .body)
+
+ var totalHeight: CGFloat = 50
+ var currentMaxWidth: CGFloat = 0
+
+ for field in section.fields {
+ let labelSize = field.label.size(withAttributes: [.font: font])
+ let descriptionSize = field.description?.size(withAttributes: [.font: font]) ?? .zero
+
+ let fieldWidth: CGFloat
+ let fieldHeight: CGFloat
+
+ if field.description != nil {
+ fieldWidth = fixedLabelWidth + dividerWidth + descriptionSize.width
+ fieldHeight = max(labelSize.height, descriptionSize.height)
+ } else {
+ fieldWidth = labelSize.width+50
+ fieldHeight = labelSize.height+2.5
+ }
+
+ if fieldWidth > maxWidth {
+ if field.description != nil {
+ let adjustedDescriptionWidth = maxWidth - fixedLabelWidth - dividerWidth
+ let descriptionHeight = ceil(descriptionSize.width / adjustedDescriptionWidth) * descriptionSize.height
+ totalHeight += max(labelSize.height, descriptionHeight)
+ currentMaxWidth = maxWidth
+ } else {
+ let adjustedLabelWidth = maxWidth
+ let labelHeight = ceil(labelSize.width / adjustedLabelWidth) * labelSize.height
+ totalHeight += labelHeight
+ currentMaxWidth = maxWidth
+ }
+ } else {
+ totalHeight += fieldHeight
+ currentMaxWidth = max(currentMaxWidth, fieldWidth)
+ }
+ }
+
+ return CGSize(width: min(maxWidth, currentMaxWidth), height: min(totalHeight, maxHeight))
+ }
+}
+struct InfoSectionView_Previews: PreviewProvider {
+ static var previews: some View {
+ InfoSectionView(section: InfoSection(fields: [InfoField(label: "First", description: "First First First FirstFirst First First First First First First FirstvFirstFirstFirstFirstFirst FirstFirstFirst First First"),
+ InfoField(label: "Second", description: "Second Second Second Second"),
+ InfoField(label: "Third", description: "Third"),
+ InfoField(label: "Fourth", description: "Fourth Fourth Fourth")]))
+ }
+}
diff --git a/Shared/Views/InfoPopOver/InfoPopOverStackItem.swift b/Shared/Views/InfoPopOver/InfoPopOverStackItem.swift
deleted file mode 100644
index 563fd64..0000000
--- a/Shared/Views/InfoPopOver/InfoPopOverStackItem.swift
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-// InfoPopOverStackItem.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 3/16/20.
-// Copyright © 2021 IBM Inc. All rights reserved
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This class manage the stack item that will be showd in an infoPopover.
-final class InfoPopOverStackItem: NSView, LoadableNib {
-
- // MARK: - Outlets
-
- @IBOutlet var contentView: NSView!
- @IBOutlet weak var containerView: NSView!
- @IBOutlet var leftText: NSTextField! {
- didSet {
- guard let text = infoLabel else { return }
- leftText.stringValue = text
- }
- }
- @IBOutlet var rightText: NSTextField! {
- didSet {
- guard let text = infoDescription else { return }
- rightText.stringValue = text
- }
- }
- @IBOutlet var separatorCenter: NSLayoutConstraint! {
- didSet {
- separatorCenter.isActive = false
- separatorCenter = NSLayoutConstraint(item: separator!,
- attribute: .centerX,
- relatedBy: .equal,
- toItem: separator.superview!,
- attribute: .centerX,
- multiplier: separatorMultiplierValue!,
- constant: 0)
- separatorCenter.isActive = true
- }
- }
- @IBOutlet weak var separator: NSBox!
-
- // MARK: - Variables
-
- var infoLabel: String?
- var infoDescription: String?
- var separatorMultiplierValue: CGFloat?
-
- // MARK: - Initializers
-
- required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- }
-
- /// Initialize the stack item with the information of the infoSection row.
- /// - Parameters:
- /// - label: the label of the row.
- /// - description: the description of the row. If nil or not set the row will display only the label in full size.
- /// - separatorMultiplierValue: the multiplier value that handle the position of
- /// an hidden separator between the label and the description.
- init(with field: InfoField, separatorMultiplierValue: CGFloat? = 0.60) {
- self.infoLabel = field.label.localized
- if let description = field.description?.localized, !description.isEmpty {
- self.infoDescription = description
- self.separatorMultiplierValue = separatorMultiplierValue
- } else {
- self.infoDescription = ""
- self.separatorMultiplierValue = 1.9
- }
- super.init(frame: .zero)
- loadViewFromNib()
- }
-}
diff --git a/Shared/Views/InfoPopOver/InfoPopOverStackItem.xib b/Shared/Views/InfoPopOver/InfoPopOverStackItem.xib
deleted file mode 100644
index f42720c..0000000
--- a/Shared/Views/InfoPopOver/InfoPopOverStackItem.xib
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Shared/Views/InfoPopOver/InfoPopOverViewController.swift b/Shared/Views/InfoPopOver/InfoPopOverViewController.swift
deleted file mode 100644
index b93cd7c..0000000
--- a/Shared/Views/InfoPopOver/InfoPopOverViewController.swift
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// InfoPopOverViewController.swift
-// Notification Agent
-//
-// Created by Simone Martorelli on 3/16/20.
-// Copyright © 2021 IBM Inc. All rights reserved
-// SPDX-License-Identifier: Apache2.0
-//
-
-import Cocoa
-
-/// This class manage the popover that shows the informations related to a label.
-final class InfoPopOverViewController: NSViewController {
-
- // MAKR: - Outlets
-
- @IBOutlet var mainStackView: NSStackView!
-
- // MARK: - Variables
-
- var infoSection: InfoSection
-
- // MARK: - Initializers
-
- /// Initialize the popover with the infoSection that needs to be showed.
- /// - Parameter info: the InfoSection object that needs to be displayed ad popover.
- init(with info: InfoSection) {
- self.infoSection = info
- super.init(nibName: .init("InfoPopOverViewController"), bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- return nil
- }
-
- // MARK: - Instance methods
-
- override func viewDidLoad() {
- super.viewDidLoad()
- for (index, field) in infoSection.fields.enumerated() {
- let itemView = InfoPopOverStackItem(with: field)
- self.mainStackView.insertArrangedSubview(itemView, at: index*2)
- guard index+1 < infoSection.fields.count else { return }
- let horizontalLine = HorizontalLine()
- let widthConstraint = NSLayoutConstraint(item: horizontalLine,
- attribute: .width,
- relatedBy: .equal,
- toItem: nil,
- attribute: .width,
- multiplier: 1,
- constant: mainStackView.frame.width-16)
- let heightConstraint = NSLayoutConstraint(item: horizontalLine,
- attribute: .height,
- relatedBy: .equal,
- toItem: nil,
- attribute: .height,
- multiplier: 1,
- constant: 1)
- horizontalLine.addConstraints([widthConstraint, heightConstraint])
- widthConstraint.isActive = true
- heightConstraint.isActive = true
- self.mainStackView.insertArrangedSubview(horizontalLine, at: (index*2)+1)
- }
- }
-}
diff --git a/Shared/Views/InfoPopOver/InfoPopOverViewController.xib b/Shared/Views/InfoPopOver/InfoPopOverViewController.xib
deleted file mode 100644
index 20354d5..0000000
--- a/Shared/Views/InfoPopOver/InfoPopOverViewController.xib
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-