Merge branch 'feature/swiftify_notifications' into 'develop'

Feature/swiftify notifications

### Summary
Tech debt: Convert Top Alert system to swift.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-4157

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>
Co-authored-by: Rajesh Pullagalla <rajesh.pullagalla1@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/961
This commit is contained in:
Hedden, Kyle Matthew 2023-06-22 17:19:54 +00:00
commit 6209e07e7b
50 changed files with 958 additions and 2771 deletions

View File

@ -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 */,

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
})
}
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}
//--------------------------------------------------

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -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

View File

@ -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];
}
}];
}

View File

@ -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>

View 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
}
}

View 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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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? {

View File

@ -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

View File

@ -20,7 +20,4 @@
return nil;
}
- (void)trackTopNotificationShown:(UIView *)topNotification topAlertObject:(MVMCoreTopAlertObject *)topAlertObject additionalData:(NSDictionary *)additionalData {
}
@end

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {}

View File

@ -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

View File

@ -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