From ea50b838d29e0fd6df7ce5bcb2593fb42f4ce3e1 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 21 Sep 2020 09:52:51 -0400 Subject: [PATCH 01/18] molecular --- MVMCoreUI.xcodeproj/project.pbxproj | 16 ++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 4 +- .../CollapsableNotification.swift | 168 ++++++++++++++++++ .../CollapsableNotificationModel.swift | 43 ++--- .../TopNotification/Notification.swift | 117 ++++++++++++ .../TopNotification/NotificationModel.swift | 52 +++++- .../NotificationStatusBar.swift | 56 ++++++ .../TopNotification/NotificationXButton.swift | 42 +++++ .../NotificationXButtonModel.swift | 23 ++- MVMCoreUI/BaseClasses/Button.swift | 4 +- .../MVMCoreUITopAlertView+Extension.swift | 12 +- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h | 3 +- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m | 16 +- 13 files changed, 503 insertions(+), 53 deletions(-) create mode 100644 MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift create mode 100644 MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift create mode 100644 MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift create mode 100644 MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b67f6235..de7e8645 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -318,6 +318,7 @@ D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */; }; D22D8393241C27B100D3DF69 /* TemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8392241C27B100D3DF69 /* TemplateModel.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 */; }; 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 */; }; @@ -481,6 +482,9 @@ D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99E23E07F8A000B42E6 /* PillButton.swift */; }; D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */; }; D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */; }; + D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D12513EA6900564112 /* NotificationXButton.swift */; }; + D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D32514F80C00564112 /* CollapsableNotification.swift */; }; + D2FA83D62515021F00564112 /* NotificationStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* NotificationStatusBar.swift */; }; D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; @@ -806,6 +810,7 @@ D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionMoleculeTableViewCell.swift; sourceTree = ""; }; D22D8392241C27B100D3DF69 /* TemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModel.swift; sourceTree = ""; }; D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = ""; }; + D23118B225124E18001C8440 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinksModel.swift; sourceTree = ""; }; D2351C7B24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinks.swift; sourceTree = ""; }; D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = ""; }; @@ -970,6 +975,9 @@ D2E2A99E23E07F8A000B42E6 /* PillButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButton.swift; sourceTree = ""; }; D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModelProtocol.swift; sourceTree = ""; }; D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableableModelProtocol.swift; sourceTree = ""; }; + D2FA83D12513EA6900564112 /* NotificationXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButton.swift; sourceTree = ""; }; + D2FA83D32514F80C00564112 /* CollapsableNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotification.swift; sourceTree = ""; }; + D2FA83D52515021F00564112 /* NotificationStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusBar.swift; sourceTree = ""; }; D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackItem.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; @@ -2077,9 +2085,13 @@ isa = PBXGroup; children = ( D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */, + D2FA83D12513EA6900564112 /* NotificationXButton.swift */, D2CAC7CC251104FE00C75681 /* NotificationModel.swift */, + D23118B225124E18001C8440 /* Notification.swift */, D2CAC7D02511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift */, D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */, + D2FA83D32514F80C00564112 /* CollapsableNotification.swift */, + D2FA83D52515021F00564112 /* NotificationStatusBar.swift */, D2CAC7D2251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift */, ); path = TopNotification; @@ -2460,6 +2472,7 @@ C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, + D23118B325124E18001C8440 /* Notification.swift in Sources */, AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, @@ -2515,6 +2528,7 @@ D2A92882241AAB67004E01C6 /* ScrollingViewController.swift in Sources */, C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */, 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */, + D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */, D2D90B442404789000DD6EC9 /* MoleculeContainerProtocol.swift in Sources */, 0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */, 94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */, @@ -2560,6 +2574,7 @@ AAB8549824DC01BD00477C40 /* ListThreeColumnBillHistoryDividerModel.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, + D2FA83D62515021F00564112 /* NotificationStatusBar.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, @@ -2605,6 +2620,7 @@ C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */, + D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */, 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a598b113..9ebb8cfb 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -233,8 +233,8 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: LockupsPlanSMLXL.self, viewModelClass: LockupsPlanSMLXLModel.self) // MARK: - Top Notifications - MoleculeObjectMapping.shared()?.register(viewClass: MVMCoreUITopAlertMainView.self, viewModelClass: NotificationModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: MVMCoreUITopAlertExpandableView.self, viewModelClass: CollapsableNotificationModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: NotificationView.self, viewModelClass: NotificationModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: CollapsableNotification.self, viewModelClass: CollapsableNotificationModel.self) // MARK:- Helper models try? ModelRegistry.register(RuleRequiredModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift new file mode 100644 index 00000000..54eeb51a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift @@ -0,0 +1,168 @@ +// +// CollapsableNotification.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/18/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class CollapsableNotification: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let topView = NotificationStatusBar() + public let bottomView = NotificationView() + public var verticalStack: UIStackView! + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + public override func setupView() { + super.setupView() + + verticalStack = UIStackView(arrangedSubviews: [topView, bottomView]) + verticalStack.translatesAutoresizingMaskIntoConstraints = false + verticalStack.axis = .vertical + verticalStack.alignment = .fill + verticalStack.distribution = .fill + addSubview(verticalStack) + NSLayoutConstraint.constraintPinSubview(verticalStack, pinTop: true, topConstant: 0, pinBottom: true, bottomConstant: 0, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0) + + reset() + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + verticalStack.updateView(size) + } + + open override func reset() { + super.reset() + verticalStack.reset() + backgroundColor = .mvmGreen() + } + + //-------------------------------------------------- + // MARK: - Molecule + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? CollapsableNotificationModel else { return } + topView.label.set(with: model.topLabel, delegateObject, additionalData) + topView.button.set(with: model.topAction, delegateObject: delegateObject, additionalData: additionalData) + bottomView.set(with: model, delegateObject, additionalData) + updateAccessibilityLabel() + + topView.isHidden = !model.alwaysShowTopLabel && !model.initiallyCollapsed + topView.button.isUserInteractionEnabled = model.initiallyCollapsed + bottomView.isHidden = model.initiallyCollapsed + verticalStack.layoutIfNeeded() + + if !model.initiallyCollapsed { + collapse(with: .now() + DispatchTimeInterval.seconds(model.collapseTime)) + } + } + + open func collapse(with delay: DispatchTime) { + DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in + self?.collapse() + } + } + + open func collapse(animated: Bool = true) { + let animation = { [weak self] in + self?.topView.isHidden = false + self?.bottomView.isHidden = true + self?.verticalStack.layoutIfNeeded() + } + if animated { + UIView.animate(withDuration: 0.5, animations: animation) { [weak self] (finished) in + self?.topView.button.isUserInteractionEnabled = true + } + } else { + animation() + } + } + + open func expand(topViewShowing: Bool = false, animated: Bool = true) { + topView.button.isUserInteractionEnabled = false + let animation = { [weak self] in + self?.topView.isHidden = !topViewShowing + self?.bottomView.isHidden = false + self?.verticalStack.layoutIfNeeded() + } + if animated { + UIView.animate(withDuration: 0.5, animations: animation) { [weak self] (finished) in + + } + } else { + animation() + } + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 96 + } + /* + func getAccessibilityMessage() -> String? { + + guard let leftImageLabel = leftImage.imageView.accessibilityLabel else { + return eyebrowHeadlineBodyLink.getAccessibilityMessage() + } + + guard let label = eyebrowHeadlineBodyLink.getAccessibilityMessage() else { + return leftImageLabel + } + + return leftImageLabel + ", " + label + }*/ + + func updateAccessibilityLabel() { + /*headline.accessibilityLabel = headline.text + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: headline) + + body.accessibilityLabel = body.text + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: body) + + + let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + isAccessibilityElement = !linkShowing + accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none + + if !linkShowing { + // Make whole cell focusable if no link. + accessibilityLabel = getAccessibilityMessage() + } else if let accessoryView = accessoryView { + // Both caret and link. Read all content on caret. + accessoryView.accessibilityLabel = getAccessibilityMessage() + accessibilityElements = [accessoryView, eyebrowHeadlineBodyLink.link] + } else { + // Only link. Manually add accessibility elements to ensure they are read in the right order. + var elements: [Any] = [] + + if let leftImageLabel = leftImage.imageView.accessibilityLabel, !leftImageLabel.isEmpty { + elements.append(leftImage.imageView) + } + + if let otherElements = eyebrowHeadlineBodyLink.getAccessibilityElements() { + elements.append(otherElements) + } + + accessibilityElements = elements + }*/ + } +} + +extension CollapsableNotification: StatusBarUI { + func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) { + let color = backgroundColor ?? UIColor.mvmGreen + var greyScale: CGFloat = 0 + topView.label.textColor.getWhite(&greyScale, alpha: nil) + return (color, greyScale > 0.5 ? .lightContent : .default) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationModel.swift index 15db1009..804bfeb6 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationModel.swift @@ -8,16 +8,12 @@ import Foundation -public class CollapsableNotificationModel: MoleculeModelProtocol { - public static var identifier: String = "collapsableNotification" - public var moleculeName: String = CollapsableNotificationModel.identifier - public var backgroundColor: Color? +public class CollapsableNotificationModel: NotificationModel { + public class override var identifier: String { + return "collapsableNotification" + } public var topLabel: LabelModel public var topAction: ActionModelProtocol? - public var headline: LabelModel - public var body: LabelModel? - public var button: ButtonModel? - public var closeButton: NotificationXButtonModel? public var alwaysShowTopLabel = false public var collapseTime: Int = 5 public var initiallyCollapsed = false @@ -25,18 +21,23 @@ public class CollapsableNotificationModel: MoleculeModelProtocol { init(with topLabel: LabelModel, headline: LabelModel) { self.topLabel = topLabel - self.headline = headline + super.init(with: headline) + } + + override func setDefault() { + super.setDefault() + if topLabel.textColor == nil { + topLabel.textColor = Color(uiColor: .white) + } + if topLabel.textAlignment == nil { + topLabel.textAlignment = .center + } } private enum CodingKeys: String, CodingKey { case moleculeName - case backgroundColor case topLabel case topAction - case headline - case body - case button - case closeButton case alwaysShowTopLabel case collapseTime case initiallyCollapsed @@ -46,12 +47,7 @@ public class CollapsableNotificationModel: MoleculeModelProtocol { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) topLabel = try typeContainer.decode(LabelModel.self, forKey: .topLabel) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) topAction = try typeContainer.decodeModelIfPresent(codingKey: .topAction) - headline = try typeContainer.decode(LabelModel.self, forKey: .headline) - 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 alwaysShowTopLabel = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowTopLabel) { self.alwaysShowTopLabel = alwaysShowTopLabel } @@ -62,18 +58,15 @@ public class CollapsableNotificationModel: MoleculeModelProtocol { self.initiallyCollapsed = initiallyCollapsed } pages = try typeContainer.decodeIfPresent([String].self, forKey: .pages) + try super.init(from: decoder) } - public func encode(to encoder: Encoder) throws { + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encode(topLabel, forKey: .topLabel) try container.encodeModelIfPresent(topAction, forKey: .topAction) - try container.encode(headline, forKey: .headline) - try container.encodeIfPresent(body, forKey: .body) - try container.encodeIfPresent(button, forKey: .button) - try container.encodeIfPresent(closeButton, forKey: .closeButton) try container.encode(alwaysShowTopLabel, forKey: .alwaysShowTopLabel) try container.encode(collapseTime, forKey: .collapseTime) try container.encode(initiallyCollapsed, forKey: .initiallyCollapsed) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift new file mode 100644 index 00000000..823158fc --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift @@ -0,0 +1,117 @@ +// +// Notification.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class NotificationView: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let headline = Label(fontStyle: .BoldBodySmall) + public let body = Label(fontStyle: .RegularBodySmall) + public let button = PillButton(asPrimaryButton: false, makeTiny: true) + public let closeButton = NotificationXButton() + public var labelStack: Stack! + public var horizontalStack: Stack! + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + public override func setupView() { + super.setupView() + reset() + + labelStack = Stack.createStack(with: [headline, body], spacing: 0) + horizontalStack = Stack.createStack(with: [(view: labelStack, model: StackItemModel()),(view: button, model: StackItemModel(horizontalAlignment: .fill)),(view: closeButton, model: StackItemModel(horizontalAlignment: .fill))], axis: .horizontal) + addSubview(horizontalStack) + NSLayoutConstraint.constraintPinSubview(horizontalStack, pinTop: true, topConstant: PaddingTwo, pinBottom: true, bottomConstant: PaddingTwo, pinLeft: true, leftConstant: PaddingThree, pinRight: true, rightConstant: PaddingThree) + labelStack.restack() + horizontalStack.restack() + + heightAnchor.constraint(equalToConstant: 96).isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + horizontalStack.updateView(size) + } + + open override func reset() { + super.reset() + backgroundColor = .mvmGreen() + headline.textColor = .white + body.textColor = .white + } + + //-------------------------------------------------- + // MARK: - Molecule + //-------------------------------------------------- + + 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 } + labelStack.updateContainedMolecules(with: [model.headline, model.body], delegateObject, nil) + horizontalStack.updateContainedMolecules(with: [labelStack.stackModel, model.button, model.closeButton], delegateObject, nil) + + updateAccessibilityLabel() + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 96 + } + /* + func getAccessibilityMessage() -> String? { + + guard let leftImageLabel = leftImage.imageView.accessibilityLabel else { + return eyebrowHeadlineBodyLink.getAccessibilityMessage() + } + + guard let label = eyebrowHeadlineBodyLink.getAccessibilityMessage() else { + return leftImageLabel + } + + return leftImageLabel + ", " + label + }*/ + + func updateAccessibilityLabel() { + /*headline.accessibilityLabel = headline.text + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: headline) + + body.accessibilityLabel = body.text + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: body) + + + let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + isAccessibilityElement = !linkShowing + accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none + + if !linkShowing { + // Make whole cell focusable if no link. + accessibilityLabel = getAccessibilityMessage() + } else if let accessoryView = accessoryView { + // Both caret and link. Read all content on caret. + accessoryView.accessibilityLabel = getAccessibilityMessage() + accessibilityElements = [accessoryView, eyebrowHeadlineBodyLink.link] + } else { + // Only link. Manually add accessibility elements to ensure they are read in the right order. + var elements: [Any] = [] + + if let leftImageLabel = leftImage.imageView.accessibilityLabel, !leftImageLabel.isEmpty { + elements.append(leftImage.imageView) + } + + if let otherElements = eyebrowHeadlineBodyLink.getAccessibilityElements() { + elements.append(otherElements) + } + + accessibilityElements = elements + }*/ + } +} diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationModel.swift index ac74abb6..e2f94326 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationModel.swift @@ -9,8 +9,9 @@ import Foundation public class NotificationModel: MoleculeModelProtocol { - public static var identifier: String = "notification" - public var moleculeName: String = NotificationModel.identifier + public class var identifier: String { + return "notification" + } public var backgroundColor: Color? public var headline: LabelModel public var body: LabelModel? @@ -20,4 +21,51 @@ public class NotificationModel: MoleculeModelProtocol { init(with headline: LabelModel) { self.headline = headline } + + func setDefault() { + if backgroundColor == nil { + backgroundColor = Color(uiColor: .mvmGreen()) + } + if headline.textColor == nil { + headline.textColor = Color(uiColor: .white) + } + if body?.textColor == nil { + body?.textColor = Color(uiColor: .white) + } + if button?.style == nil { + button?.style = .secondary + } + button?.size = .tiny + button?.enabledTextColor = Color(uiColor: .white) + button?.enabledBorderColor = Color(uiColor: .white) + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case headline + case body + case button + case closeButton + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + headline = try typeContainer.decode(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button) + closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton) + setDefault() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(button, forKey: .button) + try container.encodeIfPresent(closeButton, forKey: .closeButton) + } } diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift new file mode 100644 index 00000000..73bde3f1 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift @@ -0,0 +1,56 @@ +// +// NotificationStatusBar.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/18/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class NotificationStatusBar: View { + public let label: Label = { + let label = Label(fontStyle: .BoldBodySmall) + label.numberOfLines = 1 + label.textAlignment = .center + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + return label + }() + + public let button: Button = { + let button = Button(type: .custom) + button.backgroundColor = .clear + button.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + button.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + return button + }() + + public override func setupView() { + super.setupView() + + addSubview(label) + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: 0, pinBottom: true, bottomConstant: 0, pinLeft: true, leftConstant: PaddingThree, pinRight: true, rightConstant: PaddingThree) + + addSubview(button) + NSLayoutConstraint.constraintPinSubview(button, pinTop: true, topConstant: 0, pinBottom: true, bottomConstant: -5, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0) + + // Listen for status bar touches. + NotificationCenter.default.addObserver(self, selector: #selector(pressed(_:)), name: NSNotification.Name(rawValue: NotificationStatusBarTouched), object: nil) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + } + + open override func reset() { + super.reset() + label.setFontStyle(.BoldBodySmall) + label.textColor = .white + label.textAlignment = .center + } + + @objc func pressed(_ sender: Notification) { + button.callActionBlock(button) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift new file mode 100644 index 00000000..434ddc3d --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift @@ -0,0 +1,42 @@ +// +// NotificationXButton.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class NotificationXButton: Button { + + open func closeTopAlert() { + if let delegate = MVMCoreUITopAlertView.sharedGlobal()?.animationDelegate { + delegate.topAlertCloseButtonPressed() + } else { + MVMCoreUISession.sharedGlobal()?.topAlertView?.hideAlertView(true, completionHandler: nil) + } + } + + open override func setupView() { + if let image = MVMCoreUIUtility.imageNamed("nav_close")?.withRenderingMode(.alwaysTemplate) { + setImage(image, for: .normal) + } + tintColor = .white + adjustsImageWhenHighlighted = false + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "AccCloseButton") + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? NotificationXButtonModel else { return } + tintColor = model.color.uiColor + + // temporary + if model.action.actionType == ActionNoopModel.identifier { + addActionBlock(event: .touchUpInside) { (button) in + (button as? NotificationXButton)?.closeTopAlert() + } + } + } +} diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButtonModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButtonModel.swift index 368ec1cf..81dae17e 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButtonModel.swift @@ -8,27 +8,32 @@ import Foundation -public class NotificationXButtonModel: Codable { - public var color: Color? - public var action: ActionModelProtocol? +public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtocol { + public static var identifier: String = "notificationXButton" + public var backgroundColor: Color? + public var color = Color(uiColor: .white) + public var action: ActionModelProtocol = ActionNoopModel() private enum CodingKeys: String, CodingKey { + case moleculeName case color case action } - public init() { - } + public init() {} public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) - action = try typeContainer.decodeModelIfPresent(codingKey: .action) + color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) ?? Color(uiColor: .white) + if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) { + self.action = action + } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(color, forKey: .color) - try container.encodeModelIfPresent(action, forKey: .action) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(color, forKey: .color) + try container.encodeModel(action, forKey: .action) } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 757855ed..535d3830 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -72,12 +72,12 @@ public typealias ButtonAction = (Button) -> () buttonAction?(self) } - open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + open func set(with actionModel: ActionModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.actionModel = actionModel buttonDelegate = delegateObject?.buttonDelegate addActionBlock(event: .touchUpInside) { [weak self] sender in - guard let self = self else { return } + guard let self = self, let actionModel = actionModel else { return } Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData) } } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index e6df7178..09cef092 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -56,21 +56,23 @@ public extension MVMCoreUITopAlertView { } /// Returns the top alert molecule to use and status bar color legacy style. - @objc func molecule(for topAlertObject: MVMCoreTopAlertObject, statusBarColor: AutoreleasingUnsafeMutablePointer?, statusBarStyle: UnsafeMutablePointer?) -> MVMCoreUITopAlertBaseView? { + @objc func molecule(for topAlertObject: MVMCoreTopAlertObject, statusBarColor: AutoreleasingUnsafeMutablePointer?, statusBarStyle: UnsafeMutablePointer?) -> UIView? { do { let delegateObject = MVMCoreUIDelegateObject.create(withDelegateForAll: self) guard let json = topAlertObject.json else { return nil } let model = try TopNotificationModel.decode(json: json, delegateObject: delegateObject) - guard let molecule = MoleculeObjectMapping.shared()?.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil), - let view = molecule as? MVMCoreUITopAlertBaseView else { + guard let molecule = MoleculeObjectMapping.shared()?.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil)/*, + let view = molecule as? MVMCoreUITopAlertBaseView*/ else { throw ModelRegistry.Error.decoderOther(message: "Molecule not a top alert") } - if let castView = view as? StatusBarUI { + if let castView = molecule as? StatusBarUI { let (color, style) = castView.getStatusBarUI() statusBarColor?.pointee = color statusBarStyle?.pointee = style } - return view + // Temporary + molecule.heightAnchor.constraint(lessThanOrEqualToConstant: 150).isActive = true + return molecule } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") { MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject) diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h index 4b757bf7..82c0a7e9 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h @@ -16,7 +16,6 @@ #import @class MVMCoreTopAlertObject; -@class MVMCoreUITopAlertBaseView; @interface MVMCoreUITopAlertView : UIView @@ -45,7 +44,7 @@ - (void)resetDefaultBackgroundColor:(nullable UIColor *)backgroundColor basedOnStatusBarStyle:(UIStatusBarStyle)style; // Can be subclassed for custom views. -- (nonnull MVMCoreUITopAlertBaseView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle; +- (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle; /// Get the background color based on the type - (nonnull UIColor *)getBackgroundColorForType:(nullable NSString *)type; diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 903a24d3..570b9297 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -124,7 +124,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (nonnull MVMCoreUITopAlertBaseView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle { +- (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle { if (topAlertObject.json) { return [self moleculeFor:topAlertObject statusBarColor:statusBarColor statusBarStyle:statusBarStyle]; } else { @@ -169,8 +169,10 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; UIColor *statusBarColor = nil; UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; - MVMCoreUITopAlertBaseView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; - [view updateView:CGRectGetWidth(self.bounds)]; + UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; + if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) { + [((UIView *)view) updateView:CGRectGetWidth(self.bounds)]; + } if (!statusBarColor) { statusBarColor = [UIColor whiteColor]; } @@ -185,7 +187,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreUISession sharedGlobal].splitViewController.parentViewController setNeedsStatusBarAppearanceUpdate]; } -- (void)showAlertView:(nullable MVMCoreUITopAlertBaseView *)view topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { +- (void)showAlertView:(nullable UIView *)view topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { __weak typeof(self) weakSelf = self; MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) { @@ -205,8 +207,10 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } completion:^(BOOL finished) { [weakSelf.superview layoutIfNeeded]; [weakSelf.animationDelegate topAlertViewFinishAnimation]; - [view handleAccessibility]; - + if ([view isKindOfClass:[MVMCoreUITopAlertBaseView class]]) { + [((MVMCoreUITopAlertBaseView *)view) handleAccessibility]; + } + [MVMCoreDispatchUtility performBlockInBackground:^{ if ([weakSelf.topAlertObject.delegate respondsToSelector:@selector(topAlertViewShown:topAlertObject:)]) { [weakSelf.topAlertObject.delegate topAlertViewShown:view topAlertObject:topAlertObject]; From 324605cdeba16e0ac46331af9d9a9ddc8eacad9d Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Mon, 21 Sep 2020 12:17:25 -0400 Subject: [PATCH 02/18] prevent double selection and code review items --- .../FormFields/TextFields/BaseDropdownEntryField.swift | 7 +++---- MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift | 8 +++----- MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift | 7 +++---- MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift | 8 +++----- MVMCoreUI/Atomic/Atoms/Views/Toggle.swift | 9 ++++----- MVMCoreUI/BaseClasses/Button.swift | 9 +++++++-- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/BaseDropdownEntryField.swift index 38da2bdb..82e46306 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/BaseDropdownEntryField.swift @@ -92,10 +92,9 @@ import UIKit } func performDropdownAction() { - if let actionModel = baseDropdownEntryFieldModel?.action, let actionMap = actionModel.toJSON() { - var additionalData = self.additionalData ?? [:] - additionalData[KeySourceModel] = baseDropdownEntryFieldModel - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + if let baseDropdownEntryFieldModel = baseDropdownEntryFieldModel, let actionModel = baseDropdownEntryFieldModel.action, let actionMap = actionModel.toJSON() { + let additionalDataWithSource = additionalData.dictionaryAdding(key: KeySourceModel, value: baseDropdownEntryFieldModel) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject) } } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index 46b4cef5..d4fa0232 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -137,13 +137,11 @@ open class RadioBox: Control, MFButtonProtocol { } @objc open func selectBox() { - guard isEnabled else { return } + guard isEnabled, !isSelected else { return } isSelected = true radioBoxModel?.selected = isSelected - if let actionModel = radioBoxModel?.action { - var additionalData = self.additionalData ?? [:] - additionalData[KeySourceModel] = radioBoxModel - Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData) + if let radioBoxModel = radioBoxModel, let actionModel = radioBoxModel.action { + Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel) } layer.setNeedsDisplay() } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index 9e18f936..bd720be8 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -95,15 +95,14 @@ import UIKit if !isEnabled { return } + let wasPreviouslySelected = isSelected if let radioButtonModel = radioButtonSelectionHelper { radioButtonModel.selected(self) } else { isSelected = !isSelected } - if let actionModel = radioModel?.action, isSelected { - var additionalData = self.additionalData ?? [:] - additionalData[KeySourceModel] = radioModel - Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData) + if let radioModel = radioModel, let actionModel = radioModel.action, isSelected, !wasPreviouslySelected { + Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioModel) } _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) setNeedsDisplay() diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift index e1502515..70f2a459 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift @@ -119,13 +119,11 @@ open class RadioSwatch: Control, MFButtonProtocol { } @objc open func selectSwatch() { - guard isEnabled else { return } + guard isEnabled, !isSelected else { return } isSelected = true radioSwatchModel?.selected = isSelected - if let actionModel = radioSwatchModel?.action { - var additionalData = self.additionalData ?? [:] - additionalData[KeySourceModel] = radioSwatchModel - Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData) + if let radioSwatchModel = radioSwatchModel, let actionModel = radioSwatchModel.action { + Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioSwatchModel) } layer.setNeedsDisplay() } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift index dd7dc9f0..5712695b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift @@ -394,20 +394,19 @@ public typealias ActionBlockConfirmation = () -> (Bool) let actionMap = model.action?.toJSON() let alternateActionMap = model.alternateAction?.toJSON() + let additionalDataWithSource = additionalData.dictionaryAdding(key: KeySourceModel, value: model) if actionMap != nil || alternateActionMap != nil { - var additionalDatatoUpdate = additionalData ?? [:] - additionalDatatoUpdate[KeySourceModel] = model didToggleAction = { [weak self] in guard let self = self else { return } if self.isOn { if actionMap != nil { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDatatoUpdate, delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject) } } else { if alternateActionMap != nil { - MVMCoreActionHandler.shared()?.handleAction(with: alternateActionMap, additionalData: additionalDatatoUpdate, delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.handleAction(with: alternateActionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject) } else if actionMap != nil { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDatatoUpdate, delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject) } } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 757855ed..599fc48c 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -82,11 +82,16 @@ public typealias ButtonAction = (Button) -> () } } - open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, sourceModel: MoleculeModelProtocol? = nil) { if let data = try? model.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], delegateObject?.buttonDelegate?.button?(button, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + if let sourceModel = sourceModel { + let additionalDataWithSource = additionalData.dictionaryAdding(key: KeySourceModel, value: sourceModel) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject) + } else { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } } } From 4986bb09a9e3a40d6ad6762bab5a520b94c7f1c9 Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Mon, 21 Sep 2020 16:11:58 -0400 Subject: [PATCH 03/18] Add array protectioons. --- MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m index e0eef9d6..9a1d3099 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m @@ -393,8 +393,12 @@ weakSelf.buttonView.label.alpha = 0; weakSelf.shortViewHeight.active = NO; } completion:^(BOOL finished) { - [weakSelf.viewToLayout layoutIfNeeded]; - weakSelf.accessibilityElements = @[weakSelf.shortView.label]; + typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + [strongSelf.viewToLayout layoutIfNeeded]; + strongSelf.accessibilityElements = @[strongSelf.shortView.label]; UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); [MVMCoreDispatchUtility performBlockInBackground:^{ // Must notify animation delegate when animating finished. From cba21e3b4458b9ce4d4a53c33dc447a02c25b19a Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Mon, 21 Sep 2020 17:41:59 -0400 Subject: [PATCH 04/18] extract method for animations. --- .../MVMCoreUITopAlertExpandableView.m | 154 +++++++++--------- 1 file changed, 81 insertions(+), 73 deletions(-) diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m index 9a1d3099..76ddb1ed 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m @@ -298,58 +298,64 @@ __weak typeof(self) weakSelf = self; MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ - // Must notify animation delegate before animating. - if (animated && weakSelf.animationDelegate) { - [weakSelf.animationDelegate topAlertViewBeginAnimation]; - } - - [weakSelf.viewToLayout layoutIfNeeded]; - weakSelf.topLabelConstraintBottom.active = NO; - weakSelf.topConstraint.active = YES; - weakSelf.expanded = YES; - - void(^animation)(void) = ^(void) { - weakSelf.buttonView.button.alpha = 1; - weakSelf.buttonView.label.alpha = 1; - if (weakSelf.onlyShowTopMessageWhenCollapsed) { - weakSelf.shortViewHeight.active = YES; - } - [weakSelf.viewToLayout layoutIfNeeded]; - }; - - //accessibility - added to make only top alert label and close button accessible. Posted notification when top alert is displayed - weakSelf.accessibilityElements = @[weakSelf.buttonView]; - weakSelf.shortView.isAccessibilityElement = NO; - - void(^completion)(void) = ^(void) { - UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.buttonView.label); + [weakSelf performExpansion:animated onCompletion:^{ [operation markAsFinished]; - }; - if (animated) { - [UIView animateWithDuration:.5 animations:animation completion:^(BOOL finished) { - [weakSelf.viewToLayout layoutIfNeeded]; - - // Must notify animation delegate when animating finished. - [MVMCoreDispatchUtility performBlockInBackground:^{ - if (weakSelf.animationDelegate) { - [weakSelf.animationDelegate topAlertViewFinishAnimation]; - } - }]; - completion(); - }]; - } else { - animation(); - completion(); - } - - // Collapse after 5 seconds (if the view still exists) - [weakSelf autoCollapse]; + }]; }]; }]; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; } } +- (void)performExpansion:(BOOL)animated onCompletion:(void(^)(void))completionHandler { + // Must notify animation delegate before animating. + if (animated && self.animationDelegate) { + [self.animationDelegate topAlertViewBeginAnimation]; + } + + [self.viewToLayout layoutIfNeeded]; + self.topLabelConstraintBottom.active = NO; + self.topConstraint.active = YES; + self.expanded = YES; + + void(^animation)(void) = ^(void) { + self.buttonView.button.alpha = 1; + self.buttonView.label.alpha = 1; + if (self.onlyShowTopMessageWhenCollapsed) { + self.shortViewHeight.active = YES; + } + [self.viewToLayout layoutIfNeeded]; + }; + + //accessibility - added to make only top alert label and close button accessible. Posted notification when top alert is displayed + self.accessibilityElements = @[self.buttonView]; + self.shortView.isAccessibilityElement = NO; + + void(^completion)(void) = ^(void) { + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.buttonView.label); + completionHandler(); + }; + if (animated) { + [UIView animateWithDuration:.5 animations:animation completion:^(BOOL finished) { + [self.viewToLayout layoutIfNeeded]; + + // Must notify animation delegate when animating finished. + [MVMCoreDispatchUtility performBlockInBackground:^{ + if (self.animationDelegate) { + [self.animationDelegate topAlertViewFinishAnimation]; + } + }]; + completion(); + }]; + } else { + animation(); + completion(); + } + + // Collapse after 5 seconds (if the view still exists) + [self autoCollapse]; +} + - (void)autoCollapse { if (self.collapseAutomaticallyAfterExpanded) { __weak typeof(self) weakSelf = self; @@ -379,34 +385,8 @@ __weak typeof(self) weakSelf = self; MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ - // Must notify animation delegate before animating. - if (weakSelf.animationDelegate) { - [weakSelf.animationDelegate topAlertViewBeginAnimation]; - } - [weakSelf.viewToLayout layoutIfNeeded]; - weakSelf.topConstraint.active = NO; - weakSelf.topLabelConstraintBottom.active = YES; - weakSelf.expanded = NO; - [UIView animateWithDuration:.5 animations:^{ - [weakSelf.viewToLayout layoutIfNeeded]; - weakSelf.buttonView.button.alpha = 0; - weakSelf.buttonView.label.alpha = 0; - weakSelf.shortViewHeight.active = NO; - } completion:^(BOOL finished) { - typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongSelf.viewToLayout layoutIfNeeded]; - strongSelf.accessibilityElements = @[strongSelf.shortView.label]; - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); - [MVMCoreDispatchUtility performBlockInBackground:^{ - // Must notify animation delegate when animating finished. - if (weakSelf.animationDelegate) { - [weakSelf.animationDelegate topAlertViewFinishAnimation]; - } - [operation markAsFinished]; - }]; + [weakSelf performCollapseAnimationThen:^{ + [operation markAsFinished]; }]; }]; }]; @@ -414,6 +394,34 @@ } } +- (void)performCollapseAnimationThen:(void(^)(void))completionHandler { + // Must notify animation delegate before animating. + if (self.animationDelegate) { + [self.animationDelegate topAlertViewBeginAnimation]; + } + [self.viewToLayout layoutIfNeeded]; + self.topConstraint.active = NO; + self.topLabelConstraintBottom.active = YES; + self.expanded = NO; + [UIView animateWithDuration:.5 animations:^{ + [self.viewToLayout layoutIfNeeded]; + self.buttonView.button.alpha = 0; + self.buttonView.label.alpha = 0; + self.shortViewHeight.active = NO; + } completion:^(BOOL finished) { + [self.viewToLayout layoutIfNeeded]; + self.accessibilityElements = @[self.shortView.label]; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + // Must notify animation delegate when animating finished. + [MVMCoreDispatchUtility performBlockInBackground:^{ + if (self.animationDelegate) { + [self.animationDelegate topAlertViewFinishAnimation]; + } + }]; + completionHandler(); + }]; +} + - (void)accessibilityFocusChanged:(NSNotification *)notification { if (![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil]; From f771469bc5b067712ed041c624ff2a63dcc7350d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 09:11:19 -0400 Subject: [PATCH 05/18] Molecular top alerts --- MVMCoreUI.xcodeproj/project.pbxproj | 12 +- .../CollapsableNotification.swift | 162 ++++++++++-------- ...t => CollapsableNotificationTopView.swift} | 11 +- .../TopNotification/Notification.swift | 55 +----- .../Protocols/AccessibilityProtocol.swift | 14 ++ .../MVMCoreUISplitViewController.m | 11 +- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m | 18 +- MVMCoreUI/Utility/MVMCoreUIUtility.m | 6 +- 8 files changed, 155 insertions(+), 134 deletions(-) rename MVMCoreUI/Atomic/Molecules/TopNotification/{NotificationStatusBar.swift => CollapsableNotificationTopView.swift} (82%) create mode 100644 MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index de7e8645..404ae487 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -484,9 +484,10 @@ D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */; }; D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D12513EA6900564112 /* NotificationXButton.swift */; }; D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D32514F80C00564112 /* CollapsableNotification.swift */; }; - D2FA83D62515021F00564112 /* NotificationStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* NotificationStatusBar.swift */; }; + D2FA83D62515021F00564112 /* CollapsableNotificationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */; }; D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */; }; + D2FD4A4925199BD9000C28A9 /* AccessibilityProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FD4A4825199BD9000C28A9 /* AccessibilityProtocol.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; DBC4391822442197001AB423 /* CaretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391622442196001AB423 /* CaretView.swift */; }; DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; @@ -977,9 +978,10 @@ D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableableModelProtocol.swift; sourceTree = ""; }; D2FA83D12513EA6900564112 /* NotificationXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButton.swift; sourceTree = ""; }; D2FA83D32514F80C00564112 /* CollapsableNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotification.swift; sourceTree = ""; }; - D2FA83D52515021F00564112 /* NotificationStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusBar.swift; sourceTree = ""; }; + D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationTopView.swift; sourceTree = ""; }; D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackItem.swift; sourceTree = ""; }; + D2FD4A4825199BD9000C28A9 /* AccessibilityProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityProtocol.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; DB891E822253FA8500022516 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; @@ -1067,6 +1069,7 @@ children = ( D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */, + D2FD4A4825199BD9000C28A9 /* AccessibilityProtocol.swift */, ); path = Protocols; sourceTree = ""; @@ -2091,7 +2094,7 @@ D2CAC7D02511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift */, D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */, D2FA83D32514F80C00564112 /* CollapsableNotification.swift */, - D2FA83D52515021F00564112 /* NotificationStatusBar.swift */, + D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */, D2CAC7D2251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift */, ); path = TopNotification; @@ -2400,6 +2403,7 @@ AA633B3124989EC000731E80 /* HeadersH2PricingTwoRowsModel.swift in Sources */, 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */, D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */, + D2FD4A4925199BD9000C28A9 /* AccessibilityProtocol.swift in Sources */, D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, D20923592450ECE00044AD09 /* TableView.swift in Sources */, @@ -2574,7 +2578,7 @@ AAB8549824DC01BD00477C40 /* ListThreeColumnBillHistoryDividerModel.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, - D2FA83D62515021F00564112 /* NotificationStatusBar.swift in Sources */, + D2FA83D62515021F00564112 /* CollapsableNotificationTopView.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift index 54eeb51a..6c8d84b8 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift @@ -13,7 +13,7 @@ import Foundation // MARK: - Outlets //-------------------------------------------------- - public let topView = NotificationStatusBar() + public let topView = CollapsableNotificationTopView() public let bottomView = NotificationView() public var verticalStack: UIStackView! @@ -53,108 +53,112 @@ 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? CollapsableNotificationModel else { return } + topView.label.set(with: model.topLabel, delegateObject, additionalData) topView.button.set(with: model.topAction, delegateObject: delegateObject, additionalData: additionalData) + topView.updateAccessibility() + bottomView.set(with: model, delegateObject, additionalData) - updateAccessibilityLabel() + // Set initial collapse/expand state. topView.isHidden = !model.alwaysShowTopLabel && !model.initiallyCollapsed topView.button.isUserInteractionEnabled = model.initiallyCollapsed bottomView.isHidden = model.initiallyCollapsed verticalStack.layoutIfNeeded() if !model.initiallyCollapsed { - collapse(with: .now() + DispatchTimeInterval.seconds(model.collapseTime)) + autoCollapse() } } - open func collapse(with delay: DispatchTime) { - DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in - self?.collapse() + open func performBlockOperation(with block: @escaping (MVMCoreBlockOperation) -> Void) { + let operation = MVMCoreBlockOperation(block: block)! + MVMCoreNavigationHandler.shared()?.addNavigationOperation(operation) + } + + /// Collapses after a delay + open func autoCollapse() { + let delay: DispatchTimeInterval = DispatchTimeInterval.seconds((model as? CollapsableNotificationModel)?.collapseTime ?? 5) + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + // If accessibility focused, delay collapse. + guard let localSelf = self else { return } + if MVMCoreUIUtility.viewContainsAccessiblityFocus(localSelf) { + NotificationCenter.default.addObserver(localSelf, selector: #selector(localSelf.accessibilityFocusChanged(notification:)), name: UIAccessibility.elementFocusedNotification, object: nil) + } else { + localSelf.collapse() + } } } + /// Collapses to show just the top view. open func collapse(animated: Bool = true) { - let animation = { [weak self] in - self?.topView.isHidden = false - self?.bottomView.isHidden = true - self?.verticalStack.layoutIfNeeded() - } - if animated { - UIView.animate(withDuration: 0.5, animations: animation) { [weak self] (finished) in - self?.topView.button.isUserInteractionEnabled = true - } - } else { - animation() + guard !bottomView.isHidden else { return } + performBlockOperation { [weak self] (operation) in + let strongSelf = self + MVMCoreDispatchUtility.performBlock(onMainThread: { + MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded() + let animation = { + strongSelf?.topView.isHidden = false + strongSelf?.bottomView.isHidden = true + strongSelf?.verticalStack.layoutIfNeeded() + } + let completion: (Bool) -> Void = { (finished) in + strongSelf?.topView.button.isUserInteractionEnabled = true + MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded() + UIAccessibility.post(notification: .layoutChanged, argument: strongSelf?.getAccessibilityLayoutChangedArgument()) + operation.markAsFinished() + } + + if animated { + UIView.animate(withDuration: 0.5, animations: animation, completion: completion) + } else { + animation() + completion(true) + } + }) } } + /// Expands to show the bottom view. open func expand(topViewShowing: Bool = false, animated: Bool = true) { - topView.button.isUserInteractionEnabled = false - let animation = { [weak self] in - self?.topView.isHidden = !topViewShowing - self?.bottomView.isHidden = false - self?.verticalStack.layoutIfNeeded() - } - if animated { - UIView.animate(withDuration: 0.5, animations: animation) { [weak self] (finished) in + guard bottomView.isHidden else { return } + performBlockOperation { [weak self] (operation) in + let strongSelf = self + MVMCoreDispatchUtility.performBlock(onMainThread: { + MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded() + strongSelf?.topView.button.isUserInteractionEnabled = false + let animation = { + strongSelf?.topView.isHidden = !topViewShowing + strongSelf?.bottomView.isHidden = false + strongSelf?.verticalStack.layoutIfNeeded() + } + let completion: (Bool) -> Void = { (finished) in + MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded() + UIAccessibility.post(notification: .layoutChanged, argument: strongSelf?.getAccessibilityLayoutChangedArgument()) + strongSelf?.autoCollapse() + operation.markAsFinished() + } - } - } else { - animation() + if animated { + UIView.animate(withDuration: 0.5, animations: animation, completion: completion) + } else { + animation() + completion(true) + } + }) } } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 96 } - /* - func getAccessibilityMessage() -> String? { - - guard let leftImageLabel = leftImage.imageView.accessibilityLabel else { - return eyebrowHeadlineBodyLink.getAccessibilityMessage() + + /// Collapse if focus is no longer on this top alert. + @objc func accessibilityFocusChanged(notification: Notification) { + if !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) { + NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil) + collapse() } - - guard let label = eyebrowHeadlineBodyLink.getAccessibilityMessage() else { - return leftImageLabel - } - - return leftImageLabel + ", " + label - }*/ - - func updateAccessibilityLabel() { - /*headline.accessibilityLabel = headline.text - MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: headline) - - body.accessibilityLabel = body.text - MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: body) - - - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 - isAccessibilityElement = !linkShowing - accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none - - if !linkShowing { - // Make whole cell focusable if no link. - accessibilityLabel = getAccessibilityMessage() - } else if let accessoryView = accessoryView { - // Both caret and link. Read all content on caret. - accessoryView.accessibilityLabel = getAccessibilityMessage() - accessibilityElements = [accessoryView, eyebrowHeadlineBodyLink.link] - } else { - // Only link. Manually add accessibility elements to ensure they are read in the right order. - var elements: [Any] = [] - - if let leftImageLabel = leftImage.imageView.accessibilityLabel, !leftImageLabel.isEmpty { - elements.append(leftImage.imageView) - } - - if let otherElements = eyebrowHeadlineBodyLink.getAccessibilityElements() { - elements.append(otherElements) - } - - accessibilityElements = elements - }*/ } } @@ -166,3 +170,13 @@ extension CollapsableNotification: StatusBarUI { return (color, greyScale > 0.5 ? .lightContent : .default) } } + +extension CollapsableNotification: AccessibilityProtocol { + public func getAccessibilityLayoutChangedArgument() -> Any? { + if !topView.isHidden { + return topView + } else { + return bottomView.headline + } + } +} diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift similarity index 82% rename from MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift rename to MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift index 73bde3f1..36ddc8a6 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationStatusBar.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift @@ -1,5 +1,5 @@ // -// NotificationStatusBar.swift +// CollapsableNotificationTopView.swift // MVMCoreUI // // Created by Scott Pfeil on 9/18/20. @@ -8,7 +8,7 @@ import Foundation -@objcMembers open class NotificationStatusBar: View { +@objcMembers open class CollapsableNotificationTopView: View { public let label: Label = { let label = Label(fontStyle: .BoldBodySmall) label.numberOfLines = 1 @@ -50,6 +50,13 @@ import Foundation label.textAlignment = .center } + open func updateAccessibility() { + isAccessibilityElement = true + accessibilityLabel = label.text + accessibilityTraits = (button.isUserInteractionEnabled && button.actionModel != nil) ? .button : .none + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: self) + } + @objc func pressed(_ sender: Notification) { button.callActionBlock(button) } diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift index 823158fc..c7d869c2 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift @@ -59,59 +59,22 @@ import Foundation guard let model = model as? NotificationModel else { return } labelStack.updateContainedMolecules(with: [model.headline, model.body], delegateObject, nil) horizontalStack.updateContainedMolecules(with: [labelStack.stackModel, model.button, model.closeButton], delegateObject, nil) - - updateAccessibilityLabel() + updateAccessibility() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 96 } - /* - func getAccessibilityMessage() -> String? { - - guard let leftImageLabel = leftImage.imageView.accessibilityLabel else { - return eyebrowHeadlineBodyLink.getAccessibilityMessage() - } - - guard let label = eyebrowHeadlineBodyLink.getAccessibilityMessage() else { - return leftImageLabel - } - - return leftImageLabel + ", " + label - }*/ - func updateAccessibilityLabel() { - /*headline.accessibilityLabel = headline.text + open func updateAccessibility() { MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: headline) - - body.accessibilityLabel = body.text MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: body) - - - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 - isAccessibilityElement = !linkShowing - accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none - - if !linkShowing { - // Make whole cell focusable if no link. - accessibilityLabel = getAccessibilityMessage() - } else if let accessoryView = accessoryView { - // Both caret and link. Read all content on caret. - accessoryView.accessibilityLabel = getAccessibilityMessage() - accessibilityElements = [accessoryView, eyebrowHeadlineBodyLink.link] - } else { - // Only link. Manually add accessibility elements to ensure they are read in the right order. - var elements: [Any] = [] - - if let leftImageLabel = leftImage.imageView.accessibilityLabel, !leftImageLabel.isEmpty { - elements.append(leftImage.imageView) - } - - if let otherElements = eyebrowHeadlineBodyLink.getAccessibilityElements() { - elements.append(otherElements) - } - - accessibilityElements = elements - }*/ + MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: button) + } +} + +extension NotificationView: AccessibilityProtocol { + public func getAccessibilityLayoutChangedArgument() -> Any? { + return headline } } diff --git a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift new file mode 100644 index 00000000..6c7ada50 --- /dev/null +++ b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift @@ -0,0 +1,14 @@ +// +// AccessibilityProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc public protocol AccessibilityProtocol { + /// Should return the argument to use for posting a layout change. + func getAccessibilityLayoutChangedArgument() -> Any? +} diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index 06e44c73..288878b8 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -1073,10 +1073,13 @@ CGFloat const PanelAnimationDuration = 0.2; } - (UIViewController *)getCurrentDetailViewController { - UIViewController *viewController = self.navigationController.topViewController; - if ([viewController conformsToProtocol:@protocol(MVMCoreViewManagerProtocol)]) { - viewController = [viewController performSelector:@selector(getCurrentViewController)]; - } + __block UIViewController *viewController = nil; + [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ + viewController = self.navigationController.topViewController; + if ([viewController conformsToProtocol:@protocol(MVMCoreViewManagerProtocol)]) { + viewController = [viewController performSelector:@selector(getCurrentViewController)]; + } + }]; return viewController; } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 570b9297..31899d3f 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -187,6 +187,19 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreUISession sharedGlobal].splitViewController.parentViewController setNeedsStatusBarAppearanceUpdate]; } +- (void)updateAccessibilityForTopAlert:(nullable UIView *)view { + // Update accessibility with top alert + if ([view isKindOfClass:[MVMCoreUITopAlertBaseView class]]) { + [((MVMCoreUITopAlertBaseView *)view) handleAccessibility]; + } else { + id accessibilityArgument = view; + if ([view conformsToProtocol:@protocol(AccessibilityProtocol)]) { + accessibilityArgument = [((id )view) getAccessibilityLayoutChangedArgument]; + } + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, accessibilityArgument); + } +} + - (void)showAlertView:(nullable UIView *)view topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { __weak typeof(self) weakSelf = self; @@ -207,9 +220,8 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } completion:^(BOOL finished) { [weakSelf.superview layoutIfNeeded]; [weakSelf.animationDelegate topAlertViewFinishAnimation]; - if ([view isKindOfClass:[MVMCoreUITopAlertBaseView class]]) { - [((MVMCoreUITopAlertBaseView *)view) handleAccessibility]; - } + + [weakSelf updateAccessibilityForTopAlert:view]; [MVMCoreDispatchUtility performBlockInBackground:^{ if ([weakSelf.topAlertObject.delegate respondsToSelector:@selector(topAlertViewShown:topAlertObject:)]) { diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 60ff4872..fed0bb30 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -89,7 +89,11 @@ if (![focusedElement isKindOfClass:[UIView class]]) { return NO; } - return [(UIView *)focusedElement isDescendantOfView:view]; + __block BOOL containsFocus; + [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ + containsFocus = [(UIView *)focusedElement isDescendantOfView:view]; + }]; + return containsFocus; } #pragma mark - Setters From d3fd303988860f9fd767fc01a96b462a03eded16 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 09:24:38 -0400 Subject: [PATCH 06/18] comments --- .../Molecules/TopNotification/NotificationXButton.swift | 2 +- MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift index 434ddc3d..84b1abbf 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift @@ -32,7 +32,7 @@ import Foundation guard let model = model as? NotificationXButtonModel else { return } tintColor = model.color.uiColor - // temporary + // TODO: Temporary, consider action for dismissing top alert if model.action.actionType == ActionNoopModel.identifier { addActionBlock(event: .touchUpInside) { (button) in (button as? NotificationXButton)?.closeTopAlert() diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index 09cef092..8bf4860f 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -61,17 +61,16 @@ public extension MVMCoreUITopAlertView { let delegateObject = MVMCoreUIDelegateObject.create(withDelegateForAll: self) guard let json = topAlertObject.json else { return nil } let model = try TopNotificationModel.decode(json: json, delegateObject: delegateObject) - guard let molecule = MoleculeObjectMapping.shared()?.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil)/*, - let view = molecule as? MVMCoreUITopAlertBaseView*/ else { - throw ModelRegistry.Error.decoderOther(message: "Molecule not a top alert") + guard let molecule = MoleculeObjectMapping.shared()?.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil) else { + throw ModelRegistry.Error.decoderOther(message: "Molecule not mapped") } if let castView = molecule as? StatusBarUI { let (color, style) = castView.getStatusBarUI() statusBarColor?.pointee = color statusBarStyle?.pointee = style } - // Temporary - molecule.heightAnchor.constraint(lessThanOrEqualToConstant: 150).isActive = true + // TODO: Temporary, waiting for actual restriction from design. + molecule.heightAnchor.constraint(lessThanOrEqualToConstant: 140).isActive = true return molecule } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") { From 30924c6c504186171e45c90b316db4431d3a1ee3 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 09:56:31 -0400 Subject: [PATCH 07/18] review comment --- .../TopNotification/CollapsableNotification.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift index 6c8d84b8..f5181511 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift @@ -81,11 +81,11 @@ import Foundation let delay: DispatchTimeInterval = DispatchTimeInterval.seconds((model as? CollapsableNotificationModel)?.collapseTime ?? 5) DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in // If accessibility focused, delay collapse. - guard let localSelf = self else { return } - if MVMCoreUIUtility.viewContainsAccessiblityFocus(localSelf) { - NotificationCenter.default.addObserver(localSelf, selector: #selector(localSelf.accessibilityFocusChanged(notification:)), name: UIAccessibility.elementFocusedNotification, object: nil) + guard let self = self else { return } + if MVMCoreUIUtility.viewContainsAccessiblityFocus(self) { + NotificationCenter.default.addObserver(self, selector: #selector(self.accessibilityFocusChanged(notification:)), name: UIAccessibility.elementFocusedNotification, object: nil) } else { - localSelf.collapse() + self.collapse() } } } @@ -150,7 +150,7 @@ import Foundation } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - return 96 + return 120 } /// Collapse if focus is no longer on this top alert. From f6c4609deb58214663792572a0b596cd31dd2d9d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 09:57:40 -0400 Subject: [PATCH 08/18] comments --- MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift index c7d869c2..d8d4fc08 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift @@ -63,6 +63,7 @@ import Foundation } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + // Currently hardcoded to 96 above. return 96 } From 961625caf3eb792a04ce74095c0d6be3369d91ed Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 10:16:33 -0400 Subject: [PATCH 09/18] review update --- .../Atomic/Molecules/TopNotification/Notification.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift index d8d4fc08..44ce254b 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift @@ -20,6 +20,9 @@ import Foundation public var labelStack: Stack! public var horizontalStack: Stack! + // Legacy constant + private static let viewHeight: CGFloat = 96.0 + //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -35,7 +38,7 @@ import Foundation labelStack.restack() horizontalStack.restack() - heightAnchor.constraint(equalToConstant: 96).isActive = true + heightAnchor.constraint(equalToConstant: Self.viewHeight).isActive = true } open override func updateView(_ size: CGFloat) { @@ -63,8 +66,7 @@ import Foundation } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - // Currently hardcoded to 96 above. - return 96 + return viewHeight } open func updateAccessibility() { From 3e2fd50e171d60fdc6f27b04b43a4948db62b329 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 22 Sep 2020 13:05:40 -0400 Subject: [PATCH 10/18] tag showing the alert --- MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h | 4 ++++ MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m | 3 +++ MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m | 1 + 3 files changed, 8 insertions(+) diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h index 14da21ad..b61c2b0a 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h @@ -8,6 +8,7 @@ @import MVMCore.MVMCoreLoggingHandler; @class MFViewController; +@class MVMCoreTopAlertObject; NS_ASSUME_NONNULL_BEGIN @@ -20,6 +21,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)defaultLogActionForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; - (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; +// Logging top notification. +- (void)trackTopNotificationShown:(nonnull UIView *)topNotification topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject additionalData:(nullable NSDictionary *)additionalData; + @end NS_ASSUME_NONNULL_END diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m index 45a13acf..ed073722 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m @@ -20,4 +20,7 @@ return nil; } +- (void)trackTopNotificationShown:(UIView *)topNotification topAlertObject:(MVMCoreTopAlertObject *)topAlertObject additionalData:(NSDictionary *)additionalData { +} + @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 31899d3f..d54190b0 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -227,6 +227,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; if ([weakSelf.topAlertObject.delegate respondsToSelector:@selector(topAlertViewShown:topAlertObject:)]) { [weakSelf.topAlertObject.delegate topAlertViewShown:view topAlertObject:topAlertObject]; } + [[MVMCoreUILoggingHandler sharedLoggingHandler] trackTopNotificationShown:view topAlertObject:topAlertObject additionalData:nil]; [operation markAsFinished]; completionHandler(finished); }]; From cac7180be50060e9d676c84d7efe92ddc0bded1d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 10:35:22 -0400 Subject: [PATCH 11/18] notification bug fixes --- .../TopNotification/CollapsableNotification.swift | 14 +++++++++----- .../CollapsableNotificationTopView.swift | 7 +++++++ .../Molecules/TopNotification/Notification.swift | 10 ++++++++++ .../TopNotification/NotificationXButton.swift | 1 + 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift index f5181511..53585b74 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift @@ -53,12 +53,16 @@ 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? CollapsableNotificationModel else { return } - - topView.label.set(with: model.topLabel, delegateObject, additionalData) - topView.button.set(with: model.topAction, delegateObject: delegateObject, additionalData: additionalData) - topView.updateAccessibility() - + topView.set(with: model, delegateObject, additionalData) bottomView.set(with: model, delegateObject, additionalData) + + // Update top view default noop to expand. + if model.topAction?.actionType == ActionNoopModel.identifier { + topView.button.addActionBlock(event: .touchUpInside) { [weak self] (button) in + Button.performButtonAction(with: model.topAction!, button: button, delegateObject: delegateObject, additionalData: additionalData) + self?.expand(topViewShowing: model.alwaysShowTopLabel) + } + } // Set initial collapse/expand state. topView.isHidden = !model.alwaysShowTopLabel && !model.initiallyCollapsed diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift index 36ddc8a6..c23edeae 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotificationTopView.swift @@ -43,6 +43,13 @@ import Foundation label.updateView(size) } + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = model as? CollapsableNotificationModel else { return } + label.set(with: model.topLabel, delegateObject, additionalData) + button.set(with: model.topAction, delegateObject: delegateObject, additionalData: additionalData) + updateAccessibility() + } + open override func reset() { super.reset() label.setFontStyle(.BoldBodySmall) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift index 44ce254b..09edbbcc 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/Notification.swift @@ -31,6 +31,16 @@ import Foundation super.setupView() reset() + // Buttons should have highest priority, then headline, then body + headline.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 500), for: .horizontal) + headline.setContentHuggingPriority(.required, for: .vertical) + body.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 500), for: .horizontal) + body.setContentHuggingPriority(.required, for: .vertical) + headline.setContentCompressionResistancePriority(UILayoutPriority(rawValue: body.contentCompressionResistancePriority(for: .vertical).rawValue + 40), for: .vertical) + headline.lineBreakMode = .byTruncatingTail + body.lineBreakMode = .byTruncatingTail + button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + labelStack = Stack.createStack(with: [headline, body], spacing: 0) horizontalStack = Stack.createStack(with: [(view: labelStack, model: StackItemModel()),(view: button, model: StackItemModel(horizontalAlignment: .fill)),(view: closeButton, model: StackItemModel(horizontalAlignment: .fill))], axis: .horizontal) addSubview(horizontalStack) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift index 84b1abbf..65c6e18c 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift @@ -25,6 +25,7 @@ import Foundation tintColor = .white adjustsImageWhenHighlighted = false accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "AccCloseButton") + setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { From 2984a1e05c5cdca01e16b25f9d737f5aa717c0c0 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 10:50:37 -0400 Subject: [PATCH 12/18] comment --- .../Molecules/TopNotification/CollapsableNotification.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift index 53585b74..8539c817 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/CollapsableNotification.swift @@ -57,9 +57,10 @@ import Foundation bottomView.set(with: model, delegateObject, additionalData) // Update top view default noop to expand. - if model.topAction?.actionType == ActionNoopModel.identifier { + if let topAction = model.topAction, + topAction.actionType == ActionNoopModel.identifier { topView.button.addActionBlock(event: .touchUpInside) { [weak self] (button) in - Button.performButtonAction(with: model.topAction!, button: button, delegateObject: delegateObject, additionalData: additionalData) + Button.performButtonAction(with: topAction, button: button, delegateObject: delegateObject, additionalData: additionalData) self?.expand(topViewShowing: model.alwaysShowTopLabel) } } From e05d3e3e1ae1cf03b6a2e70551e05140c41c1c6a Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 14:45:26 -0400 Subject: [PATCH 13/18] fix to legacy back button --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++++---- ...ocol.swift => NavigationButtonModelProtocol.swift} | 0 .../Molecules/NavigationBar/NavigationItemModel.swift | 8 +++----- .../ModelProtocols/NavigationItemModelProtocol.swift | 1 + MVMCoreUI/Containers/NavigationController.swift | 2 +- .../MVMCoreUISplitViewController+Extension.swift | 11 +++++------ .../MVMCoreUISplitViewController.m | 1 - 7 files changed, 14 insertions(+), 17 deletions(-) rename MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/{NavigationButtomModelProtocol.swift => NavigationButtonModelProtocol.swift} (100%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 404ae487..72326c6c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -378,7 +378,7 @@ D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */; }; D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */; }; D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */; }; - D28BA730247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift */; }; + D28BA730247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA72F247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift */; }; D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA740248025A300B75CB8 /* TabBarModel.swift */; }; D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA7422480284E00B75CB8 /* TabBar.swift */; }; D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */; }; @@ -869,7 +869,7 @@ D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonViewModel.swift; sourceTree = ""; }; D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabelsModel.swift; sourceTree = ""; }; D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyCaretLinkImageModel.swift; sourceTree = ""; }; - D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtomModelProtocol.swift; sourceTree = ""; }; + D28BA72F247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonModelProtocol.swift; sourceTree = ""; }; D28BA740248025A300B75CB8 /* TabBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarModel.swift; sourceTree = ""; }; D28BA7422480284E00B75CB8 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarProtocol.swift; sourceTree = ""; }; @@ -1525,7 +1525,7 @@ D23EA7FC247EBB7500D60C34 /* Buttons */ = { isa = PBXGroup; children = ( - D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift */, + D28BA72F247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift */, D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */, D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */, D23EA7FD247EBBB700D60C34 /* NavigationLabelButtonModel.swift */, @@ -2522,7 +2522,7 @@ 323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, - D28BA730247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift in Sources */, + D28BA730247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */, AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationButtomModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationButtonModelProtocol.swift similarity index 100% rename from MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationButtomModelProtocol.swift rename to MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationButtonModelProtocol.swift diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index 4ba785f3..daf6c013 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -19,7 +19,7 @@ public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProt public var tintColor: Color public var line: LineModel? public var alwaysShowBackButton: Bool? - public var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? = NavigationImageButtonModel(with: "nav_back", action: ActionBackModel()) + public var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? public var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? public var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? public var titleView: MoleculeModelProtocol? @@ -54,10 +54,8 @@ public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProt backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .white) tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .black) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .standard) - alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton) - if let backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol) = try typeContainer.decodeModelIfPresent(codingKey: .backButton) { - self.backButton = backButton - } + alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton) ?? false + backButton = try typeContainer.decodeModelIfPresent(codingKey: .backButton) additionalLeftButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalLeftButtons) additionalRightButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalRightButtons) titleView = try typeContainer.decodeModelIfPresent(codingKey: .titleView) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift index 38d95555..8f82c946 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift @@ -14,6 +14,7 @@ public protocol NavigationItemModelProtocol { var backgroundColor: Color? { get set } var tintColor: Color { get set } var line: LineModel? { get set } + var hidesSystemBackButton: Bool { get set } var alwaysShowBackButton: Bool? { get set } var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? { get set } var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set } diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 74f24957..22864027 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -47,7 +47,7 @@ import UIKit public static func setNavigationItem(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title viewController.navigationItem.accessibilityLabel = navigationItemModel.title - viewController.navigationItem.hidesBackButton = (navigationItemModel.backButton != nil) + viewController.navigationItem.hidesBackButton = navigationItemModel.hidesSystemBackButton setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) setNavigationTitleView(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index 24a8d9f7..515166c6 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -44,13 +44,12 @@ public extension MVMCoreUISplitViewController { let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject // Add back button first. - if navigationItemModel?.alwaysShowBackButton != false { + if navigationItemModel?.alwaysShowBackButton != false, + navigationController.viewControllers.count > 1 || navigationItemModel!.alwaysShowBackButton ?? false { if let backButtonModel = navigationItemModel?.backButton { - if navigationController.viewControllers.count > 1 || navigationItemModel!.alwaysShowBackButton ?? false { - leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - } else if let backButton = backButton, - navigationController.viewControllers.count > 1 { + leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } else if let backButton = backButton { + // Default to legacy if we have default back button. leftItems.append(backButton) } } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index 288878b8..177d0b8a 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -923,7 +923,6 @@ CGFloat const PanelAnimationDuration = 0.2; // Creates the back button self.backButton = [[UIBarButtonItem alloc] initWithImage:[self imageForBackButton] style:UIBarButtonItemStylePlain target:self action:@selector(backButtonPressed:)]; - self.backButton.imageInsets = UIEdgeInsetsMake(0, 4, 0, -8); // Dismisses a panel if the user taps the main view. if (!self.tapToDismissGesture) { From b4cec30ab0920eec0f21c05169bc9cb3eec3d6f7 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 14:53:18 -0400 Subject: [PATCH 14/18] remove debug code --- .../Atomic/Molecules/NavigationBar/NavigationItemModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index daf6c013..283da64d 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -18,6 +18,7 @@ public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProt public var backgroundColor: Color? public var tintColor: Color public var line: LineModel? + public var hidesSystemBackButton = true public var alwaysShowBackButton: Bool? public var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? public var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? @@ -54,7 +55,7 @@ public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProt backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .white) tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .black) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .standard) - alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton) ?? false + alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton) backButton = try typeContainer.decodeModelIfPresent(codingKey: .backButton) additionalLeftButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalLeftButtons) additionalRightButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalRightButtons) From c3040c24d3c09d6fd790ddc0f0daf22c8ec74702 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 16:26:55 -0400 Subject: [PATCH 15/18] code review --- .../MVMCoreUISplitViewController+Extension.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index 515166c6..d37d88f1 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -44,8 +44,13 @@ public extension MVMCoreUISplitViewController { let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject // Add back button first. - if navigationItemModel?.alwaysShowBackButton != false, - navigationController.viewControllers.count > 1 || navigationItemModel!.alwaysShowBackButton ?? false { + var showBackButton: Bool + if let forceBackButton = navigationItemModel?.alwaysShowBackButton { + showBackButton = forceBackButton + } else { + showBackButton = navigationController.viewControllers.count > 1 + } + if showBackButton { if let backButtonModel = navigationItemModel?.backButton { leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } else if let backButton = backButton { From ddd30a0f26789e801ffa237b85cce5e82a41dc52 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 24 Sep 2020 16:29:25 -0400 Subject: [PATCH 16/18] comment --- .../Atomic/Molecules/NavigationBar/NavigationItemModel.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index 283da64d..d8c8c7ac 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -19,8 +19,11 @@ public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProt public var tintColor: Color public var line: LineModel? public var hidesSystemBackButton = true + + /// If true, we add the button in the backButton property. If false we do not add the button. If nil, we add the button if the controller is not the bottom of the stack public var alwaysShowBackButton: Bool? public var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? + public var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? public var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? public var titleView: MoleculeModelProtocol? From fa99ba6b42e8047491300caf41c7133101eb9459 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 29 Sep 2020 12:01:12 -0400 Subject: [PATCH 17/18] Improving accordion behavior. Not the +/- button responds to user interaction. --- .../AccordionMoleculeTableViewCell.swift | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift index 8488703d..07bd9049 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -6,8 +6,6 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import UIKit - @objcMembers public class AccordionMoleculeTableViewCell: MoleculeTableViewCell { //-------------------------------------------------- @@ -28,6 +26,8 @@ import UIKit return accordionButton }() + var delegateObject: MVMCoreUIDelegateObject? + //-------------------------------------------------- // MARK: - Setup //-------------------------------------------------- @@ -36,10 +36,27 @@ import UIKit customAccessoryView = true super.setupView() accessoryView = accordionButton + + accordionButton.addActionBlock(event: .touchUpInside) { _ in + guard let model = self.accordionListItemModel else { return } + + if let indexPath = self.delegateObject?.moleculeDelegate?.getIndexPath(for: model) { + self.toggleAccordion(at: indexPath, delegateObject: self.delegateObject) + } + } } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + override public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + toggleAccordion(at: index, delegateObject: delegateObject) + } + + func toggleAccordion(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?) { + accordionButton.isSelected.toggle() accordionButton.setTitle(accordionButton.isSelected ? "-" : "+", for: .normal) @@ -57,4 +74,9 @@ import UIKit bottomSeparatorView?.isHidden = accordionButton.isSelected } } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + } } From 5398008a0dc7783f6f750c0d0e38f1dea45768f0 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 29 Sep 2020 13:34:32 -0400 Subject: [PATCH 18/18] rollback some behavior --- .../AccordionMoleculeTableViewCell.swift | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift index 07bd9049..f7a493d7 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -26,8 +26,6 @@ return accordionButton }() - var delegateObject: MVMCoreUIDelegateObject? - //-------------------------------------------------- // MARK: - Setup //-------------------------------------------------- @@ -36,27 +34,11 @@ customAccessoryView = true super.setupView() accessoryView = accordionButton - - accordionButton.addActionBlock(event: .touchUpInside) { _ in - guard let model = self.accordionListItemModel else { return } - - if let indexPath = self.delegateObject?.moleculeDelegate?.getIndexPath(for: model) { - self.toggleAccordion(at: indexPath, delegateObject: self.delegateObject) - } - } + accessoryView?.isUserInteractionEnabled = false } - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - override public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - toggleAccordion(at: index, delegateObject: delegateObject) - } - - func toggleAccordion(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?) { - accordionButton.isSelected.toggle() accordionButton.setTitle(accordionButton.isSelected ? "-" : "+", for: .normal) @@ -74,9 +56,4 @@ bottomSeparatorView?.isHidden = accordionButton.isSelected } } - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - self.delegateObject = delegateObject - } }