Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Modal Layout Picker] Create Layout Picker category bar UI using static data #14631

Merged
merged 33 commits into from
Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
53c6bca
Create basic category bar cells
chipsnyder Aug 4, 2020
e4b193e
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 4, 2020
64ebf88
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 4, 2020
2fd02b2
Fill in labels and emoji's for filterbar
chipsnyder Aug 4, 2020
1e1860e
Configure dynamic font for filter bar
chipsnyder Aug 4, 2020
43c1b76
Handle selection background color of filter cells
chipsnyder Aug 4, 2020
af0b82d
Update dark colors for the selected pill color
chipsnyder Aug 5, 2020
93772d4
Handle selection background color of filter cells on dark mode
chipsnyder Aug 5, 2020
10864da
Add checkmarks to selected filter pills
chipsnyder Aug 5, 2020
45f693c
Adjust checkmark estimated size and support no emoji's
chipsnyder Aug 5, 2020
925767e
Animate out the change it category selections
chipsnyder Aug 6, 2020
b143db9
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 10, 2020
7882abe
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 10, 2020
8d1fcdd
Adjust the selection of categories to maintain the correct layout sel…
chipsnyder Aug 11, 2020
1f7d4fd
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 11, 2020
2716f1a
Adjust shadow to fill in footer when there is one item
chipsnyder Aug 11, 2020
6a0329a
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 11, 2020
8b83cc6
Remove duplicate file from rename
chipsnyder Aug 11, 2020
4347988
Rename category bar for more generic naming convention
chipsnyder Aug 11, 2020
8586e7b
Update inline documentation
chipsnyder Aug 11, 2020
63d20cb
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 11, 2020
d614251
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 12, 2020
c090097
Update category animation style
chipsnyder Aug 13, 2020
c9754b3
Continue to refine shadow
chipsnyder Aug 13, 2020
5d12e74
Support Multi select
chipsnyder Aug 13, 2020
4cecbd6
Add support for SF image on the filter checkmark
chipsnyder Aug 13, 2020
0931631
Merge branch 'gutenberg/issue/2436-thumbailPreviews' into gutenberg/i…
chipsnyder Aug 14, 2020
bbdece4
Merge remote-tracking branch 'origin/develop' into gutenberg/issue/24…
chipsnyder Aug 20, 2020
8e1631f
Remove unused var
chipsnyder Aug 24, 2020
348f2ab
Adjust the presentation style to account for difference in Max size d…
chipsnyder Aug 25, 2020
6896894
Resolve style related feedback on using constants
chipsnyder Aug 25, 2020
f567888
Adjust Filter bar colors in iOS 12
chipsnyder Aug 25, 2020
e4fccb6
Fix display issue on iOS 13 devices
chipsnyder Aug 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion WordPress/Classes/Services/PageCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ class PageCoordinator {
return
}
rootView.completion = completion
navigationController.modalPresentationStyle = .pageSheet

if #available(iOS 13.0, *) {
navigationController.modalPresentationStyle = .pageSheet
} else {
// Specifically using fullScreen instead of pageSheet to get the desired behavior on Max devices running iOS 12 and below.
navigationController.modalPresentationStyle = UIDevice.current.userInterfaceIdiom == .pad ? .pageSheet : .fullScreen
}

controller.present(navigationController, animated: true, completion: nil)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import UIKit

protocol FilterBarDelegate {
func numberOfFilters() -> Int
func filter(forIndex: Int) -> GutenbergLayoutSection
func didSelectFilter(withIndex selectedIndex: IndexPath, withSelectedIndexes selectedIndexes: [IndexPath])
func didDeselectFilter(withIndex index: IndexPath, withSelectedIndexes selectedIndexes: [IndexPath])
}

class GutenbergLayoutFilterBar: UICollectionView {
var filterDelegate: FilterBarDelegate?
private let defaultCellHeight: CGFloat = 44
private let defaultCellWidth: CGFloat = 105

required init?(coder: NSCoder) {
super.init(coder: coder)
register(LayoutPickerFilterCollectionViewCell.nib, forCellWithReuseIdentifier: LayoutPickerFilterCollectionViewCell.cellReuseIdentifier)
self.delegate = self
self.dataSource = self
}

private func deselectItem(_ indexPath: IndexPath) {
deselectItem(at: indexPath, animated: true)
collectionView(self, didDeselectItemAt: indexPath)
}
}

