Notification Swiftify: Name changes and reorganization.
This commit is contained in:
parent
7da3af94ec
commit
b5e3f42d6e
@ -349,8 +349,8 @@
|
||||
D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */; };
|
||||
D20923592450ECE00044AD09 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20923582450ECE00044AD09 /* TableView.swift */; };
|
||||
D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; };
|
||||
D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C7008250BF99B0095B21C /* TopNotificationModel.swift */; };
|
||||
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */; };
|
||||
D20C7009250BF99B0095B21C /* NotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C7008250BF99B0095B21C /* NotificationModel.swift */; };
|
||||
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C700A250BFDE40095B21C /* NotificationContainerView.swift */; };
|
||||
D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20F3B43252E00E4004B3F56 /* PageProtocol.swift */; };
|
||||
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */; };
|
||||
D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; };
|
||||
@ -369,7 +369,7 @@
|
||||
D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */; };
|
||||
D22D8393241C27B100D3DF69 /* BaseTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */; };
|
||||
D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */; };
|
||||
D23118B325124E18001C8440 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23118B225124E18001C8440 /* Notification.swift */; };
|
||||
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23118B225124E18001C8440 /* NotificationMoleculeView.swift */; };
|
||||
D2351C7A24A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */; };
|
||||
D2351C7C24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2351C7B24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift */; };
|
||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; };
|
||||
@ -518,7 +518,7 @@
|
||||
D2C521A923EDE79E00CA2634 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C521A823EDE79E00CA2634 /* ViewController.swift */; };
|
||||
D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C78CD124228BBD00B69FDE /* ActionOpenPanelModel.swift */; };
|
||||
D2CAC7CB251104E100C75681 /* NotificationXButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */; };
|
||||
D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CC251104FE00C75681 /* NotificationModel.swift */; };
|
||||
D2CAC7CD251104FE00C75681 /* NotificationMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */; };
|
||||
D2CAC7CF2511052300C75681 /* CollapsableNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */; };
|
||||
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D2FCEF252B72AF0033EAAA /* MoleculeSectionFooterModel.swift */; };
|
||||
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D2FCF2252B72CF0033EAAA /* MoleculeSectionFooter.swift */; };
|
||||
@ -934,8 +934,8 @@
|
||||
D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = "<group>"; };
|
||||
D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
|
||||
D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = "<group>"; };
|
||||
D20C7008250BF99B0095B21C /* TopNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationModel.swift; sourceTree = "<group>"; };
|
||||
D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUITopAlertView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D20C7008250BF99B0095B21C /* NotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModel.swift; sourceTree = "<group>"; };
|
||||
D20C700A250BFDE40095B21C /* NotificationContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContainerView.swift; sourceTree = "<group>"; };
|
||||
D20F3B43252E00E4004B3F56 /* PageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageProtocol.swift; sourceTree = "<group>"; };
|
||||
D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModel.swift; sourceTree = "<group>"; };
|
||||
D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
|
||||
@ -954,7 +954,7 @@
|
||||
D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionMoleculeTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTemplateModel.swift; sourceTree = "<group>"; };
|
||||
D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D23118B225124E18001C8440 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
D23118B225124E18001C8440 /* NotificationMoleculeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMoleculeView.swift; sourceTree = "<group>"; };
|
||||
D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinksModel.swift; sourceTree = "<group>"; };
|
||||
D2351C7B24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinks.swift; sourceTree = "<group>"; };
|
||||
D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = "<group>"; };
|
||||
@ -1105,7 +1105,7 @@
|
||||
D2C521A823EDE79E00CA2634 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
D2C78CD124228BBD00B69FDE /* ActionOpenPanelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionOpenPanelModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButtonModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CC251104FE00C75681 /* NotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMoleculeModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationModel.swift; sourceTree = "<group>"; };
|
||||
D2D2FCEF252B72AF0033EAAA /* MoleculeSectionFooterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionFooterModel.swift; sourceTree = "<group>"; };
|
||||
D2D2FCF2252B72CF0033EAAA /* MoleculeSectionFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionFooter.swift; sourceTree = "<group>"; };
|
||||
@ -2114,8 +2114,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */,
|
||||
D20C7008250BF99B0095B21C /* TopNotificationModel.swift */,
|
||||
D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */,
|
||||
D20C7008250BF99B0095B21C /* NotificationModel.swift */,
|
||||
D20C700A250BFDE40095B21C /* NotificationContainerView.swift */,
|
||||
);
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
@ -2421,8 +2421,8 @@
|
||||
children = (
|
||||
D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */,
|
||||
D2FA83D12513EA6900564112 /* NotificationXButton.swift */,
|
||||
D2CAC7CC251104FE00C75681 /* NotificationModel.swift */,
|
||||
D23118B225124E18001C8440 /* Notification.swift */,
|
||||
D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */,
|
||||
D23118B225124E18001C8440 /* NotificationMoleculeView.swift */,
|
||||
D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */,
|
||||
D2FA83D32514F80C00564112 /* CollapsableNotification.swift */,
|
||||
D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */,
|
||||
@ -2675,7 +2675,7 @@
|
||||
011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */,
|
||||
EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */,
|
||||
BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */,
|
||||
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */,
|
||||
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */,
|
||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */,
|
||||
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */,
|
||||
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */,
|
||||
@ -2816,7 +2816,7 @@
|
||||
8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */,
|
||||
D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */,
|
||||
D2FD4A4925199BD9000C28A9 /* AccessibilityProtocol.swift in Sources */,
|
||||
D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */,
|
||||
D2CAC7CD251104FE00C75681 /* NotificationMoleculeModel.swift in Sources */,
|
||||
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */,
|
||||
EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */,
|
||||
D20923592450ECE00044AD09 /* TableView.swift in Sources */,
|
||||
@ -2903,7 +2903,7 @@
|
||||
32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */,
|
||||
011D958524042432000E3791 /* RulesProtocol.swift in Sources */,
|
||||
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */,
|
||||
D23118B325124E18001C8440 /* Notification.swift in Sources */,
|
||||
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */,
|
||||
AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */,
|
||||
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
|
||||
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
|
||||
@ -2948,7 +2948,7 @@
|
||||
D253BB9E2458751F002DE544 /* BGImageMoleculeModel.swift in Sources */,
|
||||
AA104AC924472DC7004D2810 /* HeadersH1ButtonModel.swift in Sources */,
|
||||
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */,
|
||||
D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */,
|
||||
D20C7009250BF99B0095B21C /* NotificationModel.swift in Sources */,
|
||||
D29C558A25C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift in Sources */,
|
||||
8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */,
|
||||
AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */,
|
||||
|
||||
@ -12,6 +12,7 @@ import MVMCore
|
||||
/// Notifications that conform are collapsable and can collapse.
|
||||
public protocol CollapsableNotificationProtocol {
|
||||
/// Collapses the notification.
|
||||
@MainActor
|
||||
func collapse()
|
||||
}
|
||||
|
||||
@ -22,9 +23,9 @@ open class ActionCollapseNotificationHandler: MVMCoreActionHandlerProtocol {
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
guard let notification = await NotificationHandler.shared().getCurrentNotification() else { return }
|
||||
guard let notification = notification.0 as? CollapsableNotificationProtocol else {
|
||||
NotificationHandler.shared().hideTopAlertView()
|
||||
NotificationHandler.shared().hideNotification()
|
||||
return
|
||||
}
|
||||
notification.collapse()
|
||||
await notification.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,6 @@ open class ActionDismissNotificationHandler: MVMCoreActionHandlerProtocol {
|
||||
required public init() {}
|
||||
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
NotificationHandler.shared().hideTopAlertView()
|
||||
NotificationHandler.shared().hideNotification()
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ public struct ActionTopNotificationModel: ActionModelProtocol {
|
||||
|
||||
public static var identifier: String = "topNotification"
|
||||
public var actionType: String = ActionTopNotificationModel.identifier
|
||||
public var topNotification: TopNotificationModel
|
||||
public var topNotification: NotificationModel
|
||||
public var extraParameters: JSONValueDictionary?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
public init(topNotification: TopNotificationModel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
public init(topNotification: NotificationModel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
self.topNotification = topNotification
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
|
||||
@ -14,7 +14,7 @@ import Foundation
|
||||
//--------------------------------------------------
|
||||
|
||||
public let topView = CollapsableNotificationTopView()
|
||||
public let bottomView = NotificationView()
|
||||
public let bottomView = NotificationMoleculeView()
|
||||
public var verticalStack: UIStackView!
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -170,7 +170,7 @@ import Foundation
|
||||
}
|
||||
|
||||
extension CollapsableNotification: StatusBarUI {
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
|
||||
public func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
|
||||
let color = backgroundColor ?? UIColor.mvmGreen
|
||||
var greyScale: CGFloat = 0
|
||||
topView.label.textColor.getWhite(&greyScale, alpha: nil)
|
||||
@ -189,6 +189,7 @@ extension CollapsableNotification: AccessibilityProtocol {
|
||||
}
|
||||
|
||||
extension CollapsableNotification: CollapsableNotificationProtocol {
|
||||
@MainActor
|
||||
public func collapse() {
|
||||
collapse(animated: true)
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
open class CollapsableNotificationModel: NotificationModel {
|
||||
open class CollapsableNotificationModel: NotificationMoleculeModel {
|
||||
public class override var identifier: String {
|
||||
return "collapsableNotification"
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ import Foundation
|
||||
isAccessibilityElement = true
|
||||
accessibilityLabel = label.text
|
||||
accessibilityTraits = (button.isUserInteractionEnabled && button.actionModel != nil) ? .button : .none
|
||||
NotificationView.amendAccesibilityLabel(for: self)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: self)
|
||||
}
|
||||
|
||||
@objc func pressed(_ sender: Notification) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// NotificationModel.swift
|
||||
// NotificationMoleculeModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/15/20.
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
|
||||
|
||||
/**
|
||||
The style of the notification:
|
||||
@ -34,7 +34,7 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
public var body: LabelModel?
|
||||
public var button: ButtonModel?
|
||||
public var closeButton: NotificationXButtonModel?
|
||||
public var style: NotificationModel.Style = .success
|
||||
public var style: NotificationMoleculeModel.Style = .success
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
@ -132,7 +132,7 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
|
||||
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
|
||||
closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton)
|
||||
if let style = try typeContainer.decodeIfPresent(NotificationModel.Style.self, forKey: .style) {
|
||||
if let style = try typeContainer.decodeIfPresent(NotificationMoleculeModel.Style.self, forKey: .style) {
|
||||
self.style = style
|
||||
}
|
||||
super.init()
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Notification.swift
|
||||
// NotificationMoleculeView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/16/20.
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@objcMembers open class NotificationView: Container {
|
||||
@objcMembers open class NotificationMoleculeView: Container {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
@ -63,7 +63,7 @@ import Foundation
|
||||
|
||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
guard let model = model as? NotificationModel else { return }
|
||||
guard let model = model as? NotificationMoleculeModel else { return }
|
||||
labelStack.updateContainedMolecules(with: [model.headline, model.body], delegateObject, nil)
|
||||
horizontalStack.updateContainedMolecules(with: [labelStack.stackModel, model.button, model.closeButton], delegateObject, nil)
|
||||
updateAccessibility()
|
||||
@ -74,9 +74,9 @@ import Foundation
|
||||
}
|
||||
|
||||
open func updateAccessibility() {
|
||||
NotificationView.amendAccesibilityLabel(for: headline)
|
||||
NotificationView.amendAccesibilityLabel(for: body)
|
||||
NotificationView.amendAccesibilityLabel(for: button)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: headline)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: body)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: button)
|
||||
}
|
||||
|
||||
/// Formats the accessibilityLabel so voice over users know it's in the notification.
|
||||
@ -88,7 +88,7 @@ import Foundation
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationView: AccessibilityProtocol {
|
||||
extension NotificationMoleculeView: AccessibilityProtocol {
|
||||
public func getAccessibilityLayoutChangedArgument() -> Any? {
|
||||
return headline
|
||||
}
|
||||
@ -8,6 +8,14 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
import MVMCore
|
||||
|
||||
/// Allows overrides of the status bar color and style.
|
||||
public protocol StatusBarUI {
|
||||
/// Returns the background color of the status bar view and the style of the status bar.
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle)
|
||||
}
|
||||
|
||||
// Navigation bar update functions
|
||||
public extension MVMCoreUISplitViewController {
|
||||
@ -225,3 +233,31 @@ extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
|
||||
updateState(with: viewController)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public extension MVMCoreUISplitViewController {
|
||||
/// Subscribes for notification events.
|
||||
@objc func subscribeForNotifications() {
|
||||
guard cancellables == nil else { return }
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// Ensure the status bar background color and tint are proper for the notification.
|
||||
NotificationHandler.shared().onNotificationWillShow.sink { [weak self] (notification, model) in
|
||||
guard let conformer = notification as? StatusBarUI else { return }
|
||||
let statusBarUI = conformer.getStatusBarUI()
|
||||
self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
NotificationHandler.shared().onNotificationUpdated.sink { [weak self] (notification, model) in
|
||||
guard let conformer = notification as? StatusBarUI else { return }
|
||||
let statusBarUI = conformer.getStatusBarUI()
|
||||
self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
// Ensure the status bar background color and tint are proper for the view controller.
|
||||
NotificationHandler.shared().onNotificationDismissed.sink { (notification, model) in
|
||||
guard let conformer = notification as? StatusBarUI else { return }
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarForCurrentViewController()
|
||||
}.store(in: &cancellables)
|
||||
self.cancellables = cancellables
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +57,8 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
/// Tab bar index history. Contains either indices (0, 1, etc) or NSNull if there was no tab bar indice to set.
|
||||
@property (nonnull, strong, nonatomic) NSMutableArray <NSNumber *>*tabBarIndices;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSSet *cancellables;
|
||||
|
||||
// Convenience getter
|
||||
+ (nullable instancetype)mainSplitViewController;
|
||||
|
||||
|
||||
@ -84,12 +84,18 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
MVMCoreUISplitViewController *splitViewController = [[self alloc] initWithLeftPanel:leftPanel rightPanel:rightPanel];
|
||||
splitViewController.topAlertView = topAlertView;
|
||||
[MVMCoreUISession sharedGlobal].splitViewController = splitViewController;
|
||||
if (topAlertView) {
|
||||
[splitViewController subscribeForNotifications];
|
||||
}
|
||||
return splitViewController;
|
||||
}
|
||||
|
||||
+ (nullable instancetype)setupAsMainController:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nullable UIView*)topAlertView {
|
||||
MVMCoreUISplitViewController *splitViewController = [self setup:leftPanel rightPanel:rightPanel topAlertView:topAlertView];
|
||||
[[MVMCoreUISession sharedGlobal] setupAsStandardLoadViewDelegate:splitViewController];
|
||||
if (topAlertView) {
|
||||
[splitViewController subscribeForNotifications];
|
||||
}
|
||||
return splitViewController;
|
||||
}
|
||||
|
||||
|
||||
@ -9,14 +9,10 @@
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// Allows top alerts to determine the status bar color and style.
|
||||
protocol StatusBarUI {
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle)
|
||||
}
|
||||
|
||||
/// A simple container view that shows and hides a notification.
|
||||
public class NotificationContainerView: UIView {
|
||||
|
||||
public var currentModel: TopNotificationModel?
|
||||
public var currentModel: NotificationModel?
|
||||
public var currentNotificationView: UIView?
|
||||
|
||||
lazy private var height = heightAnchor.constraint(equalToConstant: 0)
|
||||
@ -31,7 +27,8 @@ public class NotificationContainerView: UIView {
|
||||
setupView()
|
||||
}
|
||||
|
||||
func updateAccessibilityForTopAlert(_ view: UIView) {
|
||||
/// Posts a layout change with taking the arguments from the view following the AccessibilityProtocol.
|
||||
private func updateAccessibilityForTopAlert(_ view: UIView) {
|
||||
// Update accessibility with top alert
|
||||
var accessibilityArgument: Any? = view
|
||||
if let view = view as? AccessibilityProtocol {
|
||||
@ -39,8 +36,6 @@ public class NotificationContainerView: UIView {
|
||||
}
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: accessibilityArgument)
|
||||
}
|
||||
|
||||
// accessibilityFocusChanged; No longer seeing this function, needs a testing.
|
||||
}
|
||||
|
||||
extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
@ -54,11 +49,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
if let conformer = notification as? MVMCoreViewProtocol {
|
||||
conformer.updateView(bounds.width)
|
||||
}
|
||||
|
||||
if let conformer = notification as? StatusBarUI {
|
||||
let statusBarUI = conformer.getStatusBarUI()
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}
|
||||
|
||||
superview?.layoutIfNeeded()
|
||||
await withCheckedContinuation { continuation in
|
||||
@ -83,9 +73,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
self.superview?.layoutIfNeeded()
|
||||
} completion: { finished in
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: nil)
|
||||
if let _ = self.currentNotificationView as? StatusBarUI {
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarForCurrentViewController()
|
||||
}
|
||||
self.currentNotificationView?.removeFromSuperview()
|
||||
self.currentNotificationView = nil
|
||||
continuation.resume()
|
||||
@ -94,10 +81,9 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public func update(with model: TopNotificationModel) {
|
||||
public func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
guard let currentModel = currentModel,
|
||||
currentModel.type == model.type else { return }
|
||||
let delegateObject = MVMCoreUIDelegateObject.create(withDelegateForAll: self)
|
||||
guard let molecule = currentNotificationView as? MoleculeViewProtocol,
|
||||
currentModel.molecule.moleculeName == model.molecule.moleculeName else {
|
||||
// Log that we couldn't update.
|
||||
@ -111,11 +97,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
molecule.reset()
|
||||
molecule.set(with: model.molecule, delegateObject, nil)
|
||||
(molecule as? MVMCoreViewProtocol)?.updateView(self.bounds.width)
|
||||
|
||||
// Update status bar.
|
||||
guard let statusBarDelegate = molecule as? StatusBarUI else { return }
|
||||
let statusBarUI = statusBarDelegate.getStatusBarUI()
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import MVMCore
|
||||
import Dispatch
|
||||
import Combine
|
||||
|
||||
/// Handles the UI tasks for the notification.
|
||||
public protocol NotificationTransitionDelegateProtocol {
|
||||
@MainActor
|
||||
func show(notification: UIView) async
|
||||
@ -18,25 +19,26 @@ public protocol NotificationTransitionDelegateProtocol {
|
||||
func hide(notification: UIView) async
|
||||
|
||||
@MainActor
|
||||
func update(with model: TopNotificationModel)
|
||||
func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?)
|
||||
}
|
||||
|
||||
/// An operation for managing the life cycle of the notification.
|
||||
public class NotificationOperation: MVMCoreOperation {
|
||||
|
||||
public let notification: UIView
|
||||
|
||||
public var notificationModel: TopNotificationModel
|
||||
public var notificationModel: NotificationModel
|
||||
|
||||
/// The delegate that manages transitioning the notification.
|
||||
private let transitionDelegate: NotificationTransitionDelegateProtocol
|
||||
|
||||
/// The notification animation transition operation (show or hide).
|
||||
private var transitionOperation: MVMCoreOperation?
|
||||
|
||||
/// The showing animation transition operation.
|
||||
private weak var showTransitionOperation: Operation?
|
||||
|
||||
/// The stop timer for non-persistent notifications.
|
||||
private var timerSource: DispatchSourceTimer?
|
||||
|
||||
/// Determines if the operation is ready. Certain notifications are only meant to be displayed on certain pages.
|
||||
/// Determines if the operation is ready. For example, certain notifications are only meant to be displayed on certain pages and this can be set accordingly.
|
||||
public var isDisplayable: Bool {
|
||||
get {
|
||||
var isDisplayable: Bool = true
|
||||
@ -52,7 +54,9 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Thread safety.
|
||||
private var displayableQueue = DispatchQueue(label: "displayable", attributes: .concurrent)
|
||||
/// Updates the operation readiness accordingly.
|
||||
private var _isDisplayable: Bool = true {
|
||||
willSet {
|
||||
guard super.isReady else { return }
|
||||
@ -73,7 +77,10 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
}
|
||||
|
||||
public actor Properties {
|
||||
/// If the notification is currently displayed.
|
||||
private var isDisplayed: Bool = false
|
||||
|
||||
/// If the notification is currently animating (showing/hiding).
|
||||
private var isAnimating: Bool = false
|
||||
|
||||
fileprivate func set(displayed: Bool) {
|
||||
@ -92,12 +99,13 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
return isAnimating
|
||||
}
|
||||
}
|
||||
/// Actor isolated properties for the operation.
|
||||
public var properties = Properties()
|
||||
|
||||
// A flag for tracking if the operation needs to be re-added because it was cancelled for a higher priority notification.
|
||||
/// A flag for tracking if the operation needs to be re-added because it was cancelled for a higher priority notification.
|
||||
public var reAddAfterCancel = false
|
||||
|
||||
public init(with notification: UIView, notificationModel: TopNotificationModel, transitionDelegate: NotificationTransitionDelegateProtocol) {
|
||||
public init(with notification: UIView, notificationModel: NotificationModel, transitionDelegate: NotificationTransitionDelegateProtocol) {
|
||||
self.notification = notification
|
||||
self.notificationModel = notificationModel
|
||||
self.transitionDelegate = transitionDelegate
|
||||
@ -107,15 +115,32 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
|
||||
public override func main() {
|
||||
guard !checkAndHandleForCancellation() else { return }
|
||||
add { [weak self] in
|
||||
guard let self = self else { return }
|
||||
await self.showNotification()
|
||||
guard !self.isCancelled else {
|
||||
// Cancelled, dismiss immediately.
|
||||
self.stop()
|
||||
Task {
|
||||
await withCheckedContinuation { continuation in
|
||||
// Show the notification.
|
||||
showTransitionOperation = add(transition: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
NotificationHandler.shared().onNotificationWillShow.send((self.notification, self.notificationModel))
|
||||
await self.showNotification()
|
||||
}, completionBlock: {
|
||||
continuation.resume()
|
||||
})
|
||||
}
|
||||
guard await properties.getIsDisplayed() else {
|
||||
// If the animation did not complete...
|
||||
markAsFinished()
|
||||
return
|
||||
}
|
||||
self.updateStopTimer()
|
||||
|
||||
// Publish that the notification has been shown.
|
||||
NotificationHandler.shared().onNotificationShown.send((notification, notificationModel))
|
||||
|
||||
guard !isCancelled else {
|
||||
// If cancelled during the animation, dismiss immediately.
|
||||
stop()
|
||||
return
|
||||
}
|
||||
updateStopTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,69 +151,33 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
Task {
|
||||
guard await properties.getIsDisplayed(),
|
||||
await !properties.getIsAnimating() else { return }
|
||||
add { [weak self] in
|
||||
guard let self = self else { return }
|
||||
await self.hideNotification()
|
||||
guard !self.isCancelled,
|
||||
!self.notificationModel.persistent else { return }
|
||||
self.markAsFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the transition of the notification to the queue.
|
||||
private func add(transition: @escaping () async -> Void) {
|
||||
Task {
|
||||
guard await properties.getIsDisplayed(),
|
||||
await !properties.getIsAnimating() else { return }
|
||||
transitionOperation = MVMCoreBlockOperation(block: { blockOperation in
|
||||
guard !blockOperation.checkAndHandleForCancellation() else { return }
|
||||
Task {
|
||||
await transition()
|
||||
blockOperation.markAsFinished()
|
||||
}
|
||||
// Hide the notification
|
||||
await withCheckedContinuation({ continuation in
|
||||
_ = add(transition: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
await self.hideNotification()
|
||||
}, completionBlock: {
|
||||
continuation.resume()
|
||||
})
|
||||
})
|
||||
transitionOperation?.completionBlock = { [weak self] in
|
||||
self?.transitionOperation = nil
|
||||
}
|
||||
// Add the animation to the navigation queue to avoid animation collisions.
|
||||
await MVMCoreNavigationHandler.shared()?.addNavigationOperation(transitionOperation!)
|
||||
// The animation must complete...
|
||||
guard await !self.properties.getIsDisplayed() else { return }
|
||||
|
||||
// Publish that the notification has been hidden.
|
||||
NotificationHandler.shared().onNotificationDismissed.send((notification, notificationModel))
|
||||
|
||||
guard !isCancelled,
|
||||
!notificationModel.persistent else { return }
|
||||
markAsFinished()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStopTimer() {
|
||||
if let timerSource = timerSource {
|
||||
timerSource.cancel()
|
||||
}
|
||||
guard !notificationModel.persistent else { return }
|
||||
timerSource = DispatchSource.makeTimerSource()
|
||||
timerSource?.setEventHandler(handler: { [weak self] in
|
||||
print("SSSS TIMER EVENT FIRED FOR: \(String(describing: self?.notificationModel.type))")
|
||||
guard let self = self,
|
||||
!self.isFinished,
|
||||
!self.checkAndHandleForCancellation() else { return }
|
||||
/*
|
||||
// If accessible and focused, do not collapse until unfocused.
|
||||
if (!forceful && [MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||
self.hideCompletionHandler = completionHandler;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accessibilityFocusChanged:) name:UIAccessibilityElementFocusedNotification object:nil];
|
||||
return;
|
||||
}
|
||||
*/
|
||||
self.stop()
|
||||
})
|
||||
timerSource?.setCancelHandler(handler: { [weak self] in
|
||||
print("SSSS TIMER EVENT CANCELLED FOR: \(String(describing: self?.notificationModel.type))")
|
||||
})
|
||||
timerSource?.schedule(deadline: .now() + .seconds(notificationModel.dismissTime))
|
||||
}
|
||||
|
||||
public override func cancel() {
|
||||
super.cancel()
|
||||
Task {
|
||||
if await !properties.getIsDisplayed() {
|
||||
// Cancel any pending show transitions.
|
||||
transitionOperation?.cancel()
|
||||
showTransitionOperation?.cancel()
|
||||
}
|
||||
|
||||
// Do nothing if animating.
|
||||
@ -201,37 +190,7 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func showNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.show(notification: notification)
|
||||
await properties.set(displayed: true)
|
||||
await properties.set(animating: false)
|
||||
NotificationHandler.shared().onNotificationShown.send((notification, notificationModel))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func hideNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.hide(notification: notification)
|
||||
await properties.set(displayed: false)
|
||||
await properties.set(animating: false)
|
||||
NotificationHandler.shared().onNotificationDismissed.send((notification, notificationModel))
|
||||
}
|
||||
|
||||
/// Updates the notification with the new model.
|
||||
public func update(with model: TopNotificationModel) {
|
||||
self.notificationModel = model
|
||||
queuePriority = model.priority
|
||||
guard isExecuting,
|
||||
!isCancelled else { return }
|
||||
updateStopTimer()
|
||||
Task { @MainActor in
|
||||
transitionDelegate.update(with: notificationModel)
|
||||
}
|
||||
}
|
||||
|
||||
func copy(with zone: NSZone? = nil) -> Any {
|
||||
public func copy(with zone: NSZone? = nil) -> Any {
|
||||
let operation = NotificationOperation(with: notification, notificationModel: notificationModel, transitionDelegate: transitionDelegate)
|
||||
operation.reAddAfterCancel = reAddAfterCancel
|
||||
operation.isDisplayable = isDisplayable
|
||||
@ -242,22 +201,96 @@ public class NotificationOperation: MVMCoreOperation {
|
||||
operation.qualityOfService = qualityOfService
|
||||
return operation
|
||||
}
|
||||
|
||||
// MARK: - Automatic
|
||||
|
||||
/// Sets up a timer to hide the notification.
|
||||
private func updateStopTimer() {
|
||||
if let timerSource = timerSource {
|
||||
timerSource.cancel()
|
||||
}
|
||||
guard !notificationModel.persistent else { return }
|
||||
timerSource = DispatchSource.makeTimerSource()
|
||||
timerSource?.setEventHandler(handler: { [weak self] in
|
||||
print("SSSS TIMER EVENT FIRED FOR: \(String(describing: self?.notificationModel.type))")
|
||||
guard let self = self,
|
||||
!self.isFinished,
|
||||
!self.checkAndHandleForCancellation() else { return }
|
||||
|
||||
// If voice over is on and the notification is focused, do not collapse until unfocused.
|
||||
guard !MVMCoreUIUtility.viewContainsAccessiblityFocus(notification) else {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accessibilityFocusChanged), name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
return
|
||||
}
|
||||
self.stop()
|
||||
})
|
||||
timerSource?.setCancelHandler(handler: { [weak self] in
|
||||
print("SSSS TIMER EVENT CANCELLED FOR: \(String(describing: self?.notificationModel.type))")
|
||||
})
|
||||
timerSource?.schedule(deadline: .now() + .seconds(notificationModel.dismissTime))
|
||||
}
|
||||
|
||||
/// If the voice over user leaves top alert focus, hide.
|
||||
@objc func accessibilityFocusChanged(_ notification: NSNotification) {
|
||||
guard let _ = notification.userInfo?[UIAccessibility.focusedElementUserInfoKey],
|
||||
!MVMCoreUIUtility.viewContainsAccessiblityFocus(self.notification) else { return }
|
||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
stop()
|
||||
}
|
||||
|
||||
// MARK: - Transitions
|
||||
|
||||
/// Adds the transition of the notification to the navigation queue to avoid animation collisions.
|
||||
private func add(transition: @escaping () async -> Void, completionBlock: (() -> Void)?) -> Operation {
|
||||
let transitionOperation = MVMCoreBlockOperation(block: { blockOperation in
|
||||
guard !blockOperation.checkAndHandleForCancellation() else { return }
|
||||
Task {
|
||||
await transition()
|
||||
blockOperation.markAsFinished()
|
||||
}
|
||||
})!
|
||||
transitionOperation.completionBlock = completionBlock
|
||||
MVMCoreNavigationHandler.shared()?.addNavigationOperation(transitionOperation)
|
||||
return transitionOperation
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func showNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.show(notification: notification)
|
||||
await properties.set(displayed: true)
|
||||
await properties.set(animating: false)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func hideNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.hide(notification: notification)
|
||||
await properties.set(displayed: false)
|
||||
await properties.set(animating: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages notifications.
|
||||
public class NotificationHandler {
|
||||
|
||||
/// The operation queue of top notification operations.
|
||||
private var queue = OperationQueue()
|
||||
|
||||
public var transitionDelegate: NotificationTransitionDelegateProtocol
|
||||
private var transitionDelegate: NotificationTransitionDelegateProtocol
|
||||
|
||||
private var delegateObject: MVMCoreUIDelegateObject?
|
||||
|
||||
/// Publishes when a notification will show.
|
||||
public let onNotificationWillShow = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is shown.
|
||||
public let onNotificationShown = PassthroughSubject<(UIView, TopNotificationModel), Never>()
|
||||
public let onNotificationShown = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is dismissed.
|
||||
public let onNotificationDismissed = PassthroughSubject<(UIView, TopNotificationModel), Never>()
|
||||
public let onNotificationDismissed = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is updated.
|
||||
public let onNotificationUpdated = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Returns the handler stored in the CoreUIObject
|
||||
public static func shared() -> Self {
|
||||
@ -285,10 +318,13 @@ public class NotificationHandler {
|
||||
/// Checks for new top alert json
|
||||
@objc private func responseJSONUpdated(notification: Notification) async {
|
||||
guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return }
|
||||
let delegateObject = loadObject.delegateObject as? MVMCoreUIDelegateObject
|
||||
|
||||
// Dismiss any top alerts that server wants us to dismiss/
|
||||
// Dismiss any top alerts that server wants us to dismiss.
|
||||
if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") {
|
||||
NotificationHandler.shared().hideTopAlertView(of: disableType)
|
||||
NotificationHandler.shared().cancelNotification(using: { view, model in
|
||||
return model.type == disableType
|
||||
})
|
||||
}
|
||||
|
||||
// Show any new top alert.
|
||||
@ -299,9 +335,10 @@ public class NotificationHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the json to a model and creates the view and queues up the notification.
|
||||
public func showNotification(for json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) async {
|
||||
do {
|
||||
let model = try TopNotificationModel.decode(json: json, delegateObject: delegateObject)
|
||||
let model = try NotificationModel.decode(json: json, delegateObject: delegateObject)
|
||||
try await showNotification(for: model, delegateObject: delegateObject)
|
||||
} catch {
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") {
|
||||
@ -332,10 +369,10 @@ public class NotificationHandler {
|
||||
}
|
||||
|
||||
/// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated.
|
||||
private func checkAndUpdateExisting(with model: TopNotificationModel) -> Bool {
|
||||
private func checkAndUpdateExisting(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) -> Bool {
|
||||
for case let operation as NotificationOperation in queue.operations {
|
||||
guard operation.notificationModel.type == model.type else { continue }
|
||||
operation.update(with: model)
|
||||
operation.update(with: model, delegateObject: delegateObject)
|
||||
let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
||||
operation.updateDisplayable(by: pageType)
|
||||
reevaluteQueue()
|
||||
@ -377,76 +414,28 @@ public class NotificationHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Show and hide
|
||||
|
||||
public func isTopAlertShowing() -> Bool {
|
||||
// MARK: - Verify
|
||||
|
||||
/// Returns if any notification is executing
|
||||
public func isNotificationShowing() -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) != nil
|
||||
}
|
||||
|
||||
public func hasPersistentTopAlert(of type: String) -> Bool {
|
||||
/** Returns if the first executing operation matches the provided predicate.
|
||||
* @param predicate The predicate block to decide if it is the notification.
|
||||
*/
|
||||
public func hasNotification(using predicate: ((UIView, NotificationModel) -> Bool)) -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
guard operation.isExecuting,
|
||||
let operation = operation as? NotificationOperation else { return false }
|
||||
return operation.notificationModel.persistent && operation.notificationModel.type == type
|
||||
return predicate(operation.notification, operation.notificationModel)
|
||||
}) as? NotificationOperation != nil
|
||||
}
|
||||
|
||||
/// Creates the view and queues up the notification.
|
||||
public func showNotification(for model: TopNotificationModel, delegateObject: MVMCoreUIDelegateObject? = nil) async throws {
|
||||
guard !checkAndUpdateExisting(with: model) else { return }
|
||||
let view = try await createMolecule(for: model, delegateObject: delegateObject)
|
||||
let operation = NotificationOperation(with: view, notificationModel: model, transitionDelegate: transitionDelegate)
|
||||
NotificationHandler.shared().add(operation: operation)
|
||||
}
|
||||
|
||||
/// Cancel the current top alert view.
|
||||
public func hideTopAlertView() {
|
||||
guard let currentOperation = queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) as? NotificationOperation else { return }
|
||||
currentOperation.notificationModel.persistent = false
|
||||
currentOperation.reAddAfterCancel = false
|
||||
currentOperation.cancel()
|
||||
}
|
||||
|
||||
/// Cancel all operations of this type.
|
||||
public func hideTopAlertView(of type: String) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? NotificationOperation,
|
||||
operation.notificationModel.type == type else { continue }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all persistent operations of this type.
|
||||
public func hidePersistentTopAlertView(of type: String) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? NotificationOperation,
|
||||
operation.notificationModel.persistent,
|
||||
operation.notificationModel.type == type else { continue }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds an cancels top alerts associated with the object.
|
||||
public func removeTopAlert(for object: TopNotificationModel) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? NotificationOperation,
|
||||
operation.notificationModel.id == object.id else { return }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeAllTopAlerts() {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
|
||||
public func getCurrentNotification() async -> (UIView, TopNotificationModel)? {
|
||||
/// Returns the current executing notification view and model
|
||||
public func getCurrentNotification() async -> (UIView, NotificationModel)? {
|
||||
for operation in queue.operations {
|
||||
guard operation.isExecuting,
|
||||
let operation = operation as? NotificationOperation,
|
||||
@ -456,15 +445,43 @@ public class NotificationHandler {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Creates and returns the molecule view.
|
||||
@MainActor
|
||||
private func createMolecule(for model: TopNotificationModel, delegateObject: MVMCoreUIDelegateObject? = nil) throws -> UIView {
|
||||
do {
|
||||
guard let molecule = ModelRegistry.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil) else {
|
||||
throw ModelRegistry.Error.decoderOther(message: "Molecule not mapped")
|
||||
}
|
||||
return molecule
|
||||
// MARK: - Show and hide
|
||||
|
||||
/// Creates the view and queues up the notification.
|
||||
public func showNotification(for model: NotificationModel, delegateObject: MVMCoreUIDelegateObject? = nil) async throws {
|
||||
guard !checkAndUpdateExisting(with: model, delegateObject: delegateObject) else { return }
|
||||
guard let view = ModelRegistry.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil) else {
|
||||
throw ModelRegistry.Error.decoderOther(message: "Molecule not mapped")
|
||||
}
|
||||
let operation = NotificationOperation(with: view, notificationModel: model, transitionDelegate: transitionDelegate)
|
||||
NotificationHandler.shared().add(operation: operation)
|
||||
}
|
||||
|
||||
/// Cancel the current top alert view.
|
||||
public func hideNotification() {
|
||||
guard let currentOperation = queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) as? NotificationOperation else { return }
|
||||
currentOperation.notificationModel.persistent = false
|
||||
currentOperation.reAddAfterCancel = false
|
||||
currentOperation.cancel()
|
||||
}
|
||||
|
||||
/** Iterates through all scheduled notifications and cancels any that match the provided predicate.
|
||||
* @param predicate The predicate block to decide whether to cancel an notification.
|
||||
*/
|
||||
public func cancelNotification(using predicate: ((UIView, NotificationModel) -> Bool)) {
|
||||
for case let operation as NotificationOperation in queue.operations {
|
||||
if predicate(operation.notification, operation.notificationModel) {
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all notifications, current or pending.
|
||||
public func removeAllNotifications() {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,6 +504,18 @@ extension NotificationHandler: MVMCorePresentationDelegateProtocol {
|
||||
}
|
||||
|
||||
extension NotificationOperation {
|
||||
/// Updates the operation and notification with the new model.
|
||||
public func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
self.notificationModel = model
|
||||
queuePriority = model.priority
|
||||
guard isExecuting,
|
||||
!isCancelled else { return }
|
||||
updateStopTimer()
|
||||
Task { @MainActor in
|
||||
transitionDelegate.update(with: notificationModel, delegateObject: delegateObject)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates if the operation is displayable based on the page type.
|
||||
func updateDisplayable(by pageType: String?) {
|
||||
guard let pages = notificationModel.pages else {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// TopNotification.swift
|
||||
// NotificationModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/11/20.
|
||||
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
open class TopNotificationModel: Codable, Identifiable {
|
||||
open class NotificationModel: Codable, Identifiable {
|
||||
public var type: String
|
||||
public var priority = Operation.QueuePriority.normal
|
||||
public var molecule: MoleculeModelProtocol
|
||||
@ -209,7 +209,7 @@ open class CoreUIModelMapping: ModelMapping {
|
||||
ModelRegistry.register(handler: TitleLockup.self, for: TitleLockupModel.self)
|
||||
|
||||
// MARK: - Top Notifications
|
||||
ModelRegistry.register(handler: NotificationView.self, for: NotificationModel.self)
|
||||
ModelRegistry.register(handler: NotificationMoleculeView.self, for: NotificationMoleculeModel.self)
|
||||
ModelRegistry.register(handler: CollapsableNotification.self, for: CollapsableNotificationModel.self)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user