This commit is contained in:
Pfeil, Scott Robert 2021-05-07 09:20:54 -04:00
commit fc318a03cc
12 changed files with 154 additions and 48 deletions

View File

@ -160,7 +160,7 @@ import Foundation
/// Collapse if focus is no longer on this top alert.
@objc func accessibilityFocusChanged(notification: Notification) {
if !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
if (notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] != nil) && !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
collapse()
}

View File

@ -8,6 +8,21 @@
open class NotificationModel: ContainerModel, MoleculeModelProtocol {
/**
The style of the notification:
- success, green background, white content
- error, orange background, black content
- \warning, yellow background, black content
- information, blue background, white content
*/
public enum Style: String, Codable {
case success
case error
case warning
case information
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -19,6 +34,7 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
public var body: LabelModel?
public var button: ButtonModel?
public var closeButton: NotificationXButtonModel?
public var style: NotificationModel.Style = .success
//--------------------------------------------------
// MARK: - Initializer
@ -40,20 +56,63 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
bottomPadding = PaddingTwo
if backgroundColor == nil {
backgroundColor = Color(uiColor: .mvmGreen)
switch style {
case .error:
backgroundColor = Color(uiColor: .mvmOrange)
case .warning:
backgroundColor = Color(uiColor: .mvmYellow)
case .information:
backgroundColor = Color(uiColor: .mvmBlue)
default:
backgroundColor = Color(uiColor: .mvmGreen)
}
}
if headline.textColor == nil {
headline.textColor = Color(uiColor: .mvmWhite)
switch style {
case .error, .warning:
headline.textColor = Color(uiColor: .mvmBlack)
default:
headline.textColor = Color(uiColor: .mvmWhite)
}
}
if body?.textColor == nil {
body?.textColor = Color(uiColor: .mvmWhite)
switch style {
case .error, .warning:
body?.textColor = Color(uiColor: .mvmBlack)
default:
body?.textColor = Color(uiColor: .mvmWhite)
}
}
button?.size = .tiny
if button?.enabledTextColor == nil {
switch style {
case .error, .warning:
button?.enabledTextColor = Color(uiColor: .mvmBlack)
default:
button?.enabledTextColor = Color(uiColor: .mvmWhite)
}
}
if button?.enabledBorderColor == nil {
switch style {
case .error, .warning:
button?.enabledBorderColor = Color(uiColor: .mvmBlack)
default:
button?.enabledBorderColor = Color(uiColor: .mvmWhite)
}
}
if button?.style == nil {
button?.style = .secondary
}
button?.size = .tiny
button?.enabledTextColor = Color(uiColor: .mvmWhite)
button?.enabledBorderColor = Color(uiColor: .mvmWhite)
if closeButton?.color == nil {
switch style {
case .error, .warning:
closeButton?.color = Color(uiColor: .mvmBlack)
default:
closeButton?.color = Color(uiColor: .mvmWhite)
}
}
}
//--------------------------------------------------
@ -68,6 +127,7 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
case body
case button
case closeButton
case style
}
//--------------------------------------------------
@ -82,9 +142,12 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton)
if let style = try typeContainer.decodeIfPresent(NotificationModel.Style.self, forKey: .style) {
self.style = style
}
super.init()
}
open override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
@ -94,5 +157,6 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
try container.encodeIfPresent(body, forKey: .body)
try container.encodeIfPresent(button, forKey: .button)
try container.encodeIfPresent(closeButton, forKey: .closeButton)
try container.encode(style, forKey: .style)
}
}

View File

@ -33,7 +33,7 @@ import Foundation
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? NotificationXButtonModel else { return }
tintColor = model.color.uiColor
tintColor = model.color?.uiColor ?? .white
// TODO: Temporary, consider action for dismissing top alert
if model.action.actionType == ActionNoopModel.identifier {

View File

@ -11,7 +11,7 @@ import Foundation
public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtocol {
public static var identifier: String = "notificationXButton"
public var backgroundColor: Color?
public var color = Color(uiColor: .white)
public var color: Color?
public var action: ActionModelProtocol = ActionNoopModel()
private enum CodingKeys: String, CodingKey {
@ -24,7 +24,7 @@ public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtoco
public required init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) ?? Color(uiColor: .white)
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color)
if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) {
self.action = action
}
@ -33,7 +33,7 @@ public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtoco
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(color, forKey: .color)
try container.encodeIfPresent(color, forKey: .color)
try container.encodeModel(action, forKey: .action)
}
}