extension GutenbergLayoutFilterBar: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
if collectionView.cellForItem(at: indexPath)?.isSelected ?? false {
deselectItem(indexPath)
return false
}
return true
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems else { return }
filterDelegate?.didSelectFilter(withIndex: indexPath, withSelectedIndexes: indexPathsForSelectedItems)
}

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
filterDelegate?.didDeselectFilter(withIndex: indexPath, withSelectedIndexes: collectionView.indexPathsForSelectedItems ?? [])
}
}

extension GutenbergLayoutFilterBar: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let filter = filterDelegate?.filter(forIndex: indexPath.item) else {
return CGSize(width: defaultCellWidth, height: defaultCellHeight)
}

let width = LayoutPickerFilterCollectionViewCell.estimatedWidth(forFilter: filter)
return CGSize(width: width, height: defaultCellHeight)
}
}

extension GutenbergLayoutFilterBar: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return filterDelegate?.numberOfFilters() ?? 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LayoutPickerFilterCollectionViewCell.cellReuseIdentifier, for: indexPath) as! LayoutPickerFilterCollectionViewCell
cell.filter = filterDelegate?.filter(forIndex: indexPath.item)
return cell
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class GutenbergLayoutPickerViewController: UIViewController {
@IBOutlet weak var titleView: UILabel!
@IBOutlet weak var largeTitleView: UILabel!
@IBOutlet weak var promptView: UILabel!
@IBOutlet weak var categoryBar: UICollectionView!
@IBOutlet weak var filterBar: GutenbergLayoutFilterBar!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this just to support a more generic naming structure as this might be used for Gutenboarding later.

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var footerView: UIView!
@IBOutlet weak var createBlankPageBtn: UIButton!
Expand Down Expand Up @@ -108,7 +108,9 @@ class GutenbergLayoutPickerViewController: UIViewController {
layoutSelected(selectedLayout != nil)
}
}
private var sections = [GutenbergLayoutSection]()

private var filteredSections: [GutenbergLayoutSection]?
private var sections: [GutenbergLayoutSection] = []
var layouts = GutenbergPageLayouts(layouts: [], categories: []) {
didSet {
sections = layouts.categories.map({
Expand All @@ -135,11 +137,14 @@ class GutenbergLayoutPickerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(LayoutPickerSectionTableViewCell.nib, forCellReuseIdentifier: LayoutPickerSectionTableViewCell.cellReuseIdentifier)
filterBar.filterDelegate = self
filterBar.allowsMultipleSelection = true
setStaticText()
closeButton.setImage(UIImage.gridicon(.crossSmall), for: .normal)
styleButtons()
layoutHeader()
layouts = GutenbergPageLayoutFactory.makeDefaultPageLayouts()
filterBar.reloadData()
}

override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -231,8 +236,8 @@ class GutenbergLayoutPickerViewController: UIViewController {
}

private func calculateHeaderSnapPoints() {
minHeaderHeight = categoryBar.frame.height + minHeaderBottomSpacing.constant
_midHeaderHeight = titleToSubtitleSpacing.constant + promptView.frame.height + subtitleToCategoryBarSpacing.constant + categoryBar.frame.height + maxHeaderBottomSpacing.constant
minHeaderHeight = filterBar.frame.height + minHeaderBottomSpacing.constant
_midHeaderHeight = titleToSubtitleSpacing.constant + promptView.frame.height + subtitleToCategoryBarSpacing.constant + filterBar.frame.height + maxHeaderBottomSpacing.constant
_maxHeaderHeight = largeTitleView.frame.height + _midHeaderHeight
}

Expand Down Expand Up @@ -317,15 +322,15 @@ extension GutenbergLayoutPickerViewController: UITableViewDelegate {
}

private func containsSelectedLayout(_ layout: GutenbergSelectedLayout, atIndexPath indexPath: IndexPath) -> Bool {
let rowSection = sections[indexPath.row]
let rowSection = (filteredSections ?? sections)[indexPath.row]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about creating a getter for (filteredSections ?? sections) but I felt like that was clouding when I was explicitly trying to reference filteredSections or sections vs when I wanted the one that was appropriate for the current display mode

return (layout.sectionSlug == rowSection.section.slug)
}
}

extension GutenbergLayoutPickerViewController: UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections.count
return (filteredSections ?? sections).count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
Expand All @@ -335,8 +340,9 @@ extension GutenbergLayoutPickerViewController: UITableViewDataSource {
}
cell.delegate = self
cell.selectionStyle = UITableViewCell.SelectionStyle.none
cell.section = sections[indexPath.row]

cell.section = (filteredSections ?? sections)[indexPath.row]
cell.layer.masksToBounds = false
cell.clipsToBounds = false
if let selectedLayout = selectedLayout, containsSelectedLayout(selectedLayout, atIndexPath: indexPath) {
cell.selectItemAt(selectedLayout.position)
}
Expand Down Expand Up @@ -374,3 +380,86 @@ extension GutenbergLayoutPickerViewController: LayoutPickerSectionTableViewCellD
}
}
}

