diff --git a/Components/Sources/Components/Assets.xcassets/editor/Contents.json b/Components/Sources/Components/Assets.xcassets/editor/Contents.json deleted file mode 100644 index 6e965652df6..00000000000 --- a/Components/Sources/Components/Assets.xcassets/editor/Contents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "provides-namespace" : true - } -} diff --git a/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/Contents.json b/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/Contents.json deleted file mode 100644 index df5dab3d469..00000000000 --- a/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "format-heading.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/format-heading.pdf b/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/format-heading.pdf deleted file mode 100644 index e07d8a33641..00000000000 Binary files a/Components/Sources/Components/Assets.xcassets/editor/format-heading.imageset/format-heading.pdf and /dev/null differ diff --git a/Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/Contents.json b/Components/Sources/Components/Assets.xcassets/separator.imageset/Contents.json similarity index 83% rename from Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/Contents.json rename to Components/Sources/Components/Assets.xcassets/separator.imageset/Contents.json index 7efb62f1c78..68b1578a788 100644 --- a/Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/Contents.json +++ b/Components/Sources/Components/Assets.xcassets/separator.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "format-text.pdf", + "filename" : "separator.pdf", "idiom" : "universal" } ], diff --git a/Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/format-text.pdf b/Components/Sources/Components/Assets.xcassets/separator.imageset/separator.pdf similarity index 54% rename from Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/format-text.pdf rename to Components/Sources/Components/Assets.xcassets/separator.imageset/separator.pdf index 2a69d1a6c52..04d07b798e4 100644 Binary files a/Components/Sources/Components/Assets.xcassets/editor/format-text.imageset/format-text.pdf and b/Components/Sources/Components/Assets.xcassets/separator.imageset/separator.pdf differ diff --git a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift index cf60cdc87e2..b10a7623b57 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift @@ -5,10 +5,8 @@ class WKEditorToolbarButton: WKComponentView { // MARK: - Properties - private lazy var button: UIButton = { - let button = UIButton(type: .custom) - return button - }() + private var button: UIButton? + private var image: UIImage? // MARK: - Lifecycle @@ -23,6 +21,13 @@ class WKEditorToolbarButton: WKComponentView { } private func setup() { + + button = createButton() + + guard let button else { + return + } + layer.cornerRadius = 4 clipsToBounds = true @@ -39,8 +44,6 @@ class WKEditorToolbarButton: WKComponentView { button.topAnchor.constraint(equalTo: topAnchor), button.bottomAnchor.constraint(equalTo: bottomAnchor) ]) - - updateColors() } // MARK: - Overrides @@ -52,49 +55,87 @@ class WKEditorToolbarButton: WKComponentView { } override func appEnvironmentDidChange() { - updateColors() + + guard let button else { + return + } + + let buttonConfig = createButtonConfig(image: image) + button.configuration = buttonConfig + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) } // MARK: - Button passthrough methods var isSelected: Bool { get { - return button.isSelected + return button?.isSelected ?? false } set { - button.isSelected = newValue - updateColors() - accessibilityTraits = button.accessibilityTraits + button?.isSelected = newValue + accessibilityTraits = button?.accessibilityTraits ?? [] } } var isEnabled: Bool { get { - return button.isEnabled + return button?.isEnabled ?? true } set { - button.isEnabled = newValue - updateColors() - accessibilityTraits = button.accessibilityTraits + button?.isEnabled = newValue + accessibilityTraits = button?.accessibilityTraits ?? [] } } - func setImage(_ image: UIImage?, for state: UIControl.State) { - button.setImage(image, for: state) + func setImage(_ image: UIImage?) { + + guard let button else { + return + } + + self.image = image + + var buttonConfig = button.configuration + buttonConfig?.image = image + button.configuration = buttonConfig } func addTarget(_ target: Any?, action: Selector, for controlEvent: UIControl.Event) { - button.addTarget(target, action: action, for: controlEvent) + button?.addTarget(target, action: action, for: controlEvent) } func removeTarget(_ target: Any?, action: Selector?, for controlEvent: UIControl.Event) { - button.removeTarget(target, action: action, for: controlEvent) + button?.removeTarget(target, action: action, for: controlEvent) } // MARK: - Private Helpers - func updateColors() { - button.tintColor = isSelected ? WKAppEnvironment.current.theme.inputAccessoryButtonSelectedTint : WKAppEnvironment.current.theme.inputAccessoryButtonTint - backgroundColor = self.isSelected ? WKAppEnvironment.current.theme.inputAccessoryButtonSelectedBackgroundColor : .clear + private func createButtonConfig(image: UIImage? = nil) -> UIButton.Configuration { + var buttonConfig = UIButton.Configuration.plain() + + buttonConfig.baseForegroundColor = (button?.isSelected ?? false) ? theme.inputAccessoryButtonSelectedTint : theme.inputAccessoryButtonTint + buttonConfig.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) + buttonConfig.background.cornerRadius = 0 + if let image { + buttonConfig.image = image + } + + return buttonConfig + } + + private func buttonConfigurationUpdateHandler(button: UIButton) { + var buttonConfig = button.configuration + buttonConfig?.baseForegroundColor = button.isSelected ? theme.inputAccessoryButtonSelectedTint : theme.inputAccessoryButtonTint + buttonConfig?.background.backgroundColor = button.isSelected ? theme.inputAccessoryButtonSelectedBackgroundColor : theme.accessoryBackground + button.configuration = buttonConfig + } + + private func createButton() -> UIButton { + + let buttonConfig = createButtonConfig() + let button = UIButton(configuration: buttonConfig) + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) + + return button } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarView.swift b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarView.swift index 6bad2c45cbd..9fe2eb37f79 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarView.swift @@ -14,6 +14,7 @@ class WKEditorToolbarView: WKComponentView { super.awakeFromNib() accessibilityElements = buttons updateColors() + maximumContentSizeCategory = .accessibilityMedium } // MARK: - Overrides @@ -32,5 +33,10 @@ class WKEditorToolbarView: WKComponentView { private func updateColors() { tintColor = WKAppEnvironment.current.theme.link backgroundColor = WKAppEnvironment.current.theme.accessoryBackground + + layer.shadowOffset = CGSize(width: 0, height: -2) + layer.shadowRadius = 10 + layer.shadowOpacity = 1.0 + layer.shadowColor = theme.editorKeyboardShadow.cgColor } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift index 09b15ebca68..79d64bd89b3 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift @@ -85,70 +85,77 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { expandButton.setImage(WKSFSymbolIcon.for(symbol: .chevronRightCircle), for: .normal) expandButton.addTarget(self, action: #selector(tappedExpand), for: .touchUpInside) expandButton.isAccessibilityElement = false + updateExpandButtonVisibility() - formatTextButton.setImage(WKIcon.formatText, for: .normal) + formatTextButton.setImage(WKSFSymbolIcon.for(symbol: .textFormat)) formatTextButton.addTarget(self, action: #selector(tappedFormatText), for: .touchUpInside) formatTextButton.accessibilityIdentifier = WKSourceEditorAccessibilityIdentifiers.current?.formatTextButton formatTextButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonFormatText - referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) + referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening)) referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCitation - linkButton.setImage(WKSFSymbolIcon.for(symbol: .link), for: .normal) + linkButton.setImage(WKSFSymbolIcon.for(symbol: .link)) linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) linkButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonLink - templateButton.setImage(WKSFSymbolIcon.for(symbol: .curlybraces), for: .normal) + templateButton.setImage(WKSFSymbolIcon.for(symbol: .curlybraces)) templateButton.addTarget(self, action: #selector(tappedTemplate), for: .touchUpInside) templateButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonTemplate - imageButton.setImage(WKSFSymbolIcon.for(symbol: .photo), for: .normal) + imageButton.setImage(WKSFSymbolIcon.for(symbol: .photo)) imageButton.addTarget(self, action: #selector(tappedMedia), for: .touchUpInside) imageButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonMedia - findInPageButton.setImage(WKSFSymbolIcon.for(symbol: .docTextMagnifyingGlass), for: .normal) + findInPageButton.setImage(WKSFSymbolIcon.for(symbol: .docTextMagnifyingGlass)) findInPageButton.addTarget(self, action: #selector(tappedFindInPage), for: .touchUpInside) findInPageButton.accessibilityIdentifier = WKSourceEditorAccessibilityIdentifiers.current?.findButton findInPageButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonFind - unorderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listBullet), for: .normal) + unorderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listBullet)) unorderedListButton.addTarget(self, action: #selector(tappedUnorderedList), for: .touchUpInside) unorderedListButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonListUnordered - orderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listNumber), for: .normal) + orderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listNumber)) orderedListButton.addTarget(self, action: #selector(tappedOrderedList), for: .touchUpInside) orderedListButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonListOrdered - decreaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .decreaseIndent), for: .normal) + decreaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .decreaseIndent)) decreaseIndentionButton.addTarget(self, action: #selector(tappedDecreaseIndentation), for: .touchUpInside) decreaseIndentionButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonDecreaseIndent decreaseIndentionButton.isEnabled = false - increaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .increaseIndent), for: .normal) + increaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .increaseIndent)) increaseIndentionButton.addTarget(self, action: #selector(tappedIncreaseIndentation), for: .touchUpInside) increaseIndentionButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonInceaseIndent increaseIndentionButton.isEnabled = false - cursorUpButton.setImage(WKSFSymbolIcon.for(symbol: .chevronUp), for: .normal) + cursorUpButton.setImage(WKSFSymbolIcon.for(symbol: .chevronUp)) cursorUpButton.addTarget(self, action: #selector(tappedCursorUp), for: .touchUpInside) cursorUpButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorUp - cursorDownButton.setImage(WKSFSymbolIcon.for(symbol: .chevronDown), for: .normal) + cursorDownButton.setImage(WKSFSymbolIcon.for(symbol: .chevronDown)) cursorDownButton.addTarget(self, action: #selector(tappedCursorDown), for: .touchUpInside) cursorDownButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorDown - cursorLeftButton.setImage(WKSFSymbolIcon.for(symbol: .chevronBackward), for: .normal) + cursorLeftButton.setImage(WKSFSymbolIcon.for(symbol: .chevronBackward)) cursorLeftButton.addTarget(self, action: #selector(tappedCursorLeft), for: .touchUpInside) cursorLeftButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorLeft - cursorRightButton.setImage(WKSFSymbolIcon.for(symbol: .chevronForward), for: .normal) + cursorRightButton.setImage(WKSFSymbolIcon.for(symbol: .chevronForward)) cursorRightButton.addTarget(self, action: #selector(tappedCursorRight), for: .touchUpInside) cursorRightButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorRight NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) } + // MARK: - Overrides + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + updateExpandButtonVisibility() + } + // MARK: - Notifications @objc private func updateButtonSelectionState(_ notification: NSNotification) { @@ -281,5 +288,11 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { @objc private func tappedMedia() { delegate?.toolbarExpandingViewDidTapImage(toolbarView: self) } + + // MARK: - Private Helpers + + private func updateExpandButtonVisibility() { + expandButton.isHidden = traitCollection.horizontalSizeClass == .regular + } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.swift index 3a478fda465..fd4d876635b 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.swift @@ -54,29 +54,39 @@ class WKFindAndReplaceView: WKComponentView { override func awakeFromNib() { super.awakeFromNib() + + maximumContentSizeCategory = .accessibilityLarge closeButton.setImage(WKSFSymbolIcon.for(symbol: .close), for: .normal) closeButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelFindButtonClose previousButton.setImage(WKSFSymbolIcon.for(symbol: .chevronUp), for: .normal) previousButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelFindButtonPrevious + previousButton.imageView?.contentMode = .center nextButton.setImage(WKSFSymbolIcon.for(symbol: .chevronDown), for: .normal) nextButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelFindButtonNext + nextButton.imageView?.contentMode = .center replaceButton.setImage(WKIcon.replace, for: .normal) replaceButton.accessibilityLabel = String.localizedStringWithFormat(WKSourceEditorLocalizedStrings.current.accessibilityLabelReplaceButtonPerformFormat, WKSourceEditorLocalizedStrings.current.accessibilityLabelReplaceTypeSingle) + replaceButton.imageView?.contentMode = .center replaceSwitchButton.setImage(WKSFSymbolIcon.for(symbol: .ellipsis), for: .normal) replaceSwitchButton.accessibilityLabel = String.localizedStringWithFormat(WKSourceEditorLocalizedStrings.current.accessibilityLabelReplaceButtonSwitchFormat, WKSourceEditorLocalizedStrings.current.accessibilityLabelReplaceTypeSingle) + replaceSwitchButton.imageView?.contentMode = .center replaceSwitchButton.showsMenuAsPrimaryAction = true replaceSwitchButton.menu = replaceSwitchButtonMenu() magnifyImageView.image = WKSFSymbolIcon.for(symbol: .magnifyingGlass) - pencilImageView.image = WKSFSymbolIcon.for(symbol: .pencil) + magnifyImageView.contentMode = .center + pencilImageView.image = WKIcon.pencil + pencilImageView.contentMode = .center findClearButton.setImage(WKSFSymbolIcon.for(symbol: .multiplyCircleFill), for: .normal) findClearButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelFindButtonClear + findClearButton.imageView?.contentMode = .center replaceClearButton.setImage(WKSFSymbolIcon.for(symbol: .multiplyCircleFill), for: .normal) replaceClearButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelReplaceButtonClear + replaceClearButton.imageView?.contentMode = .center findTextField.adjustsFontForContentSizeCategory = true findTextField.font = WKFont.for(.caption1, compatibleWith: appEnvironment.traitCollection) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.xib index 5feca0d9b7f..e8c6d2c46b7 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.xib +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Find and Replace/WKFindAndReplaceView.xib @@ -51,7 +51,7 @@ - + diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift index 33238ec9cd9..69d26511737 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift @@ -32,23 +32,23 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { stackView.isLayoutMarginsRelativeArrangement = true stackView.layoutMargins = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0) - boldButton.setImage(WKSFSymbolIcon.for(symbol: .bold), for: .normal) + boldButton.setImage(WKSFSymbolIcon.for(symbol: .bold)) boldButton.addTarget(self, action: #selector(tappedBold), for: .touchUpInside) boldButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonBold - italicsButton.setImage(WKSFSymbolIcon.for(symbol: .italic), for: .normal) + italicsButton.setImage(WKSFSymbolIcon.for(symbol: .italic)) italicsButton.addTarget(self, action: #selector(tappedItalics), for: .touchUpInside) italicsButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonItalics - referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) + referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening)) referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation - linkButton.setImage(WKSFSymbolIcon.for(symbol: .link), for: .normal) + linkButton.setImage(WKSFSymbolIcon.for(symbol: .link)) linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) linkButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonLink - templateButton.setImage(WKSFSymbolIcon.for(symbol: .curlybraces), for: .normal) + templateButton.setImage(WKSFSymbolIcon.for(symbol: .curlybraces)) templateButton.addTarget(self, action: #selector(tappedTemplate), for: .touchUpInside) templateButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonTemplate showMoreButton.setImage(WKSFSymbolIcon.for(symbol: .plusCircleFill), for: .normal) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectButton.swift new file mode 100644 index 00000000000..b46a1ae4df4 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectButton.swift @@ -0,0 +1,124 @@ +import Foundation +import UIKit + +final class WKEditorHeaderSelectButton: WKComponentView { + + // MARK: - Nested Types + + struct Configuration { + let title: String + let font: UIFont + } + + // MARK: - Properties + + let configuration: Configuration + var tapAction: (() -> Void)? + private var button: UIButton? + + var isSelected: Bool { + get { + button?.isSelected ?? false + } + set { + button?.isSelected = newValue + } + } + + // MARK: - Lifecycle + + init(configuration: Configuration) { + self.configuration = configuration + super.init(frame: .zero) + setup() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override var intrinsicContentSize: CGSize { + return button?.intrinsicContentSize ?? .zero + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return button?.sizeThatFits(size) ?? .zero + } + + override func appEnvironmentDidChange() { + + guard let button else { + return + } + + let buttonConfig = createButtonConfig() + button.configuration = buttonConfig + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) + } + + // MARK: - Private + + private func setup() { + + button = createButton() + + guard let button else { + return + } + + button.translatesAutoresizingMaskIntoConstraints = false + addSubview(button) + NSLayoutConstraint.activate([ + topAnchor.constraint(equalTo: button.topAnchor), + leadingAnchor.constraint(equalTo: button.leadingAnchor), + trailingAnchor.constraint(equalTo: button.trailingAnchor), + bottomAnchor.constraint(equalTo: button.bottomAnchor) + ]) + } + + private func createButtonConfig() -> UIButton.Configuration { + var buttonConfig = UIButton.Configuration.plain() + + var container = AttributeContainer() + container.font = configuration.font + container.foregroundColor = theme.text + + buttonConfig.attributedTitle = AttributedString(configuration.title, attributes: container) + buttonConfig.baseForegroundColor = theme.text + buttonConfig.contentInsets = NSDirectionalEdgeInsets(top: 19, leading: 12, bottom: 19, trailing: 12) + buttonConfig.background.cornerRadius = 10 + + return buttonConfig + } + + private func createButtonAction() -> UIAction { + return UIAction(title: configuration.title, handler: { [weak self] _ in + self?.tapAction?() + }) + } + + private func buttonConfigurationUpdateHandler(button: UIButton) { + var buttonConfig = button.configuration + buttonConfig?.background.backgroundColor = button.isSelected ? self.theme.link : self.theme.paperBackground + + var container = AttributeContainer() + container.font = self.configuration.font + container.foregroundColor = button.isSelected ? self.theme.paperBackground : self.theme.text + + buttonConfig?.attributedTitle = AttributedString(self.configuration.title, attributes: container) + + button.configuration = buttonConfig + } + + private func createButton() -> UIButton { + + let buttonConfig = createButtonConfig() + let action = createButtonAction() + let button = UIButton(configuration: buttonConfig, primaryAction: action) + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) + + return button + } +} diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectScrollView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectScrollView.swift new file mode 100644 index 00000000000..1df48927083 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorHeaderSelectScrollView.swift @@ -0,0 +1,135 @@ +import Foundation +import UIKit + +protocol WKEditorHeaderSelectScrollViewDelegate: AnyObject { + func didSelectIndex(_ index: Int, headerSelectScrollView: WKEditorHeaderSelectScrollView) +} + +final class WKEditorHeaderSelectScrollView: WKComponentView { + + // MARK: - Properties + + private weak var delegate: WKEditorHeaderSelectScrollViewDelegate? + private var buttons: [WKEditorHeaderSelectButton] = [] + private let configurations: [WKEditorHeaderSelectButton.Configuration] + + private lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.showsHorizontalScrollIndicator = false + scrollView.showsVerticalScrollIndicator = false + return scrollView + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .horizontal + stackView.distribution = .fill + stackView.alignment = .fill + return stackView + }() + + // MARK: - Lifecycle + + init(configurations: [WKEditorHeaderSelectButton.Configuration], delegate: WKEditorHeaderSelectScrollViewDelegate?) { + self.configurations = configurations + self.delegate = delegate + super.init(frame: .zero) + setup() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + + addButtonsToStackView(stackView) + setupButtonTapActions() + + scrollView.addSubview(stackView) + + // pin stack view to scroll view content guide + // ensure it only scrolls horizontally + // set scroll view height to largest button height + NSLayoutConstraint.activate([ + scrollView.contentLayoutGuide.topAnchor.constraint(equalTo: stackView.topAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 16), + scrollView.contentLayoutGuide.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 16), + scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + scrollView.contentLayoutGuide.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor), + scrollView.heightAnchor.constraint(equalToConstant: largestButtonHeight) + ]) + + // Add scroll view to self, pin to edges + addSubview(scrollView) + NSLayoutConstraint.activate([ + scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: topAnchor), + scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor), + scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + + // MARK: - Internal + + func selectIndex(_ index: Int) { + guard buttons.count > index else { + return + } + + buttons.forEach { $0.isSelected = false } + + buttons[index].isSelected = true + } + + // MARK: - Private + + private func addButtonsToStackView(_ stackView: UIStackView) { + + var buttons: [WKEditorHeaderSelectButton] = [] + + for configuration in configurations { + let button = WKEditorHeaderSelectButton(configuration: configuration) + button.translatesAutoresizingMaskIntoConstraints = false + buttons.append(button) + + stackView.addArrangedSubview(button) + } + + self.buttons = buttons + } + + private func setupButtonTapActions() { + for (index, button) in buttons.enumerated() { + button.tapAction = { [weak self] in + + guard let self else { + return + } + + for innerButton in self.buttons { + if button != innerButton { + innerButton.isSelected = false + } else { + innerButton.isSelected = true + self.delegate?.didSelectIndex(index, headerSelectScrollView: self) + } + } + } + } + } + + private var largestButtonHeight: CGFloat { + var largestButtonHeight: CGFloat = 0 + for button in buttons { + let buttonHeight = button.sizeThatFits(CGSize(width: .max, height: .max)).height + if buttonHeight > largestButtonHeight { + largestButtonHeight = buttonHeight + } + } + + return largestButtonHeight + } +} diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift index e7793b0cdb6..85f80be61fe 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift @@ -20,6 +20,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapComment(isSelected: Bool) } + class WKEditorInputView: WKComponentView { // MARK: - Nested Types @@ -42,6 +43,14 @@ class WKEditorInputView: WKComponentView { case .subheading4: return WKSourceEditorLocalizedStrings.current.inputViewSubheading4 } } + + func font(traitCollection: UITraitCollection) -> UIFont { + switch self { + case .paragraph: return WKFont.for(.body, compatibleWith: traitCollection) + default: + return WKFont.for(.headline, compatibleWith: traitCollection) + } + } } // MARK: - Properties @@ -59,7 +68,7 @@ class WKEditorInputView: WKComponentView { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.adjustsFontForContentSizeCategory = true - label.font = WKFont.for(.headline, compatibleWith: appEnvironment.traitCollection) + label.font = WKFont.for(.boldTitle3, compatibleWith: appEnvironment.traitCollection) label.text = WKSourceEditorLocalizedStrings.current.inputViewTextFormatting label.setContentHuggingPriority(.defaultLow, for: .horizontal) return label @@ -68,16 +77,20 @@ class WKEditorInputView: WKComponentView { private lazy var closeButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false - button.setImage(WKSFSymbolIcon.for(symbol: .multiplyCircleFill), for: .normal) + setCloseButtonImage(button: button) button.addTarget(self, action: #selector(close(_:)), for: .touchUpInside) button.accessibilityIdentifier = WKSourceEditorAccessibilityIdentifiers.current?.closeButton button.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCloseMainInputView + button.setContentHuggingPriority(.required, for: .horizontal) return button }() private lazy var containerScrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.bounces = false + scrollView.clipsToBounds = true + scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) return scrollView }() @@ -87,51 +100,148 @@ class WKEditorInputView: WKComponentView { stackView.axis = .vertical stackView.distribution = .fill stackView.alignment = .fill + stackView.spacing = 8 return stackView }() - private lazy var headingScrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.translatesAutoresizingMaskIntoConstraints = false - return scrollView + private lazy var headingButtonTypes: [HeadingButtonType] = { + return [.paragraph, .heading, .subheading1, .subheading2, .subheading3, .subheading4] + }() + + private lazy var headerSelectScrollView: WKEditorHeaderSelectScrollView = { + let configurations: [WKEditorHeaderSelectButton.Configuration] = headingButtonTypes.map { + + let title = $0.title + let font = $0.font(traitCollection: traitCollection) + let item = WKEditorHeaderSelectButton.Configuration(title: title, font: font) + + return item + } + + let scrollingChoiceView = WKEditorHeaderSelectScrollView(configurations: configurations, delegate: self) + scrollingChoiceView.translatesAutoresizingMaskIntoConstraints = false + return scrollingChoiceView }() - private lazy var headingStackView: UIStackView = { + private lazy var multiSelectStackView1: UIStackView = { let stackView = UIStackView() stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .horizontal - stackView.distribution = .fill + stackView.distribution = .fillEqually stackView.alignment = .fill + stackView.spacing = 8 + stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16) + stackView.isLayoutMarginsRelativeArrangement = true return stackView }() - private lazy var plainToolbarView: WKEditorToolbarPlainView = { - let view = UINib(nibName: String(describing: WKEditorToolbarPlainView.self), bundle: Bundle.module).instantiate(withOwner: nil).first as! WKEditorToolbarPlainView - view.delegate = delegate - view.translatesAutoresizingMaskIntoConstraints = false - return view + private lazy var multiSelectStackView2: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.alignment = .fill + stackView.spacing = 8 + stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16) + stackView.isLayoutMarginsRelativeArrangement = true + return stackView }() - private lazy var groupedToolbarView: WKEditorToolbarGroupedView = { - let view = UINib(nibName: String(describing: WKEditorToolbarGroupedView.self), bundle: Bundle.module).instantiate(withOwner: nil).first as! WKEditorToolbarGroupedView - view.delegate = delegate - view.translatesAutoresizingMaskIntoConstraints = false - return view + private lazy var accessibilityMultiSelectStackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.alignment = .fill + stackView.spacing = 8 + stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16) + stackView.isLayoutMarginsRelativeArrangement = true + return stackView }() - // Heading Buttons - private var paragraphButton: UIButton! - private var headerButton: UIButton! - private var subheader1Button: UIButton! - private var subheader2Button: UIButton! - private var subheader3Button: UIButton! - private var subheader4Button: UIButton! + private lazy var multiButtonBoldItalic: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .bold), + WKSFSymbolIcon.for(symbol: .italic) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() - var headingButtons: [UIButton] { - return [paragraphButton, headerButton, subheader1Button, subheader2Button, subheader3Button, subheader4Button] - } + private lazy var multiButtonUnderlineStrikethrough: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .underline), + WKSFSymbolIcon.for(symbol: .strikethrough) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private lazy var multiButtonReferenceLink: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .quoteOpening), + WKSFSymbolIcon.for(symbol: .link) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() - private var divViews: [UIView] = [] + private lazy var multiButtonBulletNumberList: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .listBullet), + WKSFSymbolIcon.for(symbol: .listNumber) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private lazy var multiButtonIndentIncreaseDecrease: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .decreaseIndent), + WKSFSymbolIcon.for(symbol: .increaseIndent) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private lazy var multiButtonSubscriptSuperscript: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .textFormatSuperscript), + WKSFSymbolIcon.for(symbol: .textFormatSubscript) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private lazy var multiButtonTemplateComment: WKEditorMultiButton = { + + let configuration = WKEditorMultiButton.Configuration(icons: [ + WKSFSymbolIcon.for(symbol: .curlybraces), + WKSFSymbolIcon.for(symbol: .exclamationMarkCircle) + ]) + + let button = WKEditorMultiButton(configuration: configuration, delegate: self) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() private weak var delegate: WKEditorInputViewDelegate? @@ -160,54 +270,37 @@ class WKEditorInputView: WKComponentView { safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: navigationStackView.trailingAnchor, constant: 16) ]) - // ---- Headings ---- - - self.paragraphButton = headingButton(type: .paragraph) - self.paragraphButton.isSelected = true - self.headerButton = headingButton(type: .heading) - self.subheader1Button = headingButton(type: .subheading1) - self.subheader2Button = headingButton(type: .subheading2) - self.subheader3Button = headingButton(type: .subheading3) - self.subheader4Button = headingButton(type: .subheading4) - - headingStackView.addArrangedSubview(paragraphButton) - headingStackView.addArrangedSubview(headerButton) - headingStackView.addArrangedSubview(subheader1Button) - headingStackView.addArrangedSubview(subheader2Button) - headingStackView.addArrangedSubview(subheader3Button) - headingStackView.addArrangedSubview(subheader4Button) - - headingScrollView.addSubview(headingStackView) - - let headerButtonSize = headerButton.sizeThatFits(bounds.size) - - // pin heading stack to heading scroll view content guide - // ensure it only scrolls horizontally - // set heading scroll view height to largest button height - NSLayoutConstraint.activate([ - headingScrollView.contentLayoutGuide.topAnchor.constraint(equalTo: headingStackView.topAnchor), - headingStackView.leadingAnchor.constraint(equalTo: headingScrollView.contentLayoutGuide.leadingAnchor, constant: 16), - headingScrollView.contentLayoutGuide.trailingAnchor.constraint(equalTo: headingStackView.trailingAnchor, constant: 16), - headingScrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: headingStackView.bottomAnchor), - headingScrollView.contentLayoutGuide.heightAnchor.constraint(equalTo: headingScrollView.frameLayoutGuide.heightAnchor), - headingScrollView.heightAnchor.constraint(equalToConstant: headerButtonSize.height) - ]) - // ---- Container ---- - containerStackView.addArrangedSubview(headingScrollView) - let divView1 = divView() - containerStackView.addArrangedSubview(divView1) - containerStackView.addArrangedSubview(plainToolbarView) - let divView2 = divView() - containerStackView.addArrangedSubview(divView1) - containerStackView.addArrangedSubview(groupedToolbarView) - let divView3 = divView() - containerStackView.addArrangedSubview(divView3) - containerScrollView.addSubview(containerStackView) - - self.divViews = [divView1, divView2, divView3] + containerStackView.addArrangedSubview(headerSelectScrollView) + if traitCollection.preferredContentSizeCategory.isAccessibilityCategory { + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonBoldItalic) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonUnderlineStrikethrough) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonReferenceLink) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonBulletNumberList) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonIndentIncreaseDecrease) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonSubscriptSuperscript) + accessibilityMultiSelectStackView.addArrangedSubview(multiButtonTemplateComment) + accessibilityMultiSelectStackView.addArrangedSubview(multiSelectStackView1) + accessibilityMultiSelectStackView.addArrangedSubview(multiSelectStackView1) + containerStackView.addArrangedSubview(accessibilityMultiSelectStackView) + } else { + multiSelectStackView1.addArrangedSubview(multiButtonBoldItalic) + multiSelectStackView1.addArrangedSubview(multiButtonUnderlineStrikethrough) + multiSelectStackView1.addArrangedSubview(multiButtonReferenceLink) + containerStackView.addArrangedSubview(multiSelectStackView1) + + multiSelectStackView2.addArrangedSubview(multiButtonBulletNumberList) + multiSelectStackView2.addArrangedSubview(multiButtonIndentIncreaseDecrease) + multiSelectStackView2.addArrangedSubview(multiButtonSubscriptSuperscript) + multiSelectStackView2.addArrangedSubview(multiButtonTemplateComment) + containerStackView.addArrangedSubview(multiSelectStackView2) + } + + addSubview(containerScrollView) + containerScrollView.addSubview(containerStackView) + // pin container stack view to container scroll view content guide NSLayoutConstraint.activate([ containerScrollView.contentLayoutGuide.topAnchor.constraint(equalTo: containerStackView.topAnchor), @@ -216,14 +309,12 @@ class WKEditorInputView: WKComponentView { containerScrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor) ]) - addSubview(containerScrollView) - - // pin scroll view frame guide to outer views + // pin scroll view to outer views NSLayoutConstraint.activate([ - containerScrollView.frameLayoutGuide.topAnchor.constraint(equalTo: navigationStackView.bottomAnchor, constant: 18), - containerScrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - containerScrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), - containerScrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor) + containerScrollView.topAnchor.constraint(equalTo: navigationStackView.bottomAnchor, constant: 18), + containerScrollView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + containerScrollView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + containerScrollView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor) ]) // Ensure it only scrolls vertically @@ -243,7 +334,46 @@ class WKEditorInputView: WKComponentView { return } - configure(selectionState: selectionState) + // Headers + if selectionState.isHeading { + headerSelectScrollView.selectIndex(1) + } else if selectionState.isSubheading1 { + headerSelectScrollView.selectIndex(2) + } else if selectionState.isSubheading2 { + headerSelectScrollView.selectIndex(3) + } else if selectionState.isSubheading3 { + headerSelectScrollView.selectIndex(4) + } else if selectionState.isSubheading4 { + headerSelectScrollView.selectIndex(5) + } else { + headerSelectScrollView.selectIndex(0) // Paragraph + } + + // Top row + multiButtonBoldItalic.toggleSelectionIndex(0, shouldSelect: selectionState.isBold) + multiButtonBoldItalic.toggleSelectionIndex(1, shouldSelect: selectionState.isItalics) + + multiButtonUnderlineStrikethrough.toggleSelectionIndex(0, shouldSelect: selectionState.isUnderline) + multiButtonUnderlineStrikethrough.toggleSelectionIndex(1, shouldSelect: selectionState.isStrikethrough) + + multiButtonReferenceLink.toggleSelectionIndex(0, shouldSelect: selectionState.isHorizontalReference) + multiButtonReferenceLink.toggleSelectionIndex(1, shouldSelect: selectionState.isSimpleLink) + + // Bottom row + multiButtonBulletNumberList.toggleSelectionIndex(0, shouldSelect: selectionState.isBulletSingleList || selectionState.isBulletMultipleList) + multiButtonBulletNumberList.toggleEnableIndex(0, shouldEnable: !selectionState.isNumberSingleList && !selectionState.isNumberMultipleList) + + multiButtonBulletNumberList.toggleSelectionIndex(1, shouldSelect: selectionState.isNumberSingleList || selectionState.isNumberMultipleList) + multiButtonBulletNumberList.toggleEnableIndex(1, shouldEnable: !selectionState.isBulletSingleList && !selectionState.isBulletMultipleList) + + multiButtonIndentIncreaseDecrease.toggleEnableIndex(0, shouldEnable: selectionState.isBulletMultipleList || selectionState.isNumberMultipleList) + multiButtonIndentIncreaseDecrease.toggleEnableIndex(1, shouldEnable: selectionState.isBulletSingleList || selectionState.isBulletMultipleList || selectionState.isNumberSingleList || selectionState.isNumberMultipleList) + + multiButtonSubscriptSuperscript.toggleSelectionIndex(0, shouldSelect: selectionState.isSuperscript) + multiButtonSubscriptSuperscript.toggleSelectionIndex(1, shouldSelect: selectionState.isSubscript) + + multiButtonTemplateComment.toggleSelectionIndex(0, shouldSelect: selectionState.isHorizontalTemplate) + multiButtonTemplateComment.toggleSelectionIndex(1, shouldSelect: selectionState.isComment) } // MARK: - Overrides @@ -252,6 +382,11 @@ class WKEditorInputView: WKComponentView { updateColors() } + override func layoutSubviews() { + super.layoutSubviews() + containerScrollView.flashScrollIndicators() + } + // MARK: - Button Actions @objc private func close(_ sender: UIBarButtonItem) { @@ -261,85 +396,106 @@ class WKEditorInputView: WKComponentView { // MARK: - Private Helpers private func updateColors() { - backgroundColor = WKAppEnvironment.current.theme.accessoryBackground - titleLabel.textColor = WKAppEnvironment.current.theme.text - closeButton.tintColor = WKAppEnvironment.current.theme.inputAccessoryButtonTint - divViews.forEach { view in - view.backgroundColor = WKAppEnvironment.current.theme.border - } + backgroundColor = theme.paperBackground + titleLabel.textColor = theme.text + setCloseButtonImage(button: closeButton) + + layer.shadowOffset = CGSize(width: 0, height: -2) + layer.shadowRadius = 10 + layer.shadowOpacity = 1.0 + layer.shadowColor = theme.editorKeyboardShadow.cgColor } - private func divView() -> UIView { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.heightAnchor.constraint(equalToConstant: 0.5).isActive = true - return view + private func setCloseButtonImage(button: UIButton) { + let image = WKSFSymbolIcon.for(symbol: .multiplyCircleFill, font: .title1, paletteColors: [theme.secondaryText, theme.midBackground]) + button.setImage(image, for: .normal) } - - private func headingButton(type: HeadingButtonType) -> UIButton { - - let font: UIFont - switch type { - case .paragraph: font = WKFont.for(.body, compatibleWith: traitCollection) - case .heading: font = WKFont.for(.headline, compatibleWith: traitCollection) - default: font = WKFont.for(.subheadline, compatibleWith: traitCollection) +} + +// MARK: - WKEditorHeaderSelectScrollViewDelegate + +extension WKEditorInputView: WKEditorHeaderSelectScrollViewDelegate { + func didSelectIndex(_ index: Int, headerSelectScrollView: WKEditorHeaderSelectScrollView) { + guard headingButtonTypes.count > index else { + return } - var configuration = UIButton.Configuration.plain() - configuration.titleTextAttributesTransformer = - UIConfigurationTextAttributesTransformer { incoming in - var outgoing = incoming - outgoing.font = font - outgoing.foregroundColor = WKAppEnvironment.current.theme.text - return outgoing - } - configuration.contentInsets = NSDirectionalEdgeInsets(top: 19, leading: 12, bottom: 19, trailing: 12) - let action = UIAction(title: type.title, handler: { [weak self] _ in - - guard let self else { - return + let headingType = headingButtonTypes[index] + delegate?.didTapHeading(type: headingType) + } +} + +// MARK: - WKEditorHeaderSelectScrollViewDelegate + +extension WKEditorInputView: WKEditorMultiSelectButtonDelegate { + func didSelectIndex(_ index: Int, isSelected: Bool, multiSelectButton: WKEditorMultiButton) { + + switch multiSelectButton { + case multiButtonBoldItalic: + switch index { + case 0: + delegate?.didTapBold(isSelected: isSelected) + case 1: + delegate?.didTapItalics(isSelected: isSelected) + default: + break } - - self.headingButtons.forEach { button in - button.isSelected = false + case multiButtonUnderlineStrikethrough: + switch index { + case 0: + delegate?.didTapUnderline(isSelected: isSelected) + case 1: + delegate?.didTapStrikethrough(isSelected: isSelected) + default: + break } - - switch type { - case .paragraph: - paragraphButton.isSelected = true - case .heading: - headerButton.isSelected = true - case .subheading1: - subheader1Button.isSelected = true - case .subheading2: - subheader2Button.isSelected = true - case .subheading3: - subheader3Button.isSelected = true - case .subheading4: - subheader4Button.isSelected = true + case multiButtonReferenceLink: + switch index { + case 0: + delegate?.didTapReference(isSelected: isSelected) + case 1: + delegate?.didTapLink(isSelected: isSelected) + default: + break } - - delegate?.didTapHeading(type: type) - }) - - return UIButton(configuration: configuration, primaryAction: action) - } - - func configure(selectionState: WKSourceEditorSelectionState) { - headingButtons.forEach { $0.isSelected = false } - - if selectionState.isHeading { - headerButton.isSelected = true - } else if selectionState.isSubheading1 { - subheader1Button.isSelected = true - } else if selectionState.isSubheading2 { - subheader2Button.isSelected = true - } else if selectionState.isSubheading3 { - subheader3Button.isSelected = true - } else if selectionState.isSubheading4 { - subheader4Button.isSelected = true - } else { - paragraphButton.isSelected = true + case multiButtonBulletNumberList: + switch index { + case 0: + delegate?.didTapBulletList(isSelected: isSelected) + case 1: + delegate?.didTapNumberList(isSelected: isSelected) + default: + break + } + case multiButtonIndentIncreaseDecrease: + switch index { + case 0: + delegate?.didTapDecreaseIndent() + case 1: + delegate?.didTapIncreaseIndent() + default: + break + } + case multiButtonSubscriptSuperscript: + switch index { + case 0: + delegate?.didTapSuperscript(isSelected: isSelected) + case 1: + delegate?.didTapSubscript(isSelected: isSelected) + default: + break + } + case multiButtonTemplateComment: + switch index { + case 0: + delegate?.didTapTemplate(isSelected: isSelected) + case 1: + delegate?.didTapComment(isSelected: isSelected) + default: + break + } + default: + break } } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorMultiButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorMultiButton.swift new file mode 100644 index 00000000000..084b680fbd9 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorMultiButton.swift @@ -0,0 +1,143 @@ +import Foundation +import UIKit + +protocol WKEditorMultiSelectButtonDelegate: AnyObject { + func didSelectIndex(_ index: Int, isSelected: Bool, multiSelectButton: WKEditorMultiButton) +} + +final class WKEditorMultiButton: WKComponentView { + + // MARK: - Nested Types + + struct Configuration { + let icons: [UIImage?] + } + + // MARK: - Properties + + private let configuration: Configuration + private var buttons: [UIButton] = [] + private weak var delegate: WKEditorMultiSelectButtonDelegate? + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.alignment = .center + stackView.spacing = 2 + return stackView + }() + + // MARK: - Lifecycle + + init(configuration: Configuration, delegate: WKEditorMultiSelectButtonDelegate) { + self.configuration = configuration + self.delegate = delegate + super.init(frame: .zero) + setup() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + + createAndAddButtonsToStackView() + + addSubview(stackView) + NSLayoutConstraint.activate([ + topAnchor.constraint(equalTo: stackView.topAnchor), + leadingAnchor.constraint(equalTo: stackView.leadingAnchor), + trailingAnchor.constraint(equalTo: stackView.trailingAnchor), + bottomAnchor.constraint(equalTo: stackView.bottomAnchor) + ]) + + layer.cornerRadius = 10 + layer.masksToBounds = true + } + + // MARK: - Overrides + + override func appEnvironmentDidChange() { + + for (icon, button) in zip(configuration.icons, buttons) { + let buttonConfig = createButtonConfig(image: icon) + button.configuration = buttonConfig + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) + } + } + + // MARK: - Internal + + func toggleSelectionIndex(_ index: Int, shouldSelect: Bool) { + guard buttons.count > index else { + return + } + + buttons[index].isSelected = shouldSelect + } + + func toggleEnableIndex(_ index: Int, shouldEnable: Bool) { + guard buttons.count > index else { + return + } + + buttons[index].isEnabled = shouldEnable + } + + // MARK: - Private + + private func createAndAddButtonsToStackView() { + var buttons: [UIButton] = [] + for (index, icon) in configuration.icons.enumerated() { + let button = createButton(icon: icon, tapAction: { [weak self] in + guard let self else { + return + } + let isSelected = buttons[index].isSelected + delegate?.didSelectIndex(index, isSelected: isSelected, multiSelectButton: self) + }) + buttons.append(button) + stackView.addArrangedSubview(button) + } + self.buttons = buttons + } + + private func createButtonConfig(image: UIImage?) -> UIButton.Configuration { + var buttonConfig = UIButton.Configuration.plain() + + buttonConfig.baseForegroundColor = theme.text + buttonConfig.contentInsets = NSDirectionalEdgeInsets(top: 19, leading: 19, bottom: 19, trailing: 19) + buttonConfig.background.cornerRadius = 0 + buttonConfig.image = image + + return buttonConfig + } + + private func createButtonAction(action: @escaping () -> Void) -> UIAction { + return UIAction(title: "", handler: { _ in + action() + }) + } + + private func buttonConfigurationUpdateHandler(button: UIButton) { + var buttonConfig = button.configuration + + buttonConfig?.background.backgroundColor = button.isSelected ? self.theme.editorMultiButtonSelectedBackground : self.theme.midBackground + + button.configuration = buttonConfig + } + + private func createButton(icon: UIImage?, tapAction: @escaping () -> Void) -> UIButton { + + let buttonConfig = createButtonConfig(image: icon) + let action = createButtonAction(action: tapAction) + let button = UIButton(configuration: buttonConfig, primaryAction: action) + button.configurationUpdateHandler = buttonConfigurationUpdateHandler(button:) + button.translatesAutoresizingMaskIntoConstraints = false + + return button + } +} diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.swift deleted file mode 100644 index a77124d43a4..00000000000 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.swift +++ /dev/null @@ -1,127 +0,0 @@ -import Foundation - -class WKEditorToolbarGroupedView: WKEditorToolbarView { - - // MARK: - Properties - - @IBOutlet private weak var unorderedListButton: WKEditorToolbarButton! - @IBOutlet private weak var orderedListButton: WKEditorToolbarButton! - @IBOutlet private weak var decreaseIndentButton: WKEditorToolbarButton! - @IBOutlet private weak var increaseIndentButton: WKEditorToolbarButton! - @IBOutlet private weak var superscriptButton: WKEditorToolbarButton! - @IBOutlet private weak var subscriptButton: WKEditorToolbarButton! - @IBOutlet private weak var underlineButton: WKEditorToolbarButton! - @IBOutlet private weak var strikethroughButton: WKEditorToolbarButton! - - weak var delegate: WKEditorInputViewDelegate? - - // MARK: - Lifecycle - - override func awakeFromNib() { - super.awakeFromNib() - - unorderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listBullet), for: .normal) - unorderedListButton.addTarget(self, action: #selector(tappedUnorderedList), for: .touchUpInside) - unorderedListButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonListUnordered - - orderedListButton.setImage(WKSFSymbolIcon.for(symbol: .listNumber), for: .normal) - orderedListButton.addTarget(self, action: #selector(tappedOrderedList), for: .touchUpInside) - orderedListButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonListOrdered - - decreaseIndentButton.setImage(WKSFSymbolIcon.for(symbol: .decreaseIndent), for: .normal) - decreaseIndentButton.addTarget(self, action: #selector(tappedDecreaseIndent), for: .touchUpInside) - decreaseIndentButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonDecreaseIndent - decreaseIndentButton.isEnabled = false - - increaseIndentButton.setImage(WKSFSymbolIcon.for(symbol: .increaseIndent), for: .normal) - increaseIndentButton.addTarget(self, action: #selector(tappedIncreaseIndent), for: .touchUpInside) - increaseIndentButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonInceaseIndent - increaseIndentButton.isEnabled = false - - superscriptButton.setImage(WKSFSymbolIcon.for(symbol: .textFormatSuperscript), for: .normal) - superscriptButton.addTarget(self, action: #selector(tappedSuperscript), for: .touchUpInside) - superscriptButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonSuperscript - - subscriptButton.setImage(WKSFSymbolIcon.for(symbol: .textFormatSubscript), for: .normal) - subscriptButton.addTarget(self, action: #selector(tappedSubscript), for: .touchUpInside) - subscriptButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonSubscript - - underlineButton.setImage(WKSFSymbolIcon.for(symbol: .underline), for: .normal) - underlineButton.addTarget(self, action: #selector(tappedUnderline), for: .touchUpInside) - underlineButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonUnderline - - strikethroughButton.setImage(WKSFSymbolIcon.for(symbol: .strikethrough), for: .normal) - strikethroughButton.addTarget(self, action: #selector(tappedStrikethrough), for: .touchUpInside) - strikethroughButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonStrikethrough - - NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) - } - - // MARK: - Notifications - - @objc private func updateButtonSelectionState(_ notification: NSNotification) { - guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { - return - } - - unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList - unorderedListButton.isEnabled = !selectionState.isNumberSingleList && !selectionState.isNumberMultipleList - - orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList - orderedListButton.isEnabled = !selectionState.isBulletSingleList && !selectionState.isBulletMultipleList - - decreaseIndentButton.isEnabled = false - if selectionState.isBulletMultipleList || selectionState.isNumberMultipleList { - decreaseIndentButton.isEnabled = true - } - - if selectionState.isBulletSingleList || - selectionState.isBulletMultipleList || - selectionState.isNumberSingleList || - selectionState.isNumberMultipleList { - increaseIndentButton.isEnabled = true - } else { - increaseIndentButton.isEnabled = false - } - - strikethroughButton.isSelected = selectionState.isStrikethrough - subscriptButton.isSelected = selectionState.isSubscript - superscriptButton.isSelected = selectionState.isSuperscript - underlineButton.isSelected = selectionState.isUnderline - } - - // MARK: - Button Actions - - @objc private func tappedIncreaseIndent() { - delegate?.didTapIncreaseIndent() - } - - @objc private func tappedDecreaseIndent() { - delegate?.didTapDecreaseIndent() - } - - @objc private func tappedUnorderedList() { - delegate?.didTapBulletList(isSelected: unorderedListButton.isSelected) - } - - @objc private func tappedOrderedList() { - delegate?.didTapNumberList(isSelected: orderedListButton.isSelected) - } - - @objc private func tappedSuperscript() { - delegate?.didTapSuperscript(isSelected: superscriptButton.isSelected) - } - - @objc private func tappedSubscript() { - delegate?.didTapSubscript(isSelected: subscriptButton.isSelected) - } - - @objc private func tappedUnderline() { - delegate?.didTapUnderline(isSelected: underlineButton.isSelected) - } - - @objc private func tappedStrikethrough() { - delegate?.didTapStrikethrough(isSelected: strikethroughButton.isSelected) - } - -} diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.xib deleted file mode 100644 index 4de041a4125..00000000000 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarGroupedView.xib +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift deleted file mode 100644 index c04248ff088..00000000000 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift +++ /dev/null @@ -1,88 +0,0 @@ -import Foundation - -class WKEditorToolbarPlainView: WKEditorToolbarView { - - // MARK: Properties - - @IBOutlet private weak var boldButton: WKEditorToolbarButton! - @IBOutlet private weak var italicsButton: WKEditorToolbarButton! - @IBOutlet private weak var referenceButton: WKEditorToolbarButton! - @IBOutlet private weak var linkButton: WKEditorToolbarButton! - @IBOutlet private weak var templateButton: WKEditorToolbarButton! - @IBOutlet private weak var commentButton: WKEditorToolbarButton! - - weak var delegate: WKEditorInputViewDelegate? - - // MARK: Lifecycle - - override func awakeFromNib() { - super.awakeFromNib() - - boldButton.setImage(WKSFSymbolIcon.for(symbol: .bold), for: .normal) - boldButton.addTarget(self, action: #selector(tappedBold), for: .touchUpInside) - boldButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonBold - - italicsButton.setImage(WKSFSymbolIcon.for(symbol: .italic), for: .normal) - italicsButton.addTarget(self, action: #selector(tappedItalics), for: .touchUpInside) - italicsButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonItalics - - referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) - referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) - referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation - - linkButton.setImage(WKIcon.link, for: .normal) - linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) - linkButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonLink - - templateButton.setImage(WKSFSymbolIcon.for(symbol: .curlybraces), for: .normal) - templateButton.addTarget(self, action: #selector(tappedTemplate), for: .touchUpInside) - templateButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonTemplate - - commentButton.setImage(WKIcon.exclamationPointCircle, for: .normal) - commentButton.addTarget(self, action: #selector(tappedComment), for: .touchUpInside) - commentButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonComment - - NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) - } - - // MARK: - Notifications - - @objc private func updateButtonSelectionState(_ notification: NSNotification) { - guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { - return - } - - boldButton.isSelected = selectionState.isBold - italicsButton.isSelected = selectionState.isItalics - templateButton.isSelected = selectionState.isHorizontalTemplate - referenceButton.isSelected = selectionState.isHorizontalReference - linkButton.isSelected = selectionState.isSimpleLink - commentButton.isSelected = selectionState.isComment - } - - // MARK: Button Actions - - @objc private func tappedBold() { - delegate?.didTapBold(isSelected: boldButton.isSelected) - } - - @objc private func tappedItalics() { - delegate?.didTapItalics(isSelected: italicsButton.isSelected) - } - - @objc private func tappedReference() { - delegate?.didTapReference(isSelected: referenceButton.isSelected) - } - - @objc private func tappedTemplate() { - delegate?.didTapTemplate(isSelected: templateButton.isSelected) - } - - @objc private func tappedComment() { - delegate?.didTapComment(isSelected: commentButton.isSelected) - } - - @objc private func tappedLink() { - delegate?.didTapLink(isSelected: linkButton.isSelected) - } -} diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib deleted file mode 100644 index 61d1e9b3fb0..00000000000 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Components/Sources/Components/Components/Editors/Common Views/WKEditorToolbarNavigatorButton.swift b/Components/Sources/Components/Components/Editors/Common Views/WKEditorToolbarNavigatorButton.swift index 2f11153f3a1..c5b9f627650 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/WKEditorToolbarNavigatorButton.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/WKEditorToolbarNavigatorButton.swift @@ -4,11 +4,9 @@ import UIKit class WKEditorToolbarNavigatorButton: WKComponentView { // MARK: - Properties - - private lazy var button: UIButton = { - let button = UIButton(type: .custom) - return button - }() + + private var button: UIButton? + private var image: UIImage? // MARK: - Lifecycle @@ -23,6 +21,12 @@ class WKEditorToolbarNavigatorButton: WKComponentView { } private func setup() { + button = createButton() + + guard let button else { + return + } + isAccessibilityElement = true accessibilityTraits = [.button] button.isAccessibilityElement = false @@ -36,21 +40,69 @@ class WKEditorToolbarNavigatorButton: WKComponentView { button.topAnchor.constraint(equalTo: topAnchor), button.bottomAnchor.constraint(equalTo: bottomAnchor) ]) + } + + // MARK: - Overrides + + override var intrinsicContentSize: CGSize { + // Increase touch targets & make widths more consistent + let superSize = super.intrinsicContentSize + return CGSize(width: max(superSize.width, 36), height: max(superSize.height, 36)) + } + + override func appEnvironmentDidChange() { + + guard let button else { + return + } - button.imageView?.contentMode = .scaleAspectFit + let buttonConfig = createButtonConfig(image: image) + button.configuration = buttonConfig } // MARK: - Button passthrough methods func setImage(_ image: UIImage?, for state: UIControl.State) { - button.setImage(image, for: state) + + guard let button else { + return + } + + self.image = image + + var buttonConfig = button.configuration + buttonConfig?.image = image + button.configuration = buttonConfig } func addTarget(_ target: Any?, action: Selector, for controlEvent: UIControl.Event) { - button.addTarget(target, action: action, for: controlEvent) + button?.addTarget(target, action: action, for: controlEvent) } func removeTarget(_ target: Any?, action: Selector?, for controlEvent: UIControl.Event) { - button.removeTarget(target, action: action, for: controlEvent) + button?.removeTarget(target, action: action, for: controlEvent) + } + + // MARK: - Private Helpers + + private func createButtonConfig(image: UIImage? = nil) -> UIButton.Configuration { + var buttonConfig = UIButton.Configuration.plain() + + buttonConfig.baseForegroundColor = theme.link + buttonConfig.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) + buttonConfig.background.cornerRadius = 0 + if let image { + buttonConfig.image = image + } + + return buttonConfig + } + + private func createButton() -> UIButton { + + let buttonConfig = createButtonConfig() + let button = UIButton(configuration: buttonConfig) + + return button } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 1afcdae0f11..c794aef101b 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -267,6 +267,10 @@ public class WKSourceEditorViewController: WKComponentViewController { public func redo() { textView.undoManager?.redo() } + + public func removeFocus() { + textView.resignFirstResponder() + } } // MARK: - Private diff --git a/Components/Sources/Components/Components/Onboarding/WKOnboardingView.swift b/Components/Sources/Components/Components/Onboarding/WKOnboardingView.swift index 3d91b738309..bafb1334f3f 100644 --- a/Components/Sources/Components/Components/Onboarding/WKOnboardingView.swift +++ b/Components/Sources/Components/Components/Onboarding/WKOnboardingView.swift @@ -25,7 +25,7 @@ public struct WKOnboardingView: View { ScrollView(showsIndicators: true) { VStack { Text(viewModel.title) - .font(Font(WKFont.for(.boldTitle))) + .font(Font(WKFont.for(.boldTitle1))) .foregroundColor(Color(appEnvironment.theme.text)) .padding([.bottom, .top], 44) .multilineTextAlignment(.center) diff --git a/Components/Sources/Components/Components/Shared/WKCheckmarkView.swift b/Components/Sources/Components/Components/Shared/WKCheckmarkView.swift index c94ccf268e7..cc3b29c2779 100644 --- a/Components/Sources/Components/Components/Shared/WKCheckmarkView.swift +++ b/Components/Sources/Components/Components/Shared/WKCheckmarkView.swift @@ -23,7 +23,7 @@ struct WKCheckmarkView: View { private var uiImage: UIImage? { switch configuration.style { case .checkbox: - return isSelected ? WKSFSymbolIcon.for(symbol: .checkmarkSquareFill, font: .subheadline) : WKSFSymbolIcon.for(symbol: .square, font: .subheadline) + return isSelected ? WKSFSymbolIcon.for(symbol: .checkmarkSquareFill) : WKSFSymbolIcon.for(symbol: .square) case .`default`: return isSelected ? WKSFSymbolIcon.for(symbol: .checkmark, font: .boldFootnote) : nil } diff --git a/Components/Sources/Components/Components/Shared/WKPriceTextField.swift b/Components/Sources/Components/Components/Shared/WKPriceTextField.swift index 29661a007fb..dd0b92c232c 100644 --- a/Components/Sources/Components/Components/Shared/WKPriceTextField.swift +++ b/Components/Sources/Components/Components/Shared/WKPriceTextField.swift @@ -22,7 +22,7 @@ struct WKPriceTextField: View { var body: some View { TextField("", value: $amount, format: .currency(code: configuration.currencyCode)) .keyboardType(.decimalPad) - .font(Font(WKFont.for(.boldTitle))) + .font(Font(WKFont.for(.boldTitle1))) .foregroundColor(Color(appEnvironment.theme.text)) .padding(5) .background( diff --git a/Components/Sources/Components/Style/WKFont.swift b/Components/Sources/Components/Style/WKFont.swift index fd8b50b5d1b..8286dd8aa78 100644 --- a/Components/Sources/Components/Style/WKFont.swift +++ b/Components/Sources/Components/Style/WKFont.swift @@ -4,8 +4,9 @@ import SwiftUI public enum WKFont { case headline - case title - case boldTitle + case title1 + case boldTitle1 + case boldTitle3 case body case boldBody case italicsBody @@ -28,9 +29,14 @@ public enum WKFont { switch font { case .headline: return UIFont.preferredFont(forTextStyle: .headline, compatibleWith: traitCollection) - case .title: + case .title1: return UIFont.preferredFont(forTextStyle: .title1, compatibleWith: traitCollection) - case .boldTitle: + case .boldTitle3: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title3, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .boldTitle1: guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { fatalError() } diff --git a/Components/Sources/Components/Style/WKIcon.swift b/Components/Sources/Components/Style/WKIcon.swift index a08796dd175..7b9ccf37b70 100644 --- a/Components/Sources/Components/Style/WKIcon.swift +++ b/Components/Sources/Components/Style/WKIcon.swift @@ -20,8 +20,7 @@ public enum WKIcon { static let userContributions = UIImage(named: "user-contributions", in: .module, with: nil) // Editor-specific icons - static let formatText = UIImage(named: "editor/format-text", in: .module, with: nil)// - static let formatHeading = UIImage(named: "editor/format-heading", in: .module, with: nil)// + public static let separator = UIImage(named: "separator", in: .module, with: nil) // Project icons static let commons = UIImage(named: "project-icons/commons", in: .module, with: nil) @@ -29,6 +28,7 @@ public enum WKIcon { } public enum WKSFSymbolIcon { + case checkmark case checkmarkSquareFill case square @@ -55,6 +55,7 @@ public enum WKSFSymbolIcon { case bold case italic case exclamationMarkCircle + case exclamationMarkCircleFill case textFormatSuperscript case textFormatSubscript case underline @@ -65,86 +66,108 @@ public enum WKSFSymbolIcon { case ellipsis case pencil case plusCircleFill + case undo + case redo + case textFormatSize + case textFormat - public static func `for`(symbol: WKSFSymbolIcon, font: WKFont = .body, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection) -> UIImage? { + public static func `for`(symbol: WKSFSymbolIcon, font: WKFont = .subheadline, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection, renderingMode: UIImage.RenderingMode = .alwaysTemplate, paletteColors: [UIColor]? = nil) -> UIImage? { let font = WKFont.for(font) let configuration = UIImage.SymbolConfiguration(font: font) + var image: UIImage? switch symbol { case .checkmark: - return UIImage(systemName: "checkmark", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "checkmark", withConfiguration: configuration) case .checkmarkSquareFill: - return UIImage(systemName: "checkmark.square.fill", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "checkmark.square.fill", withConfiguration: configuration) case .square: - return UIImage(systemName: "square", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "square", withConfiguration: configuration) case .star: - return UIImage(systemName: "star", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "star", withConfiguration: configuration) case .person: - return UIImage(systemName: "person", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "person", withConfiguration: configuration) case .personFilled: - return UIImage(systemName: "person.fill", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "person.fill", withConfiguration: configuration) case .starLeadingHalfFilled: - return UIImage(systemName: "star.leadinghalf.filled", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "star.leadinghalf.filled", withConfiguration: configuration) case .heart: - return UIImage(systemName: "heart", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "heart", withConfiguration: configuration) case .conversation: - return UIImage(systemName: "bubble.left.and.bubble.right", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "bubble.left.and.bubble.right", withConfiguration: configuration) case .quoteOpening: - return UIImage(systemName: "quote.opening", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "quote.opening", withConfiguration: configuration) case .link: - return UIImage(systemName: "link", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "link", withConfiguration: configuration) case .curlybraces: - return UIImage(systemName: "curlybraces", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "curlybraces", withConfiguration: configuration) case .photo: - return UIImage(systemName: "photo", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "photo", withConfiguration: configuration) case .docTextMagnifyingGlass: - return UIImage(systemName: "doc.text.magnifyingglass", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "doc.text.magnifyingglass", withConfiguration: configuration) case .magnifyingGlass: - return UIImage(systemName: "magnifyingglass", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "magnifyingglass", withConfiguration: configuration) case .listBullet: - return UIImage(systemName: "list.bullet", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "list.bullet", withConfiguration: configuration) case .listNumber: - return UIImage(systemName: "list.number", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "list.number", withConfiguration: configuration) case .increaseIndent: - return UIImage(systemName: "increase.indent", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate).imageFlippedForRightToLeftLayoutDirection() + image = UIImage(systemName: "increase.indent", withConfiguration: configuration)?.imageFlippedForRightToLeftLayoutDirection() case .decreaseIndent: - return UIImage(systemName: "decrease.indent", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate).imageFlippedForRightToLeftLayoutDirection() + image = UIImage(systemName: "decrease.indent", withConfiguration: configuration)?.imageFlippedForRightToLeftLayoutDirection() case .chevronUp: - return UIImage(systemName: "chevron.up", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "chevron.up", withConfiguration: configuration) case .chevronDown: - return UIImage(systemName: "chevron.down", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "chevron.down", withConfiguration: configuration) case .chevronBackward: - return UIImage(systemName: "chevron.backward", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "chevron.backward", withConfiguration: configuration) case .chevronForward: - return UIImage(systemName: "chevron.forward", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "chevron.forward", withConfiguration: configuration) case .bold: - return UIImage(systemName: "bold", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "bold", withConfiguration: configuration) case .italic: - return UIImage(systemName: "italic", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "italic", withConfiguration: configuration) case .exclamationMarkCircle: - return UIImage(systemName: "exclamationmark.circle", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "exclamationmark.circle", withConfiguration: configuration) + case .exclamationMarkCircleFill: + image = UIImage(systemName: "exclamationmark.circle.fill", withConfiguration: configuration) case .textFormatSuperscript: - return UIImage(systemName: "textformat.superscript", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "textformat.superscript", withConfiguration: configuration) case .textFormatSubscript: - return UIImage(systemName: "textformat.subscript", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "textformat.subscript", withConfiguration: configuration) case .underline: - return UIImage(systemName: "underline", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "underline", withConfiguration: configuration) case .strikethrough: - return UIImage(systemName: "strikethrough", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "strikethrough", withConfiguration: configuration) case .multiplyCircleFill: - return UIImage(systemName: "multiply.circle.fill", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "multiply.circle.fill", withConfiguration: configuration) case .chevronRightCircle: - return UIImage(systemName: "chevron.right.circle.fill", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate).imageFlippedForRightToLeftLayoutDirection() + image = UIImage(systemName: "chevron.right.circle.fill", withConfiguration: configuration)?.imageFlippedForRightToLeftLayoutDirection() case .close: - return UIImage(systemName: "multiply", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "multiply", withConfiguration: configuration) case .ellipsis: - return UIImage(systemName: "ellipsis", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "ellipsis", withConfiguration: configuration) case .pencil: - return UIImage(systemName: "pencil", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) + image = UIImage(systemName: "pencil", withConfiguration: configuration) case .plusCircleFill: - return UIImage(systemName: "plus.circle.fill", withConfiguration: configuration)?.withRenderingMode(.alwaysTemplate) - + image = UIImage(systemName: "plus.circle.fill", withConfiguration: configuration) + case .undo: + image = UIImage(systemName: "arrow.uturn.backward", withConfiguration: configuration) + case .redo: + image = UIImage(systemName: "arrow.uturn.forward", withConfiguration: configuration) + case .textFormatSize: + image = UIImage(systemName: "textformat.size", withConfiguration: configuration) + case .textFormat: + image = UIImage(systemName: "textformat", withConfiguration: configuration) + } + + image = image?.withRenderingMode(.alwaysTemplate) + if let paletteColors { + let paletteSymbolConfiguration = UIImage.SymbolConfiguration(paletteColors: paletteColors) + image = image?.applyingSymbolConfiguration(paletteSymbolConfiguration) } + + return image } } diff --git a/Components/Sources/Components/Style/WKTheme.swift b/Components/Sources/Components/Style/WKTheme.swift index 6e0b766fec3..e04449e88f8 100644 --- a/Components/Sources/Components/Style/WKTheme.swift +++ b/Components/Sources/Components/Style/WKTheme.swift @@ -33,6 +33,8 @@ public struct WKTheme: Equatable { public let editorMatchBackground: UIColor public let editorSelectedMatchBackground: UIColor public let editorReplacedMatchBackground: UIColor + public let editorMultiButtonSelectedBackground: UIColor + public let editorKeyboardShadow: UIColor public static let light = WKTheme( name: "Light", @@ -64,7 +66,9 @@ public struct WKTheme: Equatable { editorMatchForeground: .black, editorMatchBackground: WKColor.lightMatchBackground, editorSelectedMatchBackground: WKColor.yellow600, - editorReplacedMatchBackground: WKColor.matchReplacedBackground + editorReplacedMatchBackground: WKColor.matchReplacedBackground, + editorMultiButtonSelectedBackground: WKColor.gray200, + editorKeyboardShadow: WKColor.gray200 ) public static let sepia = WKTheme( @@ -97,7 +101,9 @@ public struct WKTheme: Equatable { editorMatchForeground: .black, editorMatchBackground: WKColor.lightMatchBackground, editorSelectedMatchBackground: WKColor.yellow600, - editorReplacedMatchBackground: WKColor.matchReplacedBackground + editorReplacedMatchBackground: WKColor.matchReplacedBackground, + editorMultiButtonSelectedBackground: WKColor.beige400, + editorKeyboardShadow: WKColor.taupe200 ) public static let dark = WKTheme( @@ -130,7 +136,9 @@ public struct WKTheme: Equatable { editorMatchForeground: .black, editorMatchBackground: WKColor.darkMatchBackground, editorSelectedMatchBackground: WKColor.yellow600, - editorReplacedMatchBackground: WKColor.matchReplacedBackground + editorReplacedMatchBackground: WKColor.matchReplacedBackground, + editorMultiButtonSelectedBackground: WKColor.gray600, + editorKeyboardShadow: WKColor.gray800 ) public static let black = WKTheme( @@ -163,7 +171,9 @@ public struct WKTheme: Equatable { editorMatchForeground: .black, editorMatchBackground: WKColor.darkMatchBackground, editorSelectedMatchBackground: WKColor.yellow600, - editorReplacedMatchBackground: WKColor.matchReplacedBackground + editorReplacedMatchBackground: WKColor.matchReplacedBackground, + editorMultiButtonSelectedBackground: WKColor.gray600, + editorKeyboardShadow: WKColor.gray700 ) } diff --git a/Wikipedia/Code/FocusNavigationView.swift b/Wikipedia/Code/FocusNavigationView.swift index 240ee38021a..c1e50d63dd4 100644 --- a/Wikipedia/Code/FocusNavigationView.swift +++ b/Wikipedia/Code/FocusNavigationView.swift @@ -1,4 +1,5 @@ import UIKit +import Components protocol FocusNavigationViewDelegate: AnyObject { func focusNavigationViewDidTapClose(_ focusNavigationView: FocusNavigationView) @@ -13,6 +14,13 @@ final class FocusNavigationView: UIView { weak var delegate: FocusNavigationViewDelegate? + override func awakeFromNib() { + super.awakeFromNib() + + closeButton.imageView?.contentMode = .center + closeButton.setImage(WKSFSymbolIcon.for(symbol: .multiplyCircleFill), for: .normal) + } + func configure(titleText: String, closeButtonAccessibilityText: String, traitCollection: UITraitCollection, isTitleAccessible: Bool = false) { titleLabel.text = titleText diff --git a/Wikipedia/Code/FocusNavigationView.xib b/Wikipedia/Code/FocusNavigationView.xib index 6b0cd6c1c08..347e91da5f0 100644 --- a/Wikipedia/Code/FocusNavigationView.xib +++ b/Wikipedia/Code/FocusNavigationView.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -17,18 +15,13 @@ - - - -