-
-
Notifications
You must be signed in to change notification settings - Fork 770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor out ArticleViewController into extensions #4942
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@l-olson1214 Thanks for taking this on! I'm totally on board to move related methods out into extensions to more closely align with the original way ArticleViewController was built.
I commented on some methods in your extensions that I think should be shuffled around, and maybe some new extensions could be made - let me know what you think. One other thing:
For any new file that lives in the Wikipedia target, we should also check the Staging and Experimental targets in this screenshot. Otherwise the Experimental and Staging schemes won't compile. Thanks!
@@ -385,6 +385,16 @@ | |||
0EF8634E1C19E02700006D2D /* WMFEmptyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0EF8634D1C19E02700006D2D /* WMFEmptyView.xib */; }; | |||
0EF863511C19E4F100006D2D /* WMFEmptyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EF863501C19E4F100006D2D /* WMFEmptyView.m */; }; | |||
19A175F095F5197BA20EA8BA /* NSUserActivity+WMFExtensionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 19A172FA6AE61E76FCEF4259 /* NSUserActivity+WMFExtensionsTest.m */; }; | |||
375B0AC92C7AC0DE00BDE00A /* ArticleViewController + AltTextExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375B0AC82C7AC0DE00BDE00A /* ArticleViewController + AltTextExperiment.swift */; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A space in the filename slipped in here.
self.navigationItem.titleView = nil | ||
self.title = altTextExperimentViewModel.localizedStrings.articleNavigationBarTitle | ||
|
||
let rightBarButtonItem = | ||
UIBarButtonItem( | ||
image: WMFSFSymbolIcon.for(symbol: .ellipsisCircle), | ||
primaryAction: nil, | ||
menu: overflowMenu | ||
) | ||
navigationItem.rightBarButtonItem = rightBarButtonItem | ||
rightBarButtonItem.tintColor = theme.colors.link | ||
|
||
self.navigationBar.updateNavigationItems() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is a little tricky in that only the code in this conditional applies to Alt Text. Can you rework so that only this code lives in this extension, but the rest (else code, setupWebView
, etc.) is back in ArticleViewController.swift?
extension ArticleViewController: UISheetPresentationControllerDelegate { | ||
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) { | ||
|
||
guard altTextExperimentViewModel != nil else { | ||
return | ||
} | ||
|
||
let oldContentInset = webView.scrollView.contentInset | ||
|
||
if let selectedDetentIdentifier = sheetPresentationController.selectedDetentIdentifier { | ||
switch selectedDetentIdentifier { | ||
case .medium, .large: | ||
webView.scrollView.contentInset = UIEdgeInsets(top: oldContentInset.top, left: oldContentInset.left, bottom: view.bounds.height * 0.65, right: oldContentInset.right) | ||
default: | ||
logMinimized() | ||
webView.scrollView.contentInset = UIEdgeInsets(top: oldContentInset.top, left: oldContentInset.left, bottom: 75, right: oldContentInset.right) | ||
} | ||
} | ||
} | ||
|
||
private func logMinimized() { | ||
if let project = project { | ||
EditInteractionFunnel.shared.logAltTextInputDidMinimize(project: project) | ||
} | ||
} | ||
} | ||
|
||
extension ArticleViewController: WMFAltTextExperimentModalSheetLoggingDelegate { | ||
|
||
func didTriggerCharacterWarning() { | ||
if let project = project { | ||
EditInteractionFunnel.shared.logAltTextInputDidTriggerWarning(project: project) | ||
} | ||
} | ||
|
||
func didTapFileName() { | ||
if let project = project { | ||
EditInteractionFunnel.shared.logAltTextInputDidTapFileName(project: project) | ||
} | ||
} | ||
|
||
func didAppear() { | ||
if let project = project { | ||
EditInteractionFunnel.shared.logAltTextInputDidAppear(project: project) | ||
} | ||
} | ||
|
||
func didFocusTextView() { | ||
if let project = project { | ||
EditInteractionFunnel.shared.logAltTextInputDidFocus(project: project) | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This chunk of code is actually Alt Text related, can you move it to the Alt Text extension file?
private func setupForAltTextExperiment() { | ||
|
||
guard let altTextExperimentViewModel, | ||
altTextBottomSheetViewModel != nil else { | ||
return | ||
} | ||
|
||
let oldContentInset = webView.scrollView.contentInset | ||
webView.scrollView.contentInset = UIEdgeInsets(top: oldContentInset.top, left: oldContentInset.left, bottom: view.bounds.height * 0.65, right: oldContentInset.right) | ||
messagingController.hideEditPencils() | ||
messagingController.scrollToNewImage(filename: altTextExperimentViewModel.filename) | ||
|
||
presentAltTextModalSheet() | ||
} | ||
|
||
func presentAltTextModalSheet() { | ||
|
||
guard altTextExperimentViewModel != nil, | ||
let altTextBottomSheetViewModel else { | ||
return | ||
} | ||
|
||
let bottomSheetViewController = WMFAltTextExperimentModalSheetViewController(viewModel: altTextBottomSheetViewModel, delegate: self, loggingDelegate: self) | ||
|
||
if #available(iOS 16.0, *) { | ||
if let sheet = bottomSheetViewController.sheetPresentationController { | ||
sheet.delegate = self | ||
let customSmallId = UISheetPresentationController.Detent.Identifier("customSmall") | ||
let customSmallDetent = UISheetPresentationController.Detent.custom(identifier: customSmallId) { context in | ||
return 44 | ||
} | ||
sheet.detents = [customSmallDetent, .medium(), .large()] | ||
sheet.selectedDetentIdentifier = .medium | ||
sheet.largestUndimmedDetentIdentifier = .medium | ||
sheet.prefersGrabberVisible = true | ||
} | ||
bottomSheetViewController.isModalInPresentation = true | ||
self.altTextBottomSheetViewController = bottomSheetViewController | ||
|
||
present(bottomSheetViewController, animated: true) { [weak self] in | ||
self?.presentAltTextTooltipsIfNecessary(force: false) | ||
} | ||
} | ||
} | ||
|
||
internal func presentAltTextTooltipsIfNecessary(force: Bool = false) { | ||
guard let altTextExperimentViewModel, | ||
let bottomSheetViewController = altTextBottomSheetViewController, | ||
let tooltip1SourceView = view, | ||
let tooltip2SourceView = bottomSheetViewController.tooltip2SourceView, | ||
let tooltip2SourceRect = bottomSheetViewController.tooltip2SourceRect, | ||
let tooltip3SourceView = bottomSheetViewController.tooltip3SourceView, | ||
let tooltip3SourceRect = bottomSheetViewController.tooltip3SourceRect, | ||
let dataController = WMFAltTextDataController.shared else { | ||
return | ||
} | ||
|
||
if !force && dataController.hasPresentedOnboardingTooltips { | ||
return | ||
} | ||
|
||
let tooltip1SourceRect = CGRect(x: 30, y: navigationBar.frame.height + 30, width: 0, height: 0) | ||
|
||
let viewModel1 = WMFTooltipViewModel(localizedStrings: altTextExperimentViewModel.firstTooltipLocalizedStrings, buttonNeedsDisclosure: true, sourceView: tooltip1SourceView, sourceRect: tooltip1SourceRect, permittedArrowDirections: .up) { [weak self] in | ||
|
||
if let siteURL = self?.articleURL.wmf_site, | ||
let project = WikimediaProject(siteURL: siteURL) { | ||
EditInteractionFunnel.shared.logAltTextOnboardingDidTapNextOnFirstTooltip(project: project) | ||
} | ||
} | ||
|
||
|
||
let viewModel2 = WMFTooltipViewModel(localizedStrings: altTextExperimentViewModel.secondTooltipLocalizedStrings, buttonNeedsDisclosure: true, sourceView: tooltip2SourceView, sourceRect: tooltip2SourceRect, permittedArrowDirections: .down) | ||
|
||
let viewModel3 = WMFTooltipViewModel(localizedStrings: altTextExperimentViewModel.thirdTooltipLocalizedStrings, buttonNeedsDisclosure: false, sourceView: tooltip3SourceView, sourceRect: tooltip3SourceRect, permittedArrowDirections: .down) { [weak self] in | ||
|
||
if let siteURL = self?.articleURL.wmf_site, | ||
let project = WikimediaProject(siteURL: siteURL) { | ||
EditInteractionFunnel.shared.logAltTextOnboardingDidTapDoneOnLastTooltip(project: project) | ||
} | ||
|
||
} | ||
|
||
bottomSheetViewController.displayTooltips(tooltipViewModels: [viewModel1, viewModel2, viewModel3]) | ||
|
||
if !force { | ||
dataController.hasPresentedOnboardingTooltips = true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 3 methods are also alt text related, can you move them to the alt text extension?
setupForAltTextExperiment
presentAltTextTooltipsIfNecessary
presentAltTextTooltipsIfNecessary
} | ||
} | ||
|
||
extension ArticleViewController: WKNavigationDelegate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer putting this conformance code in it's own ArticleViewController+WKNavigationDelegate extension file actually. I think there are pieces to these methods that aren't dealing with failures, so living in a errors extension might be misleading.
import WMFData | ||
|
||
// MARK: - ArticleViewController + Loading | ||
extension ArticleViewController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should scrap this one and move these back into ArticleViewController.swift. I myself usually expect these lifecycle methods (viewDidLoad, viewWillAppear, etc.) to be one of the first things I see when looking at a view controller definition file, after the properties and the init methods.
surveyTimerController?.didBecomeActive(withState: state) | ||
} | ||
|
||
func setupSearchButton() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think everything from this point down should move elsewhere, as to me they look unrelated to Notifications.
} | ||
|
||
/// Adds the lead image view to the web view's scroll view and configures the associated constraints | ||
func setupLeadImageView() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one would make more sense in your +LeadImage extension.
func setupPageContentServiceJavaScriptInterface(with completion: @escaping () -> Void) { | ||
guard let siteURL = articleURL.wmf_site else { | ||
DDLogError("Missing site for \(articleURL)") | ||
showGenericError() | ||
return | ||
} | ||
|
||
// Need user groups to let the Page Content Service know if the page is editable for this user | ||
authManager.getLoggedInUser(for: siteURL) { (result) in | ||
assert(Thread.isMainThread) | ||
switch result { | ||
case .success(let user): | ||
self.setupPageContentServiceJavaScriptInterface(with: user?.groups ?? []) | ||
case .failure: | ||
DDLogError("Error getting userinfo for \(siteURL)") | ||
self.setupPageContentServiceJavaScriptInterface(with: []) | ||
} | ||
completion() | ||
} | ||
} | ||
|
||
func setupPageContentServiceJavaScriptInterface(with userGroups: [String]) { | ||
let areTablesInitiallyExpanded = altTextExperimentViewModel != nil ? true : UserDefaults.standard.wmf_isAutomaticTableOpeningEnabled | ||
|
||
messagingController.shouldAttemptToShowArticleAsLivingDoc = articleAsLivingDocController.shouldAttemptToShowArticleAsLivingDoc | ||
|
||
messagingController.setup(with: webView, languageCode: articleLanguageCode, theme: theme, layoutMargins: articleMargins, leadImageHeight: leadImageHeight, areTablesInitiallyExpanded: areTablesInitiallyExpanded, userGroups: userGroups) | ||
} | ||
|
||
func setupToolbar() { | ||
enableToolbar() | ||
toolbarController.apply(theme: theme) | ||
toolbarController.setSavedState(isSaved: article.isAnyVariantSaved) | ||
setToolbarHidden(false, animated: false) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several setup
-y method names, here and elsewhere. Maybe they could live in their own +Setup extensions file?
@@ -0,0 +1,67 @@ | |||
//~~~**DELETE THIS HEADER**~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need this header deleted. At the time this default was made, Xcode didn't support the ability to have an empty header when a new file was created. It's possible that has since changed.
Right now it adds DELETE THIS HEADER
, and there's a git commit hook that checks if this exists in any file before allowing the commit.
} | ||
|
||
var shouldAttemptToShowArticleAsLivingDoc: Bool { | ||
return articleAsLivingDocController.shouldAttemptToShowArticleAsLivingDoc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return articleAsLivingDocController.shouldAttemptToShowArticleAsLivingDoc | |
articleAsLivingDocController.shouldAttemptToShowArticleAsLivingDoc |
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
var shouldShowArticleAsLivingDoc: Bool { | ||
return articleAsLivingDocController.shouldShowArticleAsLivingDoc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return articleAsLivingDocController.shouldShowArticleAsLivingDoc | |
articleAsLivingDocController.shouldShowArticleAsLivingDoc |
} | ||
|
||
var livingDocSurveyLinkState: ArticleAsLivingDocSurveyLinkState { | ||
return articleAsLivingDocController.surveyLinkState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return articleAsLivingDocController.surveyLinkState | |
articleAsLivingDocController.surveyLinkState |
} | ||
} | ||
|
||
internal func presentAltTextTooltipsIfNecessary(force: Bool = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to specify internal explicitly? As swift has all method as internal by default.
Phabricator: N/A
Notes
Test Steps
Screenshots/Videos