extension GutenbergLayoutPickerViewController: FilterBarDelegate {
func numberOfFilters() -> Int {
return sections.count
}

func filter(forIndex index: Int) -> GutenbergLayoutSection {
return sections[index]
}

func didSelectFilter(withIndex selectedIndex: IndexPath, withSelectedIndexes selectedIndexes: [IndexPath]) {
guard filteredSections == nil else {
insertFilterRow(withIndex: selectedIndex, withSelectedIndexes: selectedIndexes)
return
}

let rowsToRemove = (0..<sections.count).compactMap { ($0 == selectedIndex.item) ? nil : IndexPath(row: $0, section: 0) }

filteredSections = [sections[selectedIndex.item]]
tableView.performBatchUpdates({
tableView.deleteRows(at: rowsToRemove, with: .fade)
}) { _ in
self.snapToHeight(self.tableView, height: self.maxHeaderHeight)
}
}

func insertFilterRow(withIndex selectedIndex: IndexPath, withSelectedIndexes selectedIndexes: [IndexPath]) {

var row: IndexPath? = nil
let sortedIndexes = selectedIndexes.sorted(by: { $0.item < $1.item })
for i in 0..<sortedIndexes.count {
if sortedIndexes[i].item == selectedIndex.item {
let indexPath = IndexPath(row: i, section: 0)
filteredSections?.insert(sections[selectedIndex.item], at: i)
row = indexPath
break
}
}

guard let rowToAdd = row else { return }
tableView.performBatchUpdates({
tableView.insertRows(at: [rowToAdd], with: .fade)
})
}

func didDeselectFilter(withIndex index: IndexPath, withSelectedIndexes selectedIndexes: [IndexPath]) {
guard selectedIndexes.count == 0 else {
removeFilterRow(withIndex: index)
return
}

let currentRowSlug = filteredSections?.first?.section.slug
filteredSections = nil
let rowsToAdd = (0..<sections.count).compactMap { (sections[$0].section.slug == currentRowSlug) ? nil : IndexPath(row: $0, section: 0) }
tableView.performBatchUpdates({
tableView.insertRows(at: rowsToAdd, with: .fade)
})
}

func removeFilterRow(withIndex index: IndexPath) {
guard let filteredSections = filteredSections else { return }

var row: IndexPath? = nil
let rowSlug = sections[index.item].section.slug
for i in 0..<filteredSections.count {
if filteredSections[i].section.slug == rowSlug {
let indexPath = IndexPath(row: i, section: 0)
self.filteredSections?.remove(at: i)
row = indexPath
break
}
}

guard let rowToRemove = row else { return }
tableView.performBatchUpdates({
tableView.deleteRows(at: [rowToRemove], with: .fade)
}) { _ in
if (self.filteredSections?.count ?? 0) < 2 {
self.snapToHeight(self.tableView, height: self.maxHeaderHeight)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import UIKit

class LayoutPickerFilterCollectionViewCell: UICollectionViewCell {

static var cellReuseIdentifier: String {
return "LayoutPickerCategoryCollectionViewCell"
}

static var nib: UINib {
return UINib(nibName: "LayoutPickerCategoryCollectionViewCell", bundle: Bundle.main)
}
guarani marked this conversation as resolved.
Show resolved Hide resolved

@IBOutlet weak var filterLabel: UILabel!

var displayCategory: GutenbergLayoutDisplayCategory? = nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,9 @@ class LayoutPickerCollectionViewCell: UICollectionViewCell {

func addShadow() {
layer.shadowColor = UIColor.black.cgColor

let scale = UIScreen.main.scale
let shadowRadius: CGFloat = 12 / scale
layer.shadowRadius = shadowRadius
layer.shadowRadius = 5.0
layer.shadowOpacity = 0.16
layer.shadowOffset = CGSize(width: 0, height: (5/scale))
layer.shadowOffset = CGSize(width: 0, height: 2.0)

backgroundColor = nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import UIKit

class LayoutPickerFilterCollectionViewCell: UICollectionViewCell {

static let cellReuseIdentifier = "LayoutPickerFilterCollectionViewCell"
static let nib = UINib(nibName: "LayoutPickerFilterCollectionViewCell", bundle: Bundle.main)

static var font: UIFont {
return WPStyleGuide.fontForTextStyle(.subheadline, fontWeight: .semibold)
}
guarani marked this conversation as resolved.
Show resolved Hide resolved

private static let combinedLeftRightMargin: CGFloat = 32
static func estimatedWidth(forFilter filter: GutenbergLayoutSection) -> CGFloat {
/// The emoji below is used as a placeholder to estimate the size of the title. We don't use the actual emoji provided by the API because this could be nil
/// and we want to allow space for a checkmark when the cell is selected.
let size = "👋 \(filter.section.title)".size(withAttributes: [
NSAttributedString.Key.font: font
])

return size.width + combinedLeftRightMargin
}

@IBOutlet weak var filterLabel: UILabel!
@IBOutlet weak var pillBackgroundView: UIView!
@IBOutlet weak var checkmark: UIImageView!

var filter: GutenbergLayoutSection? = nil {
didSet {
let section = filter?.section
filterLabel.text = filterTitle
filterLabel.accessibilityLabel = section?.title
}
}

var filterTitle: String {
let section = filter?.section
let emoji = isSelected ? nil : section?.emoji
return [emoji, section?.title].compactMap { $0 }.joined(separator: " ")
}

var checkmarkTintColor: UIColor {
if #available(iOS 13.0, *) {
return UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return UIColor.darkText
} else {
return UIColor.white
}
}
} else {
return UIColor.white
}
}

override var isSelected: Bool {
didSet {
checkmark.isHidden = !isSelected
filterLabel.text = filterTitle
updateSelectedStyle()
}
}

override func awakeFromNib() {
super.awakeFromNib()
filterLabel.font = LayoutPickerFilterCollectionViewCell.font
if #available(iOS 13.0, *) {
checkmark.image = UIImage(systemName: "checkmark")
} else {
checkmark.image = UIImage.gridicon(.checkmark)
}
checkmark.tintColor = checkmarkTintColor
updateSelectedStyle()
}

override func prepareForReuse() {
super.prepareForReuse()
filter = nil
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)

if #available(iOS 13.0, *) {
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
updateSelectedStyle()
}
}
}

private func updateSelectedStyle() {
if #available(iOS 13.0, *) {
let oppositeInterfaceStyle: UIUserInterfaceStyle = (traitCollection.userInterfaceStyle == .dark) ? .light : .dark
let selectedColor: UIColor = UIColor.systemGray6.color(for: UITraitCollection(userInterfaceStyle: oppositeInterfaceStyle))
pillBackgroundView.backgroundColor = isSelected ? selectedColor : .quaternarySystemFill
} else {
pillBackgroundView.backgroundColor = isSelected ? .black : .gray(.shade0)
}

if #available(iOS 13.0, *), traitCollection.userInterfaceStyle == .dark {
filterLabel.textColor = isSelected ? .darkText : .white
} else {
filterLabel.textColor = isSelected ? .white : .darkText
}
}
}
Loading