Merge branch 'develop' into bugfix/PRODDEF-1289_accessibilityChanges
This commit is contained in:
commit
38fa102c34
@ -204,7 +204,6 @@
|
||||
9458C3172406C8FD00930963 /* UIFont+FontWrapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 9458C3152406C8FD00930963 /* UIFont+FontWrapping.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
9458C3182406C8FD00930963 /* UIFont+FontWrapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 9458C3162406C8FD00930963 /* UIFont+FontWrapping.m */; };
|
||||
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; };
|
||||
94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C0150924215643005811A9 /* ActionTopAlertModel.swift */; };
|
||||
94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C0150B2421564A005811A9 /* ActionCollapseNotificationModel.swift */; };
|
||||
94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */; };
|
||||
94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */; };
|
||||
@ -281,7 +280,6 @@
|
||||
AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */; };
|
||||
AF1C33652883B5A4006B1001 /* ActionTopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33642883B5A4006B1001 /* ActionTopNotificationHandler.swift */; };
|
||||
AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336828859778006B1001 /* ActionAlertHandler.swift */; };
|
||||
AF1C336B28859C73006B1001 /* ActionTopAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336A28859C73006B1001 /* ActionTopAlertHandler.swift */; };
|
||||
AF1C336D28859EE1006B1001 /* ActionOpenPanelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336C28859EE1006B1001 /* ActionOpenPanelHandler.swift */; };
|
||||
AF1C336F2885A16A006B1001 /* ActionCollapseNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336E2885A16A006B1001 /* ActionCollapseNotificationHandler.swift */; };
|
||||
AF1C33712885AE76006B1001 /* MVMCoreUIActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33702885AE76006B1001 /* MVMCoreUIActionHandler.swift */; };
|
||||
@ -291,7 +289,7 @@
|
||||
AF7E509829E477C1009DC2AD /* AlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509629E477C0009DC2AD /* AlertHandler.swift */; };
|
||||
AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509729E477C0009DC2AD /* AlertController.swift */; };
|
||||
AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931F29E5CA73001A9663 /* AlertOperation.swift */; };
|
||||
AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */; };
|
||||
AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */; };
|
||||
AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */; };
|
||||
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; };
|
||||
AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; };
|
||||
@ -351,8 +349,8 @@
|
||||
D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */; };
|
||||
D20923592450ECE00044AD09 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20923582450ECE00044AD09 /* TableView.swift */; };
|
||||
D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; };
|
||||
D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C7008250BF99B0095B21C /* TopNotificationModel.swift */; };
|
||||
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */; };
|
||||
D20C7009250BF99B0095B21C /* NotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C7008250BF99B0095B21C /* NotificationModel.swift */; };
|
||||
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20C700A250BFDE40095B21C /* NotificationContainerView.swift */; };
|
||||
D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20F3B43252E00E4004B3F56 /* PageProtocol.swift */; };
|
||||
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */; };
|
||||
D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; };
|
||||
@ -371,7 +369,7 @@
|
||||
D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */; };
|
||||
D22D8393241C27B100D3DF69 /* BaseTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */; };
|
||||
D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */; };
|
||||
D23118B325124E18001C8440 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23118B225124E18001C8440 /* Notification.swift */; };
|
||||
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23118B225124E18001C8440 /* NotificationMoleculeView.swift */; };
|
||||
D2351C7A24A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */; };
|
||||
D2351C7C24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2351C7B24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift */; };
|
||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; };
|
||||
@ -462,16 +460,6 @@
|
||||
D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF11421E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m */; };
|
||||
D29DF11C21E684A9003B2FB9 /* MVMCoreUISplitViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF11A21E684A9003B2FB9 /* MVMCoreUISplitViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF11B21E684A9003B2FB9 /* MVMCoreUISplitViewController.m */; };
|
||||
D29DF12921E6851E003B2FB9 /* MVMCoreUITopAlertMainView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF11F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF12A21E6851E003B2FB9 /* MVMCoreUITopAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF12021E6851E003B2FB9 /* MVMCoreUITopAlertView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */; };
|
||||
D29DF12C21E6851E003B2FB9 /* MVMCoreUITopAlertShortView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF12221E6851E003B2FB9 /* MVMCoreUITopAlertShortView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF12321E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF12421E6851E003B2FB9 /* MVMCoreUITopAlertView.m */; };
|
||||
D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF12521E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m */; };
|
||||
D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF12621E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m */; };
|
||||
D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF12821E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m */; };
|
||||
D29DF15421E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF15321E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -530,10 +518,8 @@
|
||||
D2C521A923EDE79E00CA2634 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C521A823EDE79E00CA2634 /* ViewController.swift */; };
|
||||
D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C78CD124228BBD00B69FDE /* ActionOpenPanelModel.swift */; };
|
||||
D2CAC7CB251104E100C75681 /* NotificationXButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */; };
|
||||
D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CC251104FE00C75681 /* NotificationModel.swift */; };
|
||||
D2CAC7CD251104FE00C75681 /* NotificationMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */; };
|
||||
D2CAC7CF2511052300C75681 /* CollapsableNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */; };
|
||||
D2CAC7D12511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7D02511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift */; };
|
||||
D2CAC7D3251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CAC7D2251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift */; };
|
||||
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D2FCEF252B72AF0033EAAA /* MoleculeSectionFooterModel.swift */; };
|
||||
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D2FCF2252B72CF0033EAAA /* MoleculeSectionFooter.swift */; };
|
||||
D2D3957A252FDBB300047B11 /* ModalSectionListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D39579252FDBB300047B11 /* ModalSectionListTemplate.swift */; };
|
||||
@ -566,14 +552,6 @@
|
||||
D2ED27EE254B0CE700A1C293 /* ActionAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */; };
|
||||
D2ED27EF254B0CE700A1C293 /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27EA254B0CE700A1C293 /* AlertModel.swift */; };
|
||||
D2ED27FC254B0E0300A1C293 /* AlertObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F3254B0E0200A1C293 /* AlertObject.swift */; };
|
||||
D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2806254B0EB700A1C293 /* MVMCoreTopAlertOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2ED280E254B0EB800A1C293 /* MVMCoreTopAlertOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */; };
|
||||
D2ED280F254B0EB800A1C293 /* MVMCoreTopAlertViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2808254B0EB700A1C293 /* MVMCoreTopAlertViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2ED2810254B0EB800A1C293 /* MVMCoreTopAlertDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2ED2811254B0EB800A1C293 /* MVMCoreTopAlertObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED280A254B0EB700A1C293 /* MVMCoreTopAlertObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED280B254B0EB800A1C293 /* MVMCoreTopAlertObject.m */; };
|
||||
D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D12513EA6900564112 /* NotificationXButton.swift */; };
|
||||
D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D32514F80C00564112 /* CollapsableNotification.swift */; };
|
||||
D2FA83D62515021F00564112 /* CollapsableNotificationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */; };
|
||||
@ -812,7 +790,6 @@
|
||||
9458C3152406C8FD00930963 /* UIFont+FontWrapping.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+FontWrapping.h"; sourceTree = "<group>"; };
|
||||
9458C3162406C8FD00930963 /* UIFont+FontWrapping.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+FontWrapping.m"; sourceTree = "<group>"; };
|
||||
948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; };
|
||||
94C0150924215643005811A9 /* ActionTopAlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionTopAlertModel.swift; sourceTree = "<group>"; };
|
||||
94C0150B2421564A005811A9 /* ActionCollapseNotificationModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionCollapseNotificationModel.swift; sourceTree = "<group>"; };
|
||||
94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeModel.swift; sourceTree = "<group>"; };
|
||||
94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeUnderlineModel.swift; sourceTree = "<group>"; };
|
||||
@ -888,7 +865,6 @@
|
||||
AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = "<group>"; };
|
||||
AF1C33642883B5A4006B1001 /* ActionTopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTopNotificationHandler.swift; sourceTree = "<group>"; };
|
||||
AF1C336828859778006B1001 /* ActionAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionAlertHandler.swift; sourceTree = "<group>"; };
|
||||
AF1C336A28859C73006B1001 /* ActionTopAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTopAlertHandler.swift; sourceTree = "<group>"; };
|
||||
AF1C336C28859EE1006B1001 /* ActionOpenPanelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPanelHandler.swift; sourceTree = "<group>"; };
|
||||
AF1C336E2885A16A006B1001 /* ActionCollapseNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCollapseNotificationHandler.swift; sourceTree = "<group>"; };
|
||||
AF1C33702885AE76006B1001 /* MVMCoreUIActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIActionHandler.swift; sourceTree = "<group>"; };
|
||||
@ -898,7 +874,7 @@
|
||||
AF7E509629E477C0009DC2AD /* AlertHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertHandler.swift; sourceTree = "<group>"; };
|
||||
AF7E509729E477C0009DC2AD /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = "<group>"; };
|
||||
AFA4931F29E5CA73001A9663 /* AlertOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = "<group>"; };
|
||||
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationHandler.swift; sourceTree = "<group>"; };
|
||||
AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
|
||||
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
|
||||
@ -958,8 +934,8 @@
|
||||
D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = "<group>"; };
|
||||
D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
|
||||
D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = "<group>"; };
|
||||
D20C7008250BF99B0095B21C /* TopNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationModel.swift; sourceTree = "<group>"; };
|
||||
D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUITopAlertView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D20C7008250BF99B0095B21C /* NotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModel.swift; sourceTree = "<group>"; };
|
||||
D20C700A250BFDE40095B21C /* NotificationContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContainerView.swift; sourceTree = "<group>"; };
|
||||
D20F3B43252E00E4004B3F56 /* PageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageProtocol.swift; sourceTree = "<group>"; };
|
||||
D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModel.swift; sourceTree = "<group>"; };
|
||||
D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
|
||||
@ -978,7 +954,7 @@
|
||||
D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionMoleculeTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTemplateModel.swift; sourceTree = "<group>"; };
|
||||
D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D23118B225124E18001C8440 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
D23118B225124E18001C8440 /* NotificationMoleculeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMoleculeView.swift; sourceTree = "<group>"; };
|
||||
D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinksModel.swift; sourceTree = "<group>"; };
|
||||
D2351C7B24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinks.swift; sourceTree = "<group>"; };
|
||||
D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = "<group>"; };
|
||||
@ -1069,16 +1045,6 @@
|
||||
D29DF11421E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSLayoutConstraint+MFConvenience.m"; sourceTree = "<group>"; };
|
||||
D29DF11A21E684A9003B2FB9 /* MVMCoreUISplitViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUISplitViewController.h; sourceTree = "<group>"; };
|
||||
D29DF11B21E684A9003B2FB9 /* MVMCoreUISplitViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISplitViewController.m; sourceTree = "<group>"; };
|
||||
D29DF11F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITopAlertMainView.h; sourceTree = "<group>"; };
|
||||
D29DF12021E6851E003B2FB9 /* MVMCoreUITopAlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITopAlertView.h; sourceTree = "<group>"; };
|
||||
D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITopAlertExpandableView.m; sourceTree = "<group>"; };
|
||||
D29DF12221E6851E003B2FB9 /* MVMCoreUITopAlertShortView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITopAlertShortView.h; sourceTree = "<group>"; };
|
||||
D29DF12321E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITopAlertBaseView.h; sourceTree = "<group>"; };
|
||||
D29DF12421E6851E003B2FB9 /* MVMCoreUITopAlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITopAlertView.m; sourceTree = "<group>"; };
|
||||
D29DF12521E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITopAlertMainView.m; sourceTree = "<group>"; };
|
||||
D29DF12621E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITopAlertShortView.m; sourceTree = "<group>"; };
|
||||
D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITopAlertExpandableView.h; sourceTree = "<group>"; };
|
||||
D29DF12821E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITopAlertBaseView.m; sourceTree = "<group>"; };
|
||||
D29DF13821E68636003B2FB9 /* MFStyler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFStyler.h; sourceTree = "<group>"; };
|
||||
D29DF13921E68637003B2FB9 /* MFStyler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFStyler.m; sourceTree = "<group>"; };
|
||||
D29DF14421E68728003B2FB9 /* MFSizeObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFSizeObject.m; sourceTree = "<group>"; };
|
||||
@ -1139,10 +1105,8 @@
|
||||
D2C521A823EDE79E00CA2634 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
D2C78CD124228BBD00B69FDE /* ActionOpenPanelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionOpenPanelModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButtonModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CC251104FE00C75681 /* NotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMoleculeModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationModel.swift; sourceTree = "<group>"; };
|
||||
D2CAC7D02511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUITopAlertMainView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D2CAC7D2251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUITopAlertExpandableView+Extension.swift"; sourceTree = "<group>"; };
|
||||
D2D2FCEF252B72AF0033EAAA /* MoleculeSectionFooterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionFooterModel.swift; sourceTree = "<group>"; };
|
||||
D2D2FCF2252B72CF0033EAAA /* MoleculeSectionFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionFooter.swift; sourceTree = "<group>"; };
|
||||
D2D39579252FDBB300047B11 /* ModalSectionListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalSectionListTemplate.swift; sourceTree = "<group>"; };
|
||||
@ -1174,14 +1138,6 @@
|
||||
D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionAlertModel.swift; sourceTree = "<group>"; };
|
||||
D2ED27EA254B0CE700A1C293 /* AlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = "<group>"; };
|
||||
D2ED27F3254B0E0200A1C293 /* AlertObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertObject.swift; sourceTree = "<group>"; };
|
||||
D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertAnimationDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
D2ED2806254B0EB700A1C293 /* MVMCoreTopAlertOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertOperation.h; sourceTree = "<group>"; };
|
||||
D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreTopAlertOperation.m; sourceTree = "<group>"; };
|
||||
D2ED2808254B0EB700A1C293 /* MVMCoreTopAlertViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertViewProtocol.h; sourceTree = "<group>"; };
|
||||
D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
D2ED280A254B0EB700A1C293 /* MVMCoreTopAlertObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertObject.h; sourceTree = "<group>"; };
|
||||
D2ED280B254B0EB800A1C293 /* MVMCoreTopAlertObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreTopAlertObject.m; sourceTree = "<group>"; };
|
||||
D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGlobalTopAlertDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
D2FA83D12513EA6900564112 /* NotificationXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButton.swift; sourceTree = "<group>"; };
|
||||
D2FA83D32514F80C00564112 /* CollapsableNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotification.swift; sourceTree = "<group>"; };
|
||||
D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationTopView.swift; sourceTree = "<group>"; };
|
||||
@ -1513,8 +1469,6 @@
|
||||
94C01508242155FE005811A9 /* Actions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
94C0150924215643005811A9 /* ActionTopAlertModel.swift */,
|
||||
AF1C336A28859C73006B1001 /* ActionTopAlertHandler.swift */,
|
||||
94C0150B2421564A005811A9 /* ActionCollapseNotificationModel.swift */,
|
||||
AF1C336E2885A16A006B1001 /* ActionCollapseNotificationHandler.swift */,
|
||||
D2C78CD124228BBD00B69FDE /* ActionOpenPanelModel.swift */,
|
||||
@ -2033,7 +1987,7 @@
|
||||
D29DF11921E68467003B2FB9 /* Containers */,
|
||||
D22D1F582204D2590077CEC0 /* Legacy */,
|
||||
D29DF10F21E67A7D003B2FB9 /* BaseControllers */,
|
||||
D29DF11E21E6851E003B2FB9 /* TopAlert */,
|
||||
D29DF11E21E6851E003B2FB9 /* Notification */,
|
||||
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */,
|
||||
D29DF0D021E404D4003B2FB9 /* Info.plist */,
|
||||
);
|
||||
@ -2156,31 +2110,14 @@
|
||||
path = Containers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D29DF11E21E6851E003B2FB9 /* TopAlert */ = {
|
||||
D29DF11E21E6851E003B2FB9 /* Notification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */,
|
||||
D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */,
|
||||
D2ED2809254B0EB700A1C293 /* MVMCoreTopAlertDelegateProtocol.h */,
|
||||
D2ED280A254B0EB700A1C293 /* MVMCoreTopAlertObject.h */,
|
||||
D2ED280B254B0EB800A1C293 /* MVMCoreTopAlertObject.m */,
|
||||
D2ED2806254B0EB700A1C293 /* MVMCoreTopAlertOperation.h */,
|
||||
D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */,
|
||||
D2ED2808254B0EB700A1C293 /* MVMCoreTopAlertViewProtocol.h */,
|
||||
D20C7008250BF99B0095B21C /* TopNotificationModel.swift */,
|
||||
D20C700A250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift */,
|
||||
D29DF12021E6851E003B2FB9 /* MVMCoreUITopAlertView.h */,
|
||||
D29DF12421E6851E003B2FB9 /* MVMCoreUITopAlertView.m */,
|
||||
D29DF12321E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h */,
|
||||
D29DF12821E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m */,
|
||||
D29DF11F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.h */,
|
||||
D29DF12521E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m */,
|
||||
D29DF12221E6851E003B2FB9 /* MVMCoreUITopAlertShortView.h */,
|
||||
D29DF12621E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m */,
|
||||
D29DF12721E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h */,
|
||||
D29DF12121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m */,
|
||||
AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */,
|
||||
D20C7008250BF99B0095B21C /* NotificationModel.swift */,
|
||||
D20C700A250BFDE40095B21C /* NotificationContainerView.swift */,
|
||||
);
|
||||
path = TopAlert;
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D29DF13321E68604003B2FB9 /* Styles */ = {
|
||||
@ -2484,13 +2421,11 @@
|
||||
children = (
|
||||
D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */,
|
||||
D2FA83D12513EA6900564112 /* NotificationXButton.swift */,
|
||||
D2CAC7CC251104FE00C75681 /* NotificationModel.swift */,
|
||||
D23118B225124E18001C8440 /* Notification.swift */,
|
||||
D2CAC7D02511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift */,
|
||||
D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */,
|
||||
D23118B225124E18001C8440 /* NotificationMoleculeView.swift */,
|
||||
D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */,
|
||||
D2FA83D32514F80C00564112 /* CollapsableNotification.swift */,
|
||||
D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */,
|
||||
D2CAC7D2251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift */,
|
||||
);
|
||||
path = TopNotification;
|
||||
sourceTree = "<group>";
|
||||
@ -2531,7 +2466,6 @@
|
||||
AF7E509729E477C0009DC2AD /* AlertController.swift */,
|
||||
AF7E509629E477C0009DC2AD /* AlertHandler.swift */,
|
||||
AFA4931F29E5CA73001A9663 /* AlertOperation.swift */,
|
||||
AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */,
|
||||
);
|
||||
path = Alerts;
|
||||
sourceTree = "<group>";
|
||||
@ -2567,7 +2501,6 @@
|
||||
D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */,
|
||||
D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */,
|
||||
D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */,
|
||||
D29DF12A21E6851E003B2FB9 /* MVMCoreUITopAlertView.h in Headers */,
|
||||
D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */,
|
||||
D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */,
|
||||
D20492A424329A2800A5EED6 /* MVMCoreUIPagingProtocol.h in Headers */,
|
||||
@ -2576,24 +2509,14 @@
|
||||
D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */,
|
||||
D29DF11621E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h in Headers */,
|
||||
01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */,
|
||||
D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */,
|
||||
D29DF2CA21E7BFC8003B2FB9 /* MFSizeThreshold.h in Headers */,
|
||||
D29DF28021E7AA51003B2FB9 /* MVMCoreUIDetailViewProtocol.h in Headers */,
|
||||
D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */,
|
||||
D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */,
|
||||
D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */,
|
||||
D2ED2811254B0EB800A1C293 /* MVMCoreTopAlertObject.h in Headers */,
|
||||
D2ED280F254B0EB800A1C293 /* MVMCoreTopAlertViewProtocol.h in Headers */,
|
||||
D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */,
|
||||
D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */,
|
||||
D2ED2810254B0EB800A1C293 /* MVMCoreTopAlertDelegateProtocol.h in Headers */,
|
||||
D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */,
|
||||
D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */,
|
||||
D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */,
|
||||
D29DF2C821E7BFC1003B2FB9 /* MFSizeObject.h in Headers */,
|
||||
D29DF2E121E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h in Headers */,
|
||||
D29DF12921E6851E003B2FB9 /* MVMCoreUITopAlertMainView.h in Headers */,
|
||||
D29DF12C21E6851E003B2FB9 /* MVMCoreUITopAlertShortView.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -2752,11 +2675,10 @@
|
||||
011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */,
|
||||
EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */,
|
||||
BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */,
|
||||
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */,
|
||||
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */,
|
||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */,
|
||||
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */,
|
||||
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */,
|
||||
D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */,
|
||||
942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */,
|
||||
BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */,
|
||||
012A88C8238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift in Sources */,
|
||||
@ -2797,11 +2719,9 @@
|
||||
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */,
|
||||
D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */,
|
||||
D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */,
|
||||
D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */,
|
||||
AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */,
|
||||
D23A8FEB26122F69007E14CE /* VisibleBehaviorForVideoModel.swift in Sources */,
|
||||
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */,
|
||||
D2CAC7D3251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift in Sources */,
|
||||
AA07EA932510A451009A2AE3 /* Star.swift in Sources */,
|
||||
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
|
||||
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
|
||||
@ -2828,7 +2748,6 @@
|
||||
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */,
|
||||
525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */,
|
||||
D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */,
|
||||
D2ED280E254B0EB800A1C293 /* MVMCoreTopAlertOperation.m in Sources */,
|
||||
D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */,
|
||||
D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */,
|
||||
AA37CBD3251907200027344C /* StarsModel.swift in Sources */,
|
||||
@ -2840,7 +2759,6 @@
|
||||
0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */,
|
||||
D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */,
|
||||
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */,
|
||||
D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */,
|
||||
94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */,
|
||||
943820842432382400B43AF3 /* WebView.swift in Sources */,
|
||||
0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */,
|
||||
@ -2889,7 +2807,6 @@
|
||||
01509D952327ED1900EF99AA /* HeadlineBodyLinkToggle.swift in Sources */,
|
||||
AA104ADA244734DB004D2810 /* HeadersH1LandingPageHeader.swift in Sources */,
|
||||
31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */,
|
||||
D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */,
|
||||
94F6516D2437954100631BF9 /* Tabs.swift in Sources */,
|
||||
5248BFEC23F12E350059236A /* ListThreeColumnPlanDataDivider.swift in Sources */,
|
||||
0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */,
|
||||
@ -2899,7 +2816,7 @@
|
||||
8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */,
|
||||
D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */,
|
||||
D2FD4A4925199BD9000C28A9 /* AccessibilityProtocol.swift in Sources */,
|
||||
D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */,
|
||||
D2CAC7CD251104FE00C75681 /* NotificationMoleculeModel.swift in Sources */,
|
||||
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */,
|
||||
EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */,
|
||||
D20923592450ECE00044AD09 /* TableView.swift in Sources */,
|
||||
@ -2984,10 +2901,9 @@
|
||||
D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */,
|
||||
C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */,
|
||||
32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */,
|
||||
AF1C336B28859C73006B1001 /* ActionTopAlertHandler.swift in Sources */,
|
||||
011D958524042432000E3791 /* RulesProtocol.swift in Sources */,
|
||||
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */,
|
||||
D23118B325124E18001C8440 /* Notification.swift in Sources */,
|
||||
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */,
|
||||
AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */,
|
||||
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
|
||||
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
|
||||
@ -3032,10 +2948,10 @@
|
||||
D253BB9E2458751F002DE544 /* BGImageMoleculeModel.swift in Sources */,
|
||||
AA104AC924472DC7004D2810 /* HeadersH1ButtonModel.swift in Sources */,
|
||||
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */,
|
||||
D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */,
|
||||
D20C7009250BF99B0095B21C /* NotificationModel.swift in Sources */,
|
||||
D29C558A25C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift in Sources */,
|
||||
8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */,
|
||||
AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */,
|
||||
AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */,
|
||||
BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */,
|
||||
AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */,
|
||||
BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */,
|
||||
@ -3057,7 +2973,6 @@
|
||||
D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */,
|
||||
D2D90B442404789000DD6EC9 /* MoleculeContainerProtocol.swift in Sources */,
|
||||
0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */,
|
||||
94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */,
|
||||
BB3BC12F2550094500297977 /* ListLeftVariableIconWithRightCaretAllTextLinks.swift in Sources */,
|
||||
012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */,
|
||||
D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */,
|
||||
@ -3096,7 +3011,6 @@
|
||||
AA45AA0D24BF0276007A6EA7 /* LockUpsPlanNames.swift in Sources */,
|
||||
8DE5BECF2456F7B100772E76 /* ListTwoColumnDropdownSelectors.swift in Sources */,
|
||||
D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */,
|
||||
D2CAC7D12511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift in Sources */,
|
||||
0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */,
|
||||
0AE98BB723FF18E9004C5109 /* ArrowModel.swift in Sources */,
|
||||
01F2C20427C81F9700DC3D36 /* SubNavInteractor.swift.swift in Sources */,
|
||||
@ -3156,7 +3070,6 @@
|
||||
EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */,
|
||||
D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */,
|
||||
52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */,
|
||||
D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */,
|
||||
0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */,
|
||||
EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */,
|
||||
C003506123AA94CD00B6AC29 /* Button.swift in Sources */,
|
||||
@ -3171,7 +3084,6 @@
|
||||
D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */,
|
||||
AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */,
|
||||
C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */,
|
||||
D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */,
|
||||
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */,
|
||||
BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */,
|
||||
D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */,
|
||||
|
||||
@ -1,236 +0,0 @@
|
||||
//
|
||||
// TopNotificationHandler.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 4/11/23.
|
||||
// Copyright © 2023 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import MVMCore
|
||||
|
||||
public class TopNotificationHandler {
|
||||
|
||||
/// The operation queue of top notification operations.
|
||||
private var queue = OperationQueue()
|
||||
|
||||
/// Returns the handler stored in the CoreUIObject
|
||||
public static func shared() -> Self {
|
||||
return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.topNotificationHandler)
|
||||
}
|
||||
|
||||
public init() {
|
||||
registerWithNotificationCenter()
|
||||
registerForPageChanges()
|
||||
}
|
||||
|
||||
// MARK: - JSON Handling
|
||||
|
||||
/// Registers with the notification center to know when json is updated.
|
||||
private func registerWithNotificationCenter() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
|
||||
}
|
||||
|
||||
/// Registers to know when pages change.
|
||||
private func registerForPageChanges() {
|
||||
MVMCoreNavigationHandler.shared()?.addDelegate(self)
|
||||
}
|
||||
|
||||
private func getDelegateObject() -> MVMCoreUIDelegateObject? {
|
||||
// TODO: Top alert view is current delegate. Should move to current view controller eventually?
|
||||
guard let alertView = MVMCoreUISplitViewController.main()?.topAlertView else { return nil }
|
||||
return MVMCoreUIDelegateObject.create(withDelegateForAll: alertView)
|
||||
}
|
||||
|
||||
/// Checks for new top alert json
|
||||
@objc private func responseJSONUpdated(notification: Notification) {
|
||||
guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return }
|
||||
|
||||
// Dismiss any top alerts that server wants us to dismiss/
|
||||
if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") {
|
||||
TopNotificationHandler.shared().hideTopAlertView(of: disableType)
|
||||
}
|
||||
|
||||
// Show any new top alert.
|
||||
guard let responseJSON = loadObject.responseJSON,
|
||||
let json = responseJSON.optionalDictionaryForKey(KeyTopAlert) else { return }
|
||||
showTopNotification(with: json)
|
||||
}
|
||||
|
||||
/// Decodes the json into a TopNotificationModel
|
||||
public func decodeTopNotification(with json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) -> TopNotificationModel? {
|
||||
do {
|
||||
return try TopNotificationModel.decode(json: json, delegateObject: delegateObject)
|
||||
} catch {
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") {
|
||||
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Operation Handling
|
||||
|
||||
private func add(operation: MVMCoreTopAlertOperation) {
|
||||
operation.completionBlock = { [weak self] in
|
||||
// If the alert was cancelled to show another with higher priority, re-add to the operation when cancelled to the queue.
|
||||
if operation.reAddAfterCancel {
|
||||
let newOperation: MVMCoreTopAlertOperation = operation.copy() as! MVMCoreTopAlertOperation
|
||||
newOperation.reAddAfterCancel = false
|
||||
self?.add(operation: newOperation)
|
||||
}
|
||||
operation.completionBlock = nil
|
||||
}
|
||||
|
||||
let currentPageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
||||
operation.updateDisplayable(byPageType: currentPageType)
|
||||
|
||||
queue.addOperation(operation)
|
||||
reevaluteQueue()
|
||||
}
|
||||
|
||||
/// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated.
|
||||
private func checkAndUpdateExisting(with topAlertObject: MVMCoreTopAlertObject) -> Bool {
|
||||
for case let operation as MVMCoreTopAlertOperation in queue.operations {
|
||||
guard topAlertObject.json != nil,
|
||||
operation.topAlertObject.type == topAlertObject.type else { continue }
|
||||
operation.update(with: topAlertObject)
|
||||
let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
||||
operation.updateDisplayable(byPageType: pageType)
|
||||
reevaluteQueue()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Re-evaluates the queue operations
|
||||
private func reevaluteQueue() {
|
||||
var highestReadyOperation: MVMCoreTopAlertOperation?
|
||||
var executingOperation: MVMCoreTopAlertOperation?
|
||||
for case let operation as MVMCoreTopAlertOperation in queue.operations {
|
||||
guard !operation.isCancelled,
|
||||
!operation.isFinished else { continue }
|
||||
if operation.isReady,
|
||||
highestReadyOperation == nil || operation.queuePriority.rawValue > highestReadyOperation!.queuePriority.rawValue {
|
||||
highestReadyOperation = operation
|
||||
}
|
||||
if operation.isExecuting {
|
||||
executingOperation = operation
|
||||
}
|
||||
}
|
||||
guard let currentOperation = executingOperation else { return }
|
||||
|
||||
// Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent.
|
||||
guard currentOperation.isReady else {
|
||||
currentOperation.reAddAfterCancel = currentOperation.topAlertObject.persistent
|
||||
currentOperation.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
// If the highest priority operation is not executing, and the executing operation is persistent, cancel it.
|
||||
if let newOperation = highestReadyOperation,
|
||||
currentOperation != newOperation,
|
||||
currentOperation.topAlertObject.persistent {
|
||||
currentOperation.reAddAfterCancel = true
|
||||
currentOperation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Show and hide
|
||||
|
||||
public func isTopAlertShowing() -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) != nil
|
||||
}
|
||||
|
||||
public func hasPersistentTopAlert(of type: String) -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
guard operation.isExecuting,
|
||||
let operation = operation as? MVMCoreTopAlertOperation else { return false }
|
||||
return operation.topAlertObject.persistent && operation.topAlertObject.type == type
|
||||
}) as? MVMCoreTopAlertOperation != nil
|
||||
}
|
||||
|
||||
/// Shows the top alert with the json.
|
||||
func showTopNotification(with json: [AnyHashable: Any]) {
|
||||
guard let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return }
|
||||
showTopNotification(with: model)
|
||||
}
|
||||
|
||||
/// Shows the top notification with the model.
|
||||
func showTopNotification(with model: TopNotificationModel) {
|
||||
let object = model.createTopAlertObject()
|
||||
guard !checkAndUpdateExisting(with: object),
|
||||
let operation = MVMCoreTopAlertOperation(topAlertObject: object) else { return }
|
||||
TopNotificationHandler.shared().add(operation: operation)
|
||||
}
|
||||
|
||||
/// Show the top alert with the legacy object.
|
||||
public func showTopAlert(with topAlertObject: MVMCoreTopAlertObject) {
|
||||
let alertOperation = MVMCoreTopAlertOperation(topAlertObject: topAlertObject)!
|
||||
add(operation: alertOperation)
|
||||
}
|
||||
|
||||
/// Cancel the current top alert view.
|
||||
public func hideTopAlertView() {
|
||||
guard let currentOperation = queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) as? MVMCoreTopAlertOperation else { return }
|
||||
currentOperation.topAlertObject.persistent = false
|
||||
currentOperation.reAddAfterCancel = false
|
||||
currentOperation.cancel()
|
||||
}
|
||||
|
||||
/// Cancel all operations of this type.
|
||||
public func hideTopAlertView(of type: String) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? MVMCoreTopAlertOperation,
|
||||
operation.topAlertObject.type == type else { continue }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all persistent operations of this type.
|
||||
public func hidePersistentTopAlertView(of type: String) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? MVMCoreTopAlertOperation,
|
||||
operation.topAlertObject.persistent,
|
||||
operation.topAlertObject.type == type else { continue }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds an cancels top alerts associated with the object.
|
||||
public func removeTopAlert(for object: MVMCoreTopAlertObject) {
|
||||
for operation in queue.operations {
|
||||
guard let operation = operation as? MVMCoreTopAlertOperation,
|
||||
operation.topAlertObject === object else { return }
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeAllTopAlerts() {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
}
|
||||
|
||||
extension TopNotificationHandler: MVMCorePresentationDelegateProtocol {
|
||||
// Update displayable for each top alert operation when page type changes, in top queue priority order.
|
||||
public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
|
||||
guard queue.operations.count > 0 else { return }
|
||||
let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController)
|
||||
guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return }
|
||||
let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
|
||||
queue.operations.compactMap {
|
||||
$0 as? MVMCoreTopAlertOperation
|
||||
}.sorted {
|
||||
$0.queuePriority.rawValue > $1.queuePriority.rawValue
|
||||
}.forEach {
|
||||
$0.updateDisplayable(byPageType: pageType)
|
||||
}
|
||||
reevaluteQueue()
|
||||
}
|
||||
}
|
||||
@ -9,11 +9,23 @@
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// Collapse the current top notification.
|
||||
/// Notifications that conform are collapsable and can collapse.
|
||||
public protocol CollapsableNotificationProtocol {
|
||||
/// Collapses the notification.
|
||||
@MainActor
|
||||
func collapse()
|
||||
}
|
||||
|
||||
/// Collapses the current notification if it can collapse, otherwise dismisses it.
|
||||
open class ActionCollapseNotificationHandler: MVMCoreActionHandlerProtocol {
|
||||
required public init() {}
|
||||
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
CoreUIObject.sharedInstance()?.globalTopAlertDelegate?.getTopAlertView?().collapseNotification?()
|
||||
guard let notification = await NotificationHandler.shared()?.getCurrentNotification() else { return }
|
||||
guard let notification = notification.0 as? CollapsableNotificationProtocol else {
|
||||
NotificationHandler.shared()?.hideNotification()
|
||||
return
|
||||
}
|
||||
await notification.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,15 +9,11 @@
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// Collapse the current top notification.
|
||||
/// Dismiss the current notification.
|
||||
open class ActionDismissNotificationHandler: MVMCoreActionHandlerProtocol {
|
||||
required public init() {}
|
||||
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
await withCheckedContinuation { continuation in
|
||||
CoreUIObject.sharedInstance()?.globalTopAlertDelegate?.getTopAlertView?().hideAlertView?(true, completionHandler: { finished in
|
||||
continuation.resume()
|
||||
}) ?? continuation.resume()
|
||||
}
|
||||
NotificationHandler.shared()?.hideNotification()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
//
|
||||
// ActionTopAlertHandler.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/18/22.
|
||||
// Copyright © 2022 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// Creates and shows an alert using the ResponseInfo of a Page found in the cache.
|
||||
open class ActionTopAlertHandler: MVMCoreActionHandlerProtocol {
|
||||
required public init() {}
|
||||
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
guard let model = model as? ActionTopAlertModel else { return }
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
MVMCoreCache.shared()?.fetchJSON(forPageType: model.pageType, queue: nil, waitUntilFinished: true, completionHandler: { json in
|
||||
guard let responseInfo = json?.optionalDictionaryForKey(KeyResponseInfo) else {
|
||||
continuation.resume(throwing: ModelRegistry.Error.decoderOther(message: "Alert Page \(model.pageType) missing ResponseInfo"))
|
||||
return
|
||||
}
|
||||
|
||||
let topAlertObject = MVMCoreTopAlertObject(responseInfo: responseInfo)!
|
||||
topAlertObject.delegate = (delegateObject as? MVMCoreUIDelegateObject)?.topAlertDelegate
|
||||
topAlertObject.pageType = model.pageType
|
||||
TopNotificationHandler.shared().showTopAlert(with: topAlertObject)
|
||||
continuation.resume()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
//
|
||||
// ActionTopAlertModel.swift
|
||||
// MVMCore
|
||||
//
|
||||
// Created by Suresh, Kamlesh on 12/16/19.
|
||||
// Copyright © 2019 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct ActionTopAlertModel: ActionModelProtocol {
|
||||
|
||||
public static var identifier: String = "topAlert"
|
||||
public var actionType: String = ActionTopAlertModel.identifier
|
||||
public var pageType: String
|
||||
public var extraParameters: JSONValueDictionary?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
public init(pageType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
self.pageType = pageType
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,6 @@ open class ActionTopNotificationHandler: MVMCoreActionHandlerProtocol {
|
||||
|
||||
open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
|
||||
guard let model = model as? ActionTopNotificationModel else { return }
|
||||
TopNotificationHandler.shared().showTopNotification(with: model.topNotification)
|
||||
try await NotificationHandler.shared()?.showNotification(for: model.topNotification, delegateObject: delegateObject as? MVMCoreUIDelegateObject)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ public struct ActionTopNotificationModel: ActionModelProtocol {
|
||||
|
||||
public static var identifier: String = "topNotification"
|
||||
public var actionType: String = ActionTopNotificationModel.identifier
|
||||
public var topNotification: TopNotificationModel
|
||||
public var topNotification: NotificationModel
|
||||
public var extraParameters: JSONValueDictionary?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
public init(topNotification: TopNotificationModel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
public init(topNotification: NotificationModel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
self.topNotification = topNotification
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
|
||||
@ -26,7 +26,7 @@ import VDSFormControlsTokens
|
||||
}
|
||||
}
|
||||
public var enabledColor: UIColor {
|
||||
return radioModel?.inverted ?? false ? VDSFormControlsColor.borderOndark : VDSFormControlsColor.borderOnlight
|
||||
return radioModel?.inverted ?? false ? VDSColor.elementsPrimaryOndark : VDSColor.elementsPrimaryOnlight
|
||||
}
|
||||
public var disabledColor: UIColor {
|
||||
return radioModel?.inverted ?? false ? VDSColor.interactiveDisabledOndark : VDSColor.interactiveDisabledOnlight
|
||||
|
||||
@ -60,9 +60,10 @@
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(id: String = UUID().uuidString, text: String) {
|
||||
public init(id: String = UUID().uuidString, text: String, textColor: Color? = nil) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import Dispatch
|
||||
import MVMCore
|
||||
|
||||
@objcMembers open class CollapsableNotification: View {
|
||||
//--------------------------------------------------
|
||||
@ -14,8 +17,11 @@ import Foundation
|
||||
//--------------------------------------------------
|
||||
|
||||
public let topView = CollapsableNotificationTopView()
|
||||
public let bottomView = NotificationView()
|
||||
public let bottomView = NotificationMoleculeView()
|
||||
public var verticalStack: UIStackView!
|
||||
|
||||
public var cancellables = Set<AnyCancellable>()
|
||||
private var timerSource: DispatchSourceTimer?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Life Cycle
|
||||
@ -33,6 +39,7 @@ import Foundation
|
||||
NSLayoutConstraint.constraintPinSubview(verticalStack, pinTop: true, topConstant: 0, pinBottom: true, bottomConstant: 0, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0)
|
||||
|
||||
reset()
|
||||
subscribeForNotifications()
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
@ -46,6 +53,42 @@ import Foundation
|
||||
backgroundColor = .mvmGreen()
|
||||
}
|
||||
|
||||
open func subscribeForNotifications() {
|
||||
// Resets state when about to show.
|
||||
NotificationHandler.shared()?.onNotificationWillShow.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] (view, _) in
|
||||
guard let self = self,
|
||||
self == view else { return }
|
||||
self.initialState()
|
||||
}).store(in: &cancellables)
|
||||
// Begins the collapse timer when shown.
|
||||
NotificationHandler.shared()?.onNotificationShown.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] (view, _) in
|
||||
guard let self = self,
|
||||
self == view,
|
||||
let model = self.model as? CollapsableNotificationModel else { return }
|
||||
if !model.initiallyCollapsed {
|
||||
self.autoCollapse()
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
// Cancels any collapse timer when dismissing
|
||||
NotificationHandler.shared()?.onNotificationWillDismiss.sink(receiveValue: { [weak self] (view, _) in
|
||||
guard let self = self,
|
||||
self == view else { return }
|
||||
timerSource?.cancel()
|
||||
timerSource = nil
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
/// Set initial collapse/expand state.
|
||||
open func initialState() {
|
||||
guard let model = model as? CollapsableNotificationModel else { return }
|
||||
topView.isHidden = !model.alwaysShowTopLabel && !model.initiallyCollapsed
|
||||
topView.button.isUserInteractionEnabled = model.initiallyCollapsed
|
||||
bottomView.isHidden = model.initiallyCollapsed
|
||||
verticalStack.layoutIfNeeded()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Molecule
|
||||
//--------------------------------------------------
|
||||
@ -66,16 +109,7 @@ import Foundation
|
||||
self?.expand(topViewShowing: model.alwaysShowTopLabel)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
autoCollapse()
|
||||
}
|
||||
initialState()
|
||||
}
|
||||
|
||||
open func performBlockOperation(with block: @escaping (MVMCoreBlockOperation) -> Void) {
|
||||
@ -86,33 +120,41 @@ import Foundation
|
||||
/// 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
|
||||
timerSource = DispatchSource.makeTimerSource()
|
||||
timerSource?.setEventHandler(handler: { [weak self] in
|
||||
// If accessibility focused, delay collapse.
|
||||
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 {
|
||||
self.collapse()
|
||||
}
|
||||
}
|
||||
self.timerSource = nil
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
if MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.accessibilityFocusChanged(notification:)), name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.accessibilityFocusChanged(notification:)), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
|
||||
|
||||
} else {
|
||||
self.collapse()
|
||||
}
|
||||
})
|
||||
})
|
||||
timerSource?.schedule(deadline: .now() + delay)
|
||||
timerSource?.activate()
|
||||
}
|
||||
|
||||
/// Collapses to show just the top view.
|
||||
open func collapse(animated: Bool = true) {
|
||||
guard !bottomView.isHidden else { return }
|
||||
performBlockOperation { [weak self] (operation) in
|
||||
let strongSelf = self
|
||||
guard let strongSelf = self else { return }
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded()
|
||||
strongSelf.superview?.superview?.layoutIfNeeded()
|
||||
let animation = {
|
||||
strongSelf?.topView.isHidden = false
|
||||
strongSelf?.bottomView.isHidden = true
|
||||
strongSelf?.verticalStack.layoutIfNeeded()
|
||||
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())
|
||||
strongSelf.topView.button.isUserInteractionEnabled = true
|
||||
strongSelf.superview?.superview?.layoutIfNeeded()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: strongSelf.getAccessibilityLayoutChangedArgument())
|
||||
operation.markAsFinished()
|
||||
}
|
||||
|
||||
@ -130,19 +172,19 @@ import Foundation
|
||||
open func expand(topViewShowing: Bool = false, animated: Bool = true) {
|
||||
guard bottomView.isHidden else { return }
|
||||
performBlockOperation { [weak self] (operation) in
|
||||
let strongSelf = self
|
||||
guard let strongSelf = self else { return }
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
MVMCoreUITopAlertView.sharedGlobal()?.superview?.layoutIfNeeded()
|
||||
strongSelf?.topView.button.isUserInteractionEnabled = false
|
||||
strongSelf.superview?.superview?.layoutIfNeeded()
|
||||
strongSelf.topView.button.isUserInteractionEnabled = false
|
||||
let animation = {
|
||||
strongSelf?.topView.isHidden = !topViewShowing
|
||||
strongSelf?.bottomView.isHidden = false
|
||||
strongSelf?.verticalStack.layoutIfNeeded()
|
||||
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()
|
||||
strongSelf.superview?.superview?.layoutIfNeeded()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: strongSelf.getAccessibilityLayoutChangedArgument())
|
||||
strongSelf.autoCollapse()
|
||||
operation.markAsFinished()
|
||||
}
|
||||
|
||||
@ -161,16 +203,17 @@ import Foundation
|
||||
}
|
||||
|
||||
/// Collapse if focus is no longer on this top alert.
|
||||
@objc func accessibilityFocusChanged(notification: Notification) {
|
||||
if (notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] != nil) && !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
|
||||
@objc private func accessibilityFocusChanged(notification: Notification) {
|
||||
if !UIAccessibility.isVoiceOverRunning || ((notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] != nil) && !MVMCoreUIUtility.viewContainsAccessiblityFocus(self)) {
|
||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
|
||||
collapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollapsableNotification: StatusBarUI {
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
|
||||
public func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
|
||||
let color = backgroundColor ?? UIColor.mvmGreen
|
||||
var greyScale: CGFloat = 0
|
||||
topView.label.textColor.getWhite(&greyScale, alpha: nil)
|
||||
@ -187,3 +230,10 @@ extension CollapsableNotification: AccessibilityProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollapsableNotification: CollapsableNotificationProtocol {
|
||||
@MainActor
|
||||
public func collapse() {
|
||||
collapse(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
open class CollapsableNotificationModel: NotificationModel {
|
||||
open class CollapsableNotificationModel: NotificationMoleculeModel {
|
||||
public class override var identifier: String {
|
||||
return "collapsableNotification"
|
||||
}
|
||||
@ -17,11 +18,14 @@ open class CollapsableNotificationModel: NotificationModel {
|
||||
public var alwaysShowTopLabel = false
|
||||
public var collapseTime: Int = 5
|
||||
public var initiallyCollapsed = false
|
||||
public var pages: [String]?
|
||||
|
||||
public init(with topLabel: LabelModel, headline: LabelModel) {
|
||||
public init(with topLabel: LabelModel, headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, topAction: ActionModelProtocol? = nil, collapseTime: Int? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil) {
|
||||
self.topLabel = topLabel
|
||||
super.init(with: headline)
|
||||
self.topAction = topAction
|
||||
if let collapseTime = collapseTime {
|
||||
self.collapseTime = collapseTime
|
||||
}
|
||||
super.init(with: headline, style: style, backgroundColor: backgroundColor, body: body, button: button, closeButton: closeButton)
|
||||
}
|
||||
|
||||
open override func setDefaults() {
|
||||
@ -30,7 +34,12 @@ open class CollapsableNotificationModel: NotificationModel {
|
||||
topLabel.numberOfLines = 1
|
||||
}
|
||||
if topLabel.textColor == nil {
|
||||
topLabel.textColor = Color(uiColor: .white)
|
||||
switch style {
|
||||
case .error, .warning:
|
||||
topLabel.textColor = Color(uiColor: .mvmBlack)
|
||||
default:
|
||||
topLabel.textColor = Color(uiColor: .mvmWhite)
|
||||
}
|
||||
}
|
||||
if topLabel.textAlignment == nil {
|
||||
topLabel.textAlignment = .center
|
||||
@ -44,7 +53,6 @@ open class CollapsableNotificationModel: NotificationModel {
|
||||
case alwaysShowTopLabel
|
||||
case collapseTime
|
||||
case initiallyCollapsed
|
||||
case pages
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
@ -60,7 +68,6 @@ open class CollapsableNotificationModel: NotificationModel {
|
||||
if let initiallyCollapsed = try typeContainer.decodeIfPresent(Bool.self, forKey: .initiallyCollapsed) {
|
||||
self.initiallyCollapsed = initiallyCollapsed
|
||||
}
|
||||
pages = try typeContainer.decodeIfPresent([String].self, forKey: .pages)
|
||||
try super.init(from: decoder)
|
||||
}
|
||||
|
||||
@ -73,6 +80,5 @@ open class CollapsableNotificationModel: NotificationModel {
|
||||
try container.encode(alwaysShowTopLabel, forKey: .alwaysShowTopLabel)
|
||||
try container.encode(collapseTime, forKey: .collapseTime)
|
||||
try container.encode(initiallyCollapsed, forKey: .initiallyCollapsed)
|
||||
try container.encodeIfPresent(pages, forKey: .pages)
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ import Foundation
|
||||
isAccessibilityElement = true
|
||||
accessibilityLabel = label.text
|
||||
accessibilityTraits = (button.isUserInteractionEnabled && button.actionModel != nil) ? .button : .none
|
||||
MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: self)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: self)
|
||||
}
|
||||
|
||||
@objc func pressed(_ sender: Notification) {
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertExpandableView+Extension.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/15/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
extension MVMCoreUITopAlertExpandableView: MoleculeViewProtocol {
|
||||
|
||||
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
defaultSetup()
|
||||
guard let model = model as? CollapsableNotificationModel else { return }
|
||||
backgroundColor = model.backgroundColor?.uiColor ?? .mvmGreen
|
||||
collapseTime = model.collapseTime
|
||||
onlyShowTopMessageWhenCollapsed = !model.alwaysShowTopLabel
|
||||
|
||||
var actionMap = model.button?.action.toJSON()
|
||||
if let title = model.button?.title {
|
||||
actionMap?.updateValue(title, forKey: KeyTitle)
|
||||
}
|
||||
buttonView?.setupCloseButton(model.closeButton != nil, animationDelegate: MVMCoreUITopAlertView.sharedGlobal()?.animationDelegate)
|
||||
setTopMessage(model.topLabel.text, message: model.headline.text, subMessage: model.body?.text, contentColor: model.headline.textColor?.uiColor ?? .white, actionMap: actionMap, additionalData: nil)
|
||||
expand(false)
|
||||
|
||||
if let button = shortView?.button,
|
||||
let topActionMap = model.topAction?.toJSON() {
|
||||
MVMCoreUITopAlertBaseView.addAction(to: button, actionMap: topActionMap, additionalData: nil)
|
||||
shortView?.label?.accessibilityTraits = .button
|
||||
}
|
||||
|
||||
if let accessibilityIdentifier = model.accessibilityIdentifier {
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MVMCoreUITopAlertExpandableView: StatusBarUI {
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
|
||||
if shortView?.label?.text?.count ?? 0 > 0 {
|
||||
let color = backgroundColor ?? UIColor.mvmGreen
|
||||
var greyScale: CGFloat = 0
|
||||
if shortView?.label?.textColor.getWhite(&greyScale, alpha: nil) ?? false {
|
||||
return (color, greyScale > 0.5 ? .lightContent : .default)
|
||||
} else {
|
||||
return (color, .default)
|
||||
}
|
||||
} else {
|
||||
return (.white, .default)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertMainView+Extension.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/15/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
extension MVMCoreUITopAlertMainView: MoleculeViewProtocol {
|
||||
|
||||
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
defaultSetup()
|
||||
guard let model = model as? NotificationModel else { return }
|
||||
|
||||
backgroundColor = model.backgroundColor?.uiColor ?? .mvmGreen
|
||||
var actionMap = model.button?.action.toJSON()
|
||||
|
||||
if let title = model.button?.title {
|
||||
actionMap?.updateValue(title, forKey: KeyTitle)
|
||||
}
|
||||
|
||||
if let accessibilityIdentifier = model.accessibilityIdentifier {
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
|
||||
setupCloseButton(model.closeButton != nil, animationDelegate: MVMCoreUITopAlertView.sharedGlobal()?.animationDelegate)
|
||||
setup(withMessage: model.headline.text, subMessage: model.body?.text, color: model.headline.textColor?.uiColor ?? .white, actionMap: actionMap, additionalData: nil)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// NotificationModel.swift
|
||||
// NotificationMoleculeModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/15/20.
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
|
||||
|
||||
/**
|
||||
The style of the notification:
|
||||
@ -34,14 +34,19 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
public var body: LabelModel?
|
||||
public var button: ButtonModel?
|
||||
public var closeButton: NotificationXButtonModel?
|
||||
public var style: NotificationModel.Style = .success
|
||||
public var style: NotificationMoleculeModel.Style = .success
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(with headline: LabelModel) {
|
||||
public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil) {
|
||||
self.headline = headline
|
||||
self.style = style
|
||||
self.backgroundColor = backgroundColor
|
||||
self.body = body
|
||||
self.button = button
|
||||
self.closeButton = closeButton
|
||||
super.init()
|
||||
}
|
||||
|
||||
@ -132,7 +137,7 @@ open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
|
||||
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
|
||||
closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton)
|
||||
if let style = try typeContainer.decodeIfPresent(NotificationModel.Style.self, forKey: .style) {
|
||||
if let style = try typeContainer.decodeIfPresent(NotificationMoleculeModel.Style.self, forKey: .style) {
|
||||
self.style = style
|
||||
}
|
||||
super.init()
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Notification.swift
|
||||
// NotificationMoleculeView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/16/20.
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@objcMembers open class NotificationView: Container {
|
||||
@objcMembers open class NotificationMoleculeView: Container {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
@ -63,7 +63,7 @@ import Foundation
|
||||
|
||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
guard let model = model as? NotificationModel else { return }
|
||||
guard let model = model as? NotificationMoleculeModel else { return }
|
||||
labelStack.updateContainedMolecules(with: [model.headline, model.body], delegateObject, nil)
|
||||
horizontalStack.updateContainedMolecules(with: [labelStack.stackModel, model.button, model.closeButton], delegateObject, nil)
|
||||
updateAccessibility()
|
||||
@ -74,13 +74,22 @@ import Foundation
|
||||
}
|
||||
|
||||
open func updateAccessibility() {
|
||||
MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: headline)
|
||||
MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: body)
|
||||
MVMCoreUITopAlertBaseView.amendAccesibilityLabel(for: button)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: headline)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: body)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: button)
|
||||
NotificationMoleculeView.amendAccesibilityLabel(for: closeButton)
|
||||
}
|
||||
|
||||
/// Formats the accessibilityLabel so voice over users know it's in the notification.
|
||||
static public func amendAccesibilityLabel(for view: UIView) {
|
||||
guard let amendment = MVMCoreUIUtility.hardcodedString(withKey: "top_alert_notification"),
|
||||
let accessibilityLabel = view.accessibilityLabel,
|
||||
!accessibilityLabel.hasPrefix(amendment) else { return }
|
||||
view.accessibilityLabel = "\(amendment) - \(accessibilityLabel)"
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationView: AccessibilityProtocol {
|
||||
extension NotificationMoleculeView: AccessibilityProtocol {
|
||||
public func getAccessibilityLayoutChangedArgument() -> Any? {
|
||||
return headline
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtocol {
|
||||
public static var identifier: String = "notificationXButton"
|
||||
@ -20,7 +21,10 @@ public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtoco
|
||||
case action
|
||||
}
|
||||
|
||||
public init() {}
|
||||
public init(color: Color? = nil, action: ActionModelProtocol = ActionNoopModel()) {
|
||||
self.color = color
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
@ -8,6 +8,14 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
import MVMCore
|
||||
|
||||
/// Allows overrides of the status bar color and style.
|
||||
public protocol StatusBarUI {
|
||||
/// Returns the background color of the status bar view and the style of the status bar.
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle)
|
||||
}
|
||||
|
||||
// Navigation bar update functions
|
||||
public extension MVMCoreUISplitViewController {
|
||||
@ -178,8 +186,10 @@ public extension MVMCoreUISplitViewController {
|
||||
navigationController.isDisplayed(viewController: viewController),
|
||||
let model = navigationController.getNavigationModel(from: viewController) else { return }
|
||||
setNavigationBar(for: viewController, navigationController: navigationController, navigationItemModel: model)
|
||||
guard !(topAlertView?.overridingStatusBar() ?? false) else { return }
|
||||
setStatusBarForCurrentViewController()
|
||||
Task {
|
||||
guard (await NotificationHandler.shared()?.getCurrentNotification()?.0 as? StatusBarUI) == nil || topAlertView == nil else { return }
|
||||
setStatusBarForCurrentViewController()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Status Bar
|
||||
@ -223,3 +233,34 @@ extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
|
||||
updateState(with: viewController)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public extension MVMCoreUISplitViewController {
|
||||
/// Subscribes for notification events.
|
||||
@objc func subscribeForNotifications() {
|
||||
guard cancellables == nil else { return }
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// Ensure the status bar background color and tint are proper for the notification.
|
||||
NotificationHandler.shared()?.onNotificationWillShow.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] (notification, model) in
|
||||
guard let conformer = notification as? StatusBarUI else { return }
|
||||
let statusBarUI = conformer.getStatusBarUI()
|
||||
self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
NotificationHandler.shared()?.onNotificationUpdated.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] (notification, model) in
|
||||
guard let conformer = notification as? StatusBarUI else { return }
|
||||
let statusBarUI = conformer.getStatusBarUI()
|
||||
self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
// Ensure the status bar background color and tint are proper for the view controller.
|
||||
NotificationHandler.shared()?.onNotificationDismissed.receive(on: DispatchQueue.main)
|
||||
.sink { (notification, model) in
|
||||
guard notification is StatusBarUI else { return }
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarForCurrentViewController()
|
||||
}.store(in: &cancellables)
|
||||
self.cancellables = cancellables
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#import <MVMCoreUI/MVMCoreUIPanelProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIPanelButtonProtocol.h>
|
||||
|
||||
@class MVMCoreUITopAlertView;
|
||||
@class MFViewController;
|
||||
@class NavigationController;
|
||||
@protocol TabBarProtocol;
|
||||
@ -42,7 +41,7 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
@property (nullable, weak, nonatomic, readonly) NavigationController *navigationController;
|
||||
|
||||
// Reference to the top alert view
|
||||
@property (nullable, weak, nonatomic) MVMCoreUITopAlertView *topAlertView;
|
||||
@property (nullable, weak, nonatomic) UIView *topAlertView;
|
||||
|
||||
// Reference to the status bar view
|
||||
@property (nullable, weak, nonatomic) UIView *statusBarView;
|
||||
@ -58,6 +57,8 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
/// Tab bar index history. Contains either indices (0, 1, etc) or NSNull if there was no tab bar indice to set.
|
||||
@property (nonnull, strong, nonatomic) NSMutableArray <NSNumber *>*tabBarIndices;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSSet *cancellables;
|
||||
|
||||
// Convenience getter
|
||||
+ (nullable instancetype)mainSplitViewController;
|
||||
|
||||
@ -65,10 +66,10 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
- (nullable instancetype)initWithLeftPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel;
|
||||
|
||||
// Returns a split controller with the mvm styling. Also sets the appropriate handlers.
|
||||
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel;
|
||||
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nullable UIView*)topAlertView;
|
||||
|
||||
// Returns a split controller with the mvm styling. Also sets the appropriate handlers. Also sets up the default load screen
|
||||
+ (nullable instancetype)setupAsMainController:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel;
|
||||
+ (nullable instancetype)setupAsMainController:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nullable UIView*)topAlertView;
|
||||
|
||||
#pragma mark - Panel Functions
|
||||
|
||||
|
||||
@ -20,8 +20,8 @@
|
||||
#import "MVMCoreUISession.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import "MVMCoreUICommonViewsUtility.h"
|
||||
#import "MVMCoreUITopAlertView.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
@import MVMCore.MVMCoreViewProtocol;
|
||||
|
||||
@interface MVMCoreUISplitViewController ()
|
||||
|
||||
@ -80,15 +80,22 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
return [MVMCoreActionUtility initializerClassCheck:[MVMCoreUISession sharedGlobal].splitViewController classToVerify:self];
|
||||
}
|
||||
|
||||
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel {
|
||||
+ (nullable instancetype)setup:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nullable UIView*)topAlertView {
|
||||
MVMCoreUISplitViewController *splitViewController = [[self alloc] initWithLeftPanel:leftPanel rightPanel:rightPanel];
|
||||
splitViewController.topAlertView = topAlertView;
|
||||
[MVMCoreUISession sharedGlobal].splitViewController = splitViewController;
|
||||
if (topAlertView) {
|
||||
[splitViewController subscribeForNotifications];
|
||||
}
|
||||
return splitViewController;
|
||||
}
|
||||
|
||||
+ (nullable instancetype)setupAsMainController:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel {
|
||||
MVMCoreUISplitViewController *splitViewController = [self setup:leftPanel rightPanel:rightPanel];
|
||||
+ (nullable instancetype)setupAsMainController:(nullable UIViewController <MVMCoreUIPanelProtocol> *)leftPanel rightPanel:(nullable UIViewController <MVMCoreUIPanelProtocol> *)rightPanel topAlertView:(nullable UIView*)topAlertView {
|
||||
MVMCoreUISplitViewController *splitViewController = [self setup:leftPanel rightPanel:rightPanel topAlertView:topAlertView];
|
||||
[[MVMCoreUISession sharedGlobal] setupAsStandardLoadViewDelegate:splitViewController];
|
||||
if (topAlertView) {
|
||||
[splitViewController subscribeForNotifications];
|
||||
}
|
||||
return splitViewController;
|
||||
}
|
||||
|
||||
@ -904,14 +911,9 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
self.statusBarView = statusBarView;
|
||||
|
||||
// Top Alert
|
||||
MVMCoreUITopAlertView *topAlertView = nil;
|
||||
if ([[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) {
|
||||
topAlertView = (MVMCoreUITopAlertView *)[[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView];
|
||||
if (topAlertView) {
|
||||
self.topAlertView = topAlertView;
|
||||
[self.view addSubview:topAlertView];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topAlertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:@{@"topAlertView":topAlertView}]];
|
||||
}
|
||||
if (self.topAlertView) {
|
||||
[self.view addSubview:self.topAlertView];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topAlertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:@{@"topAlertView":self.topAlertView}]];
|
||||
}
|
||||
|
||||
// The main view.
|
||||
@ -947,7 +949,8 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
bottomProgressHeight.active = YES;
|
||||
self.bottomProgressBarHeightConstraint = bottomProgressHeight;
|
||||
|
||||
if (topAlertView) {
|
||||
if (self.topAlertView) {
|
||||
UIView *topAlertView = self.topAlertView;
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,topAlertView, mainView, progressView)]];
|
||||
} else {
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,mainView, progressView)]];
|
||||
@ -1043,7 +1046,9 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
/*self.leftPanel.needToUpdateUI = YES;
|
||||
[self.leftPanel.view setNeedsLayout];
|
||||
[self.leftPanel.view layoutIfNeeded];*/
|
||||
[self.topAlertView updateView:size.width];
|
||||
if ([self.topAlertView conformsToProtocol:@protocol(MVMCoreViewProtocol)]) {
|
||||
[((UIView <MVMCoreViewProtocol> *)self.topAlertView) updateView:size.width];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@ -21,19 +21,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[];
|
||||
#import <MVMCoreUI/MVMCoreUIViewControllerMappingObject.h>
|
||||
#import <MVMCoreUI/MVMCoreUIViewConstrainingProtocol.h>
|
||||
|
||||
#pragma mark - TopAlert
|
||||
#import <MVMCoreUI/MVMCoreGlobalTopAlertDelegateProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertObject.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertDelegateProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertViewProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertOperation.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertView.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertBaseView.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertMainView.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertShortView.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertExpandableView.h>
|
||||
|
||||
#pragma mark - Categories
|
||||
#import <MVMCoreUI/NSLayoutConstraint+MFConvenience.h>
|
||||
#import <MVMCoreUI/UIColor+MFConvenience.h>
|
||||
|
||||
113
MVMCoreUI/Notification/NotificationContainerView.swift
Normal file
113
MVMCoreUI/Notification/NotificationContainerView.swift
Normal file
@ -0,0 +1,113 @@
|
||||
//
|
||||
// MVMCoreUITopAlertView+Extension.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/11/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// A simple container view that shows and hides a notification.
|
||||
public class NotificationContainerView: UIView {
|
||||
|
||||
public var currentModel: NotificationModel?
|
||||
public var currentNotificationView: UIView?
|
||||
|
||||
lazy private var height = heightAnchor.constraint(equalToConstant: 0)
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
setupView()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
/// Posts a layout change with taking the arguments from the view following the AccessibilityProtocol.
|
||||
private func updateAccessibilityForTopAlert(_ view: UIView) {
|
||||
// Update accessibility with top alert
|
||||
var accessibilityArgument: Any? = view
|
||||
if let view = view as? AccessibilityProtocol {
|
||||
accessibilityArgument = view.getAccessibilityLayoutChangedArgument()
|
||||
}
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: accessibilityArgument)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||
@MainActor
|
||||
public func show(notification: UIView) async {
|
||||
currentNotificationView?.removeFromSuperview()
|
||||
addSubview(notification)
|
||||
NSLayoutConstraint.constraintPinSubview(toSuperview: notification)
|
||||
currentNotificationView = notification
|
||||
|
||||
if let conformer = notification as? MVMCoreViewProtocol {
|
||||
conformer.updateView(bounds.width)
|
||||
}
|
||||
|
||||
superview?.layoutIfNeeded()
|
||||
await withCheckedContinuation { continuation in
|
||||
UIView.animate(withDuration: 0.5) {
|
||||
self.height.isActive = false
|
||||
self.superview?.layoutIfNeeded()
|
||||
} completion: { finished in
|
||||
self.superview?.layoutIfNeeded()
|
||||
self.updateAccessibilityForTopAlert(notification)
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public func hide(notification: UIView) async {
|
||||
// accessibility - below line added to notify VI user through voiceover user when the top alert is closed
|
||||
UIAccessibility.post(notification: .screenChanged, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"))
|
||||
await withCheckedContinuation { continuation in
|
||||
UIView.animate(withDuration: 0.5) {
|
||||
self.height.isActive = true
|
||||
self.superview?.layoutIfNeeded()
|
||||
} completion: { finished in
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: nil)
|
||||
self.currentNotificationView?.removeFromSuperview()
|
||||
self.currentNotificationView = nil
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
guard let currentModel = currentModel,
|
||||
currentModel.type == model.type else { return }
|
||||
guard let molecule = currentNotificationView as? MoleculeViewProtocol,
|
||||
currentModel.molecule.moleculeName == model.molecule.moleculeName else {
|
||||
// Log that we couldn't update.
|
||||
if let errorObject = MVMCoreErrorObject(title: nil, message: nil, messageToLog: nil, code: ErrorCode.parsingJSON.rawValue, domain: ErrorDomainNative, location: "TopNotification update \(String(describing: model.type))") {
|
||||
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update molecule
|
||||
molecule.reset()
|
||||
molecule.set(with: model.molecule, delegateObject, nil)
|
||||
(molecule as? MVMCoreViewProtocol)?.updateView(self.bounds.width)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationContainerView: MVMCoreViewProtocol {
|
||||
public func updateView(_ size: CGFloat) {
|
||||
(currentNotificationView as? MVMCoreViewProtocol)?.updateView(size)
|
||||
}
|
||||
|
||||
public func setupView() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
clipsToBounds = true
|
||||
height.isActive = true
|
||||
}
|
||||
}
|
||||
583
MVMCoreUI/Notification/NotificationHandler.swift
Normal file
583
MVMCoreUI/Notification/NotificationHandler.swift
Normal file
@ -0,0 +1,583 @@
|
||||
//
|
||||
// TopNotificationHandler.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 4/11/23.
|
||||
// Copyright © 2023 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import MVMCore
|
||||
import Dispatch
|
||||
import Combine
|
||||
|
||||
/// Handles the UI tasks for the notification.
|
||||
public protocol NotificationTransitionDelegateProtocol {
|
||||
@MainActor
|
||||
func show(notification: UIView) async
|
||||
|
||||
@MainActor
|
||||
func hide(notification: UIView) async
|
||||
|
||||
@MainActor
|
||||
func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?)
|
||||
}
|
||||
|
||||
/// An operation for managing the life cycle of the notification.
|
||||
public class NotificationOperation: MVMCoreOperation {
|
||||
|
||||
public let notification: UIView
|
||||
|
||||
public var notificationModel: NotificationModel
|
||||
|
||||
/// The delegate that manages transitioning the notification.
|
||||
private let transitionDelegate: NotificationTransitionDelegateProtocol
|
||||
|
||||
/// The showing animation transition operation.
|
||||
private weak var showTransitionOperation: Operation?
|
||||
|
||||
/// The stop timer for non-persistent notifications.
|
||||
private var timerSource: DispatchSourceTimer?
|
||||
|
||||
/// Determines if the operation is ready. For example, certain notifications are only meant to be displayed on certain pages and this can be set accordingly.
|
||||
public var isDisplayable: Bool {
|
||||
get {
|
||||
var isDisplayable: Bool = true
|
||||
// TODO: Causes crash. Check thoughts of reviewer.
|
||||
//displayableQueue.sync {
|
||||
isDisplayable = _isDisplayable
|
||||
//}
|
||||
return isDisplayable
|
||||
}
|
||||
set {
|
||||
guard newValue != isDisplayable else { return }
|
||||
//displayableQueue.async(flags: .barrier) { [weak self] in
|
||||
self._isDisplayable = newValue
|
||||
//}
|
||||
}
|
||||
}
|
||||
/// Thread safety.
|
||||
private var displayableQueue = DispatchQueue(label: "displayable", attributes: .concurrent)
|
||||
/// Updates the operation readiness accordingly.
|
||||
private var _isDisplayable: Bool = true {
|
||||
willSet {
|
||||
guard super.isReady else { return }
|
||||
willChangeValue(for: \.isReady)
|
||||
}
|
||||
didSet {
|
||||
guard super.isReady else { return }
|
||||
didChangeValue(for: \.isReady)
|
||||
}
|
||||
}
|
||||
|
||||
/// This operation is ready only if this notification is allowed to show.
|
||||
public override var isReady: Bool {
|
||||
get {
|
||||
guard !isCancelled else { return super.isReady }
|
||||
return super.isReady && isDisplayable
|
||||
}
|
||||
}
|
||||
|
||||
public actor Properties {
|
||||
/// If the notification is currently displayed.
|
||||
private var isDisplayed: Bool = false
|
||||
|
||||
/// If the notification is currently animating (showing/hiding).
|
||||
private var isAnimating: Bool = false
|
||||
|
||||
fileprivate func set(displayed: Bool) {
|
||||
isDisplayed = displayed
|
||||
}
|
||||
|
||||
public func getIsDisplayed() -> Bool {
|
||||
return isDisplayed
|
||||
}
|
||||
|
||||
fileprivate func set(animating: Bool) {
|
||||
isAnimating = animating
|
||||
}
|
||||
|
||||
public func getIsAnimating() -> Bool {
|
||||
return isAnimating
|
||||
}
|
||||
}
|
||||
/// Actor isolated properties for the operation.
|
||||
public var properties = Properties()
|
||||
|
||||
/// A flag for tracking if the operation needs to be re-added because it was cancelled for a higher priority notification.
|
||||
public var reAddAfterCancel = false
|
||||
|
||||
public init(with notification: UIView, notificationModel: NotificationModel, transitionDelegate: NotificationTransitionDelegateProtocol) {
|
||||
self.notification = notification
|
||||
self.notificationModel = notificationModel
|
||||
self.transitionDelegate = transitionDelegate
|
||||
super.init()
|
||||
queuePriority = notificationModel.priority
|
||||
}
|
||||
|
||||
public override func main() {
|
||||
log(message: "Operation Started")
|
||||
guard !checkAndHandleForCancellation() else { return }
|
||||
Task {
|
||||
await withCheckedContinuation { continuation in
|
||||
// Show the notification.
|
||||
showTransitionOperation = add(transition: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
NotificationHandler.shared()?.onNotificationWillShow.send((self.notification, self.notificationModel))
|
||||
self.log(message: "Operation Will Show")
|
||||
await self.showNotification()
|
||||
}, completionBlock: {
|
||||
continuation.resume()
|
||||
})
|
||||
}
|
||||
guard await properties.getIsDisplayed() else {
|
||||
// If the animation did not complete...
|
||||
log(message: "Operation Never Shown")
|
||||
markAsFinished()
|
||||
return
|
||||
}
|
||||
|
||||
// Publish that the notification has been shown.
|
||||
NotificationHandler.shared()?.onNotificationShown.send((notification, notificationModel))
|
||||
log(message: "Operation Did Show")
|
||||
|
||||
guard !isCancelled else {
|
||||
// If cancelled during the animation, dismiss immediately.
|
||||
stop()
|
||||
return
|
||||
}
|
||||
updateStopTimer()
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if let timerSource = timerSource {
|
||||
timerSource.cancel()
|
||||
}
|
||||
Task {
|
||||
guard await properties.getIsDisplayed(),
|
||||
await !properties.getIsAnimating() else { return }
|
||||
// Hide the notification
|
||||
await withCheckedContinuation({ continuation in
|
||||
_ = add(transition: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.log(message: "Operation Will Dismiss")
|
||||
NotificationHandler.shared()?.onNotificationWillDismiss.send((notification, notificationModel))
|
||||
await self.hideNotification()
|
||||
}, completionBlock: {
|
||||
continuation.resume()
|
||||
})
|
||||
})
|
||||
// The animation must complete...
|
||||
guard await !self.properties.getIsDisplayed() else { return }
|
||||
|
||||
// Publish that the notification has been hidden.
|
||||
NotificationHandler.shared()?.onNotificationDismissed.send((notification, notificationModel))
|
||||
log(message: "Operation Did Dismiss")
|
||||
|
||||
guard isCancelled || !notificationModel.persistent else { return }
|
||||
markAsFinished()
|
||||
}
|
||||
}
|
||||
|
||||
public override func markAsFinished() {
|
||||
log(message: "Operation Finished")
|
||||
super.markAsFinished()
|
||||
}
|
||||
|
||||
public override func cancel() {
|
||||
super.cancel()
|
||||
log(message: "Operation Cancelled")
|
||||
Task {
|
||||
if await !properties.getIsDisplayed() {
|
||||
// Cancel any pending show transitions.
|
||||
showTransitionOperation?.cancel()
|
||||
}
|
||||
|
||||
// Do nothing if animating.
|
||||
guard await !properties.getIsAnimating() else { return }
|
||||
if await properties.getIsDisplayed() {
|
||||
stop()
|
||||
} else if isExecuting {
|
||||
markAsFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func log(message: String) {
|
||||
MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "------Notification message: \(message) type: \(notificationModel.type) title: \(String(describing: (notificationModel.molecule as? NotificationMoleculeModel)?.headline.text)) id: \(notificationModel.id) priority: e\(notificationModel.priority.rawValue) a\(queuePriority.rawValue) operation: \(String(describing: self)) ------")
|
||||
}
|
||||
|
||||
// MARK: - Automatic
|
||||
|
||||
/// Sets up a timer to hide the notification.
|
||||
private func updateStopTimer() {
|
||||
if let timerSource = timerSource {
|
||||
timerSource.cancel()
|
||||
}
|
||||
guard !notificationModel.persistent else { return }
|
||||
timerSource = DispatchSource.makeTimerSource()
|
||||
timerSource?.setEventHandler(handler: { [weak self] in
|
||||
self?.log(message: "Operation Time Out")
|
||||
guard let self = self,
|
||||
!self.isFinished,
|
||||
!self.checkAndHandleForCancellation() else { return }
|
||||
|
||||
// If voice over is on and the notification is focused, do not collapse until unfocused.
|
||||
guard !MVMCoreUIUtility.viewContainsAccessiblityFocus(notification) else {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accessibilityFocusChanged), name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accessibilityFocusChanged), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
|
||||
return
|
||||
}
|
||||
self.stop()
|
||||
})
|
||||
timerSource?.setCancelHandler(handler: { [weak self] in
|
||||
self?.log(message: "Operation Time Out Cancel")
|
||||
})
|
||||
timerSource?.schedule(deadline: .now() + .seconds(notificationModel.dismissTime))
|
||||
timerSource?.activate()
|
||||
}
|
||||
|
||||
/// If the voice over user leaves top alert focus, hide.
|
||||
@objc func accessibilityFocusChanged(_ notification: NSNotification) {
|
||||
guard UIAccessibility.isVoiceOverRunning else {
|
||||
accessibilityFocusFinished()
|
||||
return
|
||||
}
|
||||
guard let _ = notification.userInfo?[UIAccessibility.focusedElementUserInfoKey],
|
||||
!MVMCoreUIUtility.viewContainsAccessiblityFocus(self.notification) else { return }
|
||||
accessibilityFocusFinished()
|
||||
}
|
||||
|
||||
/// Dismisses the nofitication when we lose focus.
|
||||
private func accessibilityFocusFinished() {
|
||||
log(message: "Operation Accessibility Focus Removed")
|
||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
|
||||
stop()
|
||||
}
|
||||
|
||||
// MARK: - Transitions
|
||||
|
||||
/// Adds the transition of the notification to the navigation queue to avoid animation collisions.
|
||||
private func add(transition: @escaping () async -> Void, completionBlock: (() -> Void)?) -> Operation {
|
||||
let transitionOperation = MVMCoreBlockOperation(block: { blockOperation in
|
||||
guard !blockOperation.checkAndHandleForCancellation() else { return }
|
||||
Task {
|
||||
await transition()
|
||||
blockOperation.markAsFinished()
|
||||
}
|
||||
})!
|
||||
transitionOperation.completionBlock = completionBlock
|
||||
MVMCoreNavigationHandler.shared()?.addNavigationOperation(transitionOperation)
|
||||
return transitionOperation
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func showNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.show(notification: notification)
|
||||
await properties.set(displayed: true)
|
||||
await properties.set(animating: false)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func hideNotification() async {
|
||||
await properties.set(animating: true)
|
||||
await transitionDelegate.hide(notification: notification)
|
||||
await properties.set(displayed: false)
|
||||
await properties.set(animating: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationOperation: NSCopying {
|
||||
public func copy(with zone: NSZone? = nil) -> Any {
|
||||
let operation = NotificationOperation(with: notification, notificationModel: notificationModel, transitionDelegate: transitionDelegate)
|
||||
operation.reAddAfterCancel = reAddAfterCancel
|
||||
operation.isDisplayable = isDisplayable
|
||||
for dependency in dependencies {
|
||||
operation.addDependency(dependency)
|
||||
}
|
||||
operation.name = name
|
||||
operation.qualityOfService = qualityOfService
|
||||
return operation
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages notifications.
|
||||
open class NotificationHandler {
|
||||
|
||||
private var queue: OperationQueue = {
|
||||
let queue = OperationQueue()
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
return queue
|
||||
}()
|
||||
|
||||
private var transitionDelegate: NotificationTransitionDelegateProtocol
|
||||
|
||||
// MARK: - Publishers
|
||||
|
||||
/// Publishes when a notification will show.
|
||||
public let onNotificationWillShow = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is shown.
|
||||
public let onNotificationShown = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification will dismiss.
|
||||
public let onNotificationWillDismiss = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is dismissed.
|
||||
public let onNotificationDismissed = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
/// Publishes when a notification is updated.
|
||||
public let onNotificationUpdated = PassthroughSubject<(UIView, NotificationModel), Never>()
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// Returns the handler stored in the CoreUIObject
|
||||
public static func shared() -> Self? {
|
||||
guard let shared = CoreUIObject.sharedInstance()?.topNotificationHandler else { return nil }
|
||||
return MVMCoreActionUtility.fatalClassCheck(object: shared)
|
||||
}
|
||||
|
||||
public init(with transitionDelegate: NotificationTransitionDelegateProtocol) {
|
||||
self.transitionDelegate = transitionDelegate
|
||||
registerWithNotificationCenter()
|
||||
registerForPageChanges()
|
||||
}
|
||||
|
||||
// MARK: - JSON Handling
|
||||
|
||||
/// Registers with the notification center to know when json is updated.
|
||||
private func registerWithNotificationCenter() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
|
||||
}
|
||||
|
||||
/// Registers to know when pages change.
|
||||
private func registerForPageChanges() {
|
||||
MVMCoreNavigationHandler.shared()?.addDelegate(self)
|
||||
}
|
||||
|
||||
/// Checks for new top alert json
|
||||
@objc private func responseJSONUpdated(notification: Notification) {
|
||||
guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return }
|
||||
let delegateObject = loadObject.delegateObject as? MVMCoreUIDelegateObject
|
||||
|
||||
// Dismiss any top alerts that server wants us to dismiss.
|
||||
if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") {
|
||||
NotificationHandler.shared()?.cancelNotification(using: { view, model in
|
||||
return model.type == disableType
|
||||
})
|
||||
}
|
||||
|
||||
// Show any new top alert.
|
||||
guard let responseJSON = loadObject.responseJSON,
|
||||
let json = responseJSON.optionalDictionaryForKey(KeyTopAlert) else { return }
|
||||
Task {
|
||||
do {
|
||||
try await showNotification(for: json, delegateObject: delegateObject)
|
||||
} catch {
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") {
|
||||
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the json to a model and creates the view and queues up the notification.
|
||||
open func showNotification(for json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) async throws {
|
||||
let model = try NotificationModel.decode(json: json, delegateObject: delegateObject)
|
||||
try await showNotification(for: model, delegateObject: delegateObject)
|
||||
}
|
||||
|
||||
// MARK: - Operation Handling
|
||||
|
||||
/// Adds the operation to the queue.
|
||||
private func add(operation: NotificationOperation) {
|
||||
operation.completionBlock = { [weak self] in
|
||||
// If the alert was cancelled to show another with higher priority, re-add to the operation when cancelled to the queue.
|
||||
if operation.reAddAfterCancel {
|
||||
let newOperation: NotificationOperation = operation.copy() as! NotificationOperation
|
||||
newOperation.reAddAfterCancel = false
|
||||
self?.add(operation: newOperation)
|
||||
}
|
||||
operation.completionBlock = nil
|
||||
}
|
||||
|
||||
let currentPageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
||||
operation.updateDisplayable(by: currentPageType)
|
||||
|
||||
queue.addOperation(operation)
|
||||
operation.log(message: "Operation Added")
|
||||
reevaluteQueue()
|
||||
}
|
||||
|
||||
/// Checks for existing top alert object of same type and updates it. Returns true if we updated.
|
||||
open func checkAndUpdateExisting(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) -> Bool {
|
||||
for case let operation as NotificationOperation in queue.operations {
|
||||
guard operation.notificationModel.type == model.type else { continue }
|
||||
operation.update(with: model, delegateObject: delegateObject)
|
||||
let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
||||
operation.updateDisplayable(by: pageType)
|
||||
reevaluteQueue()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Re-evaluates the queue operations
|
||||
private func reevaluteQueue() {
|
||||
var highestReadyOperation: NotificationOperation?
|
||||
var executingOperation: NotificationOperation?
|
||||
for case let operation as NotificationOperation in queue.operations {
|
||||
guard !operation.isCancelled,
|
||||
!operation.isFinished else { continue }
|
||||
if operation.isReady,
|
||||
highestReadyOperation == nil || operation.notificationModel.priority.rawValue > highestReadyOperation!.notificationModel.priority.rawValue {
|
||||
highestReadyOperation = operation
|
||||
}
|
||||
if operation.isExecuting {
|
||||
executingOperation = operation
|
||||
}
|
||||
}
|
||||
guard let currentOperation = executingOperation else { return }
|
||||
|
||||
// Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent.
|
||||
guard currentOperation.isReady else {
|
||||
currentOperation.reAddAfterCancel = currentOperation.notificationModel.persistent
|
||||
currentOperation.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
// If the highest priority operation is not executing, and the executing operation is persistent, cancel it.
|
||||
if let newOperation = highestReadyOperation,
|
||||
currentOperation != newOperation,
|
||||
currentOperation.notificationModel.persistent {
|
||||
currentOperation.reAddAfterCancel = true
|
||||
currentOperation.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Verify
|
||||
|
||||
/// Returns if any notification is executing
|
||||
open func isNotificationShowing() -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
return operation.isExecuting
|
||||
}) != nil
|
||||
}
|
||||
|
||||
/** Returns if the first executing operation matches the provided predicate.
|
||||
* @param predicate The predicate block to decide if it is the notification.
|
||||
*/
|
||||
open func hasNotification(using predicate: ((UIView, NotificationModel) -> Bool)) -> Bool {
|
||||
return queue.operations.first(where: { operation in
|
||||
guard operation.isExecuting,
|
||||
let operation = operation as? NotificationOperation else { return false }
|
||||
return predicate(operation.notification, operation.notificationModel)
|
||||
}) as? NotificationOperation != nil
|
||||
}
|
||||
|
||||
/// Returns the current executing notification view and model
|
||||
open func getCurrentNotification() async -> (UIView, NotificationModel)? {
|
||||
for operation in queue.operations {
|
||||
guard operation.isExecuting,
|
||||
let operation = operation as? NotificationOperation,
|
||||
await operation.properties.getIsDisplayed() else { continue }
|
||||
return (operation.notification, operation.notificationModel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Show and hide
|
||||
|
||||
/// Creates the view and queues up the notification.
|
||||
open func showNotification(for model: NotificationModel, delegateObject: MVMCoreUIDelegateObject? = nil) async throws {
|
||||
guard !checkAndUpdateExisting(with: model, delegateObject: delegateObject) else { return }
|
||||
let view = try await createNotification(with: model, delegateObject: delegateObject)
|
||||
let operation = NotificationOperation(with: view, notificationModel: model, transitionDelegate: transitionDelegate)
|
||||
add(operation: operation)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func createNotification(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) async throws -> UIView & MoleculeViewProtocol {
|
||||
guard let view = ModelRegistry.createMolecule(model.molecule, delegateObject: delegateObject, additionalData: nil) else {
|
||||
throw ModelRegistry.Error.decoderOther(message: "Molecule not mapped")
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
/// Cancel the current top alert view.
|
||||
open func hideNotification() {
|
||||
guard let currentOperation = queue.operations.first(where: { operation in
|
||||
return operation.isExecuting && !operation.isCancelled
|
||||
}) as? NotificationOperation else { return }
|
||||
currentOperation.notificationModel.persistent = false
|
||||
currentOperation.reAddAfterCancel = false
|
||||
currentOperation.cancel()
|
||||
}
|
||||
|
||||
/** Iterates through all scheduled notifications and cancels any that match the provided predicate.
|
||||
* @param predicate The predicate block to decide whether to cancel an notification.
|
||||
*/
|
||||
open func cancelNotification(using predicate: ((UIView, NotificationModel) -> Bool)) {
|
||||
for case let operation as NotificationOperation in queue.operations {
|
||||
if predicate(operation.notification, operation.notificationModel) {
|
||||
operation.reAddAfterCancel = false
|
||||
operation.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all notifications, current or pending.
|
||||
open func removeAllNotifications() {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationHandler: MVMCorePresentationDelegateProtocol {
|
||||
// Update displayable for each top alert operation when page type changes, in top queue priority order.
|
||||
public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
|
||||
guard queue.operations.count > 0 else { return }
|
||||
let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController)
|
||||
guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return }
|
||||
let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
|
||||
Task {
|
||||
queue.operations.compactMap {
|
||||
$0 as? NotificationOperation
|
||||
}.sorted {
|
||||
$0.notificationModel.priority.rawValue > $1.notificationModel.priority.rawValue
|
||||
}.forEach {
|
||||
$0.updateDisplayable(by: pageType)
|
||||
}
|
||||
reevaluteQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationOperation {
|
||||
/// Updates the operation and notification with the new model.
|
||||
public func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
self.notificationModel = model
|
||||
queuePriority = model.priority
|
||||
guard isExecuting,
|
||||
!isCancelled else { return }
|
||||
self.log(message: "Operation Updated")
|
||||
updateStopTimer()
|
||||
Task { @MainActor in
|
||||
transitionDelegate.update(with: notificationModel, delegateObject: delegateObject)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates if the operation is displayable based on the page type.
|
||||
func updateDisplayable(by pageType: String?) {
|
||||
guard let pages = notificationModel.pages else {
|
||||
isDisplayable = true
|
||||
return
|
||||
}
|
||||
guard let pageType = pageType else {
|
||||
isDisplayable = false
|
||||
return
|
||||
}
|
||||
isDisplayable = pages.contains(pageType)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// TopNotification.swift
|
||||
// NotificationModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/11/20.
|
||||
@ -7,8 +7,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
open class TopNotificationModel: Codable {
|
||||
open class NotificationModel: Codable, Identifiable {
|
||||
public var type: String
|
||||
public var priority = Operation.QueuePriority.normal
|
||||
public var molecule: MoleculeModelProtocol
|
||||
@ -16,7 +17,8 @@ open class TopNotificationModel: Codable {
|
||||
public var dismissTime = 5
|
||||
public var pages: [String]?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
public var id: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case priority
|
||||
@ -25,6 +27,7 @@ open class TopNotificationModel: Codable {
|
||||
case dismissTime
|
||||
case pages
|
||||
case analyticsData
|
||||
case id
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -55,17 +58,6 @@ open class TopNotificationModel: Codable {
|
||||
return (shifted / Float(scale)) * 100.0
|
||||
}
|
||||
|
||||
open func createTopAlertObject() -> MVMCoreTopAlertObject {
|
||||
let object = MVMCoreTopAlertObject()
|
||||
object.persistent = persistent
|
||||
object.type = type
|
||||
object.topAlertDismissTime = dismissTime
|
||||
object.queuePriority = priority
|
||||
object.useNewStyle = true
|
||||
object.json = toJSON()
|
||||
return object
|
||||
}
|
||||
|
||||
/// Decodes the top alert json to a model.
|
||||
public static func decode(json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) throws -> Self {
|
||||
let data = try JSONSerialization.data(withJSONObject: json)
|
||||
@ -77,14 +69,17 @@ open class TopNotificationModel: Codable {
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(with type: String, molecule: MoleculeModelProtocol, priority: Operation.QueuePriority = .normal, persistent: Bool = false, dismissTime: Int = 5, pages: [String]? = nil, analyticsData: JSONValueDictionary? = nil) {
|
||||
required public init(with type: String, molecule: MoleculeModelProtocol, priority: Operation.QueuePriority = .normal, persistent: Bool = false, dismissTime: Int? = nil, pages: [String]? = nil, analyticsData: JSONValueDictionary? = nil, id: String = UUID().uuidString) {
|
||||
self.type = type
|
||||
self.molecule = molecule
|
||||
self.priority = priority
|
||||
self.persistent = persistent
|
||||
self.dismissTime = dismissTime
|
||||
if let dismissTime = dismissTime {
|
||||
self.dismissTime = dismissTime
|
||||
}
|
||||
self.pages = pages
|
||||
self.analyticsData = analyticsData
|
||||
self.id = id
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -95,6 +90,7 @@ open class TopNotificationModel: Codable {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
type = try typeContainer.decode(String.self, forKey: .type)
|
||||
molecule = try typeContainer.decodeModel(codingKey: .molecule)
|
||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||
if let priorityPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .priority) {
|
||||
setPriority(with: priorityPercent)
|
||||
}
|
||||
@ -117,5 +113,6 @@ open class TopNotificationModel: Codable {
|
||||
try container.encode(dismissTime, forKey: .dismissTime)
|
||||
try container.encodeIfPresent(pages, forKey: .pages)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
try container.encode(id, forKey: .id)
|
||||
}
|
||||
}
|
||||
@ -209,7 +209,7 @@ open class CoreUIModelMapping: ModelMapping {
|
||||
ModelRegistry.register(handler: TitleLockup.self, for: TitleLockupModel.self)
|
||||
|
||||
// MARK: - Top Notifications
|
||||
ModelRegistry.register(handler: NotificationView.self, for: NotificationModel.self)
|
||||
ModelRegistry.register(handler: NotificationMoleculeView.self, for: NotificationMoleculeModel.self)
|
||||
ModelRegistry.register(handler: CollapsableNotification.self, for: CollapsableNotificationModel.self)
|
||||
}
|
||||
|
||||
@ -232,7 +232,6 @@ open class CoreUIModelMapping: ModelMapping {
|
||||
open override class func registerActions() {
|
||||
super.registerActions()
|
||||
ModelRegistry.register(handler: ActionAlertHandler.self, for: ActionAlertModel.self)
|
||||
ModelRegistry.register(handler: ActionTopAlertHandler.self, for: ActionTopAlertModel.self)
|
||||
ModelRegistry.register(handler: ActionCollapseNotificationHandler.self, for: ActionCollapseNotificationModel.self)
|
||||
ModelRegistry.register(handler: ActionDismissNotificationHandler.self, for: ActionDismissNotificationModel.self)
|
||||
ModelRegistry.register(handler: ActionOpenPanelHandler.self, for: ActionOpenPanelModel.self)
|
||||
|
||||
@ -10,22 +10,20 @@ import UIKit
|
||||
import MVMCore
|
||||
|
||||
@objcMembers open class CoreUIObject: MVMCoreObject {
|
||||
public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol?
|
||||
public var alertHandler: AlertHandler?
|
||||
public var topNotificationHandler: TopNotificationHandler?
|
||||
public var topNotificationHandler: NotificationHandler?
|
||||
|
||||
open override func defaultInitialSetup() {
|
||||
CoreUIModelMapping.registerObjects()
|
||||
loadHandler = MVMCoreLoadHandler()
|
||||
cache = MVMCoreCache()
|
||||
session = MVMCoreUISession()
|
||||
Task { @MainActor in
|
||||
self.sessionHandler = MVMCoreSessionTimeHandler()
|
||||
}
|
||||
actionHandler = MVMCoreUIActionHandler()
|
||||
session = MVMCoreUISession()
|
||||
viewControllerMapping = MVMCoreUIViewControllerMappingObject()
|
||||
loggingDelegate = MVMCoreUILoggingHandler()
|
||||
alertHandler = AlertHandler()
|
||||
topNotificationHandler = TopNotificationHandler()
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ open class MVMCoreUIDelegateObject: DelegateObject {
|
||||
public weak var observingTextFieldDelegate: ObservingTextFieldDelegate?
|
||||
public weak var moleculeDelegate: MoleculeDelegateProtocol?
|
||||
public weak var alertDelegate: (AlertDelegateProtocol & NSObjectProtocol)?
|
||||
public weak var topAlertDelegate: (MVMCoreTopAlertDelegateProtocol & NSObjectProtocol)?
|
||||
|
||||
open override func setAll(withDelegate delegate: Any) {
|
||||
super.setAll(withDelegate: delegate)
|
||||
@ -29,7 +28,6 @@ open class MVMCoreUIDelegateObject: DelegateObject {
|
||||
observingTextFieldDelegate = delegate as? ObservingTextFieldDelegate
|
||||
moleculeDelegate = delegate as? MoleculeDelegateProtocol
|
||||
alertDelegate = delegate as? (AlertDelegateProtocol & NSObjectProtocol)
|
||||
topAlertDelegate = delegate as? (MVMCoreTopAlertDelegateProtocol & NSObjectProtocol)
|
||||
}
|
||||
|
||||
class func delegateObject(from controller: MVMCoreViewControllerProtocol?) -> MVMCoreUIDelegateObject? {
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
//
|
||||
|
||||
@import MVMCore.MVMCoreLoggingHandler;
|
||||
@class MFViewController;
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@ -21,9 +19,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)defaultLogActionForController:(nullable id <MVMCoreViewControllerProtocol>)controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData;
|
||||
- (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id <MVMCoreViewControllerProtocol>)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
|
||||
|
||||
@ -20,7 +20,4 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)trackTopNotificationShown:(UIView *)topNotification topAlertObject:(MVMCoreTopAlertObject *)topAlertObject additionalData:(NSDictionary *)additionalData {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
@import UIKit.UIView;
|
||||
@import MVMCore.MVMCoreSessionObject;
|
||||
@class MVMCoreUITopAlertView;
|
||||
@class MVMCoreUISplitViewController;
|
||||
@class MFViewController;
|
||||
@class MFLoadingViewController;
|
||||
@ -18,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MVMCoreUISession : MVMCoreSessionObject
|
||||
|
||||
@property (weak, nonatomic, nullable) MVMCoreUITopAlertView *topAlertView;
|
||||
@property (weak, nonatomic, nullable) UIView *topAlertView;
|
||||
@property (weak, nonatomic, nullable) MVMCoreUISplitViewController *splitViewController;
|
||||
@property (weak, nonatomic, nullable) NavigationController *navigationController;
|
||||
@property (weak, nonatomic, nullable) MFLoadingViewController *loadingViewController;
|
||||
@ -32,9 +31,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// indicates if the app launched successfully
|
||||
@property (assign, nonatomic) BOOL launchAppLoadedSuccessfully;
|
||||
|
||||
/// Allows a global overload of the title view of navigation item.
|
||||
- (nullable UIView *)titleViewForController:(nonnull MFViewController *)controller;
|
||||
|
||||
/// Sets up the session as delegate for standard load view controller. Pass the view controller that will be used to present and will be disabled when load view is presented.
|
||||
- (void)setupAsStandardLoadViewDelegate:(nonnull UIViewController *)mainViewController;
|
||||
|
||||
|
||||
@ -19,10 +19,6 @@
|
||||
|
||||
@implementation MVMCoreUISession
|
||||
|
||||
- (nullable UIView *)titleViewForController:(nonnull MFViewController *)controller {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setupAsStandardLoadViewDelegate:(nonnull UIViewController *)mainViewController {
|
||||
self.mainViewController = mainViewController;
|
||||
[MVMCoreObject sharedInstance].loadingProtocol = self;
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// MVMCoreGlobalTopAlertDelegateProtocol.h
|
||||
// MVMCore
|
||||
//
|
||||
// Created by Pfeil, Scott Robert on 1/2/18.
|
||||
// Copyright © 2018 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertViewProtocol.h>
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@protocol MVMCoreGlobalTopAlertDelegateProtocol <NSObject>
|
||||
|
||||
- (NSOperationQueuePriority)priorityForTopAlertByObject:(nonnull MVMCoreTopAlertObject *)object;
|
||||
|
||||
@optional
|
||||
|
||||
- (nonnull UIView <MVMCoreTopAlertViewProtocol>*)getTopAlertView;
|
||||
|
||||
@end
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertAnimationDelegateProtocol.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 6/4/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol MVMCoreTopAlertAnimationDelegateProtocol
|
||||
|
||||
// Called when the top alert is starting an animation
|
||||
- (void)topAlertViewBeginAnimation;
|
||||
|
||||
// Called when the top alert is ending an animation
|
||||
- (void)topAlertViewFinishAnimation;
|
||||
|
||||
// Called when the top alert is starting to dismiss.
|
||||
- (void)topAlertWillDismiss;
|
||||
|
||||
// Called when the top alert is dismissed.
|
||||
- (void)topAlertDismissed;
|
||||
|
||||
@end
|
||||
@ -1,23 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertDelegateProtocol.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 6/4/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@protocol MVMCoreTopAlertDelegateProtocol
|
||||
|
||||
@optional
|
||||
|
||||
- (void)topAlertViewShown:(nonnull id)topAlert topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject;
|
||||
- (void)topAlertViewDismissed:(nonnull id)topAlert;
|
||||
|
||||
// Called when the top alert is pressed. Determines if we should load the option the default way or not.
|
||||
- (BOOL)shouldLoadTopAlertAction:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
|
||||
@end
|
||||
@ -1,64 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertObject.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 5/24/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertDelegateProtocol.h>
|
||||
|
||||
extern NSUInteger const TopAlertDismissTime;
|
||||
|
||||
@interface MVMCoreTopAlertObject : NSObject
|
||||
|
||||
@property (nonatomic) BOOL persistent;
|
||||
@property (nullable, nonatomic) NSString *type;
|
||||
@property (nonatomic) NSOperationQueuePriority queuePriority;
|
||||
|
||||
// The text
|
||||
@property (nullable, strong, nonatomic) NSString *title;
|
||||
@property (nullable, strong, nonatomic) NSString *message;
|
||||
@property (nullable, strong, nonatomic) NSString *topMessage;
|
||||
|
||||
// For the button.
|
||||
@property (nullable, strong, nonatomic) NSDictionary *buttonMap;
|
||||
@property (nullable, strong, nonatomic) NSDictionary *additionalData;
|
||||
@property (nonatomic) BOOL useCloseButton;
|
||||
|
||||
// For non action map driven button.
|
||||
@property (nullable, strong, nonatomic) NSString *buttonTitle;
|
||||
@property (nullable, copy, nonatomic) void (^userActionHandler)(id _Nonnull sender);
|
||||
|
||||
@property (nullable, weak, nonatomic) NSObject <MVMCoreTopAlertDelegateProtocol> *delegate;
|
||||
|
||||
// This is used to ensure legacy style stays intact with new changes
|
||||
@property (nonatomic) BOOL useNewStyle;
|
||||
|
||||
// The page type used for the top alert
|
||||
@property (nullable, strong, nonatomic) NSString *pageType;
|
||||
|
||||
// image name or url used imageviews
|
||||
@property (nullable, strong, nonatomic) NSString *imageNameOrURL;
|
||||
@property (nullable, strong, nonatomic) NSString *aboveTextImageString;
|
||||
|
||||
// If 0, uses default 5 seconds.
|
||||
@property (nonatomic) NSInteger topAlertDismissTime;
|
||||
|
||||
// Server can set color.
|
||||
@property (nullable, strong, nonatomic) UIColor *backgroundColor;
|
||||
@property (nullable, strong, nonatomic) UIColor *textColor;
|
||||
|
||||
// The full top alert json. Currently only used for molecular.
|
||||
@property (nullable, strong, nonatomic) NSDictionary *json;
|
||||
|
||||
- (nullable instancetype)initWithResponseInfo:(nullable NSDictionary *)responseInfo;
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message;
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler;
|
||||
|
||||
@end
|
||||
@ -1,93 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertObject.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 5/24/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreTopAlertObject.h"
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
@import MVMCore.MVMCoreGetterUtility;
|
||||
@import MVMCore.MVMCoreJSONConstants;
|
||||
|
||||
NSUInteger const TopAlertDismissTime = 5;
|
||||
|
||||
@implementation MVMCoreTopAlertObject
|
||||
|
||||
- (nullable instancetype)initWithResponseInfo:(nullable NSDictionary *)responseInfo {
|
||||
if (self = [super init]) {
|
||||
|
||||
self.type = [responseInfo string:KeyType];
|
||||
self.persistent = [[responseInfo stringForKey:KeyMessageStyle] isEqualToString:ValueMessageStyleTopPersistent];
|
||||
|
||||
self.title = [responseInfo string:KeyMessage];
|
||||
self.message = [responseInfo string:KeyUserMessage];
|
||||
self.buttonMap = [responseInfo dict:KeyButtonMap];
|
||||
self.topMessage = [responseInfo string:KeyTopMessage];
|
||||
self.imageNameOrURL = [responseInfo string:@"topAlertImageUrl"];
|
||||
self.aboveTextImageString = [responseInfo string:@"topAlertImageURLAboveText"];
|
||||
|
||||
NSString *color = [responseInfo string:@"topAlertColor"];
|
||||
if (color) {
|
||||
self.backgroundColor = [MVMCoreGetterUtility getColorForHexString:color];
|
||||
}
|
||||
color = [responseInfo string:@"messageColor"];
|
||||
if (color) {
|
||||
self.textColor = [MVMCoreGetterUtility getColorForHexString:color];
|
||||
}
|
||||
|
||||
// The default is yes if not sent by server (for legacy to work as is)
|
||||
NSNumber *closeButton = [responseInfo optionalNumberForKey:KeyCloseButton];
|
||||
if (closeButton != nil) {
|
||||
self.useCloseButton = [closeButton boolValue];
|
||||
} else {
|
||||
self.useCloseButton = YES;
|
||||
}
|
||||
|
||||
self.useNewStyle = [responseInfo boolForKey:@"newTopAlertStyle"];
|
||||
|
||||
// Server driven dismiss time.
|
||||
NSNumber *topAlertTime = [responseInfo optionalNumberForKey:@"topAlertTime"];
|
||||
if (topAlertTime != nil) {
|
||||
self.topAlertDismissTime = [topAlertTime integerValue];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message {
|
||||
return [self initWithType:type message:nil subMessage:message persistent:NO actionMap:nil additionalData:nil];
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
|
||||
if (self = [super init]) {
|
||||
|
||||
self.type = type;
|
||||
self.persistent = persistent;
|
||||
|
||||
self.title = message;
|
||||
self.message = subMessage;
|
||||
self.buttonMap = actionMap;
|
||||
self.additionalData = additionalData;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler {
|
||||
|
||||
if (self = [super init]) {
|
||||
|
||||
self.type = type;
|
||||
self.persistent = persistent;
|
||||
|
||||
self.title = message;
|
||||
self.message = subMessage;
|
||||
self.buttonTitle = buttonTitle;
|
||||
self.userActionHandler = userActionHandler;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,39 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertOperation.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 6/4/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
@import MVMCore.MVMCoreOperation;
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@interface MVMCoreTopAlertOperation : MVMCoreOperation <MVMCoreTopAlertAnimationDelegateProtocol>
|
||||
|
||||
@property (readonly, getter=isPaused) BOOL paused;
|
||||
|
||||
/// A bool for if this top alert can be displayed. It is only ready if true.
|
||||
@property (nonatomic, getter=isDisplayable) BOOL displayable;
|
||||
|
||||
@property (nonatomic) BOOL reAddAfterCancel;
|
||||
|
||||
@property (nonnull, nonatomic, strong) MVMCoreTopAlertObject *topAlertObject;
|
||||
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject;
|
||||
|
||||
/// Updates the operation with a new object
|
||||
- (void)updateWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject;
|
||||
|
||||
/// Updates the operation isDisplayable based on the page type.
|
||||
- (void)updateDisplayableByPageType:(nullable NSString *)pageType;
|
||||
|
||||
// Pauses the operation. Temporarily removes any alert.
|
||||
- (void)pause;
|
||||
|
||||
// Unpauses the operation, resuming any alert.
|
||||
- (void)unpause;
|
||||
|
||||
@end
|
||||
@ -1,319 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertOperation.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 6/4/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreTopAlertOperation.h"
|
||||
#import "MVMCoreTopAlertObject.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
#import <MVMCoreUI/MVMCoreUISplitViewController.h>
|
||||
|
||||
@interface MVMCoreTopAlertOperation () {
|
||||
__block BOOL _paused;
|
||||
__block BOOL _displayed;
|
||||
__block BOOL _animating;
|
||||
__block BOOL _displayable;
|
||||
}
|
||||
|
||||
@property (readwrite, getter=isPaused) BOOL paused;
|
||||
@property (readwrite, getter=isDisplayed) BOOL displayed;
|
||||
@property (readwrite, getter=isAnimating) BOOL animating;
|
||||
|
||||
// For thread safety
|
||||
@property (strong, nonatomic) dispatch_queue_t pausedQueue;
|
||||
@property (strong, nonatomic) dispatch_queue_t displayedQueue;
|
||||
@property (strong, nonatomic) dispatch_queue_t animatingQueue;
|
||||
@property (strong, nonatomic) dispatch_queue_t displayableQueue;
|
||||
|
||||
@property (nonatomic, strong) dispatch_source_t timerSource;
|
||||
|
||||
// A reference to the show operation so it can be cancelled.
|
||||
@property (nonatomic, weak) NSOperation *operation;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreTopAlertOperation
|
||||
|
||||
- (instancetype)init {
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.pausedQueue = dispatch_queue_create("paused", DISPATCH_QUEUE_CONCURRENT);
|
||||
self.displayedQueue = dispatch_queue_create("displayed", DISPATCH_QUEUE_CONCURRENT);
|
||||
self.animatingQueue = dispatch_queue_create("animating", DISPATCH_QUEUE_CONCURRENT);
|
||||
self.displayableQueue = dispatch_queue_create("displayable", DISPATCH_QUEUE_CONCURRENT);
|
||||
self.displayable = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject {
|
||||
|
||||
if (self = [self init]) {
|
||||
self.topAlertObject = topAlertObject;
|
||||
|
||||
// Sets the queue priority for various types of alerts.
|
||||
self.queuePriority = [[CoreUIObject sharedInstance].globalTopAlertDelegate priorityForTopAlertByObject:topAlertObject];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject {
|
||||
self.topAlertObject = topAlertObject;
|
||||
self.queuePriority = [[CoreUIObject sharedInstance].globalTopAlertDelegate priorityForTopAlertByObject:topAlertObject];
|
||||
if (self.isExecuting && !self.isCancelled && !self.isPaused) {
|
||||
[self updateDismissTimer];
|
||||
UIView <MVMCoreTopAlertViewProtocol>*topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView];
|
||||
if ([topAlertView respondsToSelector:@selector(updateTopAlertWith:)]) {
|
||||
[topAlertView updateTopAlertWith:topAlertObject];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDisplayableByPageType:(nullable NSString *)pageType {
|
||||
NSArray <NSString *>*pages = [self.topAlertObject.json array:@"pages"];
|
||||
if (pages.count == 0) {
|
||||
self.displayable = YES;
|
||||
return;
|
||||
}
|
||||
if (pageType.length == 0) {
|
||||
self.displayable = NO;
|
||||
return;
|
||||
}
|
||||
self.displayable = [pages containsObject:pageType];
|
||||
}
|
||||
|
||||
- (BOOL)isPaused {
|
||||
__block BOOL isPaused;
|
||||
dispatch_sync(self.pausedQueue, ^{
|
||||
isPaused = self->_paused;
|
||||
});
|
||||
return isPaused;
|
||||
}
|
||||
|
||||
- (void)setPaused:(BOOL)paused {
|
||||
dispatch_barrier_async(self.pausedQueue, ^{
|
||||
self->_paused = paused;
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isDisplayed {
|
||||
__block BOOL isDisplayed;
|
||||
dispatch_sync(self.displayedQueue, ^{
|
||||
isDisplayed = self->_displayed;
|
||||
});
|
||||
return isDisplayed;
|
||||
}
|
||||
|
||||
- (void)setDisplayed:(BOOL)displayed {
|
||||
dispatch_barrier_async(self.displayedQueue, ^{
|
||||
self->_displayed = displayed;
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isAnimating {
|
||||
__block BOOL isAnimating;
|
||||
dispatch_sync(self.animatingQueue, ^{
|
||||
isAnimating = self->_animating;
|
||||
});
|
||||
return isAnimating;
|
||||
}
|
||||
|
||||
- (void)setAnimating:(BOOL)animating {
|
||||
dispatch_barrier_async(self.animatingQueue, ^{
|
||||
self->_animating = animating;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isDisplayable {
|
||||
__block BOOL isDisplayable;
|
||||
dispatch_sync(self.displayableQueue, ^{
|
||||
isDisplayable = self->_displayable;
|
||||
});
|
||||
return isDisplayable;
|
||||
}
|
||||
|
||||
- (void)setDisplayable:(BOOL)displayable {
|
||||
if (displayable != self.isDisplayable) {
|
||||
BOOL isReady = [super isReady];
|
||||
if (isReady) {
|
||||
[self willChangeValueForKey:@"isReady"];
|
||||
}
|
||||
dispatch_barrier_async(self.displayableQueue, ^{
|
||||
self->_displayable = displayable;
|
||||
});
|
||||
if (isReady) {
|
||||
[self didChangeValueForKey:@"isReady"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isReady {
|
||||
if (self.isCancelled) {
|
||||
return [super isReady];
|
||||
}
|
||||
return [super isReady] && self.isDisplayable;
|
||||
}
|
||||
|
||||
- (void)main {
|
||||
|
||||
// Always check for cancellation before launching the task.
|
||||
if ([self checkAndHandleForCancellation]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do nothing if paused
|
||||
if (self.isPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show
|
||||
if (![[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) {
|
||||
|
||||
// Needs to be a top alert view....
|
||||
[self markAsFinished];
|
||||
return;
|
||||
}
|
||||
|
||||
UIView <MVMCoreTopAlertViewProtocol>*topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView];
|
||||
self.operation = [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) {
|
||||
|
||||
self.displayed = YES;
|
||||
if (self.isCancelled) {
|
||||
|
||||
// Cancelled, dismiss immediately.
|
||||
[self dismissAlertView:YES];
|
||||
} else if (self.isPaused) {
|
||||
|
||||
// Paused, dismiss for the time being if persistent.
|
||||
[self dismissAlertView:YES];
|
||||
} else {
|
||||
[self updateDismissTimer];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/// Updates the timer to dismiss the top alert.
|
||||
- (void)updateDismissTimer {
|
||||
if (self.timerSource) {
|
||||
dispatch_source_cancel(self.timerSource);
|
||||
}
|
||||
if (self.topAlertObject.persistent) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger dismissTime;
|
||||
if (self.topAlertObject.topAlertDismissTime > 0) {
|
||||
dismissTime = self.topAlertObject.topAlertDismissTime;
|
||||
} else {
|
||||
dismissTime = TopAlertDismissTime;
|
||||
}
|
||||
|
||||
self.timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
|
||||
dispatch_source_set_timer(self.timerSource, dispatch_time(DISPATCH_TIME_NOW, dismissTime * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, (1ull * NSEC_PER_SEC) / 10);
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_source_set_event_handler(self.timerSource, ^{
|
||||
if (weakSelf.isFinished || [weakSelf checkAndHandleForCancellation]) {
|
||||
return;
|
||||
}
|
||||
[weakSelf dismissAlertView:NO];
|
||||
});
|
||||
dispatch_resume(self.timerSource);
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
[super cancel];
|
||||
|
||||
[self.operation cancel];
|
||||
|
||||
// Do nothing if animating.
|
||||
if (!self.isAnimating) {
|
||||
|
||||
if (self.isDisplayed) {
|
||||
[self dismissAlertView:YES];
|
||||
} else if (self.isExecuting) {
|
||||
[self markAsFinished];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissAlertView:(BOOL)forceful {
|
||||
if (self.timerSource) {
|
||||
dispatch_source_cancel(self.timerSource);
|
||||
}
|
||||
if (self.isDisplayed && !self.isAnimating) {
|
||||
|
||||
// Dismisses.
|
||||
[[[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView] hideAlertView:forceful completionHandler:NULL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pause {
|
||||
|
||||
if (!self.paused) {
|
||||
[self willChangeValueForKey:@"isPaused"];
|
||||
self.paused = YES;
|
||||
[self didChangeValueForKey:@"isPaused"];
|
||||
}
|
||||
[self dismissAlertView:YES];
|
||||
}
|
||||
|
||||
- (void)unpause {
|
||||
|
||||
if (self.paused) {
|
||||
[self willChangeValueForKey:@"isPaused"];
|
||||
self.paused = NO;
|
||||
[self didChangeValueForKey:@"isPaused"];
|
||||
}
|
||||
if (self.executing) {
|
||||
[self start];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Delegate functions
|
||||
|
||||
- (void)topAlertViewBeginAnimation {
|
||||
self.animating = YES;
|
||||
}
|
||||
|
||||
- (void)topAlertViewFinishAnimation {
|
||||
self.animating = NO;
|
||||
}
|
||||
|
||||
- (void)topAlertDismissed {
|
||||
self.displayed = NO;
|
||||
if (!self.isCancelled && self.isPaused && self.topAlertObject.persistent) { return; }
|
||||
[self markAsFinished];
|
||||
}
|
||||
|
||||
- (void)topAlertWillDismiss {
|
||||
if (self.timerSource) {
|
||||
dispatch_source_cancel(self.timerSource);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)topAlertCloseButtonPressed {
|
||||
[self dismissAlertView:YES];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||
MVMCoreTopAlertOperation *copyObject = [[MVMCoreTopAlertOperation alloc] init];
|
||||
copyObject.topAlertObject = self.topAlertObject;
|
||||
copyObject.paused = self.paused;
|
||||
copyObject.reAddAfterCancel = self.reAddAfterCancel;
|
||||
copyObject.displayable = self.isDisplayable;
|
||||
copyObject.queuePriority = self.queuePriority;
|
||||
for (NSOperation *dependency in self.dependencies) {
|
||||
[copyObject addDependency:dependency];
|
||||
}
|
||||
copyObject.name = self.name;
|
||||
copyObject.qualityOfService = self.qualityOfService;
|
||||
return copyObject;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// MVMCoreTopAlertViewProtocol.h
|
||||
// MVMCore
|
||||
//
|
||||
// Created by Pfeil, Scott Robert on 1/2/18.
|
||||
// Copyright © 2018 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@protocol MVMCoreTopAlertViewProtocol
|
||||
|
||||
@optional
|
||||
|
||||
/// Show based on the object
|
||||
- (nonnull NSOperation *)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;
|
||||
|
||||
/// Removes the notification
|
||||
- (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;
|
||||
|
||||
/// Collapses the notification if it has a short top message. Otherwise removes notification.
|
||||
- (void)collapseNotification;
|
||||
|
||||
/// Updates the existing top alert with the new object
|
||||
- (void)updateTopAlertWith:(nullable MVMCoreTopAlertObject *)topAlertObject;
|
||||
|
||||
/// Returns if the top alert is currently utilizing the status bar.
|
||||
- (BOOL)overridingStatusBar;
|
||||
|
||||
@end
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertBaseView.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 9/6/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
@import MVMCore.MVMCoreViewProtocol;
|
||||
|
||||
@class Button;
|
||||
|
||||
@interface MVMCoreUITopAlertBaseView : UIView<MVMCoreViewProtocol>
|
||||
|
||||
// Adds a top alert action.
|
||||
+ (void)addActionToButton:(nonnull Button *)button actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
|
||||
// Returns a top alert styled label.
|
||||
+ (nonnull UILabel *)topAlertLabel;
|
||||
|
||||
// Returns a string for the passed in messages.
|
||||
+ (nullable NSAttributedString *)getStringForMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color;
|
||||
|
||||
// Adds a close button.
|
||||
- (nonnull Button *)addCloseButtonWithAnimationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
|
||||
// Handles making various parts accessible.
|
||||
- (void)handleAccessibility;
|
||||
|
||||
/// Adds the top alert accessibility prefix to the view.
|
||||
+ (void)amendAccesibilityLabelForView:(nonnull UIView *)view;
|
||||
|
||||
@end
|
||||
@ -1,93 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertBaseView.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 9/6/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUITopAlertBaseView.h"
|
||||
#import "MVMCoreUISplitViewController.h"
|
||||
@import MVMCore.MVMCoreLoadObject;
|
||||
@import MVMCore.MVMCoreRequestParameters;
|
||||
#import "MVMCoreUITopAlertView.h"
|
||||
@import MVMCore.MVMCoreJSONConstants;
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
#import "UIColor+MFConvenience.h"
|
||||
#import "MVMCoreUICommonViewsUtility.h"
|
||||
#import "MFStyler.h"
|
||||
#import "MVMCoreUISession.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
#import "MVMCoreTopAlertDelegateProtocol.h"
|
||||
|
||||
@implementation MVMCoreUITopAlertBaseView
|
||||
|
||||
+ (void)addActionToButton:(nonnull Button *)button actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
[button addActionBlockWithEvent:UIControlEventTouchUpInside :^(Button * _Nonnull button) {
|
||||
// Check with the current screen if we should perform the action (not if it's the same page type)
|
||||
BOOL performAction = YES;
|
||||
UIViewController *topViewController = [MVMCoreUISession sharedGlobal].splitViewController.getCurrentDetailViewController;
|
||||
if ([topViewController respondsToSelector:@selector(shouldLoadTopAlertAction:additionalData:)]) {
|
||||
performAction = [((NSObject <MVMCoreTopAlertDelegateProtocol>*)topViewController) shouldLoadTopAlertAction:actionMap additionalData:additionalData];
|
||||
}
|
||||
|
||||
if (performAction) {
|
||||
[[MVMCoreUIActionHandler sharedActionHandler] handleActionWithDictionary:actionMap additionalData:additionalData delegateObject:[MVMCoreUIDelegateObject createWithDelegateForAll:[MVMCoreUISession sharedGlobal].topAlertView]];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (UILabel *)topAlertLabel {
|
||||
|
||||
UILabel *label = [MVMCoreUICommonViewsUtility label];
|
||||
[label setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
|
||||
label.textColor = [UIColor whiteColor];
|
||||
label.font = [MFStyler fontB1];
|
||||
label.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
return label;
|
||||
}
|
||||
|
||||
+ (nullable NSAttributedString *)getStringForMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color {
|
||||
|
||||
UIColor *textColor = color ?: [UIColor whiteColor];
|
||||
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init];
|
||||
if (message.length > 0) {
|
||||
[string appendAttributedString:[MFStyler styleGetAttributedString:message font:[MFStyler fontB1] color:textColor]];
|
||||
}
|
||||
|
||||
if (subMessage.length > 0) {
|
||||
|
||||
NSString *subMessageToAppend = nil;
|
||||
if (string.length > 0) {
|
||||
subMessageToAppend = [NSString stringWithFormat:@"\n%@",subMessage];
|
||||
} else {
|
||||
subMessageToAppend = subMessage;
|
||||
}
|
||||
[string appendAttributedString:[MFStyler styleGetAttributedString:subMessageToAppend font:[MFStyler fontB2] color:textColor]];
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
- (nonnull Button *)addCloseButtonWithAnimationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
Button *closeButton = [MVMCoreUICommonViewsUtility addCloseButtonTo:self action:^(Button * _Nonnull button) {
|
||||
[[MVMCoreUIActionHandler sharedActionHandler] handleActionWithDictionary:@{KeyActionType: @"dismissNotification"} additionalData:nil delegateObject:nil];
|
||||
} centeredVertically:YES];
|
||||
[closeButton.heightAnchor constraintEqualToConstant:16.0].active = YES;
|
||||
[closeButton.widthAnchor constraintEqualToConstant:16.0].active = YES;
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:closeButton];
|
||||
return closeButton;
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {}
|
||||
|
||||
- (void)handleAccessibility {}
|
||||
|
||||
+ (void)amendAccesibilityLabelForView:(nonnull UIView *)view {
|
||||
NSString *amendment = [MVMCoreUIUtility hardcodedStringWithKey:@"top_alert_notification"];
|
||||
NSString *accessibilityLabel = view.accessibilityLabel;
|
||||
if (accessibilityLabel && ![accessibilityLabel hasPrefix:amendment]) {
|
||||
view.accessibilityLabel = [NSString stringWithFormat:@"%@ - %@", amendment, accessibilityLabel];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,57 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertExpandableView.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 8/18/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertBaseView.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
|
||||
@class PrimaryButton;
|
||||
@class MVMCoreUITopAlertShortView;
|
||||
@class MVMCoreUITopAlertMainView;
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@interface MVMCoreUITopAlertExpandableView : MVMCoreUITopAlertBaseView
|
||||
|
||||
@property (nullable, weak, nonatomic) MVMCoreUITopAlertShortView *shortView;
|
||||
@property (nullable, weak, nonatomic) MVMCoreUITopAlertMainView *buttonView;
|
||||
|
||||
// Setting this will hide the short view when the notification is collapsed.
|
||||
@property (nonatomic) BOOL onlyShowTopMessageWhenCollapsed;
|
||||
|
||||
// A flag for if the top alert expandable view should collapse automatically after being expanded
|
||||
@property (nonatomic) BOOL collapseAutomaticallyAfterExpanded;
|
||||
|
||||
@property (nonatomic) NSInteger collapseTime;
|
||||
|
||||
// Standard
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout;
|
||||
|
||||
// Used primarily for when button presses will expand or collapse. (Short view button will need to be set manually)
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout;
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout;
|
||||
|
||||
// Used when button uses standard action map.
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout;
|
||||
|
||||
// Convenience change functions
|
||||
- (void)defaultSetup;
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage;
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler;
|
||||
|
||||
// Setters for making buttons expand and collapse the cell.
|
||||
- (void)setButtonPressToExpand;
|
||||
- (void)setButtonPressToCollapse;
|
||||
- (void)setShortViewPressToCollapse;
|
||||
- (void)setShortViewPressToExpand;
|
||||
|
||||
// Animates
|
||||
- (void)expand:(BOOL)animated;
|
||||
- (void)collapse;
|
||||
|
||||
@end
|
||||
@ -1,431 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertExpandableView.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 8/18/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUITopAlertExpandableView.h"
|
||||
#import "MVMCoreUITopAlertShortView.h"
|
||||
#import "MVMCoreUITopAlertMainView.h"
|
||||
@import MVMCore.MVMCoreDispatchUtility;
|
||||
#import <MVMCoreUI/MVMCoreTopAlertObject.h>
|
||||
@import MVMCore.MVMCoreBlockOperation;
|
||||
@import MVMCore.MVMCoreNavigationHandler;
|
||||
#import "MFStyler.h"
|
||||
#import "NSLayoutConstraint+MFConvenience.h"
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import "MVMCoreUITopAlertView.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
|
||||
@interface MVMCoreUITopAlertExpandableView ()
|
||||
|
||||
@property (nonatomic) BOOL expanded;
|
||||
|
||||
@property (nullable, weak, nonatomic) id <MVMCoreTopAlertAnimationDelegateProtocol> animationDelegate;
|
||||
|
||||
@property (nullable, weak, nonatomic) UIView *viewToLayout;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *topLabelConstraintBottom;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *topConstraint;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *shortViewHeight;
|
||||
|
||||
// Sets up the short view.
|
||||
- (void)setupTopMessage:(nullable NSString *)topMessage;
|
||||
|
||||
// Sets up the button view.
|
||||
- (void)setupTopAlertWithButton:(MVMCoreUITopAlertMainView *)topAlertWithButton;
|
||||
|
||||
// Sets up the whole view without setting button action.
|
||||
- (void)setupViewWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle;
|
||||
|
||||
// Sets up the whole view while setting button action.
|
||||
- (void)setupViewWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreUITopAlertExpandableView
|
||||
|
||||
- (void)handleAccessibility {
|
||||
|
||||
if (self.shortView.label.text.length > 0 && !self.expanded) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.shortView.label);
|
||||
} else if (self.buttonView.label.text.length > 0) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.buttonView.label);
|
||||
} else {
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setup View
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
[self.shortView updateView:size];
|
||||
[self.buttonView updateView:size];
|
||||
}
|
||||
|
||||
- (nullable instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.clipsToBounds = YES;
|
||||
self.expanded = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout {
|
||||
if (self = [self init]) {
|
||||
self.animationDelegate = animationDelegate;
|
||||
self.viewToLayout = viewTolayout;
|
||||
|
||||
if (topAlertObject.useNewStyle) {
|
||||
|
||||
// New style, collapses automatically after x time if there is a top message.
|
||||
self.collapseTime = topAlertObject.topAlertDismissTime;
|
||||
if (topAlertObject.persistent && topAlertObject.topMessage) {
|
||||
_collapseAutomaticallyAfterExpanded = YES;
|
||||
}
|
||||
|
||||
// Setup the views.
|
||||
[self setupTopMessage:topAlertObject.topMessage];
|
||||
MVMCoreUITopAlertMainView *topAlertWithButton = [[MVMCoreUITopAlertMainView alloc] initWithTopAlertObject:topAlertObject animationDelegate:animationDelegate];
|
||||
[self setupTopAlertWithButton:topAlertWithButton];
|
||||
|
||||
// Sets the color
|
||||
self.shortView.label.textColor = topAlertObject.textColor ?: [[MVMCoreUITopAlertView sharedGlobal] getContentColorForType:topAlertObject.type];
|
||||
self.backgroundColor = topAlertObject.backgroundColor ?: [[MVMCoreUITopAlertView sharedGlobal] getBackgroundColorForType:topAlertObject.type];
|
||||
|
||||
if (topAlertWithButton.label.text.length > 0) {
|
||||
[self expand:NO];
|
||||
}
|
||||
} else {
|
||||
|
||||
// Old style, has no top alert and main view is limited.
|
||||
self.backgroundColor = [[MVMCoreUITopAlertView sharedGlobal] getBackgroundColorForType:topAlertObject.type];
|
||||
UIColor *contentColor = [[MVMCoreUITopAlertView sharedGlobal] getContentColorForType:topAlertObject.type];
|
||||
[self setupTopMessage:nil];
|
||||
self.shortView.label.textColor = contentColor;
|
||||
MVMCoreUITopAlertMainView *topAlertWithButton = [[MVMCoreUITopAlertMainView alloc] initWithColor:self.backgroundColor contentColor:contentColor message:topAlertObject.message subMessage:nil closeButton:YES animationDelegate:animationDelegate];
|
||||
[self setupTopAlertWithButton:topAlertWithButton];
|
||||
[self expand:NO];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout {
|
||||
if (self = [self init]) {
|
||||
self.animationDelegate = animationDelegate;
|
||||
self.viewToLayout = viewTolayout;
|
||||
[self setupViewWithTopMessage:topMessage message:message subMessage:nil contentColor:contentColor buttonTitle:buttonTitle];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout {
|
||||
if (self = [self init]) {
|
||||
self.animationDelegate = animationDelegate;
|
||||
self.viewToLayout = viewTolayout;
|
||||
[self setupViewWithTopMessage:topMessage message:message subMessage:subMessage contentColor:contentColor buttonTitle:buttonTitle];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate viewToLayout:(nonnull UIView *)viewTolayout {
|
||||
if (self = [self init]) {
|
||||
self.animationDelegate = animationDelegate;
|
||||
self.viewToLayout = viewTolayout;
|
||||
[self setupViewWithTopMessage:topMessage message:message subMessage:subMessage contentColor:contentColor actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)defaultSetup {
|
||||
if (!self.shortView) {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.clipsToBounds = YES;
|
||||
_collapseAutomaticallyAfterExpanded = YES;
|
||||
self.viewToLayout = MVMCoreUITopAlertView.sharedGlobal.superview;
|
||||
self.expanded = NO;
|
||||
[self setupTopMessage:nil];
|
||||
MVMCoreUITopAlertMainView *topAlertWithButton = [[MVMCoreUITopAlertMainView alloc] init];
|
||||
[topAlertWithButton defaultSetup];
|
||||
[self setupTopAlertWithButton:topAlertWithButton];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupTopMessage:(nullable NSString *)topMessage {
|
||||
|
||||
MVMCoreUITopAlertShortView *shortView = [[MVMCoreUITopAlertShortView alloc] initWithColor:[UIColor clearColor] message:nil actionMap:nil additionalData:nil topAlertObject:nil];
|
||||
shortView.label.font = [MFStyler fontB2];
|
||||
[self addSubview:shortView];
|
||||
self.shortView = shortView;
|
||||
|
||||
[NSLayoutConstraint constraintPinSubview:shortView pinTop:YES topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0];
|
||||
self.topLabelConstraintBottom = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:shortView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
|
||||
self.topLabelConstraintBottom.active = YES;
|
||||
|
||||
self.shortViewHeight = [NSLayoutConstraint constraintWithItem:shortView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:0];
|
||||
[self setTopMessage:topMessage];
|
||||
}
|
||||
|
||||
- (void)setupTopAlertWithButton:(MVMCoreUITopAlertMainView *)topAlertWithButton {
|
||||
topAlertWithButton.label.alpha = 0;
|
||||
topAlertWithButton.button.alpha = 0;
|
||||
topAlertWithButton.backgroundColor = [UIColor clearColor];
|
||||
[self insertSubview:topAlertWithButton belowSubview:self.shortView];
|
||||
self.buttonView = topAlertWithButton;
|
||||
|
||||
self.topConstraint = [NSLayoutConstraint constraintWithItem:topAlertWithButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.shortView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
|
||||
[NSLayoutConstraint constraintPinSubview:topAlertWithButton pinTop:NO topConstant:0 pinBottom:YES bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0];
|
||||
}
|
||||
|
||||
- (void)setupViewWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle {
|
||||
|
||||
[self setupTopMessage:topMessage];
|
||||
self.shortView.label.textColor = contentColor;
|
||||
|
||||
MVMCoreUITopAlertMainView *topAlertWithButton = [[MVMCoreUITopAlertMainView alloc] initWithColor:[UIColor clearColor] contentColor:contentColor message:message subMessage:subMessage buttonTitle:buttonTitle userActionHandler:nil];
|
||||
[self setupTopAlertWithButton:topAlertWithButton];
|
||||
}
|
||||
|
||||
- (void)setupViewWithTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
|
||||
[self setupTopMessage:topMessage];
|
||||
self.shortView.label.textColor = contentColor;
|
||||
|
||||
MVMCoreUITopAlertMainView *topAlertWithButton = [[MVMCoreUITopAlertMainView alloc] initWithColor:[UIColor clearColor] contentColor:contentColor message:message subMessage:subMessage actionMap:actionMap additionalData:additionalData];
|
||||
[self setupTopAlertWithButton:topAlertWithButton];
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
self.shortView.label.text = topMessage;
|
||||
self.shortView.label.accessibilityLabel = topMessage;
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.shortView.label];
|
||||
|
||||
if (topMessage && (!self.onlyShowTopMessageWhenCollapsed || !self.expanded)) {
|
||||
self.shortViewHeight.active = NO;
|
||||
} else if (!topMessage || (self.onlyShowTopMessageWhenCollapsed && self.expanded)) {
|
||||
self.shortViewHeight.active = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[self setTopMessage:topMessage];
|
||||
[self.buttonView setupWithMessage:message subMessage:subMessage color:contentColor actionMap:actionMap additionalData:additionalData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setTopMessage:(nullable NSString *)topMessage message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage contentColor:(nonnull UIColor *)contentColor buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[self setTopMessage:topMessage];
|
||||
[self.buttonView setupWithMessage:message subMessage:subMessage color:contentColor buttonTitle:buttonTitle userActionHandler:userActionHandler];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setButtonPressToExpand {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.buttonView.button addActionBlockWithEvent:UIControlEventTouchUpInside :^(Button * _Nonnull button) {
|
||||
if (weakSelf) {
|
||||
[weakSelf expand:YES];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setButtonPressToCollapse {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.buttonView.button addActionBlockWithEvent:UIControlEventTouchUpInside :^(Button * _Nonnull button) {
|
||||
if (weakSelf) {
|
||||
[weakSelf collapse];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setShortViewPressToExpand {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.shortView.label.accessibilityTraits = UIAccessibilityTraitButton;
|
||||
[self.shortView.button addActionBlockWithEvent:UIControlEventTouchUpInside :^(Button * _Nonnull button) {
|
||||
if (weakSelf) {
|
||||
[weakSelf expand:YES];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setShortViewPressToCollapse {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.shortView.label.accessibilityTraits = UIAccessibilityTraitButton;
|
||||
[self.shortView.button addActionBlockWithEvent:UIControlEventTouchUpInside :^(Button * _Nonnull button) {
|
||||
if (weakSelf) {
|
||||
[weakSelf collapse];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setOnlyShowTopMessageWhenCollapsed:(BOOL)onlyShowTopMessageWhenCollapsed {
|
||||
_onlyShowTopMessageWhenCollapsed = onlyShowTopMessageWhenCollapsed;
|
||||
if (onlyShowTopMessageWhenCollapsed && self.expanded) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
self.shortView.label.alpha = 0;
|
||||
self.shortViewHeight.active = YES;
|
||||
}];
|
||||
} else if (!onlyShowTopMessageWhenCollapsed && self.expanded) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
self.shortView.label.alpha = 1;
|
||||
self.shortViewHeight.active = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCollapseAutomaticallyAfterExpanded:(BOOL)collapseAutomaticallyAfterExpanded {
|
||||
_collapseAutomaticallyAfterExpanded = collapseAutomaticallyAfterExpanded;
|
||||
if (collapseAutomaticallyAfterExpanded) {
|
||||
[self autoCollapse];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Expand/Collapse
|
||||
|
||||
- (void)expand:(BOOL)animated {
|
||||
if (!self.expanded) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[weakSelf performExpansion:animated onCompletion:^{
|
||||
[operation markAsFinished];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
[[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;
|
||||
NSInteger dismissTime;
|
||||
if (self.collapseTime > 0) {
|
||||
dismissTime = self.collapseTime;
|
||||
} else {
|
||||
dismissTime = TopAlertDismissTime;
|
||||
}
|
||||
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, dismissTime * NSEC_PER_SEC);
|
||||
dispatch_after(dispatchTime, dispatch_get_main_queue(), ^(void){
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf && strongSelf.expanded && strongSelf.collapseAutomaticallyAfterExpanded) {
|
||||
// If accessibility focused, delay collapse.
|
||||
if ([MVMCoreUIUtility viewContainsAccessiblityFocus:strongSelf]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:strongSelf selector:@selector(accessibilityFocusChanged:) name:UIAccessibilityElementFocusedNotification object:nil];
|
||||
} else {
|
||||
[strongSelf collapse];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collapse {
|
||||
if (self.expanded) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[weakSelf performCollapseAnimationThen:^{
|
||||
[operation markAsFinished];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
[[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
||||
[self collapse];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,57 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertMainView.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 8/17/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertBaseView.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
|
||||
@class PillButton;
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@interface MVMCoreUITopAlertMainView : MVMCoreUITopAlertBaseView
|
||||
|
||||
@property (nullable, nonatomic, weak) UILabel *label;
|
||||
@property (nullable, nonatomic, weak) PillButton *button;
|
||||
|
||||
@property (nullable, nonatomic, strong) NSLayoutConstraint *height;
|
||||
|
||||
// Standard
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
|
||||
// inits with images
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor imageURL:(nullable NSString *)imageURL message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor imageURL:(nullable NSString *)imageURL message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
|
||||
// Sets up without image
|
||||
- (void)defaultSetup;
|
||||
|
||||
// Setters for label and button.
|
||||
- (void)setupWithMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
- (void)setupWithMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler;
|
||||
|
||||
// Setters for button.
|
||||
- (void)setupButtonWithActionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
- (void)setupButtonWithButtonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler;
|
||||
|
||||
// Setters for close button.
|
||||
- (void)setupCloseButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
|
||||
#pragma mark - legacy inits
|
||||
|
||||
// Legacy init: inits with a label and button, no close button or icon.
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData;
|
||||
|
||||
// Legacy init: inits with a label and possible icon and close button. No main button.
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate;
|
||||
|
||||
// Legacy init: inits with a label and button, no close button or icon. If passing in a block to use for the button, the top alert delegate button functions will not be called.
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler;
|
||||
|
||||
|
||||
@end
|
||||
@ -1,343 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertMainView.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 8/17/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUITopAlertMainView.h"
|
||||
#import "MFStyler.h"
|
||||
#import "NSLayoutConstraint+MFConvenience.h"
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
@import MVMCore.MVMCoreDispatchUtility;
|
||||
#import <MVMCoreUI/MVMCoreTopAlertObject.h>
|
||||
#import "UIColor+MFConvenience.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
@import MVMCore.MVMCoreJSONConstants;
|
||||
#import "MVMCoreUICommonViewsUtility.h"
|
||||
#import "MVMCoreUITopAlertView.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
|
||||
@interface MVMCoreUITopAlertMainView ()
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *labelRightConstraint;
|
||||
|
||||
@property (nullable, weak, nonatomic) UIView *centerView;
|
||||
@property (nullable, weak, nonatomic) LoadImageView *iconView;
|
||||
@property (nullable, weak, nonatomic) LoadImageView *topIconView;
|
||||
@property (nullable, weak, nonatomic) Button *closeButton;
|
||||
@property (nullable, strong, nonatomic) NSString *message;
|
||||
@property (nullable, strong, nonatomic) NSString *subMessage;
|
||||
@property (nullable, strong, nonatomic) UIColor *contentColor;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreUITopAlertMainView
|
||||
|
||||
- (void)handleAccessibility {
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.label);
|
||||
}
|
||||
|
||||
#pragma mark - Setup View
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
self.label.attributedText = [MVMCoreUITopAlertBaseView getStringForMessage:self.message subMessage:self.subMessage color:self.contentColor];
|
||||
self.label.accessibilityLabel = self.label.text;
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.label];
|
||||
[self.button updateView:size];
|
||||
}
|
||||
|
||||
- (nullable instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.clipsToBounds = YES;
|
||||
self.height = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:96];
|
||||
self.height.active = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithTopAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
if (self = [self init]) {
|
||||
UIColor *contentColor = topAlertObject.textColor ?: [[MVMCoreUITopAlertView sharedGlobal] getContentColorForType:topAlertObject.type];
|
||||
self.backgroundColor = topAlertObject.backgroundColor ?: [[MVMCoreUITopAlertView sharedGlobal] getBackgroundColorForType:topAlertObject.type];
|
||||
[self setupViewWithLabelAndImage:topAlertObject.imageNameOrURL topImage:topAlertObject.aboveTextImageString];
|
||||
[self setupCloseButton:topAlertObject.useCloseButton animationDelegate:animationDelegate];
|
||||
[self setupWithMessage:topAlertObject.title subMessage:topAlertObject.message color:contentColor actionMap:topAlertObject.buttonMap additionalData:topAlertObject.additionalData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
|
||||
// Handles all scenarios.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:nil topImage:nil];
|
||||
[self setupCloseButton:closeButton animationDelegate:animationDelegate];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - inits with images
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor imageURL:(nullable NSString *)imageURL message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
|
||||
// Handles all scenarios.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:imageURL topImage:nil];
|
||||
[self setupCloseButton:closeButton animationDelegate:animationDelegate];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nullable UIColor *)contentColor imageURL:(nullable NSString *)imageURL message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
|
||||
// No main button.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:imageURL topImage:nil];
|
||||
[self setupCloseButton:closeButton animationDelegate:animationDelegate];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor buttonTitle:nil userActionHandler:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - setup
|
||||
|
||||
- (void)defaultSetup {
|
||||
if (!self.label) {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.clipsToBounds = YES;
|
||||
self.height = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:96];
|
||||
self.height.active = YES;
|
||||
[self setupViewWithLabelAndImage:nil topImage:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupViewWithLabelAndImage:(NSString *)imageURL topImage:(NSString *)topImageString {
|
||||
|
||||
UIView *centerView = [MVMCoreUICommonViewsUtility commonView];
|
||||
[self addSubview:centerView];
|
||||
self.centerView = centerView;
|
||||
[centerView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor].active = YES;
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[centerView]->=0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(centerView)]];
|
||||
|
||||
// Add the label.
|
||||
UILabel *label = self.label;
|
||||
if (label) {
|
||||
[label removeFromSuperview];
|
||||
} else {
|
||||
label = [MVMCoreUITopAlertBaseView topAlertLabel];
|
||||
self.label = label;
|
||||
}
|
||||
[centerView addSubview:label];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[label]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(label)]];
|
||||
[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
|
||||
|
||||
// Add Top Image
|
||||
if (self.topIconView) {
|
||||
[self.topIconView removeFromSuperview];
|
||||
}
|
||||
if (topImageString) {
|
||||
LoadImageView *imageView = [[LoadImageView alloc] initWithPinnedEdges:UIRectEdgeLeft | UIRectEdgeTop | UIRectEdgeBottom];
|
||||
imageView.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
imageView.addSizeConstraintsForAspectRatio = YES;
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[centerView addSubview:imageView];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-out-[imageView]-between-[label]-out-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"out":@(PaddingTwo),@"between":@(PaddingOne)} views:NSDictionaryOfVariableBindings(imageView,label)]];
|
||||
[imageView.leftAnchor constraintEqualToAnchor:label.leftAnchor].active = YES;
|
||||
[imageView.rightAnchor constraintEqualToAnchor:label.rightAnchor].active = YES;
|
||||
[imageView loadImageWithName:topImageString height:@(12)];
|
||||
} else {
|
||||
[NSLayoutConstraint constraintPinSubview:label pinTop:YES topConstant:PaddingTwo pinBottom:YES bottomConstant:PaddingTwo pinLeft:NO leftConstant:0 pinRight:NO rightConstant:0];
|
||||
}
|
||||
|
||||
// Add main image.
|
||||
if (self.iconView) {
|
||||
[self.iconView removeFromSuperview];
|
||||
}
|
||||
|
||||
CGFloat horizontalPadding = [MFStyler defaultHorizontalPaddingForApplicationWidth];
|
||||
if (imageURL) {
|
||||
LoadImageView *imageView = [[LoadImageView alloc] init];
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[self addSubview:imageView];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=space-[imageView]->=space-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingFive)} views:NSDictionaryOfVariableBindings(imageView)]];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-space-[imageView]-space-[centerView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(horizontalPadding)} views:NSDictionaryOfVariableBindings(imageView,centerView)]];
|
||||
[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES;
|
||||
self.iconView = imageView;
|
||||
[imageView loadImageWithName:imageURL width:@(32)];
|
||||
} else {
|
||||
[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:horizontalPadding].active = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupCloseButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
|
||||
if (closeButton && !self.closeButton) {
|
||||
self.closeButton = [self addCloseButtonWithAnimationDelegate:animationDelegate];
|
||||
[self.closeButton setTintColor:self.contentColor ?: [UIColor whiteColor]];
|
||||
} else if (!closeButton && self.closeButton) {
|
||||
[self.closeButton removeFromSuperview];
|
||||
self.closeButton = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupWithButton:(BOOL)showButton {
|
||||
|
||||
// Setup the button.
|
||||
CGFloat horizontalPadding = [MFStyler defaultHorizontalPaddingForApplicationWidth];
|
||||
if (showButton) {
|
||||
|
||||
if (!self.button) {
|
||||
|
||||
// remove label right constraint
|
||||
self.labelRightConstraint.active = NO;
|
||||
|
||||
// Sets up to use a button action. Always uses the top view controller
|
||||
PillButton *button = [[PillButton alloc] initAsPrimaryButton:false makeTiny:true];
|
||||
[button setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[button setContentHuggingPriority:800 forAxis:UILayoutConstraintAxisHorizontal];
|
||||
|
||||
button.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:button];
|
||||
[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:PaddingThree].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : horizontalPadding)].active = YES;
|
||||
self.button = button;
|
||||
}
|
||||
} else {
|
||||
|
||||
// remove button.
|
||||
if (self.button) {
|
||||
[self.button removeFromSuperview];
|
||||
self.button = nil;
|
||||
}
|
||||
|
||||
if (!self.labelRightConstraint) {
|
||||
self.labelRightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : horizontalPadding)];
|
||||
}
|
||||
self.labelRightConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setupWithMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
self.message = message;
|
||||
self.subMessage = subMessage;
|
||||
self.contentColor = color;
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
|
||||
// Sets the string
|
||||
self.label.attributedText = [MVMCoreUITopAlertBaseView getStringForMessage:message subMessage:subMessage color:color];
|
||||
self.label.accessibilityLabel = self.label.text;
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.label];
|
||||
|
||||
// Sets the button
|
||||
[self setupButtonWithActionMap:actionMap additionalData:additionalData];
|
||||
if (color) {
|
||||
self.button.layer.borderColor = color.CGColor;
|
||||
[self.button setTitleColor:color forState:UIControlStateNormal];
|
||||
[self.closeButton setTintColor:color];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupWithMessage:(nullable NSString *)message subMessage:(nullable NSString *)subMessage color:(nullable UIColor *)color buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler {
|
||||
self.message = message;
|
||||
self.subMessage = subMessage;
|
||||
self.contentColor = color;
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
|
||||
// Sets the string
|
||||
self.label.attributedText = [MVMCoreUITopAlertBaseView getStringForMessage:message subMessage:subMessage color:color];
|
||||
self.label.accessibilityLabel = self.label.text;
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.label];
|
||||
|
||||
// Sets the color
|
||||
if (color) {
|
||||
self.button.layer.borderColor = color.CGColor;
|
||||
[self.button setTitleColor:color forState:UIControlStateNormal];
|
||||
[self.closeButton setTintColor:color];
|
||||
}
|
||||
|
||||
// Sets the button
|
||||
[self setupButtonWithButtonTitle:buttonTitle userActionHandler:userActionHandler];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupButtonWithActionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
BOOL showButton = actionMap != nil;
|
||||
[self setupWithButton:showButton];
|
||||
if (showButton) {
|
||||
[self.button setTitle:[actionMap stringForKey:KeyTitle] forState:UIControlStateNormal];
|
||||
self.button.accessibilityLabel = [self.button titleForState:UIControlStateNormal];
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.button];
|
||||
|
||||
[MVMCoreUITopAlertBaseView addActionToButton:self.button actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupButtonWithButtonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler {
|
||||
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
BOOL showButton = buttonTitle.length > 0;
|
||||
[self setupWithButton:showButton];
|
||||
[self.button setTitle:buttonTitle forState:UIControlStateNormal];
|
||||
self.button.accessibilityLabel = [self.button titleForState:UIControlStateNormal];
|
||||
[MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:self.button];
|
||||
if (showButton && userActionHandler) {
|
||||
[self.button addActionBlockWithEvent:UIControlEventTouchUpInside :userActionHandler];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - legacy inits
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData {
|
||||
|
||||
// No icon or close button.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:nil topImage:nil];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage closeButton:(BOOL)closeButton animationDelegate:(nullable id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate {
|
||||
|
||||
// No main button.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:nil topImage:nil];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor buttonTitle:nil userActionHandler:NULL];
|
||||
[self setupCloseButton:closeButton animationDelegate:animationDelegate];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color contentColor:(nonnull UIColor *)contentColor message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler {
|
||||
|
||||
// No icon or close button. Custom button action.
|
||||
if (self = [self init]) {
|
||||
self.backgroundColor = color;
|
||||
[self setupViewWithLabelAndImage:nil topImage:nil];
|
||||
[self setupWithMessage:message subMessage:subMessage color:contentColor buttonTitle:buttonTitle userActionHandler:userActionHandler];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertShortView.h
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 7/20/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MVMCoreUITopAlertBaseView.h>
|
||||
|
||||
@class MVMCoreTopAlertObject;
|
||||
@class Button;
|
||||
|
||||
@interface MVMCoreUITopAlertShortView : MVMCoreUITopAlertBaseView
|
||||
|
||||
@property (nullable, weak, nonatomic) UILabel *label;
|
||||
@property (nullable, weak, nonatomic) Button *button;
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color message:(nullable NSString *)message actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData topAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject;
|
||||
|
||||
@end
|
||||
@ -1,67 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertShortView.m
|
||||
// mobilefirst
|
||||
//
|
||||
// Created by Scott Pfeil on 7/20/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUITopAlertShortView.h"
|
||||
#import "MFStyler.h"
|
||||
#import "NSLayoutConstraint+MFConvenience.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
|
||||
@interface MVMCoreUITopAlertShortView ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreUITopAlertShortView
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
self.label.font = [MFStyler fontB1];
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithColor:(nonnull UIColor *)color message:(nullable NSString *)message actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData topAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject {
|
||||
|
||||
if (self = [super init]) {
|
||||
|
||||
self.backgroundColor = color;
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
UILabel *label = [MVMCoreUITopAlertBaseView topAlertLabel];
|
||||
label.numberOfLines = 1;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.text = message;
|
||||
[self addSubview:label];
|
||||
[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
|
||||
[NSLayoutConstraint constraintPinSubview:label pinTop:YES topConstant:0 pinBottom:YES bottomConstant:4 pinLeft:YES leftConstant:PaddingThree pinRight:YES rightConstant:PaddingThree];
|
||||
self.label = label;
|
||||
|
||||
// Sets up to use a button action.
|
||||
Button *button = [Button buttonWithType:UIButtonTypeCustom];
|
||||
button.backgroundColor = [UIColor clearColor];
|
||||
button.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[button setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisVertical];
|
||||
[button setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[self addSubview:button];
|
||||
[NSLayoutConstraint constraintPinSubview:button pinTop:YES topConstant:0 pinBottom:YES bottomConstant:-5 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0];
|
||||
if (actionMap) {
|
||||
[MVMCoreUITopAlertBaseView addActionToButton:button actionMap:actionMap additionalData:additionalData];
|
||||
}
|
||||
self.button = button;
|
||||
|
||||
// Listen for status bar touches.
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NotificationStatusBarTouched object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
|
||||
[button sendActionsForControlEvents:UIControlEventAllEvents];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)handleAccessibility {
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.label);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,81 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertView+Extension.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/11/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
/// Allows top alerts to determine the status bar color and style.
|
||||
protocol StatusBarUI {
|
||||
func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle)
|
||||
}
|
||||
|
||||
public extension MVMCoreUITopAlertView {
|
||||
|
||||
/// Shows the top alert with the json.
|
||||
@objc func showTopAlert(with json: [AnyHashable: Any]) {
|
||||
TopNotificationHandler.shared().showTopNotification(with: json)
|
||||
}
|
||||
|
||||
/// Updates the current top alert molecule with the new object
|
||||
@objc func updateMolecule(with topAlertObject: MVMCoreTopAlertObject) {
|
||||
guard topAlertObject.type == self.topAlertObject?.type else { return }
|
||||
let delegateObject = MVMCoreUIDelegateObject.create(withDelegateForAll: self)
|
||||
guard let newJson = topAlertObject.json,
|
||||
let newModel = TopNotificationHandler.shared().decodeTopNotification(with: newJson, delegateObject: delegateObject),
|
||||
let newModelName = ModelRegistry.getMoleculeClass(newModel.molecule)?.nameForReuse(with: newModel.molecule, delegateObject),
|
||||
let currentJson = self.topAlertObject?.json,
|
||||
let currentModel = TopNotificationHandler.shared().decodeTopNotification(with: currentJson, delegateObject: delegateObject),
|
||||
let currentModelName = ModelRegistry.getMoleculeClass(currentModel.molecule)?.nameForReuse(with: currentModel.molecule, delegateObject),
|
||||
newModelName == currentModelName,
|
||||
let molecule = currentAlert as? MoleculeViewProtocol else {
|
||||
// Log that we couldn't update.
|
||||
if let errorObject = MVMCoreErrorObject(title: nil, message: nil, messageToLog: nil, code: ErrorCode.parsingJSON.rawValue, domain: ErrorDomainNative, location: "TopNotification update \(String(describing: topAlertObject.type))") {
|
||||
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
||||
}
|
||||
return
|
||||
}
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
// Update molecule
|
||||
molecule.reset()
|
||||
molecule.set(with: newModel.molecule, delegateObject, nil)
|
||||
(molecule as? MVMCoreViewProtocol)?.updateView(self.bounds.width)
|
||||
|
||||
// Update status bar.
|
||||
guard let statusBarDelegate = molecule as? StatusBarUI else { return }
|
||||
let statusBarUI = statusBarDelegate.getStatusBarUI()
|
||||
MVMCoreUISplitViewController.main()?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the top alert molecule to use and status bar color legacy style.
|
||||
@objc func molecule(for topAlertObject: MVMCoreTopAlertObject, statusBarColor: AutoreleasingUnsafeMutablePointer<UIColor?>?, statusBarStyle: UnsafeMutablePointer<UIStatusBarStyle>?) -> 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 = ModelRegistry.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
|
||||
}
|
||||
// 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)") {
|
||||
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MVMCoreUITopAlertView: ActionDelegateProtocol {}
|
||||
@ -1,46 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertView.h
|
||||
// myverizon
|
||||
//
|
||||
// Created by Chris Yang on 2/3/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@import MVMCore.MVMCoreLoadDelegateProtocol;
|
||||
@import MVMCore.MVMCoreActionDelegateProtocol;
|
||||
@import MVMCore.MVMCorePresentationDelegateProtocol;
|
||||
@import MVMCore.MVMCoreViewProtocol;
|
||||
#import <MVMCoreUI/MVMCoreTopAlertAnimationDelegateProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertViewProtocol.h>
|
||||
#import <MVMCoreUI/ButtonDelegateProtocol.h>
|
||||
|
||||
@class MVMCoreTopAlertObject;
|
||||
|
||||
@interface MVMCoreUITopAlertView : UIView <MVMCoreViewProtocol, MVMCoreTopAlertViewProtocol, MVMCoreLoadDelegateProtocol, MVMCoreActionDelegateProtocol, MVMCorePresentationDelegateProtocol, ButtonDelegateProtocol>
|
||||
|
||||
// Delegate for the top alert view
|
||||
@property (nonatomic, nullable, weak) id <MVMCoreTopAlertAnimationDelegateProtocol> animationDelegate;
|
||||
|
||||
// Current top alert object
|
||||
@property (strong, nullable, nonatomic) MVMCoreTopAlertObject *topAlertObject;
|
||||
|
||||
/// Current top alert view.
|
||||
@property (weak, nullable, nonatomic, readonly) UIView *currentAlert;
|
||||
|
||||
// Returns the top alert view
|
||||
+ (nullable instancetype)sharedGlobal;
|
||||
|
||||
// Returns a TopAlertView with the mvm styling. Also sets the property in the session.
|
||||
+ (nullable instancetype)setupTopAlertView;
|
||||
|
||||
// Can be subclassed for custom views.
|
||||
- (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle;
|
||||
|
||||
/// Get the background color based on the type
|
||||
- (nonnull UIColor *)getBackgroundColorForType:(nullable NSString *)type;
|
||||
|
||||
/// Get the content color based on the type
|
||||
- (nonnull UIColor *)getContentColorForType:(nullable NSString *)type;
|
||||
|
||||
@end
|
||||
@ -1,291 +0,0 @@
|
||||
//
|
||||
// MVMCoreUITopAlertView.m
|
||||
// myverizon
|
||||
//
|
||||
// Created by Chris Yang on 2/3/16.
|
||||
// Copyright © 2016 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUITopAlertView.h"
|
||||
#import "MVMCoreUICommonViewsUtility.h"
|
||||
#import "UIColor+MFConvenience.h"
|
||||
#import "MVMCoreUITopAlertShortView.h"
|
||||
#import "MVMCoreUITopAlertMainView.h"
|
||||
#import "MVMCoreUITopAlertExpandableView.h"
|
||||
#import "MVMCoreUISplitViewController.h"
|
||||
#import "NSLayoutConstraint+MFConvenience.h"
|
||||
#import "MVMCoreUISession.h"
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
#import <MVMCoreUI/MVMCoreTopAlertObject.h>
|
||||
@import MVMCore.MVMCoreLoadHandler;
|
||||
@import MVMCore.MVMCoreNavigationHandler;
|
||||
@import MVMCore.MVMCoreBlockOperation;
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
@import MVMCore.MVMCoreRequestParameters;
|
||||
@import MVMCore.MVMCoreJSONConstants;
|
||||
@import MVMCore.MVMCoreDispatchUtility;
|
||||
|
||||
NSString * const MFAccTopAlertClosed = @"Top alert notification is closed.";
|
||||
|
||||
@interface MVMCoreUITopAlertView ()
|
||||
|
||||
@property (weak, nullable, nonatomic, readwrite) UIView *currentAlert;
|
||||
@property (strong, nonatomic) NSLayoutConstraint *height;
|
||||
|
||||
@property (weak, nonatomic) MVMCoreUITopAlertExpandableView *topAlertClearspotView;
|
||||
@property (strong, nonatomic) NSString *time;
|
||||
|
||||
/// Used if we delayed the collapse due to accessibility.
|
||||
@property (copy, nonatomic) void (^ hideCompletionHandler)(BOOL finished);
|
||||
|
||||
@property (nonatomic) BOOL currentAlertOverridingStatusBar;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreUITopAlertView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (nullable instancetype)sharedGlobal {
|
||||
return [MVMCoreUISession sharedGlobal].topAlertView;
|
||||
}
|
||||
|
||||
+ (nullable instancetype)setupTopAlertView {
|
||||
MVMCoreUITopAlertView *topAlertView = [[self alloc] init];
|
||||
topAlertView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[MVMCoreUISession sharedGlobal].topAlertView = topAlertView;
|
||||
return topAlertView;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
if (self.height) { return; }
|
||||
self.clipsToBounds = YES;
|
||||
self.height = [self.heightAnchor constraintEqualToConstant:0];
|
||||
self.height.active = YES;
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
if ([self.currentAlert respondsToSelector:@selector(updateView:)]) {
|
||||
[((UIView <MVMCoreViewProtocol>*)(self.currentAlert)) updateView:size];
|
||||
}
|
||||
}
|
||||
|
||||
- (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle {
|
||||
if (topAlertObject.json) {
|
||||
return [self moleculeFor:topAlertObject statusBarColor:statusBarColor statusBarStyle:statusBarStyle];
|
||||
} else {
|
||||
MVMCoreUITopAlertExpandableView *view = [[MVMCoreUITopAlertExpandableView alloc] initWithTopAlertObject:topAlertObject animationDelegate:animationDelegate viewToLayout:self.superview];
|
||||
if (statusBarColor && view.shortView.label.text) {
|
||||
*statusBarColor = view.backgroundColor;
|
||||
|
||||
if (statusBarStyle) {
|
||||
CGFloat greyScale = 0;
|
||||
if ([view.shortView.label.textColor getWhite:&greyScale alpha:nil]) {
|
||||
*statusBarStyle = greyScale > 0.5 ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
- (nonnull UIColor *)getBackgroundColorForType:(nullable NSString *)type {
|
||||
if ([type isEqualToString:ValueTypeError]) {
|
||||
return [UIColor mvmOrange];
|
||||
} else {
|
||||
return [UIColor mvmGreen];
|
||||
}
|
||||
}
|
||||
|
||||
- (nonnull UIColor *)getContentColorForType:(nullable NSString *)type {
|
||||
if ([type isEqualToString:ValueTypeError]) {
|
||||
return [UIColor blackColor];
|
||||
} else {
|
||||
return [UIColor whiteColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 <AccessibilityProtocol>)view) getAccessibilityLayoutChangedArgument];
|
||||
}
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, accessibilityArgument);
|
||||
}
|
||||
}
|
||||
|
||||
- (nonnull NSOperation *)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) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
if (weakSelf.currentAlert.superview) {
|
||||
[weakSelf.currentAlert removeFromSuperview];
|
||||
}
|
||||
[weakSelf addSubview:view];
|
||||
[NSLayoutConstraint constraintPinSubviewToSuperview:view];
|
||||
weakSelf.currentAlert = view;
|
||||
|
||||
[weakSelf.animationDelegate topAlertViewBeginAnimation];
|
||||
[weakSelf.superview layoutIfNeeded];
|
||||
[UIView animateWithDuration:.5 animations:^{
|
||||
weakSelf.height.active = NO;
|
||||
[weakSelf.superview layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[weakSelf.superview layoutIfNeeded];
|
||||
[weakSelf.animationDelegate topAlertViewFinishAnimation];
|
||||
|
||||
[weakSelf updateAccessibilityForTopAlert:view];
|
||||
|
||||
[MVMCoreDispatchUtility performBlockInBackground:^{
|
||||
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);
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
[[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation];
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
/// If the voice over user leaves top alert focus, hide.
|
||||
- (void)accessibilityFocusChanged:(NSNotification *)notification {
|
||||
if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
||||
[self hideAlertView:YES completionHandler:self.hideCompletionHandler];
|
||||
self.hideCompletionHandler = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreTopAlertViewProtocol
|
||||
|
||||
- (nonnull NSOperation *)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id <MVMCoreTopAlertAnimationDelegateProtocol>)animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
|
||||
|
||||
self.animationDelegate = animationDelegate;
|
||||
__block NSOperation *operation = nil;
|
||||
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
|
||||
self.topAlertObject = topAlertObject;
|
||||
self.topAlertClearspotView = nil;
|
||||
|
||||
UIColor *statusBarColor = nil;
|
||||
UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault;
|
||||
UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle];
|
||||
if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) {
|
||||
[((UIView <MVMCoreViewProtocol>*)view) updateView:CGRectGetWidth(self.bounds)];
|
||||
}
|
||||
if (statusBarColor) {
|
||||
self.currentAlertOverridingStatusBar = YES;
|
||||
[[MVMCoreUISplitViewController mainSplitViewController] setStatusBarBackgroundColor:statusBarColor style:statusBarStyle];
|
||||
}
|
||||
operation = [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler];
|
||||
}];
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
|
||||
// If accessible and focused, do not collapse until unfocused.
|
||||
if (!forceful && [MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||
self.hideCompletionHandler = completionHandler;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accessibilityFocusChanged:) name:UIAccessibilityElementFocusedNotification object:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[weakSelf.animationDelegate topAlertWillDismiss];
|
||||
[weakSelf.animationDelegate topAlertViewBeginAnimation];
|
||||
|
||||
// accessibility - below line added to notify VI user through voiceover user when the top alert is closed
|
||||
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, [MVMCoreUIUtility hardcodedStringWithKey:@"AccTopAlertClosed"]);
|
||||
|
||||
[UIView animateWithDuration:.5 animations:^{
|
||||
weakSelf.height.active = YES;
|
||||
[weakSelf.superview layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[weakSelf.animationDelegate topAlertViewFinishAnimation];
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
|
||||
|
||||
UIView *view = weakSelf.currentAlert;
|
||||
if (view.superview) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
[MVMCoreDispatchUtility performBlockInBackground:^{
|
||||
if ([weakSelf.topAlertObject.delegate respondsToSelector:@selector(topAlertViewDismissed:)]) {
|
||||
[weakSelf.topAlertObject.delegate topAlertViewDismissed:view];
|
||||
}
|
||||
[operation markAsFinished];
|
||||
if (completionHandler) {
|
||||
completionHandler(finished);
|
||||
}
|
||||
[weakSelf.animationDelegate topAlertDismissed];
|
||||
weakSelf.topAlertObject = nil;
|
||||
if (weakSelf.currentAlertOverridingStatusBar) {
|
||||
weakSelf.currentAlertOverridingStatusBar = NO;
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[[MVMCoreUISplitViewController mainSplitViewController] setStatusBarForCurrentViewController];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
[[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation];
|
||||
}
|
||||
|
||||
|
||||
- (void)updateTopAlertWith:(MVMCoreTopAlertObject *)topAlertObject {
|
||||
[self updateMoleculeWith:topAlertObject];
|
||||
}
|
||||
|
||||
- (void)collapseNotification {
|
||||
if (self.currentAlert) {
|
||||
if ([self.currentAlert isKindOfClass:[MVMCoreUITopAlertExpandableView class]] && ((MVMCoreUITopAlertExpandableView *)self.currentAlert).shortView.label.text.length > 0) {
|
||||
|
||||
// We have a short message, collapse to show short message.
|
||||
[((MVMCoreUITopAlertExpandableView *)self.currentAlert) collapse];
|
||||
} else {
|
||||
// Top alert is not collapsable, remove it instead.
|
||||
[self hideAlertView:NO completionHandler:NULL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)overridingStatusBar {
|
||||
return self.currentAlertOverridingStatusBar;
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
Reference in New Issue
Block a user