View File

@ -96,8 +96,13 @@ import MVMCore
public func validateGroup(_ group: FormGroupRule) -> Bool {
// Validate each rule.
var valid = true
var previousValidity: [String: Bool] = [:]
for rule in group.rules {
valid = valid && rule.validate(fields)
let tuple = rule.validate(fields, previousValidity)
let isValidRule = tuple.valid
let returnedValidity = tuple.fieldValidity
previousValidity = previousValidity.merging(returnedValidity) { (_, new) in new }
valid = valid && isValidRule
}
// Notify the group watchers of validity.

View File

@ -36,15 +36,23 @@ public class RuleAnyRequiredModel: RulesProtocol {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
if isValid(formField) {
return true
var fieldValidity = isValid(formField)
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
if fieldValidity {
return (fieldValidity, previousValidity)
}
previousValidity[formKey] = false
}
return false
return (valid: false, fieldValidity: previousValidity)
}
}

View File

@ -27,13 +27,21 @@ public class RuleAnyValueChangedModel: RulesProtocol {
return formField.baseValue != formField.formFieldValue()
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
if isValid(formField) {
return true
var fieldValidity = isValid(formField)
// If past rule is invalid forr a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
}
return false
if fieldValidity {
return (true, previousValidity)
}
previousValidity[formKey] = false
}
return (valid: false, fieldValidity: previousValidity)
}
}

View File

@ -27,10 +27,11 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
var valid = false
var compareText: String?
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var valid = false
var compareText: String?
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
@ -42,16 +43,23 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
if let fieldValue = formField.formFieldValue() as? String,
compareString.caseInsensitiveCompare(fieldValue) == .orderedSame {
valid = true
var fieldValidity = valid
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self)
}
break
}
previousValidity[formKey] = valid
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
}
return valid
return (valid: valid, fieldValidity: previousValidity)
}
}

View File

@ -27,9 +27,10 @@ public class RuleEqualsModel: RulesProtocol {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
var valid = true
var compareValue: AnyHashable?
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var valid = true
var compareValue: AnyHashable?
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
@ -41,13 +42,18 @@ public class RuleEqualsModel: RulesProtocol {
if compareValue != formField.formFieldValue() {
valid = false
previousValidity[formKey] = valid
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
break
} else {
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
var fieldValidity = valid
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
}
}
return valid
return (valid: valid, fieldValidity: previousValidity)
}
}

View File

@ -26,7 +26,7 @@ public protocol RulesProtocol: ModelProtocol {
func isValid(_ formField: FormFieldProtocol) -> Bool
// Validates the rule and returns the result.
func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool])
}
public extension RulesProtocol {
@ -38,14 +38,21 @@ public extension RulesProtocol {
static var categoryName: String { "\(RulesProtocol.self)" }
// Individual rule can override the function to validate based on the rule type.
func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
var valid = true
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
let fieldValidity = isValid(formField)
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
valid = valid && fieldValidity
}
return valid
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var valid = true
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
var fieldValidity = isValid(formField)
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
valid = valid && fieldValidity
previousValidity[formKey] = fieldValidity
}
return (valid: valid, fieldValidity: previousValidity)
}
}

View File

@ -423,7 +423,7 @@
}
- (void)accessibilityFocusChanged:(NSNotification *)notification {
if (![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
[self collapse];
}

View File

@ -335,7 +335,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed.";
/// If the voice over user leaves top alert focus, hide.
- (void)accessibilityFocusChanged:(NSNotification *)notification {
if (![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
[self hideAlertView:YES completionHandler:self.hideCompletionHandler];
self.hideCompletionHandler = nil;