Skip to content

Commit

Permalink
Merge pull request wikimedia#4461 from wikimedia/T201596
Browse files Browse the repository at this point in the history
Display Edit notices if available
  • Loading branch information
tonisevener authored Feb 10, 2023
2 parents fdf5b6d + 0da7a9f commit 392fd3f
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 28 deletions.
1 change: 1 addition & 0 deletions WMF Framework/CommonStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
40 changes: 40 additions & 0 deletions Wikipedia.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

31 changes: 13 additions & 18 deletions Wikipedia/Code/ArticleViewController+Editing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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) {
Expand Down
56 changes: 56 additions & 0 deletions Wikipedia/Code/EditNoticesFetcher.swift
Original file line number Diff line number Diff line change
@@ -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<Response, Error>) 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))
}
}
}

}
263 changes: 263 additions & 0 deletions Wikipedia/Code/EditNoticesView.swift
Original file line number Diff line number Diff line change
@@ -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
}

}
Loading

0 comments on commit 392fd3f

Please sign in to comment.