diff --git a/WMF Framework/CommonStrings.swift b/WMF Framework/CommonStrings.swift index 83e84b1c7df..79cc12f53a6 100644 --- a/WMF Framework/CommonStrings.swift +++ b/WMF Framework/CommonStrings.swift @@ -146,6 +146,7 @@ public class CommonStrings: NSObject { @objc public static let okTitle = WMFLocalizedString("button-ok", value: "OK", comment: "Button text for ok button used in various places {{Identical|OK}}") @objc public static let doneTitle = WMFLocalizedString("description-published-button-title", value: "Done", comment: "Title for description panel done button.") + @objc public static let editNotices = WMFLocalizedString("edit-notices", value: "Edit notices", comment: "Title text and accessibility label for edit notices button.") @objc public static let undo = WMFLocalizedString("action-undo", value: "Undo", comment: "Title text and accessibility label for undo action on buttons or info sheets.") @objc public static let redo = WMFLocalizedString("action-redo", value: "Redo", comment: "Title text and accessibility label for redo action on buttons or info sheets.") @objc public static let findInPage = WMFLocalizedString("action-find-in-page", value: "Find in page", comment: "Title text and accessibility label for find in page action on buttons or info sheets.") diff --git a/Wikipedia.xcodeproj/project.pbxproj b/Wikipedia.xcodeproj/project.pbxproj index 32d3bb6520d..b342e07a40c 100644 --- a/Wikipedia.xcodeproj/project.pbxproj +++ b/Wikipedia.xcodeproj/project.pbxproj @@ -37,6 +37,10 @@ 0033D79E24F8193900CAB5B3 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033D79C24F8193900CAB5B3 /* CGPoint+Extensions.swift */; }; 0033D7A124F8199300CAB5B3 /* Sparkline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033D7A024F8199300CAB5B3 /* Sparkline.swift */; }; 0036C8B3282C2AAA00EADB35 /* Notification+NotificationsCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0036C8B2282C2AAA00EADB35 /* Notification+NotificationsCenter.swift */; }; + 003AD72E2979C512005BDB90 /* EditNoticesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */; }; + 003AD72F2979C512005BDB90 /* EditNoticesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */; }; + 003AD7302979C512005BDB90 /* EditNoticesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */; }; + 003AD7312979C512005BDB90 /* EditNoticesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */; }; 003CD3E928EF7C77000158E4 /* TalkPageFindInPageSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003CD3E828EF7C77000158E4 /* TalkPageFindInPageSearchController.swift */; }; 003CD3EA28EF7C77000158E4 /* TalkPageFindInPageSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003CD3E828EF7C77000158E4 /* TalkPageFindInPageSearchController.swift */; }; 003CD3EB28EF7C77000158E4 /* TalkPageFindInPageSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003CD3E828EF7C77000158E4 /* TalkPageFindInPageSearchController.swift */; }; @@ -222,6 +226,14 @@ 007F5C70275AA74200E4B02C /* StackedImageLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007F5C6C275AA74200E4B02C /* StackedImageLabelView.swift */; }; 00841DE524477805003CF74A /* AppTabBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE891452445150B0058B642 /* AppTabBarDelegate.swift */; }; 00841DE724477806003CF74A /* AppTabBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE891452445150B0058B642 /* AppTabBarDelegate.swift */; }; + 009B8358298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */; }; + 009B8359298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */; }; + 009B835A298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */; }; + 009B835B298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */; }; + 009B835D298091CD00AABEA3 /* EditNoticesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B835C298091CD00AABEA3 /* EditNoticesView.swift */; }; + 009B835E298091CD00AABEA3 /* EditNoticesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B835C298091CD00AABEA3 /* EditNoticesView.swift */; }; + 009B835F298091CD00AABEA3 /* EditNoticesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B835C298091CD00AABEA3 /* EditNoticesView.swift */; }; + 009B8360298091CD00AABEA3 /* EditNoticesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009B835C298091CD00AABEA3 /* EditNoticesView.swift */; }; 009C8EC229071E720056A3AC /* NSString+Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009C8EC129071E720056A3AC /* NSString+Range.swift */; }; 009C8EC329071E720056A3AC /* NSString+Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009C8EC129071E720056A3AC /* NSString+Range.swift */; }; 009C8EC429071E720056A3AC /* NSString+Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009C8EC129071E720056A3AC /* NSString+Range.swift */; }; @@ -243,6 +255,10 @@ 00AA5AAE276BF2AE005295B0 /* TextBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AA5AAB276BF2AE005295B0 /* TextBarButtonItem.swift */; }; 00AA5AAF276BF2AE005295B0 /* TextBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AA5AAB276BF2AE005295B0 /* TextBarButtonItem.swift */; }; 00AB75BD24D4E8FB0041056A /* WMF.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D844D96C1D6CB2600042D692 /* WMF.framework */; }; + 00B0B3D02978745400DD7893 /* EditNoticesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */; }; + 00B0B3D12978745400DD7893 /* EditNoticesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */; }; + 00B0B3D22978745400DD7893 /* EditNoticesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */; }; + 00B0B3D32978745400DD7893 /* EditNoticesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */; }; 00B16E8E293AACC200EF847F /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B16E8D293AACC200EF847F /* UIImage+Extensions.swift */; }; 00BCB71826DEE04D002C3F72 /* InsetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BCB71726DEE04D002C3F72 /* InsetLabelView.swift */; }; 00BCB71926DEE11B002C3F72 /* InsetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BCB71726DEE04D002C3F72 /* InsetLabelView.swift */; }; @@ -3866,6 +3882,7 @@ 0033D79C24F8193900CAB5B3 /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; 0033D7A024F8199300CAB5B3 /* Sparkline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sparkline.swift; sourceTree = ""; }; 0036C8B2282C2AAA00EADB35 /* Notification+NotificationsCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+NotificationsCenter.swift"; sourceTree = ""; }; + 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNoticesViewModel.swift; sourceTree = ""; }; 003CD3E828EF7C77000158E4 /* TalkPageFindInPageSearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalkPageFindInPageSearchController.swift; sourceTree = ""; }; 0042804025E6E395004945B3 /* FLAnimatedImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLAnimatedImage.m; sourceTree = ""; }; 0042804125E6E395004945B3 /* FLAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLAnimatedImageView.m; sourceTree = ""; }; @@ -4016,12 +4033,15 @@ 007CCF0B26D5A5E400D5EA7C /* NotificationsCenterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsCenterViewModel.swift; sourceTree = ""; }; 007CCF1026D5BF1300D5EA7C /* NotificationsCenterPresentationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsCenterPresentationDelegate.swift; sourceTree = ""; }; 007F5C6C275AA74200E4B02C /* StackedImageLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedImageLabelView.swift; sourceTree = ""; usesTabs = 0; }; + 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNoticesViewController.swift; sourceTree = ""; }; + 009B835C298091CD00AABEA3 /* EditNoticesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNoticesView.swift; sourceTree = ""; }; 009C8EC129071E720056A3AC /* NSString+Range.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+Range.swift"; sourceTree = ""; }; 00A7946A245CA4E60063BA18 /* ArticleSurveyTimerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSurveyTimerController.swift; sourceTree = ""; usesTabs = 0; }; 00A8F58526BDD5E700175B8E /* WidgetSampleContentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetSampleContentTests.swift; sourceTree = ""; }; 00A988072829D92B006D800B /* PushNotificationContentIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationContentIdentifier.swift; sourceTree = ""; }; 00AA5AA6276BF29E005295B0 /* StatusTextBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTextBarButtonItem.swift; sourceTree = ""; }; 00AA5AAB276BF2AE005295B0 /* TextBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBarButtonItem.swift; sourceTree = ""; }; + 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNoticesFetcher.swift; sourceTree = ""; }; 00B16E8D293AACC200EF847F /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; 00BCB71726DEE04D002C3F72 /* InsetLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLabelView.swift; sourceTree = ""; }; 00BCB71C26DEE1C7002C3F72 /* VerticalSpacerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalSpacerView.swift; sourceTree = ""; }; @@ -6382,6 +6402,9 @@ 7A196F5921BF199500D9E4B5 /* SectionEditorWebView.swift */, 7A32078721E40193009E1677 /* SectionEditorMenuItemsController.swift */, 83F26B29220B62EC002D87A4 /* SectionEditorButton.swift */, + 003AD72D2979C512005BDB90 /* EditNoticesViewModel.swift */, + 009B8357298091BC00AABEA3 /* EditNoticesViewController.swift */, + 009B835C298091CD00AABEA3 /* EditNoticesView.swift */, ); name = SectionEditor; sourceTree = ""; @@ -6428,6 +6451,7 @@ B09B03F11CE0FB6300009083 /* PageHistoryFetcher.swift */, 67E8B0A7226A654D00537BC9 /* OldTalkPageFetcher.swift */, 838790B22858009000067B1D /* TalkPageFetcher.swift */, + 00B0B3CF2978745400DD7893 /* EditNoticesFetcher.swift */, ); name = Fetchers; path = Wikipedia/Code; @@ -11525,6 +11549,7 @@ B0E803441C0CD7980065EBC0 /* WMFSearchFetcher.m in Sources */, 83B01F7C23DB0BA2001185F4 /* ArticleViewController+Editing.swift in Sources */, BCA15AE51C0E213300D0A3EA /* LoggingDefaults.swift in Sources */, + 003AD72E2979C512005BDB90 /* EditNoticesViewModel.swift in Sources */, 005E004128DE1F2800721584 /* TalkPageCoffeeRollViewModel.swift in Sources */, 67B5334128416C0D00C33E13 /* UserDataExportCache.swift in Sources */, 7A741DCA207FB9CC00CBAAE2 /* SearchBarExtendedViewController.swift in Sources */, @@ -11536,6 +11561,7 @@ 67B64D5C2507E9FD00FA27F3 /* ArticleAsLivingDocSmallEventCollectionViewCell.swift in Sources */, B0E803911C0CDABE0065EBC0 /* UIView+WMFSnapshotting.m in Sources */, D818D3811ED7254D0076110D /* ColumnarCollectionViewController.swift in Sources */, + 009B835D298091CD00AABEA3 /* EditNoticesView.swift in Sources */, 672428972362113400490629 /* DiffFetcher.swift in Sources */, 83E776A320FFA4D700E26A47 /* DetailTransition.swift in Sources */, 832BD3BC28996B68002623CA /* VanishAccountContentView.swift in Sources */, @@ -11648,6 +11674,7 @@ B0E802B81C0CD2140065EBC0 /* UIBarButtonItem+WMFButtonConvenience.m in Sources */, B08E7E9B1C1FA57A00EC3C99 /* UIViewController+WMFEmptyView.m in Sources */, FFD7B85624B3B384005C2471 /* ReferenceBackLinksViewControllerDelegate.swift in Sources */, + 009B8358298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */, 003CD3E928EF7C77000158E4 /* TalkPageFindInPageSearchController.swift in Sources */, B0524B1F214854E900D8FD8D /* DescriptionWelcomeInitialViewController.swift in Sources */, D80BF0A32347735E00B3B522 /* AppSearchButton.swift in Sources */, @@ -11824,6 +11851,7 @@ 6798332922C3F28A0073CE6F /* UITextView+Extensions.swift in Sources */, B031032D1F677BEC00E2FCF6 /* WMFWelcomeExplorationViewController.swift in Sources */, 7AE99B2E21CC53AB0092BE7F /* TextFontFormattingTableViewController.swift in Sources */, + 00B0B3D02978745400DD7893 /* EditNoticesFetcher.swift in Sources */, 00E5B39F28EB8E2100D2C51A /* TalkPageTopicReplyOnboardingView.swift in Sources */, 6782DBC12343FDCA003FA21B /* DiffListChangeCell.swift in Sources */, 7A32078821E40193009E1677 /* SectionEditorMenuItemsController.swift in Sources */, @@ -12501,6 +12529,7 @@ 8351CE7B20D4424100E32FC1 /* CollectionViewHeader.swift in Sources */, 6734EE7922976AED00F00B05 /* ActionButton.swift in Sources */, 7A0CD24321DFA34100066F68 /* TextFormattingToolbarView.swift in Sources */, + 003AD7312979C512005BDB90 /* EditNoticesViewModel.swift in Sources */, 005E004428DE1F2800721584 /* TalkPageCoffeeRollViewModel.swift in Sources */, 67B5334428416C0F00C33E13 /* UserDataExportCache.swift in Sources */, 83B01F7F23DB0BA2001185F4 /* ArticleViewController+Editing.swift in Sources */, @@ -12512,6 +12541,7 @@ 6782DBFF234537D0003FA21B /* DiffHeaderExtendedView.swift in Sources */, D8A42A971E815A9C00D8E281 /* MWKTitleLanguageController.m in Sources */, D8A42A981E815A9C00D8E281 /* UIView+WMFSnapshotting.m in Sources */, + 009B8360298091CD00AABEA3 /* EditNoticesView.swift in Sources */, 6724289A2362113A00490629 /* DiffFetcher.swift in Sources */, 6747118B25072D1500287951 /* IconTitleBadge.swift in Sources */, 832BD3BF28996B68002623CA /* VanishAccountContentView.swift in Sources */, @@ -12623,6 +12653,7 @@ 83E52BB71F681F940045E776 /* ShareAFactViewController.swift in Sources */, D8A42AD51E815A9C00D8E281 /* UIViewController+WMFEmptyView.m in Sources */, BAA0D91F1F4F165A00091284 /* PageIssuesTableViewController.swift in Sources */, + 009B835B298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */, 003CD3EC28EF7C77000158E4 /* TalkPageFindInPageSearchController.swift in Sources */, B0524B22214854E900D8FD8D /* DescriptionWelcomeInitialViewController.swift in Sources */, D8A42ADD1E815A9C00D8E281 /* NSUserDefaults+WMFApplicationDefaults.swift in Sources */, @@ -12799,6 +12830,7 @@ BA7683C71F30D87D00A487AA /* ProminentSwitch.swift in Sources */, 6782DBAC2343B7FC003FA21B /* DiffHeaderEditorView.swift in Sources */, D8A42B4B1E815A9C00D8E281 /* WMFLanguageCell.m in Sources */, + 00B0B3D32978745400DD7893 /* EditNoticesFetcher.swift in Sources */, 00E5B3A228EB8E2100D2C51A /* TalkPageTopicReplyOnboardingView.swift in Sources */, 7A1469C0220BBE44000A20F1 /* EditHintViewController.swift in Sources */, 6798332C22C3F2950073CE6F /* UITextView+Extensions.swift in Sources */, @@ -13081,6 +13113,7 @@ 83A171D82819B6A70029FB89 /* UNAuthorizationStatus+String.swift in Sources */, 67DC5BEA23A03FE700B03A84 /* ArticleToolbarController.swift in Sources */, D8CE25121E698E2400DAE2E0 /* WMFEmptyView.m in Sources */, + 009B835A298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */, 7AE1FE3221B4A9790068BE9F /* TextFormattingButtonView.swift in Sources */, D8CE25151E698E2400DAE2E0 /* WMFSettingsTableViewCell.m in Sources */, 7A35CB881FD82B6300AAF3B7 /* ReadingListDetailViewController.swift in Sources */, @@ -13238,6 +13271,7 @@ 67B5334328416C0E00C33E13 /* UserDataExportCache.swift in Sources */, 8361474C24223689003E49D3 /* ArticleViewController+Announcements.swift in Sources */, 83023C2020E6584F00EC7592 /* SearchTransition.swift in Sources */, + 003AD7302979C512005BDB90 /* EditNoticesViewModel.swift in Sources */, 677129A224FFF43000E89CA5 /* ArticleAsLivingDocHorizontallyScrollingCell.swift in Sources */, 676F392A2745FB2000F4D33D /* NotificationsCenterFiltersViewModel.swift in Sources */, 0042813725E6E841004945B3 /* NYTPhotoDismissalInteractionController.m in Sources */, @@ -13246,6 +13280,7 @@ 67861A18223C13940044F69D /* FocusNavigationView.swift in Sources */, 6734EE7322976AE300F00B05 /* InfoBannerView.swift in Sources */, 00FCB2C526D839A500F5A47A /* NotificationsCenterCellViewModel.swift in Sources */, + 00B0B3D22978745400DD7893 /* EditNoticesFetcher.swift in Sources */, D8BDA8BE1E71B8C90031F4BF /* WMFDeleteBackwardReportingTextField.swift in Sources */, 83023C0720E51DDF00EC7592 /* SearchLanguagesBarViewController.swift in Sources */, D8CE25771E698E2400DAE2E0 /* MWKLanguageLinkFetcher.m in Sources */, @@ -13286,6 +13321,7 @@ BA4524251F32500C00439C42 /* TextSizeChangeExampleViewController.swift in Sources */, D8CE25891E698E2400DAE2E0 /* UIImageView+WMFFaceDetectionBasedOnUIApplicationSharedApplication.m in Sources */, D8CE258E1E698E2400DAE2E0 /* UIView+WMFFrameUtils.m in Sources */, + 009B835F298091CD00AABEA3 /* EditNoticesView.swift in Sources */, D8E6FF6D24056AC300686272 /* ArticleViewController+ContextMenu.swift in Sources */, 8386BDFC2386D754007EE89D /* SinglePageWebViewController.swift in Sources */, D8CE258F1E698E2400DAE2E0 /* WMFWelcomeAnimationViewControllers.swift in Sources */, @@ -13683,6 +13719,7 @@ 83A171D72819B6A60029FB89 /* UNAuthorizationStatus+String.swift in Sources */, 67DC5BEB23A03FE700B03A84 /* ArticleToolbarController.swift in Sources */, D8EC3DFE1E9BDA35006712EB /* PlaceSearch.swift in Sources */, + 009B8359298091BC00AABEA3 /* EditNoticesViewController.swift in Sources */, 7AE1FE3321B4A9790068BE9F /* TextFormattingButtonView.swift in Sources */, D8EC3E001E9BDA35006712EB /* PlaceSearchSuggestionController.swift in Sources */, B0B423631EF9D6A300D3DC4C /* OnThisDayViewController.swift in Sources */, @@ -13840,6 +13877,7 @@ 67B5334228416C0E00C33E13 /* UserDataExportCache.swift in Sources */, 7AFC79FA21B0367700BB0C50 /* TextFormattingTableViewController.swift in Sources */, 6782DBC32343FDCA003FA21B /* DiffListChangeCell.swift in Sources */, + 003AD72F2979C512005BDB90 /* EditNoticesViewModel.swift in Sources */, D8EC3E631E9BDA35006712EB /* WMFWelcomeContainerViewController.swift in Sources */, 676F39292745FB2000F4D33D /* NotificationsCenterFiltersViewModel.swift in Sources */, 0042813625E6E841004945B3 /* NYTPhotoDismissalInteractionController.m in Sources */, @@ -13848,6 +13886,7 @@ D8EC3E641E9BDA35006712EB /* WMFAccountCreator.swift in Sources */, D8EC3E651E9BDA35006712EB /* UIViewController+WMFHideKeyboard.swift in Sources */, 00FCB2C426D839A500F5A47A /* NotificationsCenterCellViewModel.swift in Sources */, + 00B0B3D12978745400DD7893 /* EditNoticesFetcher.swift in Sources */, 674711842507253500287951 /* ArticleAsLivingDocSnippetCollectionViewCell.swift in Sources */, D8EC3E671E9BDA35006712EB /* WeakScriptMessageDelegate.swift in Sources */, D8EC3E6D1E9BDA35006712EB /* UIView+Animations.swift in Sources */, @@ -13888,6 +13927,7 @@ D8EC3E8A1E9BDA35006712EB /* UIView+WMFFrameUtils.m in Sources */, D8E6FF6E24056AC300686272 /* ArticleViewController+ContextMenu.swift in Sources */, 8386BDFD2386D754007EE89D /* SinglePageWebViewController.swift in Sources */, + 009B835E298091CD00AABEA3 /* EditNoticesView.swift in Sources */, D8EC3E8B1E9BDA35006712EB /* WMFWelcomeAnimationViewControllers.swift in Sources */, D8EC3E8C1E9BDA35006712EB /* TableOfContentsCell.swift in Sources */, FFD7B85724B3B39A005C2471 /* ReferenceBackLinksViewControllerDelegate.swift in Sources */, diff --git a/Wikipedia/Code/ArticleViewController+Editing.swift b/Wikipedia/Code/ArticleViewController+Editing.swift index 1f281ee0f23..f52665c7e26 100644 --- a/Wikipedia/Code/ArticleViewController+Editing.swift +++ b/Wikipedia/Code/ArticleViewController+Editing.swift @@ -18,27 +18,20 @@ extension ArticleViewController { sectionEditVC.editFunnel = editFunnel let navigationController = WMFThemeableNavigationController(rootViewController: sectionEditVC, theme: theme) navigationController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext + let needsIntro = !UserDefaults.standard.didShowEditingOnboarding if needsIntro { - navigationController.view.alpha = 0 - } - - sectionEditVC.shouldFocusWebView = !needsIntro - let showIntro: (() -> Void)? = { - self.editFunnel.logOnboardingPresentation(initiatedBy: funnelSource, language: self.articleLanguageCode) - let editingWelcomeViewController = EditingWelcomeViewController(theme: self.theme) { - sectionEditVC.shouldFocusWebView = true + editFunnel.logOnboardingPresentation(initiatedBy: funnelSource, language: articleLanguageCode) + let editingWelcomeViewController = EditingWelcomeViewController(theme: theme) { + self.present(navigationController, animated: true) } - editingWelcomeViewController.apply(theme: self.theme) - navigationController.present(editingWelcomeViewController, animated: true) { + editingWelcomeViewController.apply(theme: theme) + present(editingWelcomeViewController, animated: true) { UserDefaults.standard.didShowEditingOnboarding = true - navigationController.view.alpha = 1 - } - } - present(navigationController, animated: !needsIntro) { - if needsIntro { - showIntro?() } + + } else { + present(navigationController, animated: true) } } @@ -202,8 +195,10 @@ extension ArticleViewController: SectionEditorViewControllerDelegate { } } - func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController) { - dismiss(animated: true) + func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController, navigateToURL url: URL?) { + dismiss(animated: true) { + self.navigate(to: url) + } } func sectionEditorDidFinishLoadingWikitext(_ sectionEditor: SectionEditorViewController) { diff --git a/Wikipedia/Code/EditNoticesFetcher.swift b/Wikipedia/Code/EditNoticesFetcher.swift new file mode 100644 index 00000000000..7a05284af67 --- /dev/null +++ b/Wikipedia/Code/EditNoticesFetcher.swift @@ -0,0 +1,56 @@ +class EditNoticesFetcher: Fetcher { + + // MARK: - Nested Types + + struct Notice: Codable { + let name: String + let description: String + } + + private struct Response: Codable { + struct VisualEditor: Codable { + let notices: [String: String]? + } + + enum CodingKeys: String, CodingKey { + case visualEditor = "visualeditor" + } + + let visualEditor: VisualEditor? + } + + // MARK: - Public + + func fetchNotices(for articleURL: URL, completion: @escaping (Result<[Notice], Error>) -> Void) { + guard let title = articleURL.wmf_title else { + completion(.failure(RequestError.invalidParameters)) + return + } + + let parameters: [String: Any] = [ + "action": "visualeditor", + "paction": "metadata", + "page": title, + "errorsuselocal": "1", + "formatversion" : "2", + "format": "json" + ] + + performDecodableMediaWikiAPIGET(for: articleURL, with: parameters) { (result: Result) in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success(let response): + var notices: [Notice] = [] + if let rawNotices = response.visualEditor?.notices?.filter({ $0.key.contains("editnotice")}) { + for rawNotice in rawNotices { + notices.append(Notice(name: rawNotice.key, description: rawNotice.value)) + } + } + + completion(.success(notices)) + } + } + } + +} diff --git a/Wikipedia/Code/EditNoticesView.swift b/Wikipedia/Code/EditNoticesView.swift new file mode 100644 index 00000000000..f72ae26e96b --- /dev/null +++ b/Wikipedia/Code/EditNoticesView.swift @@ -0,0 +1,263 @@ +import UIKit + +final class EditNoticesView: SetupView { + + // MARK: - UI Elements + + lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.showsVerticalScrollIndicator = false + return scrollView + }() + + lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 0 + stackView.isUserInteractionEnabled = true + return stackView + }() + + lazy var editNoticesImageContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(editNoticesImageView) + NSLayoutConstraint.activate([ + editNoticesImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10), + editNoticesImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10), + editNoticesImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + editNoticesImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + return view + }() + + lazy var editNoticesImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(systemName: "exclamationmark.circle.fill")) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFill + + let imageWidthConstraint = imageView.widthAnchor.constraint(equalToConstant: 50) + imageWidthConstraint.priority = .required + let imageHeightConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor) + imageHeightConstraint.priority = .required + + NSLayoutConstraint.activate([ + imageWidthConstraint, + imageHeightConstraint + ]) + + return imageView + }() + + lazy var editNoticesTitle: UILabel = { + let label = UILabel() + label.text = CommonStrings.editNotices + label.textAlignment = .center + label.numberOfLines = 0 + label.font = UIFont.wmf_scaledSystemFont(forTextStyle: .title1, weight: .semibold, size: 20) + label.adjustsFontForContentSizeCategory = true + return label + }() + + lazy var editNoticesSubtitle: UILabel = { + let label = UILabel() + label.text = WMFLocalizedString("edit-notices-please-read", value: "Please read before editing", comment: "Subtitle displayed in edit notices view.") + label.textAlignment = .center + label.numberOfLines = 0 + label.font = UIFont.wmf_scaledSystemFont(forTextStyle: .title2, weight: .semibold, size: 15) + label.adjustsFontForContentSizeCategory = true + return label + }() + + lazy var contentContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + lazy var doneContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + lazy var doneButton: UIButton = { + let button = UIButton(type: .system) + button.titleLabel?.font = UIFont.wmf_scaledSystemFont(forTextStyle: .body, weight: .medium, size: 17) + button.titleLabel?.adjustsFontForContentSizeCategory = true + button.setTitle(CommonStrings.doneTitle, for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + lazy var footerContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + lazy var footerStack: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .horizontal + stackView.distribution = .fill + stackView.spacing = 20 + return stackView + }() + + lazy var footerSwitchLabel: UILabel = { + let label = UILabel() + label.text = WMFLocalizedString("edit-notices-always-display", value: "Always display edit notices", comment: "Title for toggle switch label in edit notices view.") + label.numberOfLines = 0 + label.font = UIFont.wmf_scaledSystemFont(forTextStyle: .body, weight: .regular, size: 15) + label.adjustsFontForContentSizeCategory = true + label.translatesAutoresizingMaskIntoConstraints = false + + label.setContentCompressionResistancePriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + + return label + }() + + lazy var switchContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(toggleSwitch) + NSLayoutConstraint.activate([ + toggleSwitch.centerYAnchor.constraint(equalTo: view.centerYAnchor), + toggleSwitch.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor), + toggleSwitch.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor), + toggleSwitch.leadingAnchor.constraint(equalTo: view.leadingAnchor), + toggleSwitch.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + + return view + }() + + lazy var toggleSwitch: UISwitch = { + let toggle = UISwitch() + toggle.translatesAutoresizingMaskIntoConstraints = false + toggle.setContentHuggingPriority(.required, for: .vertical) + toggle.setContentCompressionResistancePriority(.required, for: .vertical) + return toggle + }() + + lazy var textView: UITextView = { + let textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isScrollEnabled = false + textView.isEditable = false + textView.adjustsFontForContentSizeCategory = true + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + return textView + }() + + // MARK: - Private Properties + + private var doneButtonTrailingConstraint: NSLayoutConstraint! + + private var doneButtonTrailingMargin: CGFloat { + return traitCollection.verticalSizeClass == .compact ? -20 : -8 + } + + // MARK: - Override + + override func setup() { + // Top "navigation" bar + + addSubview(doneContainer) + doneContainer.addSubview(doneButton) + doneButtonTrailingConstraint = doneButton.trailingAnchor.constraint(equalTo: doneContainer.readableContentGuide.trailingAnchor, constant: doneButtonTrailingMargin) + + // Primary content container, scrollable + + addSubview(contentContainer) + contentContainer.addSubview(scrollView) + scrollView.addSubview(stackView) + + stackView.addArrangedSubview(editNoticesImageContainer) + stackView.addArrangedSubview(VerticalSpacerView.spacerWith(space: 10)) + stackView.addArrangedSubview(editNoticesTitle) + stackView.addArrangedSubview(VerticalSpacerView.spacerWith(space: 6)) + stackView.addArrangedSubview(editNoticesSubtitle) + stackView.addArrangedSubview(VerticalSpacerView.spacerWith(space: 32)) + stackView.addArrangedSubview(textView) + + // Footer label/switch + + addSubview(footerContainer) + footerContainer.addSubview(footerStack) + + footerStack.addArrangedSubview(footerSwitchLabel) + footerStack.addArrangedSubview(switchContainer) + + NSLayoutConstraint.activate([ + doneContainer.topAnchor.constraint(equalTo: topAnchor), + doneContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + doneContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + doneContainer.bottomAnchor.constraint(equalTo: contentContainer.topAnchor), + + doneButtonTrailingConstraint, + doneButton.topAnchor.constraint(equalTo: doneContainer.topAnchor, constant: 16), + doneButton.bottomAnchor.constraint(equalTo: doneContainer.bottomAnchor, constant: -5), + + contentContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + contentContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + + scrollView.topAnchor.constraint(equalTo: contentContainer.topAnchor), + scrollView.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: contentContainer.readableContentGuide.leadingAnchor, constant: 24), + scrollView.trailingAnchor.constraint(equalTo: contentContainer.readableContentGuide.trailingAnchor, constant: -24), + + stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), + stackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), + + footerContainer.topAnchor.constraint(equalTo: contentContainer.bottomAnchor), + footerContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + footerContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + footerContainer.bottomAnchor.constraint(equalTo: bottomAnchor), + + footerStack.leadingAnchor.constraint(equalTo: footerContainer.readableContentGuide.leadingAnchor, constant: 20), + footerStack.trailingAnchor.constraint(equalTo: footerContainer.readableContentGuide.trailingAnchor, constant: -20), + footerStack.topAnchor.constraint(equalTo: footerContainer.topAnchor, constant: 20), + footerStack.bottomAnchor.constraint(equalTo: footerContainer.readableContentGuide.bottomAnchor) + ]) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + doneButtonTrailingConstraint.constant = doneButtonTrailingMargin + doneContainer.setNeedsLayout() + } + + // MARK: - Public + + func configure(viewModel: EditNoticesViewModel, theme: Theme) { + let attributedNoticeString = NSMutableAttributedString() + for notice in viewModel.notices { + let noticeString = notice.description.byAttributingHTML(with: .callout, matching: traitCollection, color: theme.colors.primaryText, handlingLinks: true) + attributedNoticeString.append(noticeString) + } + + textView.attributedText = attributedNoticeString.removingInitialNewlineCharacters().removingRepetitiveNewlineCharacters() + + // Update colors + backgroundColor = theme.colors.paperBackground + doneButton.setTitleColor(theme.colors.link, for: .normal) + editNoticesImageView.tintColor = theme.colors.primaryText + editNoticesTitle.textColor = theme.colors.primaryText + editNoticesSubtitle.textColor = theme.colors.primaryText + textView.backgroundColor = theme.colors.paperBackground + textView.linkTextAttributes = [.foregroundColor: theme.colors.link] + footerSwitchLabel.textColor = theme.colors.primaryText + } + +} diff --git a/Wikipedia/Code/EditNoticesViewController.swift b/Wikipedia/Code/EditNoticesViewController.swift new file mode 100644 index 00000000000..934a3549d38 --- /dev/null +++ b/Wikipedia/Code/EditNoticesViewController.swift @@ -0,0 +1,75 @@ +import UIKit + +protocol EditNoticesViewControllerDelegate: AnyObject { + func editNoticesControllerUserTapped(url: URL) +} + +class EditNoticesViewController: ThemeableViewController { + + // MARK: - Properties + + let viewModel: EditNoticesViewModel + + weak var delegate: EditNoticesViewControllerDelegate? + + var editNoticesView: EditNoticesView { + return view as! EditNoticesView + } + + // MARK: - Lifecycle + + init(theme: Theme, viewModel: EditNoticesViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + self.theme = theme + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let editNoticesView = EditNoticesView(frame: UIScreen.main.bounds) + view = editNoticesView + editNoticesView.configure(viewModel: viewModel, theme: theme) + + editNoticesView.doneButton.addTarget(self, action: #selector(dismissView), for: .primaryActionTriggered) + editNoticesView.toggleSwitch.addTarget(self, action: #selector(didToggleSwitch(_:)), for: .valueChanged) + editNoticesView.toggleSwitch.isOn = UserDefaults.standard.wmf_alwaysDisplayEditNotices + editNoticesView.textView.delegate = self + } + + // MARK: - Actions + + @objc private func dismissView() { + dismiss(animated: true) + } + + @objc private func didToggleSwitch(_ sender: UISwitch) { + UserDefaults.standard.wmf_alwaysDisplayEditNotices = sender.isOn + } + + // MARK: - Themeable + + override func apply(theme: Theme) { + super.apply(theme: theme) + editNoticesView.configure(viewModel: viewModel, theme: theme) + } + +} + +extension EditNoticesViewController: UITextViewDelegate { + + func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + guard let url = URL(string: url.absoluteString, relativeTo: viewModel.siteURL) else { + return false + } + + dismiss(animated: true) { + self.delegate?.editNoticesControllerUserTapped(url: url) + } + + return false + } + +} diff --git a/Wikipedia/Code/EditNoticesViewModel.swift b/Wikipedia/Code/EditNoticesViewModel.swift new file mode 100644 index 00000000000..555c8b0cc1c --- /dev/null +++ b/Wikipedia/Code/EditNoticesViewModel.swift @@ -0,0 +1,17 @@ +import Foundation + +final class EditNoticesViewModel { + + // MARK: - Properties + + var siteURL: URL + var notices: [EditNoticesFetcher.Notice] + + // MARK: - Public + + init(siteURL: URL, notices: [EditNoticesFetcher.Notice]) { + self.siteURL = siteURL + self.notices = notices + } + +} diff --git a/Wikipedia/Code/NSUserDefaults+WMFExtensions.swift b/Wikipedia/Code/NSUserDefaults+WMFExtensions.swift index 9a4f3c2b4bd..32ef7dd4f14 100644 --- a/Wikipedia/Code/NSUserDefaults+WMFExtensions.swift +++ b/Wikipedia/Code/NSUserDefaults+WMFExtensions.swift @@ -31,6 +31,7 @@ let WMFUserHasOnboardedToContributingToTalkPages = "WMFUserHasOnboardedToContrib let WMFDidShowNotificationsCenterPushOptInPanel = "WMFDidShowNotificationsCenterPushOptInPanel" let WMFSubscribedToEchoNotifications = "WMFSubscribedToEchoNotifications" let WMFTappedToImportSharedReadingListSurvey = "WMFTappedToImportSharedReadingListSurvey" +public let WMFAlwaysDisplayEditNotices = "WMFAlwaysDisplayEditNotices" @objc public enum WMFAppDefaultTabType: Int { case explore @@ -522,6 +523,19 @@ let WMFTappedToImportSharedReadingListSurvey = "WMFTappedToImportSharedReadingLi } } + @objc var wmf_alwaysDisplayEditNotices: Bool { + get { + if object(forKey: WMFAlwaysDisplayEditNotices) == nil { + return true + } + return bool(forKey: WMFAlwaysDisplayEditNotices) + } + set { + set(newValue, forKey: WMFAlwaysDisplayEditNotices) + } + } + + #if UI_TEST @objc func wmf_isFastlaneSnapshotInProgress() -> Bool { return bool(forKey: "FASTLANE_SNAPSHOT") diff --git a/Wikipedia/Code/SectionEditorNavigationItemController.swift b/Wikipedia/Code/SectionEditorNavigationItemController.swift index 0007db47c9c..6234ec27817 100644 --- a/Wikipedia/Code/SectionEditorNavigationItemController.swift +++ b/Wikipedia/Code/SectionEditorNavigationItemController.swift @@ -4,6 +4,7 @@ protocol SectionEditorNavigationItemControllerDelegate: AnyObject { func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapUndoButton undoButton: UIBarButtonItem) func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapRedoButton redoButton: UIBarButtonItem) func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapReadingThemesControlsButton readingThemesControlsButton: UIBarButtonItem) + func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapEditNoticesButton: UIBarButtonItem) } class SectionEditorNavigationItemController: NSObject, Themeable { @@ -73,6 +74,10 @@ class SectionEditorNavigationItemController: NSObject, Themeable { private(set) lazy var undoButton: BarButtonItem = { return BarButtonItem(image: #imageLiteral(resourceName: "undo"), style: .plain, target: self, action: #selector(undo(_ :)), tintColorKeyPath: \Theme.colors.inputAccessoryButtonTint, accessibilityLabel: CommonStrings.undo) }() + + private lazy var editNoticesButton: BarButtonItem = { + return BarButtonItem(image: UIImage(systemName: "exclamationmark.circle.fill") , style: .plain, target: self, action: #selector(editNotices(_ :)), tintColorKeyPath: \Theme.colors.inputAccessoryButtonTint, accessibilityLabel: CommonStrings.editNotices) + }() private lazy var readingThemesControlsButton: BarButtonItem = { return BarButtonItem(image: #imageLiteral(resourceName: "settings-appearance"), style: .plain, target: self, action: #selector(showReadingThemesControls(_ :)), tintColorKeyPath: \Theme.colors.inputAccessoryButtonTint, accessibilityLabel: CommonStrings.readingThemesControls) @@ -100,11 +105,22 @@ class SectionEditorNavigationItemController: NSObject, Themeable { @objc private func redo(_ sender: UIBarButtonItem) { delegate?.sectionEditorNavigationItemController(self, didTapRedoButton: sender) } + + @objc private func editNotices(_ sender: UIBarButtonItem) { + delegate?.sectionEditorNavigationItemController(self, didTapEditNoticesButton: sender) + } @objc private func showReadingThemesControls(_ sender: UIBarButtonItem) { delegate?.sectionEditorNavigationItemController(self, didTapReadingThemesControlsButton: sender) } + func addEditNoticesButton() { + navigationItem?.rightBarButtonItems?.append(contentsOf: [ + UIBarButtonItem.wmf_barButtonItem(ofFixedWidth: 20), + editNoticesButton + ]) + } + private func configureNavigationButtonItems() { let closeButton = BarButtonItem(image: #imageLiteral(resourceName: "close"), style: .plain, target: self, action: #selector(close(_ :)), tintColorKeyPath: \Theme.colors.chromeText) closeButton.accessibilityLabel = CommonStrings.closeButtonAccessibilityLabel diff --git a/Wikipedia/Code/SectionEditorViewController.swift b/Wikipedia/Code/SectionEditorViewController.swift index a6a269b3481..b44a41e3a26 100644 --- a/Wikipedia/Code/SectionEditorViewController.swift +++ b/Wikipedia/Code/SectionEditorViewController.swift @@ -1,7 +1,7 @@ import CocoaLumberjackSwift protocol SectionEditorViewControllerDelegate: AnyObject { - func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController) + func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController, navigateToURL: URL?) func sectionEditorDidFinishEditing(_ sectionEditor: SectionEditorViewController, result: Result) func sectionEditorDidFinishLoadingWikitext(_ sectionEditor: SectionEditorViewController) } @@ -20,7 +20,11 @@ class SectionEditorViewController: ViewController { private var dataStore: MWKDataStore private var webView: SectionEditorWebView! + + private let initialFetchGroup: WMFTaskGroup = WMFTaskGroup() private let sectionFetcher: SectionFetcher + private let editNoticesFetcher: EditNoticesFetcher + private var editNoticesViewModel: EditNoticesViewModel? = nil private var inputViewsController: SectionEditorInputViewsController! private var messagingController: SectionEditorWebViewMessagingController! @@ -76,6 +80,7 @@ class SectionEditorViewController: ViewController { self.messagingController = messagingController ?? SectionEditorWebViewMessagingController() languageCode = articleURL.wmf_languageCode ?? NSLocale.current.languageCode ?? "en" self.sectionFetcher = SectionFetcher(session: dataStore.session, configuration: dataStore.configuration) + self.editNoticesFetcher = EditNoticesFetcher(session: dataStore.session, configuration: dataStore.configuration) super.init(theme: theme) } @@ -84,6 +89,7 @@ class SectionEditorViewController: ViewController { } override func viewDidLoad() { + loadEditNotices() loadWikitext() navigationItemController = SectionEditorNavigationItemController(navigationItem: navigationItem) @@ -102,7 +108,13 @@ class SectionEditorViewController: ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - selectLastSelectionIfNeeded() + + initialFetchGroup.waitInBackground { + self.presentEditNoticesIfNecessary() + self.selectLastSelectionIfNeeded() + + } + } override func viewWillDisappear(_ animated: Bool) { @@ -117,6 +129,24 @@ class SectionEditorViewController: ViewController { @objc func keyboardDidHide() { inputViewsController.resetFormattingAndStyleSubmenus() } + + private func presentEditNoticesIfNecessary() { + guard UserDefaults.standard.wmf_alwaysDisplayEditNotices else { + return + } + + presentEditNoticesIfAvailable() + } + + private func presentEditNoticesIfAvailable() { + guard let editNoticesViewModel = self.editNoticesViewModel else { + return + } + + let editNoticesViewController = EditNoticesViewController(theme: self.theme, viewModel: editNoticesViewModel) + editNoticesViewController.delegate = self + present(editNoticesViewController, animated: true) + } private func setupFocusNavigationView() { @@ -327,7 +357,24 @@ class SectionEditorViewController: ViewController { messagingController.setWikitext(wikitext, completionHandler: completionHandler) } + private func loadEditNotices() { + initialFetchGroup.enter() + editNoticesFetcher.fetchNotices(for: articleURL) { (result) in + if case let .success(notices) = result, !notices.isEmpty, let siteURL = self.articleURL.wmf_site { + self.editNoticesViewModel = EditNoticesViewModel(siteURL: siteURL, notices: notices) + + DispatchQueue.main.async { + self.navigationItemController.addEditNoticesButton() + self.navigationItemController.apply(theme: self.theme) + } + } + + self.initialFetchGroup.leave() + } + } + private func loadWikitext() { + initialFetchGroup.enter() let isShowingStatusMessage = shouldFocusWebView if isShowingStatusMessage { let message = WMFLocalizedString("wikitext-downloading", value: "Loading content...", comment: "Alert text shown when obtaining latest revision of the section being edited") @@ -343,14 +390,16 @@ class SectionEditorViewController: ViewController { self.didFocusWebViewCompletion = { WMFAlertManager.sharedInstance.showErrorAlert(error as NSError, sticky: true, dismissPreviousAlerts: true) } + self.initialFetchGroup.leave() case .success(let response): self.wikitext = response.wikitext self.handle(protection: response.protection) + self.initialFetchGroup.leave() } } } } - + private func handle(protection: [SectionFetcher.Protection]) { let allowedGroups = protection.map { $0.level } guard !allowedGroups.isEmpty else { @@ -372,7 +421,7 @@ class SectionEditorViewController: ViewController { // MARK: - Accessibility override func accessibilityPerformEscape() -> Bool { - delegate?.sectionEditorDidCancelEditing(self) + delegate?.sectionEditorDidCancelEditing(self, navigateToURL: nil) return true } @@ -427,10 +476,10 @@ class SectionEditorViewController: ViewController { messagingController.setAdjustedContentInset(newInset: newContentInset) } - fileprivate func showAlert() { + fileprivate func showDestructiveDismissAlert(navigateToURLOnCompletion url: URL? = nil) { let alert = UIAlertController(title: CommonStrings.editorExitConfirmationTitle, message: CommonStrings.editorExitConfirmationBody, preferredStyle: .alert) let confirmClose = UIAlertAction(title: CommonStrings.discardEditsActionTitle, style: .destructive) { _ in - self.closeEditor() + self.closeEditor(navigateToURL: url) } alert.addAction(confirmClose) let cancel = UIAlertAction(title: CommonStrings.cancelActionTitle, style: .default) @@ -438,8 +487,8 @@ class SectionEditorViewController: ViewController { present(alert, animated: true) } - fileprivate func closeEditor() { - delegate?.sectionEditorDidCancelEditing(self) + fileprivate func closeEditor(navigateToURL url: URL? = nil) { + delegate?.sectionEditorDidCancelEditing(self, navigateToURL: url) } } @@ -476,7 +525,7 @@ extension SectionEditorViewController: SectionEditorNavigationItemControllerDele func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapCloseButton closeButton: UIBarButtonItem) { if navigationItemController.progressButton.isEnabled && navigationItemController.undoButton.isEnabled { - showAlert() + showDestructiveDismissAlert() } else { closeEditor() } @@ -489,6 +538,10 @@ extension SectionEditorViewController: SectionEditorNavigationItemControllerDele func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapRedoButton redoButton: UIBarButtonItem) { messagingController.redo() } + + func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapEditNoticesButton: UIBarButtonItem) { + presentEditNoticesIfAvailable() + } func sectionEditorNavigationItemController(_ sectionEditorNavigationItemController: SectionEditorNavigationItemController, didTapReadingThemesControlsButton readingThemesControlsButton: UIBarButtonItem) { @@ -770,6 +823,14 @@ extension SectionEditorViewController: EditingFlowViewController { } +extension SectionEditorViewController: EditNoticesViewControllerDelegate { + + func editNoticesControllerUserTapped(url: URL) { + showDestructiveDismissAlert(navigateToURLOnCompletion: url) + } + +} + #if (TEST) // MARK: Helpers for testing extension SectionEditorViewController { diff --git a/Wikipedia/Localizations/en.lproj/Localizable.strings b/Wikipedia/Localizations/en.lproj/Localizable.strings index b50f1416145..d87ba413656 100644 --- a/Wikipedia/Localizations/en.lproj/Localizable.strings +++ b/Wikipedia/Localizations/en.lproj/Localizable.strings @@ -256,6 +256,9 @@ "edit-menu-item-could-not-find-selection-alert-title" = "The text that you selected could not be located"; "edit-minor-learn-more-text" = "Learn more about minor edits"; "edit-minor-text" = "This is a minor edit"; +"edit-notices" = "Edit notices"; +"edit-notices-always-display" = "Always display edit notices"; +"edit-notices-please-read" = "Please read before editing"; "edit-ordered-list-accessibility-label" = "Make current line ordered list"; "edit-ordered-list-remove-accessibility-label" = "Remove ordered list from current line"; "edit-published" = "Edit published"; diff --git a/Wikipedia/Localizations/qqq.lproj/Localizable.strings b/Wikipedia/Localizations/qqq.lproj/Localizable.strings index cbc40d3a3e1..38765310d99 100644 --- a/Wikipedia/Localizations/qqq.lproj/Localizable.strings +++ b/Wikipedia/Localizations/qqq.lproj/Localizable.strings @@ -256,6 +256,9 @@ "edit-menu-item-could-not-find-selection-alert-title" = "Title for alert informing user their text selection could not be located in the article wikitext."; "edit-minor-learn-more-text" = "Text for minor edits learn more button"; "edit-minor-text" = "Text for minor edit label"; +"edit-notices" = "Title text and accessibility label for edit notices button."; +"edit-notices-always-display" = "Title for toggle switch label in edit notices view."; +"edit-notices-please-read" = "Subtitle displayed in edit notices view."; "edit-ordered-list-accessibility-label" = "Accessibility label for ordered list button"; "edit-ordered-list-remove-accessibility-label" = "Accessibility label for remove ordered list button"; "edit-published" = "Title edit published panel letting user know their edit was saved."; diff --git a/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings b/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings index 5f4849dc293..c8059f2387d 100644 Binary files a/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings and b/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings differ diff --git a/WikipediaUnitTests/Code/SectionEditorFindAndReplaceTests.swift b/WikipediaUnitTests/Code/SectionEditorFindAndReplaceTests.swift index 8ebe54f9d21..29102a9e12a 100644 --- a/WikipediaUnitTests/Code/SectionEditorFindAndReplaceTests.swift +++ b/WikipediaUnitTests/Code/SectionEditorFindAndReplaceTests.swift @@ -397,7 +397,7 @@ class SectionEditorFindAndReplaceTests: XCTestCase { } extension SectionEditorFindAndReplaceTests: SectionEditorViewControllerDelegate { - func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController) { + func sectionEditorDidCancelEditing(_ sectionEditor: SectionEditorViewController, navigateToURL: URL?) { // no-op }