diff --git a/Classes/Issues/Assignees/IssueAssigneeSummaryCell.swift b/Classes/Issues/Assignees/IssueAssigneeSummaryCell.swift index 6570302f7..713ae51d5 100644 --- a/Classes/Issues/Assignees/IssueAssigneeSummaryCell.swift +++ b/Classes/Issues/Assignees/IssueAssigneeSummaryCell.swift @@ -34,8 +34,8 @@ final class IssueAssigneeSummaryCell: UICollectionViewCell, UICollectionViewData label.textColor = Styles.Colors.Gray.light.color contentView.addSubview(label) label.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(contentView) + make.centerY.equalToSuperview() + make.left.equalToSuperview() } collectionView.backgroundColor = .clear @@ -44,7 +44,7 @@ final class IssueAssigneeSummaryCell: UICollectionViewCell, UICollectionViewData contentView.addSubview(collectionView) collectionView.snp.makeConstraints { make in make.left.equalTo(label.snp.right).offset(Styles.Sizes.columnSpacing) - make.top.bottom.right.equalTo(contentView) + make.top.bottom.right.equalToSuperview() } } diff --git a/Classes/Issues/Assignees/IssueAssigneeUserCell.swift b/Classes/Issues/Assignees/IssueAssigneeUserCell.swift index e44522631..1e8ac9a6f 100644 --- a/Classes/Issues/Assignees/IssueAssigneeUserCell.swift +++ b/Classes/Issues/Assignees/IssueAssigneeUserCell.swift @@ -28,8 +28,8 @@ final class IssueAssigneeUserCell: UICollectionViewCell, ListBindable { contentView.addSubview(imageView) imageView.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(contentView) + make.centerY.equalToSuperview() + make.left.equalToSuperview() make.size.equalTo(Styles.Sizes.icon) } @@ -38,7 +38,7 @@ final class IssueAssigneeUserCell: UICollectionViewCell, ListBindable { label.backgroundColor = .clear contentView.addSubview(label) label.snp.makeConstraints { make in - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.left.equalTo(imageView.snp.right).offset(Styles.Sizes.columnSpacing) } } diff --git a/Classes/Issues/Branches/IssueBranchesSectionController.swift b/Classes/Issues/Branches/IssueBranchesSectionController.swift index a1f632a27..b49649988 100644 --- a/Classes/Issues/Branches/IssueBranchesSectionController.swift +++ b/Classes/Issues/Branches/IssueBranchesSectionController.swift @@ -16,7 +16,6 @@ final class IssueTargetBranchSectionController: ListSectionController { override func didUpdate(to object: Any) { guard let object = object as? IssueTargetBranchModel else { return } self.object = object - inset = UIEdgeInsets.zero } override func sizeForItem(at index: Int) -> CGSize { diff --git a/Classes/Issues/Branches/IssueTargetBranchModel.swift b/Classes/Issues/Branches/IssueTargetBranchModel.swift index 2ac300585..6e00b75aa 100644 --- a/Classes/Issues/Branches/IssueTargetBranchModel.swift +++ b/Classes/Issues/Branches/IssueTargetBranchModel.swift @@ -32,9 +32,7 @@ final class IssueTargetBranchModel: ListDiffable { self.targetBranchText = StyledTextRenderer( string: builder.build(), - contentSizeCategory: .small, - inset: .zero, - backgroundColor: Styles.Colors.background + contentSizeCategory: .small ).warm(width: width) } diff --git a/Classes/Issues/Comments/CodeBlock/IssueCommentCodeBlockCell.swift b/Classes/Issues/Comments/CodeBlock/IssueCommentCodeBlockCell.swift index 5b274542c..53e99aca1 100644 --- a/Classes/Issues/Comments/CodeBlock/IssueCommentCodeBlockCell.swift +++ b/Classes/Issues/Comments/CodeBlock/IssueCommentCodeBlockCell.swift @@ -20,9 +20,9 @@ final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable { ) static let textViewInset = UIEdgeInsets( top: Styles.Sizes.rowSpacing, - left: Styles.Sizes.commentGutter, + left: Styles.Sizes.columnSpacing, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter + right: Styles.Sizes.columnSpacing ) let textView = StyledTextView() diff --git a/Classes/Issues/Comments/Details/IssueCommentDetailCell.swift b/Classes/Issues/Comments/Details/IssueCommentDetailCell.swift index 935498302..54ba5cbff 100644 --- a/Classes/Issues/Comments/Details/IssueCommentDetailCell.swift +++ b/Classes/Issues/Comments/Details/IssueCommentDetailCell.swift @@ -13,7 +13,6 @@ import SDWebImage import DateAgo protocol IssueCommentDetailCellDelegate: class { - func didTapMore(cell: IssueCommentDetailCell, sender: UIView) func didTapProfile(cell: IssueCommentDetailCell) } @@ -25,7 +24,6 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { private let loginLabel = UILabel() private let dateLabel = ShowMoreDetailsLabel() private let editedLabel = ShowMoreDetailsLabel() - private let moreButton = UIButton() private var login = "" override init(frame: CGRect) { @@ -42,7 +40,7 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { contentView.addSubview(imageView) imageView.snp.makeConstraints { make in make.size.equalTo(Styles.Sizes.avatar) - make.left.equalTo(Styles.Sizes.commentGutter) + make.left.equalToSuperview() make.top.equalTo(Styles.Sizes.rowSpacing) } @@ -55,7 +53,7 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { ) contentView.addSubview(loginLabel) loginLabel.snp.makeConstraints { make in - make.bottom.equalTo(imageView.snp.centerY) + make.centerY.equalTo(imageView) make.left.equalTo(imageView.snp.right).offset(Styles.Sizes.columnSpacing) } @@ -63,30 +61,16 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { dateLabel.textColor = Styles.Colors.Gray.light.color contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in - make.left.equalTo(loginLabel) - make.top.equalTo(loginLabel.snp.bottom) - } - - moreButton.setImage(UIImage(named: "bullets")?.withRenderingMode(.alwaysTemplate), for: .normal) - moreButton.contentVerticalAlignment = .center - moreButton.contentHorizontalAlignment = .right - moreButton.imageView?.contentMode = .center - moreButton.tintColor = Styles.Colors.Gray.light.color - moreButton.addTarget(self, action: #selector(IssueCommentDetailCell.onMore(sender:)), for: .touchUpInside) - moreButton.accessibilityLabel = Constants.Strings.moreOptions - contentView.addSubview(moreButton) - moreButton.snp.makeConstraints { make in - make.size.equalTo(Styles.Sizes.buttonMin) - make.centerY.equalTo(imageView) - make.right.equalTo(-Styles.Sizes.commentGutter) + make.centerY.equalTo(loginLabel) + make.right.equalToSuperview() } editedLabel.font = Styles.Text.secondary.preferredFont editedLabel.textColor = Styles.Colors.Gray.light.color contentView.addSubview(editedLabel) editedLabel.snp.makeConstraints { make in - make.left.equalTo(dateLabel.snp.right).offset(Styles.Sizes.inlineSpacing) - make.centerY.equalTo(dateLabel) + make.left.equalTo(loginLabel.snp.right).offset(Styles.Sizes.columnSpacing/2) + make.centerY.equalTo(loginLabel) } } @@ -94,18 +78,8 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { fatalError("init(coder:) has not been implemented") } - // MARK: Public API - - func setBorderVisible(_ visible: Bool) { - border = visible ? .head : .neck - } - // MARK: Private API - @objc func onMore(sender: UIButton) { - delegate?.didTapMore(cell: self, sender: sender) - } - @objc func onTapAvatar() { delegate?.didTapProfile(cell: self) } @@ -119,21 +93,15 @@ final class IssueCommentDetailCell: IssueCommentBaseCell, ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? IssueCommentDetailsViewModel else { return } - backgroundColor = viewModel.didAuthor - ? Styles.Colors.Blue.light.color - : .white - imageView.sd_setImage(with: viewModel.avatarURL) - dateLabel.setText(date: viewModel.date) + dateLabel.setText(date: viewModel.date, format: .short) loginLabel.text = viewModel.login if let editedLogin = viewModel.editedBy, let editedDate = viewModel.editedAt { editedLabel.isHidden = false - let editedByNonOwner = NSLocalizedString("Edited by %@", comment: "") - let editedByOwner = NSLocalizedString("Edited", comment: "") - let format = viewModel.login != editedLogin ? editedByNonOwner : editedByOwner - editedLabel.text = "\(Constants.Strings.bullet) \(String(format: format, editedLogin))" + let edited = NSLocalizedString("edited", comment: "") + editedLabel.text = "\(Constants.Strings.bullet) \(edited)" let detailFormat = NSLocalizedString("%@ edited this issue %@", comment: "") editedLabel.detailText = String(format: detailFormat, arguments: [editedLogin, editedDate.agoString(.long)]) diff --git a/Classes/Issues/Comments/Hr/IssueCommentHrCell.swift b/Classes/Issues/Comments/Hr/IssueCommentHrCell.swift index 6f3bfd6a6..1678c423e 100644 --- a/Classes/Issues/Comments/Hr/IssueCommentHrCell.swift +++ b/Classes/Issues/Comments/Hr/IssueCommentHrCell.swift @@ -14,9 +14,9 @@ final class IssueCommentHrCell: IssueCommentBaseCell, ListBindable { static let inset = UIEdgeInsets( top: 0, - left: Styles.Sizes.commentGutter, + left: Styles.Sizes.columnSpacing, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter + right: Styles.Sizes.columnSpacing ) let hr = UIView() @@ -27,7 +27,7 @@ final class IssueCommentHrCell: IssueCommentBaseCell, ListBindable { hr.backgroundColor = Styles.Colors.Gray.lighter.color contentView.addSubview(hr) hr.snp.makeConstraints { make in - make.edges.equalTo(contentView).inset(IssueCommentHrCell.inset) + make.edges.equalToSuperview().inset(IssueCommentHrCell.inset) } } diff --git a/Classes/Issues/Comments/Html/IssueCommentHtmlCell.swift b/Classes/Issues/Comments/Html/IssueCommentHtmlCell.swift index 4039ef249..3d6726838 100644 --- a/Classes/Issues/Comments/Html/IssueCommentHtmlCell.swift +++ b/Classes/Issues/Comments/Html/IssueCommentHtmlCell.swift @@ -43,7 +43,7 @@ final class IssueCommentHtmlCell: IssueCommentBaseCell, ListBindable, UIWebViewD // lint compiled style with http://csslint.net/ font-family: -apple-system; font-size: \(Styles.Text.body.preferredFont.pointSize)px; color: #\(Styles.Colors.Gray.dark); - padding: \(Styles.Sizes.columnSpacing)px \(Styles.Sizes.commentGutter)px 0; + padding: \(Styles.Sizes.columnSpacing)px 0 0; margin: 0; } * { font-family: -apple-system; font-size: \(Styles.Text.body.preferredFont.pointSize)px; } @@ -60,7 +60,7 @@ final class IssueCommentHtmlCell: IssueCommentBaseCell, ListBindable, UIWebViewD dl dd{padding: 0 \(Styles.Sizes.HTML.spacing)px;} blockquote{font-style: italic; color: #\(Styles.Colors.Gray.medium);} pre, code{background-color: #\(Styles.Colors.Gray.lighter); font-family: Courier;} - pre{padding: \(Styles.Sizes.columnSpacing)px \(Styles.Sizes.commentGutter)px;} + pre{padding: \(Styles.Sizes.columnSpacing)px 0;} sub{font-size: \(Styles.Text.secondary.preferredFont.pointSize)px;} sub a{font-size: \(Styles.Text.secondary.preferredFont.pointSize)px;} table{border-spacing: 0; border-collapse: collapse;} diff --git a/Classes/Issues/Comments/IssueCommentBaseCell.swift b/Classes/Issues/Comments/IssueCommentBaseCell.swift index b22a01478..4d5d5affd 100644 --- a/Classes/Issues/Comments/IssueCommentBaseCell.swift +++ b/Classes/Issues/Comments/IssueCommentBaseCell.swift @@ -14,22 +14,11 @@ protocol IssueCommentDoubleTapDelegate: class { class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate { - static let collapseCellMinHeight: CGFloat = 30 - - enum BorderType { - case head - case neck - case tail - } + static let collapseCellMinHeight: CGFloat = 45 weak var doubleTapDelegate: IssueCommentDoubleTapDelegate? - var border: BorderType = .neck { - didSet { setNeedsLayout() } - } let doubleTapGesture = UITapGestureRecognizer() - private let borderLayer = CAShapeLayer() - private let backgroundLayer = CAShapeLayer() private let collapseLayer = CAGradientLayer() private let collapseButton = UIButton() @@ -43,35 +32,26 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate { doubleTapGesture.delegate = self addGestureRecognizer(doubleTapGesture) - // insert above contentView layer - borderLayer.strokeColor = UIColor.red.cgColor - borderLayer.strokeColor = Styles.Colors.Gray.border.color.cgColor - borderLayer.lineWidth = 1 / UIScreen.main.scale - borderLayer.fillColor = nil - layer.addSublayer(borderLayer) - - // insert as base layer - backgroundLayer.strokeColor = nil - backgroundLayer.fillColor = UIColor.white.cgColor - layer.insertSublayer(backgroundLayer, at: 0) - collapseLayer.isHidden = true collapseLayer.colors = [ UIColor(white: 1, alpha: 0).cgColor, + UIColor(white: 1, alpha: 1).cgColor, UIColor(white: 1, alpha: 1).cgColor ] + collapseLayer.locations = [0, 0.5, 1] collapseButton.setImage(UIImage(named: "bullets")?.withRenderingMode(.alwaysTemplate), for: .normal) collapseButton.backgroundColor = Styles.Colors.Blue.medium.color collapseButton.accessibilityTraits = UIAccessibilityTraitNone collapseButton.tintColor = .white collapseButton.titleLabel?.font = Styles.Text.smallTitle.preferredFont - collapseButton.clipsToBounds = true collapseButton.isHidden = true collapseButton.contentEdgeInsets = UIEdgeInsets(top: -2, left: 8, bottom: -2, right: 8) collapseButton.imageEdgeInsets = .zero collapseButton.sizeToFit() collapseButton.layer.cornerRadius = collapseButton.bounds.height / 2 + collapseButton.layer.borderColor = UIColor(white: 1, alpha: 0.2).cgColor + collapseButton.layer.borderWidth = 1 collapseButton.isUserInteractionEnabled = false // allow tap to pass through to cell contentView.addSubview(collapseButton) } @@ -84,69 +64,16 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate { super.layoutSubviews() layoutContentViewForSafeAreaInsets() - let bounds = contentView.frame - let inset = borderLayer.lineWidth / 2 - let pixelSnapBounds = bounds.insetBy(dx: inset, dy: inset) - let cornerRadius = Styles.Sizes.cardCornerRadius - - let borderPath = UIBezierPath() - let fillPath: UIBezierPath - - switch border { - case .head: - borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.maxY)) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.minY + cornerRadius)) - borderPath.addQuadCurve( - to: CGPoint(x: pixelSnapBounds.minX + cornerRadius, y: pixelSnapBounds.minY), - controlPoint: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.minY) - ) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX - cornerRadius, y: pixelSnapBounds.minY)) - borderPath.addQuadCurve( - to: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.minY + cornerRadius), - controlPoint: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.minY) - ) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.maxY)) - - fillPath = borderPath.copy() as! UIBezierPath - fillPath.close() - case .neck: - borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.minY)) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.maxY)) - borderPath.move(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.minY)) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.maxY)) - - fillPath = UIBezierPath(rect: bounds) - case .tail: - borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.minY)) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.maxY - cornerRadius)) - borderPath.addQuadCurve( - to: CGPoint(x: pixelSnapBounds.minX + cornerRadius, y: pixelSnapBounds.maxY), - controlPoint: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.maxY) - ) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX - cornerRadius, y: pixelSnapBounds.maxY)) - borderPath.addQuadCurve( - to: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.maxY - cornerRadius), - controlPoint: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.maxY) - ) - borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.minY)) - - fillPath = borderPath.copy() as! UIBezierPath - fillPath.close() - } - borderLayer.path = borderPath.cgPath - backgroundLayer.path = fillPath.cgPath - - borderLayer.frame = self.bounds - backgroundLayer.frame = self.bounds + let contentBounds = contentView.bounds if collapseLayer.isHidden == false { contentView.layer.addSublayer(collapseLayer) contentView.bringSubview(toFront: collapseButton) let collapseFrame = CGRect( - x: bounds.minX, - y: bounds.height - IssueCommentBaseCell.collapseCellMinHeight, - width: bounds.width, + x: contentBounds.minX, + y: contentBounds.height - IssueCommentBaseCell.collapseCellMinHeight, + width: contentBounds.width, height: IssueCommentBaseCell.collapseCellMinHeight ) @@ -163,14 +90,6 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate { } } - override var backgroundColor: UIColor? { - get { - guard let color = backgroundLayer.fillColor else { return nil } - return UIColor(cgColor: color) - } - set { backgroundLayer.fillColor = newValue?.cgColor} - } - override func prepareForReuse() { super.prepareForReuse() collapseLayer.isHidden = true diff --git a/Classes/Issues/Comments/IssueCommentModel+Inset.swift b/Classes/Issues/Comments/IssueCommentModel+Inset.swift index fe749a6fd..cccbc66d6 100644 --- a/Classes/Issues/Comments/IssueCommentModel+Inset.swift +++ b/Classes/Issues/Comments/IssueCommentModel+Inset.swift @@ -11,20 +11,18 @@ import UIKit extension IssueCommentModel { var commentSectionControllerInset: UIEdgeInsets { - let rowSpacing = Styles.Sizes.rowSpacing - switch threadState { case .single: // title and other header objects will have bottom insetting if isRoot { - return UIEdgeInsets(top: 12, left: 0, bottom: rowSpacing, right: 0) + return UIEdgeInsets(top: Styles.Sizes.rowSpacing/2, left: 0, bottom: 0, right: 0) } else { - return UIEdgeInsets(top: rowSpacing, left: 0, bottom: rowSpacing, right: 0) + return .zero } case .neck: return .zero case .tail: - return UIEdgeInsets(top: 0, left: 0, bottom: rowSpacing, right: 0) + return .zero } } diff --git a/Classes/Issues/Comments/IssueCommentSectionController.swift b/Classes/Issues/Comments/IssueCommentSectionController.swift index 001663bb8..f802113b4 100644 --- a/Classes/Issues/Comments/IssueCommentSectionController.swift +++ b/Classes/Issues/Comments/IssueCommentSectionController.swift @@ -223,7 +223,8 @@ final class IssueCommentSectionController: repo: model.repo, width: width, viewerCanUpdate: true, - contentSizeCategory: UIApplication.shared.preferredContentSizeCategory + contentSizeCategory: UIApplication.shared.preferredContentSizeCategory, + isRoot: self.object?.isRoot == true ) bodyEdits = (markdown, bodyModels) collapsed = false @@ -320,7 +321,8 @@ final class IssueCommentSectionController: ? (object.threadState == .tail ? [tailModel] : []) : [ reactionMutation ?? object.reactions ] - return [ object.details, headModel ] +// return [ object.details, headModel ] + return [ object.details ] + bodies + tail } @@ -393,11 +395,8 @@ final class IssueCommentSectionController: // connect specific cell delegates if let cell = cell as? IssueCommentDetailCell { - cell.setBorderVisible(object?.threadState == .single) cell.delegate = self } else if let cell = cell as? IssueCommentReactionCell { - let threadState = object?.threadState - cell.configure(borderVisible: threadState == .single || threadState == .tail) cell.delegate = self } @@ -452,30 +451,6 @@ final class IssueCommentSectionController: // MARK: IssueCommentDetailCellDelegate - func didTapMore(cell: IssueCommentDetailCell, sender: UIView) { - guard let login = object?.details.login else { - Squawk.showGenericError() - return - } - - let alertTitle = NSLocalizedString("%@'s comment", comment: "Used in an action sheet title, eg. \"Basthomas's comment\".") - - let alert = UIAlertController.configured( - title: String(format: alertTitle, login), - preferredStyle: .actionSheet - ) - alert.popoverPresentationController?.sourceView = sender - alert.addActions([ - shareAction(sender: sender), - viewMarkdownAction, - editAction, - replyAction, - deleteAction, - AlertAction.cancel() - ]) - viewController?.present(alert, animated: trueUnlessReduceMotionEnabled) - } - func didTapProfile(cell: IssueCommentDetailCell) { guard let login = object?.details.login else { Squawk.showGenericError() @@ -513,6 +488,30 @@ final class IssueCommentSectionController: react(cell: cell, content: reaction, isAdd: false) } + func didTapMore(cell: IssueCommentReactionCell, sender: UIView) { + guard let login = object?.details.login else { + Squawk.showGenericError() + return + } + + let alertTitle = NSLocalizedString("%@'s comment", comment: "Used in an action sheet title, eg. \"Basthomas's comment\".") + + let alert = UIAlertController.configured( + title: String(format: alertTitle, login), + preferredStyle: .actionSheet + ) + alert.popoverPresentationController?.sourceView = sender + alert.addActions([ + shareAction(sender: sender), + viewMarkdownAction, + editAction, + replyAction, + deleteAction, + AlertAction.cancel() + ]) + viewController?.present(alert, animated: trueUnlessReduceMotionEnabled) + } + // MARK: MarkdownStyledTextViewDelegate func didTap(cell: MarkdownStyledTextView, attribute: DetectedMarkdownAttribute) { diff --git a/Classes/Issues/Comments/Markdown/CMarkParsing.swift b/Classes/Issues/Comments/Markdown/CMarkParsing.swift index 374e6ab12..357076615 100644 --- a/Classes/Issues/Comments/Markdown/CMarkParsing.swift +++ b/Classes/Issues/Comments/Markdown/CMarkParsing.swift @@ -19,6 +19,7 @@ private struct CMarkOptions { let width: CGFloat let viewerCanUpdate: Bool let contentSizeCategory: UIContentSizeCategory + let isRoot: Bool } private struct CMarkContext { @@ -201,7 +202,8 @@ private extension TableRow { case .header(let cells): builders = cells.map { $0.build( - StyledTextBuilder.markdownBase().add(traits: .traitBold), + StyledTextBuilder.markdownBase(isRoot: options.isRoot) + .add(traits: .traitBold), options: options, context: CMarkContext() ) @@ -209,7 +211,8 @@ private extension TableRow { case .row(let cells): builders = cells.map { $0.build( - StyledTextBuilder.markdownBase().add(attributes: [.backgroundColor: backgroundColor]), + StyledTextBuilder.markdownBase(isRoot: options.isRoot) + .add(attributes: [.backgroundColor: backgroundColor]), options: options, context: CMarkContext() ) @@ -239,18 +242,21 @@ private extension Array where Iterator.Element == TableRow { } private extension Element { - func model(_ options: CMarkOptions) -> ListDiffable? { + func model(_ options: CMarkOptions, lastElement: Bool) -> ListDiffable? { switch self { case .text(let items): return StyledTextRenderer( - string: items.build(StyledTextBuilder.markdownBase(), options: options, context: CMarkContext()) - .build(renderMode: .preserve), + string: items.build( + StyledTextBuilder.markdownBase(isRoot: options.isRoot), + options: options, + context: CMarkContext() + ).build(renderMode: .preserve), contentSizeCategory: options.contentSizeCategory, - inset: IssueCommentTextCell.inset, + inset: IssueCommentTextCell.inset(isLast: lastElement), backgroundColor: .white ).warm(width: options.width) case .quote(let items, let level): - let builder = StyledTextBuilder.markdownBase() + let builder = StyledTextBuilder.markdownBase(isRoot: options.isRoot) .add(attributes: [.foregroundColor: Styles.Colors.Gray.medium.color]) let string = StyledTextRenderer( string: items.build(builder, options: options, context: CMarkContext()).build(renderMode: .preserve), @@ -283,7 +289,7 @@ private extension Element { case .codeBlock(let text, let language): return CodeBlockElement(text: text, language: language, contentSizeCategory: options.contentSizeCategory) case .heading(let text, let level): - let builder = StyledTextBuilder.markdownBase() + let builder = StyledTextBuilder.markdownBase(isRoot: options.isRoot) let style: TextStyle switch level { case 1: style = Styles.Text.h1 @@ -297,15 +303,20 @@ private extension Element { return StyledTextRenderer( string: text.build(builder, options: options, context: CMarkContext()).build(renderMode: .preserve), contentSizeCategory: options.contentSizeCategory, - inset: IssueCommentTextCell.inset, + inset: IssueCommentTextCell.inset(isLast: lastElement), backgroundColor: .white ).warm(width: options.width) case .list(let items, let type): - let builder = items.build(StyledTextBuilder.markdownBase(), options: options, type: type, level: 0) + let builder = items.build( + StyledTextBuilder.markdownBase(isRoot: options.isRoot), + options: options, + type: type, + level: 0 + ) return StyledTextRenderer( string: builder.build(renderMode: .preserve), contentSizeCategory: options.contentSizeCategory, - inset: IssueCommentTextCell.inset, + inset: IssueCommentTextCell.inset(isLast: lastElement), backgroundColor: .white ).warm(width: options.width) case .table(let rows): @@ -323,7 +334,7 @@ private extension Element { } private func emptyDescription(options: CMarkOptions) -> ListDiffable { - let builder = StyledTextBuilder.markdownBase() + let builder = StyledTextBuilder.markdownBase(isRoot: options.isRoot) .add( text: NSLocalizedString("No description provided.", comment: ""), traits: .traitItalic, @@ -332,7 +343,7 @@ private func emptyDescription(options: CMarkOptions) -> ListDiffable { return StyledTextRenderer( string: builder.build(), contentSizeCategory: options.contentSizeCategory, - inset: IssueCommentTextCell.inset + inset: IssueCommentTextCell.inset(isLast: true) ).warm(width: options.width) } @@ -343,6 +354,7 @@ func MarkdownModels( width: CGFloat, viewerCanUpdate: Bool, contentSizeCategory: UIContentSizeCategory, + isRoot: Bool, branch: String = "master" ) -> [ListDiffable] { let cleaned = markdown @@ -357,9 +369,18 @@ func MarkdownModels( branch: branch, width: width, viewerCanUpdate: viewerCanUpdate, - contentSizeCategory: contentSizeCategory + contentSizeCategory: contentSizeCategory, + isRoot: isRoot ) - let models = node.flatElements.compactMap { $0.model(options) } + + var models = [ListDiffable]() + let elements = node.flatElements + let count = elements.count + for (i, el) in elements.enumerated() { + if let model = el.model(options, lastElement: i == count - 1) { + models.append(model) + } + } if models.count == 0 { return [emptyDescription(options: options)] } else { diff --git a/Classes/Issues/Comments/Markdown/StyledTextBuilder+NewBase.swift b/Classes/Issues/Comments/Markdown/StyledTextBuilder+NewBase.swift index cf6efd603..7f4f11c42 100644 --- a/Classes/Issues/Comments/Markdown/StyledTextBuilder+NewBase.swift +++ b/Classes/Issues/Comments/Markdown/StyledTextBuilder+NewBase.swift @@ -10,16 +10,20 @@ import UIKit import StyledTextKit extension StyledTextBuilder { - static func markdownBase() -> StyledTextBuilder { + static func markdownBase(isRoot: Bool) -> StyledTextBuilder { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.paragraphSpacingBefore = 12 + paragraphStyle.lineSpacing = 2 let attributes: [NSAttributedStringKey: Any] = [ .foregroundColor: Styles.Colors.Gray.dark.color, .paragraphStyle: paragraphStyle, - .backgroundColor: UIColor.white + .backgroundColor: UIColor.white, ] + let style = isRoot + ? Styles.Text.rootBody + : Styles.Text.commentBody return StyledTextBuilder(styledText: StyledText( - style: Styles.Text.body.with(attributes: attributes) + style: style.with(attributes: attributes) )) } } diff --git a/Classes/Issues/Comments/Markdown/ViewMarkdownViewController.swift b/Classes/Issues/Comments/Markdown/ViewMarkdownViewController.swift index 0c1f6823f..10efbd322 100644 --- a/Classes/Issues/Comments/Markdown/ViewMarkdownViewController.swift +++ b/Classes/Issues/Comments/Markdown/ViewMarkdownViewController.swift @@ -38,9 +38,9 @@ final class ViewMarkdownViewController: UIViewController { textView.textColor = Styles.Colors.Gray.dark.color textView.textContainerInset = UIEdgeInsets( top: Styles.Sizes.rowSpacing, - left: Styles.Sizes.rowSpacing, + left: Styles.Sizes.columnSpacing, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.rowSpacing + right: Styles.Sizes.columnSpacing ) view.addSubview(textView) } diff --git a/Classes/Issues/Comments/Quotes/IssueCommentQuoteCell.swift b/Classes/Issues/Comments/Quotes/IssueCommentQuoteCell.swift index 14c6524ab..82857b607 100644 --- a/Classes/Issues/Comments/Quotes/IssueCommentQuoteCell.swift +++ b/Classes/Issues/Comments/Quotes/IssueCommentQuoteCell.swift @@ -20,9 +20,9 @@ final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable { static func inset(quoteLevel: Int) -> UIEdgeInsets { return UIEdgeInsets( top: 0, - left: Styles.Sizes.commentGutter + (IssueCommentQuoteCell.borderWidth + Styles.Sizes.columnSpacing) * CGFloat(quoteLevel), + left: (IssueCommentQuoteCell.borderWidth + Styles.Sizes.columnSpacing) * CGFloat(quoteLevel), bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter + right: 0 ) } @@ -43,7 +43,7 @@ final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable { textView.reposition(for: contentView.bounds.width) for (i, border) in borders.enumerated() { border.frame = CGRect( - x: Styles.Sizes.commentGutter + (IssueCommentQuoteCell.borderWidth + Styles.Sizes.columnSpacing) * CGFloat(i), + x: (IssueCommentQuoteCell.borderWidth + Styles.Sizes.columnSpacing) * CGFloat(i), y: 0, width: IssueCommentQuoteCell.borderWidth, height: contentView.bounds.height - Styles.Sizes.rowSpacing diff --git a/Classes/Issues/Comments/Reactions/IssueCommentReactionCell.swift b/Classes/Issues/Comments/Reactions/IssueCommentReactionCell.swift index 9920c4422..3f9270570 100644 --- a/Classes/Issues/Comments/Reactions/IssueCommentReactionCell.swift +++ b/Classes/Issues/Comments/Reactions/IssueCommentReactionCell.swift @@ -15,6 +15,7 @@ protocol IssueCommentReactionCellDelegate: class { func didHideMenu(cell: IssueCommentReactionCell) func didAdd(cell: IssueCommentReactionCell, reaction: ReactionContent) func didRemove(cell: IssueCommentReactionCell, reaction: ReactionContent) + func didTapMore(cell: IssueCommentReactionCell, sender: UIView) } final class IssueCommentReactionCell: IssueCommentBaseCell, @@ -27,6 +28,7 @@ UICollectionViewDelegateFlowLayout { public weak var delegate: IssueCommentReactionCellDelegate? private let addButton = ResponderButton() + private let moreButton = ResponderButton() private let collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal @@ -44,25 +46,43 @@ UICollectionViewDelegateFlowLayout { addButton.tintColor = Styles.Colors.Gray.light.color addButton.setTitle("+", for: .normal) - addButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: -5, bottom: 0, right: 0) + addButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10) addButton.setTitleColor(Styles.Colors.Gray.light.color, for: .normal) - addButton.semanticContentAttribute = .forceRightToLeft addButton.setImage(UIImage(named: "smiley-small")?.withRenderingMode(.alwaysTemplate), for: .normal) addButton.addTarget(self, action: #selector(IssueCommentReactionCell.onAddButton), for: .touchUpInside) addButton.accessibilityLabel = NSLocalizedString("Add reaction", comment: "") + addButton.setContentCompressionResistancePriority(.required, for: .horizontal) contentView.addSubview(addButton) addButton.snp.makeConstraints { make in - make.left.equalTo(Styles.Sizes.commentGutter) - make.bottom.equalTo(contentView).offset(-Styles.Sizes.rowSpacing) + make.left.equalToSuperview() + make.bottom.equalToSuperview().offset(-Styles.Sizes.rowSpacing) + } + + moreButton.setImage(UIImage(named: "bullets-small")?.withRenderingMode(.alwaysTemplate), for: .normal) + moreButton.contentVerticalAlignment = .center + moreButton.contentHorizontalAlignment = .right + moreButton.imageView?.contentMode = .center + moreButton.tintColor = Styles.Colors.Gray.light.color + moreButton.addTarget(self, action: #selector(IssueCommentReactionCell.onMore(sender:)), for: .touchUpInside) + moreButton.accessibilityLabel = Constants.Strings.moreOptions + contentView.addSubview(moreButton) + moreButton.setContentCompressionResistancePriority(.required, for: .horizontal) + moreButton.snp.makeConstraints { make in + make.size.equalTo(Styles.Sizes.buttonMin) + make.centerY.equalToSuperview() + make.right.equalToSuperview() } collectionView.backgroundColor = .clear collectionView.dataSource = self collectionView.delegate = self + collectionView.clipsToBounds = true + collectionView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) contentView.addSubview(collectionView) collectionView.snp.makeConstraints { make in make.left.equalTo(addButton.snp.right).offset(Styles.Sizes.columnSpacing) - make.top.bottom.right.equalTo(contentView) + make.top.bottom.right.equalToSuperview() + make.right.equalTo(moreButton.snp.left).offset(-Styles.Sizes.columnSpacing) } let nc = NotificationCenter.default @@ -93,10 +113,6 @@ UICollectionViewDelegateFlowLayout { queuedOperation = (content, operation) } - func configure(borderVisible: Bool) { - border = borderVisible ? .tail : .neck - } - // MARK: Private API func cell(for content: ReactionContent) -> IssueReactionCell? { @@ -188,6 +204,10 @@ UICollectionViewDelegateFlowLayout { data.cell.iterate(add: add) } + @objc func onMore(sender: UIView) { + delegate?.didTapMore(cell: self, sender: sender) + } + // MARK: Notifications @objc func onMenuControllerWillShow(notification: Notification) { diff --git a/Classes/Issues/Comments/Reactions/IssueReactionCell.swift b/Classes/Issues/Comments/Reactions/IssueReactionCell.swift index 8377a72ae..788a57d22 100644 --- a/Classes/Issues/Comments/Reactions/IssueReactionCell.swift +++ b/Classes/Issues/Comments/Reactions/IssueReactionCell.swift @@ -44,8 +44,8 @@ final class IssueReactionCell: UICollectionViewCell { emojiLabel.font = UIFont.systemFont(ofSize: Styles.Text.body.size + 2) contentView.addSubview(emojiLabel) emojiLabel.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(contentView).offset(IssueReactionCell.spacing) + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(IssueReactionCell.spacing) } countLabel.textAlignment = .center diff --git a/Classes/Issues/Comments/Summary/IssueCommentSummaryCell.swift b/Classes/Issues/Comments/Summary/IssueCommentSummaryCell.swift index 9464793be..56d7d98c8 100644 --- a/Classes/Issues/Comments/Summary/IssueCommentSummaryCell.swift +++ b/Classes/Issues/Comments/Summary/IssueCommentSummaryCell.swift @@ -21,8 +21,8 @@ final class IssueCommentSummaryCell: IssueCommentBaseCell, ListBindable { label.font = Styles.Text.body.preferredFont contentView.addSubview(label) label.snp.makeConstraints { make in - make.left.equalTo(Styles.Sizes.commentGutter) - make.centerY.equalTo(contentView) + make.left.equalToSuperview() + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Comments/Text/IssueCommentTextCell.swift b/Classes/Issues/Comments/Text/IssueCommentTextCell.swift index 47d43c919..dfbd7da80 100644 --- a/Classes/Issues/Comments/Text/IssueCommentTextCell.swift +++ b/Classes/Issues/Comments/Text/IssueCommentTextCell.swift @@ -12,12 +12,14 @@ import StyledTextKit final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable { - static let inset = UIEdgeInsets( - top: 2, - left: Styles.Sizes.commentGutter, - bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter - ) + static func inset(isLast: Bool) -> UIEdgeInsets { + return UIEdgeInsets( + top: 2, + left: 0, + bottom: isLast ? 0 : Styles.Sizes.rowSpacing, + right: 0 + ) + } let textView = MarkdownStyledTextView() diff --git a/Classes/Issues/Comments/Unsupported/IssueCommentUnsupportedCell.swift b/Classes/Issues/Comments/Unsupported/IssueCommentUnsupportedCell.swift index 9d65ce92c..ba70d80f2 100644 --- a/Classes/Issues/Comments/Unsupported/IssueCommentUnsupportedCell.swift +++ b/Classes/Issues/Comments/Unsupported/IssueCommentUnsupportedCell.swift @@ -25,7 +25,7 @@ final class IssueCommentUnsupportedCell: IssueCommentBaseCell, ListBindable { label.backgroundColor = .clear contentView.addSubview(label) label.snp.makeConstraints { make in - make.center.equalTo(contentView) + make.center.equalToSuperview() } } diff --git a/Classes/Issues/Commit/IssueCommitCell.swift b/Classes/Issues/Commit/IssueCommitCell.swift index 596389f31..9f32fee3a 100644 --- a/Classes/Issues/Commit/IssueCommitCell.swift +++ b/Classes/Issues/Commit/IssueCommitCell.swift @@ -31,8 +31,8 @@ final class IssueCommitCell: UICollectionViewCell { commitImageView.tintColor = Styles.Colors.Gray.light.color contentView.addSubview(commitImageView) commitImageView.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(Styles.Sizes.eventGutter) + make.centerY.equalToSuperview() + make.left.equalToSuperview() make.size.equalTo(Styles.Sizes.icon) } @@ -51,7 +51,7 @@ final class IssueCommitCell: UICollectionViewCell { contentView.addSubview(avatarImageView) avatarImageView.snp.makeConstraints { make in - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.left.equalTo(commitImageView.snp.right).offset(Styles.Sizes.columnSpacing) make.size.equalTo(Styles.Sizes.icon) } @@ -61,9 +61,9 @@ final class IssueCommitCell: UICollectionViewCell { messageLabel.textColor = Styles.Colors.Gray.medium.color contentView.addSubview(messageLabel) messageLabel.snp.makeConstraints { make in - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.left.equalTo(avatarImageView.snp.right).offset(Styles.Sizes.columnSpacing) - make.right.lessThanOrEqualTo(contentView).offset(-Styles.Sizes.eventGutter) + make.right.lessThanOrEqualToSuperview() } // always collapse and truncate diff --git a/Classes/Issues/DiffHunk/IssueDiffHunkPathCell.swift b/Classes/Issues/DiffHunk/IssueDiffHunkPathCell.swift index 1af18ac48..65e48498b 100644 --- a/Classes/Issues/DiffHunk/IssueDiffHunkPathCell.swift +++ b/Classes/Issues/DiffHunk/IssueDiffHunkPathCell.swift @@ -23,9 +23,9 @@ final class IssueDiffHunkPathCell: UICollectionViewCell, ListBindable { label.font = Styles.Text.code.preferredFont contentView.addSubview(label) label.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(Styles.Sizes.commentGutter) - make.width.lessThanOrEqualTo(contentView).offset(-Styles.Sizes.commentGutter * 2) + make.centerY.equalToSuperview() + make.left.equalToSuperview() + make.width.lessThanOrEqualToSuperview().offset(-Styles.Sizes.rowSpacing) } } diff --git a/Classes/Issues/DiffHunk/IssueDiffHunkPreviewCell.swift b/Classes/Issues/DiffHunk/IssueDiffHunkPreviewCell.swift index b157de5f6..376f86a34 100644 --- a/Classes/Issues/DiffHunk/IssueDiffHunkPreviewCell.swift +++ b/Classes/Issues/DiffHunk/IssueDiffHunkPreviewCell.swift @@ -14,9 +14,9 @@ final class IssueDiffHunkPreviewCell: IssueCommentBaseCell, ListBindable { static let textViewInset = UIEdgeInsets( top: Styles.Sizes.rowSpacing, - left: Styles.Sizes.commentGutter, + left: 0, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter + right: 0 ) let scrollView = UIScrollView() @@ -26,7 +26,6 @@ final class IssueDiffHunkPreviewCell: IssueCommentBaseCell, ListBindable { super.init(frame: frame) contentView.addSubview(scrollView) scrollView.addSubview(textView) - border = .head } required init?(coder aDecoder: NSCoder) { diff --git a/Classes/Issues/Files/IssueFileCell.swift b/Classes/Issues/Files/IssueFileCell.swift index 05227a71c..5adffef21 100644 --- a/Classes/Issues/Files/IssueFileCell.swift +++ b/Classes/Issues/Files/IssueFileCell.swift @@ -25,7 +25,7 @@ final class IssueFileCell: SelectableCell { contentView.addSubview(disclosure) disclosure.snp.makeConstraints { make in make.right.equalTo(-Styles.Sizes.gutter) - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.size.equalTo(Styles.Sizes.icon) } diff --git a/Classes/Issues/Files/IssueViewFilesCell.swift b/Classes/Issues/Files/IssueViewFilesCell.swift index 232135cd0..c40271a36 100644 --- a/Classes/Issues/Files/IssueViewFilesCell.swift +++ b/Classes/Issues/Files/IssueViewFilesCell.swift @@ -20,8 +20,8 @@ final class IssueViewFilesCell: UICollectionViewCell { contentView.addSubview(label) label.snp.makeConstraints { make in - make.left.equalTo(contentView) - make.centerY.equalTo(contentView) + make.left.equalToSuperview() + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Files/IssueViewFilesSectionController.swift b/Classes/Issues/Files/IssueViewFilesSectionController.swift index 21865f3d9..4c908ade4 100644 --- a/Classes/Issues/Files/IssueViewFilesSectionController.swift +++ b/Classes/Issues/Files/IssueViewFilesSectionController.swift @@ -18,8 +18,7 @@ final class IssueViewFilesSectionController: ListGenericSectionController CGSize { diff --git a/Classes/Issues/GithubClient+Issues.swift b/Classes/Issues/GithubClient+Issues.swift index b6bc5ceaa..f16bbfbc6 100644 --- a/Classes/Issues/GithubClient+Issues.swift +++ b/Classes/Issues/GithubClient+Issues.swift @@ -148,12 +148,16 @@ extension GithubClient { availableMergeTypes.append(.rebase) } + let issueResult = IssueResult( id: issueType.id, pullRequest: issueType.pullRequest, - status: IssueStatusModel(status: status, pullRequest: issueType.pullRequest, locked: issueType.locked), title: titleStringSizing(title: issueType.title, contentSizeCategory: contentSizeCategory, width: width), - labels: IssueLabelsModel(labels: issueType.labelableFields.issueLabelModels), + labels: IssueLabelsModel( + status: IssueLabelStatusModel(status: status, pullRequest: issueType.pullRequest), + locked: issueType.locked, + labels: issueType.labelableFields.issueLabelModels + ), assignee: createAssigneeModel(assigneeFields: issueType.assigneeFields), rootComment: rootComment, reviewers: issueType.reviewRequestModel, @@ -217,10 +221,13 @@ extension GithubClient { number: Int, close: Bool ) { - let newStatus = IssueStatusModel( - status: close ? .closed : .open, - pullRequest: previous.status.pullRequest, - locked: previous.status.locked + let newLabels = IssueLabelsModel( + status: IssueLabelStatusModel( + status: close ? .closed : .open, + pullRequest: previous.labels.status.pullRequest + ), + locked: previous.labels.locked, + labels: previous.labels.labels ) let newEvent = IssueStatusEventModel( id: UUID().uuidString, @@ -231,7 +238,7 @@ extension GithubClient { pullRequest: previous.pullRequest ) let optimisticResult = previous.updated( - status: newStatus, + labels: newLabels, timelinePages: previous.timelinePages(appending: [newEvent]) ) @@ -260,10 +267,10 @@ extension GithubClient { locked: Bool, completion: ((Result) -> Void)? = nil ) { - let newStatus = IssueStatusModel( - status: previous.status.status, - pullRequest: previous.status.pullRequest, - locked: locked + let newLabels = IssueLabelsModel( + status: previous.labels.status, + locked: locked, + labels: previous.labels.labels ) let newEvent = IssueStatusEventModel( id: UUID().uuidString, @@ -274,7 +281,7 @@ extension GithubClient { pullRequest: previous.pullRequest ) let optimisticResult = previous.updated( - status: newStatus, + labels: newLabels, timelinePages: previous.timelinePages(appending: [newEvent]) ) @@ -383,7 +390,7 @@ extension GithubClient { } let optimistic = previous.updated( - labels: IssueLabelsModel(labels: labels), + labels: IssueLabelsModel(status: previous.labels.status, locked: previous.labels.locked, labels: labels), timelinePages: previous.timelinePages(appending: newEvents) ) diff --git a/Classes/Issues/IssueManagingContextController.swift b/Classes/Issues/IssueManagingContextController.swift index c80f8f26c..f2caceb6a 100644 --- a/Classes/Issues/IssueManagingContextController.swift +++ b/Classes/Issues/IssueManagingContextController.swift @@ -158,14 +158,14 @@ final class IssueManagingContextController: NSObject, ContextMenuDelegate { if result.pullRequest { items.append(item(.reviewers)) } - if result.status.locked { + if result.labels.locked { items.append(item(.unlock)) } else { items.append(item(.lock)) } } - switch result.status.status { + switch result.labels.status.status { case .closed: items.append(item(.reopen)) case .open: diff --git a/Classes/Issues/IssueResult.swift b/Classes/Issues/IssueResult.swift index 8de9b2203..08ff5e32e 100644 --- a/Classes/Issues/IssueResult.swift +++ b/Classes/Issues/IssueResult.swift @@ -15,7 +15,6 @@ struct IssueResult: Cachable { let id: String let pullRequest: Bool - let status: IssueStatusModel let title: StyledTextRenderer let labels: IssueLabelsModel let assignee: IssueAssigneesModel @@ -70,7 +69,6 @@ struct IssueResult: Cachable { func updated( id: String? = nil, pullRequest: Bool? = nil, - status: IssueStatusModel? = nil, title: StyledTextRenderer? = nil, labels: IssueLabelsModel? = nil, assignee: IssueAssigneesModel? = nil, @@ -84,7 +82,6 @@ struct IssueResult: Cachable { return IssueResult( id: id ?? self.id, pullRequest: pullRequest ?? self.pullRequest, - status: status ?? self.status, title: title ?? self.title, labels: labels ?? self.labels, assignee: assignee ?? self.assignee, @@ -109,7 +106,6 @@ struct IssueResult: Cachable { return IssueResult( id: self.id, pullRequest: self.pullRequest, - status: self.status, title: self.title, labels: self.labels, assignee: self.assignee, @@ -134,7 +130,6 @@ struct IssueResult: Cachable { return IssueResult( id: self.id, pullRequest: self.pullRequest, - status: self.status, title: self.title, labels: self.labels, assignee: self.assignee, diff --git a/Classes/Issues/IssueStatus.swift b/Classes/Issues/IssueStatus.swift index 459eb27ad..b0121778d 100644 --- a/Classes/Issues/IssueStatus.swift +++ b/Classes/Issues/IssueStatus.swift @@ -12,4 +12,12 @@ enum IssueStatus: Int { case closed case open case merged + + var title: String { + switch self { + case .open: return Constants.Strings.open + case .closed: return Constants.Strings.closed + case .merged: return Constants.Strings.merged + } + } } diff --git a/Classes/Issues/IssueViewModels.swift b/Classes/Issues/IssueViewModels.swift index ba823fb5a..0db80266c 100644 --- a/Classes/Issues/IssueViewModels.swift +++ b/Classes/Issues/IssueViewModels.swift @@ -21,8 +21,7 @@ func titleStringSizing( return StyledTextRenderer( string: builder.build(), contentSizeCategory: contentSizeCategory, - inset: IssueTitleCell.inset, - backgroundColor: Styles.Colors.background + inset: IssueTitleCell.inset ) } @@ -81,7 +80,8 @@ func createCommentModel( repo: repo, width: width, viewerCanUpdate: viewerCanUpdate, - contentSizeCategory: contentSizeCategory + contentSizeCategory: contentSizeCategory, + isRoot: isRoot ) let reactions = createIssueReactions(reactions: reactionFields) let collapse = IssueCollapsedBodies(bodies: bodies, width: width) diff --git a/Classes/Issues/IssuesViewController.swift b/Classes/Issues/IssuesViewController.swift index 471a63eeb..e7f0ad5b9 100644 --- a/Classes/Issues/IssuesViewController.swift +++ b/Classes/Issues/IssuesViewController.swift @@ -17,6 +17,12 @@ import Squawk import ContextMenu import GitHubAPI +extension ListDiffable { + var needsSpacer: Bool { + return self is IssueCommentModel || self is IssueReviewModel + } +} + final class IssuesViewController: MessageViewController, ListAdapterDataSource, @@ -44,6 +50,7 @@ final class IssuesViewController: lazy private var feed: Feed = { let f = Feed(viewController: self, delegate: self, managesLayout: false) f.collectionView.contentInset = Styles.Sizes.threadInset + f.collectionView.backgroundColor = .white return f }() @@ -52,7 +59,7 @@ final class IssuesViewController: let hidden: Bool if let id = resultID, let result = self.client.cache.get(id: id) as IssueResult? { - hidden = result.status.locked && !viewerIsCollaborator + hidden = result.labels.locked && !viewerIsCollaborator let bookmark = Bookmark( type: result.pullRequest ? .pullRequest : .issue, @@ -371,13 +378,11 @@ final class IssuesViewController: func objects(for listAdapter: ListAdapter) -> [ListDiffable] { guard let current = self.result else { return [] } - var objects: [ListDiffable] = [current.status] + var objects = [ListDiffable]() // BEGIN collect metadata that lives between title and root comment var metadata = [ListDiffable]() - if current.labels.labels.count > 0 { - metadata.append(current.labels) - } + metadata.append(current.labels) if let milestone = current.milestone { metadata.append(milestone) } @@ -392,29 +397,45 @@ final class IssuesViewController: } // END metadata collection - objects.append(IssueTitleModel(string: current.title, trailingMetadata: metadata.count > 0)) + objects.append(IssueTitleModel(string: current.title)) + objects += metadata if let targetBranch = current.targetBranch { objects.append(targetBranch) } - objects += metadata - if let rootComment = current.rootComment { objects.append(rootComment) + objects.append(SpacerModel(position: objects.count)) } if current.hasPreviousPage { objects.append(IssueNeckLoadModel()) } - objects += current.timelineViewModels + let timelineViewModels = current.timelineViewModels + for (i, model) in timelineViewModels.enumerated() { + let needsSpacer = model.needsSpacer + + // append a spacer if the previous timeline element wasn't a comment + if needsSpacer, i > 0 + && !(timelineViewModels[i-1].needsSpacer || timelineViewModels[i-1].needsSpacer) { + objects.append(SpacerModel(position: objects.count)) + } + + objects.append(model) + + // always append a spacer unless its the last item in the timeline + if needsSpacer, i < timelineViewModels.count - 1 { + objects.append(SpacerModel(position: objects.count)) + } + } // side effect so to jump to the last element when auto scrolling lastTimelineElement = objects.last if viewerIsCollaborator, - current.status.status == .open, + current.labels.status.status == .open, let merge = current.mergeModel { objects.append(merge) } @@ -424,7 +445,15 @@ final class IssuesViewController: func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController { switch object { + // header and metadata case is IssueTitleModel: return IssueTitleSectionController() + case is IssueLabelsModel: return IssueLabelsSectionController(issue: model) + case is IssueAssigneesModel: return IssueAssigneesSectionController() + case is Milestone: return IssueMilestoneSectionController(issueModel: model) + case is IssueFileChangesModel: return IssueViewFilesSectionController(issueModel: model, client: client) + case is IssueTargetBranchModel: return IssueTargetBranchSectionController() + + // timeline case is IssueCommentModel: return IssueCommentSectionController( model: model, @@ -432,28 +461,28 @@ final class IssuesViewController: autocomplete: autocompleteController.autocomplete.copy, issueCommentDelegate: self ) - case is IssueLabelsModel: return IssueLabelsSectionController(issue: model) - case is IssueStatusModel: return IssueStatusSectionController() case is IssueLabeledModel: return IssueLabeledSectionController(issueModel: model) case is IssueStatusEventModel: return IssueStatusEventSectionController(issueModel: model) - case is IssueDiffHunkModel: return IssueDiffHunkSectionController() - case is IssueReviewModel: return IssueReviewSectionController( - model: model, - client: client, - autocomplete: autocompleteController.autocomplete.copy - ) case is IssueReferencedModel: return IssueReferencedSectionController(client: client) case is IssueReferencedCommitModel: return IssueReferencedCommitSectionController() case is IssueRenamedModel: return IssueRenamedSectionController() case is IssueRequestModel: return IssueRequestSectionController() - case is IssueAssigneesModel: return IssueAssigneesSectionController() case is IssueMilestoneEventModel: return IssueMilestoneEventSectionController() case is IssueCommitModel: return IssueCommitSectionController(issueModel: model) + case is SpacerModel: return SpacerSectionController() + + // controls case is IssueNeckLoadModel: return IssueNeckLoadSectionController(delegate: self) - case is Milestone: return IssueMilestoneSectionController(issueModel: model) - case is IssueFileChangesModel: return IssueViewFilesSectionController(issueModel: model, client: client) case is IssueMergeModel: return IssueMergeSectionController(model: model, client: client, resultID: resultID) - case is IssueTargetBranchModel: return IssueTargetBranchSectionController() + + // deprecated + case is IssueDiffHunkModel: return IssueDiffHunkSectionController() + case is IssueReviewModel: return IssueReviewSectionController( + model: model, + client: client, + autocomplete: autocompleteController.autocomplete.copy + ) + default: fatalError("Unhandled object: \(object)") } } diff --git a/Classes/Issues/Labeled/IssueLabeledCell.swift b/Classes/Issues/Labeled/IssueLabeledCell.swift index f240101d7..faccec8ad 100644 --- a/Classes/Issues/Labeled/IssueLabeledCell.swift +++ b/Classes/Issues/Labeled/IssueLabeledCell.swift @@ -13,9 +13,9 @@ final class IssueLabeledCell: StyledTextViewCell { static let insets = UIEdgeInsets( top: Styles.Sizes.inlineSpacing, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.inlineSpacing, - right: Styles.Sizes.eventGutter + right: 0 ) // MARK: Public API diff --git a/Classes/Issues/Labeled/IssueLabeledModel.swift b/Classes/Issues/Labeled/IssueLabeledModel.swift index de6fdd5b9..04b98595e 100644 --- a/Classes/Issues/Labeled/IssueLabeledModel.swift +++ b/Classes/Issues/Labeled/IssueLabeledModel.swift @@ -78,7 +78,6 @@ final class IssueLabeledModel: ListDiffable { string: builder.build(), contentSizeCategory: contentSizeCategory, inset: IssueLabeledCell.insets, - backgroundColor: Styles.Colors.Gray.lighter.color, layoutManager: LabelLayoutManager() ).warm(width: width) } diff --git a/Classes/Issues/Labels/IssueLabelEditCell.swift b/Classes/Issues/Labels/IssueLabelEditCell.swift index 063a23c92..643226d45 100644 --- a/Classes/Issues/Labels/IssueLabelEditCell.swift +++ b/Classes/Issues/Labels/IssueLabelEditCell.swift @@ -23,7 +23,7 @@ final class IssueLabelEditCell: SelectableCell, ListBindable { contentView.addSubview(label) label.snp.makeConstraints { make in make.left.equalTo(Styles.Sizes.gutter) - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Labels/IssueLabelStatusCell.swift b/Classes/Issues/Labels/IssueLabelStatusCell.swift new file mode 100644 index 000000000..bd0be80e8 --- /dev/null +++ b/Classes/Issues/Labels/IssueLabelStatusCell.swift @@ -0,0 +1,80 @@ +// +// IssueLabelStatusCell.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit +import SnapKit +import IGListKit + +final class IssueLabelStatusCell: UICollectionViewCell, ListBindable { + + private static let font = Styles.Text.title.preferredFont + + static func size(_ string: String) -> CGSize { + return string.size( + font: font, + xPadding: (Styles.Sizes.icon.width + Styles.Sizes.rowSpacing/2)/2, + yPadding: 2 + ) + } + + private let imageView = UIImageView() + private let label = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + contentView.addSubview(imageView) + contentView.addSubview(label) + + imageView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerY.equalToSuperview() + } + + label.font = IssueLabelStatusCell.font + label.snp.makeConstraints { make in + make.left.equalTo(imageView.snp.right).offset(Styles.Sizes.inlineSpacing) + make.centerY.equalToSuperview() + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: ListBindable + + func bindViewModel(_ viewModel: Any) { + if let viewModel = viewModel as? IssueLabelStatusModel { + let color: UIColor + let icon: String + switch viewModel.status { + case .closed: + color = Styles.Colors.Red.medium.color + icon = viewModel.pullRequest ? "git-pull-request" : "issue-closed" + case .open: + color = Styles.Colors.Green.medium.color + icon = viewModel.pullRequest ? "git-pull-request" : "issue-opened" + case .merged: + color = Styles.Colors.purple.color + icon = "git-merge" + } + label.text = viewModel.status.title + label.textColor = color + imageView.tintColor = color + imageView.image = UIImage(named: "\(icon)-small")?.withRenderingMode(.alwaysTemplate) + } else if let viewModel = viewModel as? String { + let tint = Styles.Colors.Gray.dark.color + label.text = viewModel + label.textColor = tint + imageView.tintColor = tint + imageView.image = UIImage(named: "lock-small")?.withRenderingMode(.alwaysTemplate) + } + } + +} diff --git a/Classes/Issues/Labels/IssueLabelStatusModel.swift b/Classes/Issues/Labels/IssueLabelStatusModel.swift new file mode 100644 index 000000000..ff7b1a991 --- /dev/null +++ b/Classes/Issues/Labels/IssueLabelStatusModel.swift @@ -0,0 +1,35 @@ +// +// IssueLabelStatusModel.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import IGListKit + +final class IssueLabelStatusModel: NSObject, ListDiffable { + + let status: IssueStatus + let pullRequest: Bool + + init(status: IssueStatus, pullRequest: Bool) { + self.status = status + self.pullRequest = pullRequest + } + + // MARK: ListDiffable + + func diffIdentifier() -> NSObjectProtocol { + return "status" as NSObjectProtocol + } + + func isEqual(toDiffableObject object: ListDiffable?) -> Bool { + if self === object { return true } + guard let object = object as? IssueLabelStatusModel else { return false } + return status == object.status + && pullRequest == object.pullRequest + } + +} diff --git a/Classes/Issues/Labels/IssueLabelsModel.swift b/Classes/Issues/Labels/IssueLabelsModel.swift index b70cc63e5..5eb8d35be 100644 --- a/Classes/Issues/Labels/IssueLabelsModel.swift +++ b/Classes/Issues/Labels/IssueLabelsModel.swift @@ -11,9 +11,17 @@ import IGListKit final class IssueLabelsModel: ListDiffable { + let status: IssueLabelStatusModel + let locked: Bool let labels: [RepositoryLabel] - init(labels: [RepositoryLabel]) { + init( + status: IssueLabelStatusModel, + locked: Bool, + labels: [RepositoryLabel] + ) { + self.status = status + self.locked = locked self.labels = labels } diff --git a/Classes/Issues/Labels/IssueLabelsSectionController.swift b/Classes/Issues/Labels/IssueLabelsSectionController.swift index a13de235c..56581c6c2 100644 --- a/Classes/Issues/Labels/IssueLabelsSectionController.swift +++ b/Classes/Issues/Labels/IssueLabelsSectionController.swift @@ -15,14 +15,15 @@ ListBindingSectionControllerSelectionDelegate { private let issue: IssueDetailsModel private var sizeCache = [String: CGSize]() + private let lockedModel = Constants.Strings.locked init(issue: IssueDetailsModel) { self.issue = issue super.init() minimumInteritemSpacing = Styles.Sizes.labelSpacing minimumLineSpacing = Styles.Sizes.labelSpacing - let spacing = Styles.Sizes.rowSpacing / 2 - inset = UIEdgeInsets(top: spacing, left: 0, bottom: spacing, right: 0) + let row = Styles.Sizes.rowSpacing + inset = UIEdgeInsets(top: row, left: 0, bottom: row/2, right: 0) dataSource = self selectionDelegate = self } @@ -33,7 +34,13 @@ ListBindingSectionControllerSelectionDelegate { _ sectionController: ListBindingSectionController, viewModelsFor object: Any ) -> [ListDiffable] { - return self.object?.labels ?? [] + guard let object = self.object else { return [] } + var viewModels: [ListDiffable] = [object.status] + if object.locked { + viewModels.append(lockedModel as ListDiffable) + } + viewModels += object.labels as [ListDiffable] + return viewModels } func sectionController( @@ -41,16 +48,32 @@ ListBindingSectionControllerSelectionDelegate { sizeForViewModel viewModel: Any, at index: Int ) -> CGSize { - guard let viewModel = viewModel as? RepositoryLabel - else { fatalError("Collection context must be set") } - let key = viewModel.name + let key: String + if let viewModel = viewModel as? RepositoryLabel { + key = viewModel.name + } else { + if let viewModel = viewModel as? IssueLabelStatusModel { + key = "status-\(viewModel.status.title)" + } else { + key = "locked-key" + } + } if let size = sizeCache[key] { return size } - let size = LabelListCell.size(key) + let size: CGSize + if let viewModel = viewModel as? RepositoryLabel { + size = LabelListCell.size(viewModel.name) + } else { + if let viewModel = viewModel as? IssueLabelStatusModel { + size = IssueLabelStatusCell.size(viewModel.status.title) + } else { + size = IssueLabelStatusCell.size(lockedModel) + } + } sizeCache[key] = size return size } @@ -60,7 +83,12 @@ ListBindingSectionControllerSelectionDelegate { cellForViewModel viewModel: Any, at index: Int ) -> UICollectionViewCell & ListBindable { - guard let cell = collectionContext?.dequeueReusableCell(of: LabelListCell.self, for: self, at: index) as? LabelListCell + let cellType: AnyClass + switch viewModel { + case is RepositoryLabel: cellType = LabelListCell.self + default: cellType = IssueLabelStatusCell.self + } + guard let cell = collectionContext?.dequeueReusableCell(of: cellType, for: self, at: index) as? UICollectionViewCell & ListBindable else { fatalError("Missing context or wrong cell") } return cell } diff --git a/Classes/Issues/Managing/IssueManagingExpansionCell.swift b/Classes/Issues/Managing/IssueManagingExpansionCell.swift index b7eef97eb..9a7ccb403 100644 --- a/Classes/Issues/Managing/IssueManagingExpansionCell.swift +++ b/Classes/Issues/Managing/IssueManagingExpansionCell.swift @@ -37,8 +37,8 @@ final class IssueManagingExpansionCell: UICollectionViewCell, ListBindable { chevron.tintColor = tint contentView.addSubview(chevron) chevron.snp.makeConstraints { make in - make.centerY.equalTo(contentView).offset(1) - make.right.equalTo(contentView) + make.centerY.equalToSuperview().offset(1) + make.right.equalToSuperview() } label.text = NSLocalizedString("Manage", comment: "") @@ -46,7 +46,7 @@ final class IssueManagingExpansionCell: UICollectionViewCell, ListBindable { label.textColor = tint contentView.addSubview(label) label.snp.makeConstraints { make in - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.right.equalTo(chevron.snp.left).offset(-Styles.Sizes.rowSpacing+3) } @@ -54,7 +54,7 @@ final class IssueManagingExpansionCell: UICollectionViewCell, ListBindable { make.left.equalTo(label).offset(-Styles.Sizes.rowSpacing) make.right.equalTo(chevron).offset(Styles.Sizes.rowSpacing) make.height.equalTo(label).offset(4) - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Managing/IssueManagingSectionController.swift b/Classes/Issues/Managing/IssueManagingSectionController.swift index 3392b196c..312a95710 100644 --- a/Classes/Issues/Managing/IssueManagingSectionController.swift +++ b/Classes/Issues/Managing/IssueManagingSectionController.swift @@ -206,13 +206,13 @@ ContextMenuDelegate { if object.pullRequest { models.append(Action.reviewers) } - if result.status.locked { + if result.labels.locked { models.append(Action.unlock) } else { models.append(Action.lock) } } - switch result.status.status { + switch result.labels.status.status { case .closed: models.append(Action.reopen) case .open: models.append(Action.close) case .merged: break // can't do anything diff --git a/Classes/Issues/Merge/GithubClient+Merge.swift b/Classes/Issues/Merge/GithubClient+Merge.swift index d52cd370b..459ddcb9a 100644 --- a/Classes/Issues/Merge/GithubClient+Merge.swift +++ b/Classes/Issues/Merge/GithubClient+Merge.swift @@ -20,10 +20,13 @@ extension GithubClient { type: IssueMergeType, error: @escaping () -> Void ) { - let newStatus = IssueStatusModel( - status: .merged, - pullRequest: previous.status.pullRequest, - locked: previous.status.locked + let newLabels = IssueLabelsModel( + status: IssueLabelStatusModel( + status: .merged, + pullRequest: previous.labels.status.pullRequest + ), + locked: previous.labels.locked, + labels: previous.labels.labels ) let newEvent = IssueStatusEventModel( id: UUID().uuidString, @@ -34,7 +37,7 @@ extension GithubClient { pullRequest: previous.pullRequest ) let optimisticResult = previous.updated( - status: newStatus, + labels: newLabels, timelinePages: previous.timelinePages(appending: [newEvent]) ) diff --git a/Classes/Issues/Merge/IssueMergeButtonCell.swift b/Classes/Issues/Merge/IssueMergeButtonCell.swift index 903f10701..e035c0dc9 100644 --- a/Classes/Issues/Merge/IssueMergeButtonCell.swift +++ b/Classes/Issues/Merge/IssueMergeButtonCell.swift @@ -10,7 +10,7 @@ import UIKit import IGListKit import SnapKit -final class IssueMergeButtonCell: IssueCommentBaseCell, ListBindable { +final class IssueMergeButtonCell: CardCollectionViewCell, ListBindable { var delegate: MergeButtonDelegate? { get { return mergeButton.delegate } @@ -27,9 +27,9 @@ final class IssueMergeButtonCell: IssueCommentBaseCell, ListBindable { mergeButton.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets( top: Styles.Sizes.rowSpacing/2, - left: Styles.Sizes.commentGutter, + left: Styles.Sizes.columnSpacing, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.commentGutter + right: Styles.Sizes.columnSpacing )) } } diff --git a/Classes/Issues/Merge/IssueMergeContextCell.swift b/Classes/Issues/Merge/IssueMergeContextCell.swift index 46db1dbc1..b5b8cc045 100644 --- a/Classes/Issues/Merge/IssueMergeContextCell.swift +++ b/Classes/Issues/Merge/IssueMergeContextCell.swift @@ -11,7 +11,7 @@ import IGListKit import SnapKit import SDWebImage -final class IssueMergeContextCell: IssueCommentBaseCell, ListBindable { +final class IssueMergeContextCell: CardCollectionViewCell, ListBindable { private let avatarView = UIImageView() private let iconView = UIImageView() @@ -33,7 +33,7 @@ final class IssueMergeContextCell: IssueCommentBaseCell, ListBindable { avatarView.snp.makeConstraints { make in make.size.equalTo(Styles.Sizes.icon) make.centerY.equalToSuperview() - make.left.equalTo(Styles.Sizes.commentGutter) + make.left.equalTo(Styles.Sizes.cardGutter) } titleLabel.font = Styles.Text.secondaryBold.preferredFont @@ -51,7 +51,7 @@ final class IssueMergeContextCell: IssueCommentBaseCell, ListBindable { iconView.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.right.equalToSuperview().offset(-Styles.Sizes.commentGutter) + make.right.equalTo(-Styles.Sizes.cardGutter) } backgroundColor = Styles.Colors.Gray.lighter.color diff --git a/Classes/Issues/Merge/IssueMergeSectionController.swift b/Classes/Issues/Merge/IssueMergeSectionController.swift index fbe53c6d0..1cb4c6e54 100644 --- a/Classes/Issues/Merge/IssueMergeSectionController.swift +++ b/Classes/Issues/Merge/IssueMergeSectionController.swift @@ -26,7 +26,8 @@ ListBindingSectionControllerSelectionDelegate { super.init() dataSource = self selectionDelegate = self - inset = UIEdgeInsets(top: Styles.Sizes.rowSpacing, left: 0, bottom: 0, right: 0) + let row = Styles.Sizes.rowSpacing + inset = UIEdgeInsets(top: row, left: 0, bottom: row, right: 0) } // MARK: Private API @@ -138,7 +139,7 @@ ListBindingSectionControllerSelectionDelegate { guard let cell = collectionContext?.dequeueReusableCell(of: cellType, for: self, at: index) as? UICollectionViewCell & ListBindable else { fatalError() } - if let cell = cell as? IssueCommentBaseCell { + if let cell = cell as? CardCollectionViewCell { cell.border = index == 0 ? .head : index == self.viewModels.count - 1 ? .tail : .neck } if let cell = cell as? IssueMergeButtonCell { diff --git a/Classes/Issues/Merge/IssueMergeSummaryCell.swift b/Classes/Issues/Merge/IssueMergeSummaryCell.swift index f10b938f8..6eee20e2d 100644 --- a/Classes/Issues/Merge/IssueMergeSummaryCell.swift +++ b/Classes/Issues/Merge/IssueMergeSummaryCell.swift @@ -10,7 +10,7 @@ import UIKit import IGListKit import SnapKit -final class IssueMergeSummaryCell: IssueCommentBaseCell, ListBindable { +final class IssueMergeSummaryCell: CardCollectionViewCell, ListBindable { private let imageView = UIImageView() private let label = UILabel() @@ -29,7 +29,7 @@ final class IssueMergeSummaryCell: IssueCommentBaseCell, ListBindable { imageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.size.equalTo(Styles.Sizes.avatar) - make.left.equalTo(Styles.Sizes.commentGutter) + make.left.equalTo(Styles.Sizes.cardGutter) } label.textColor = Styles.Colors.Gray.dark.color @@ -38,7 +38,7 @@ final class IssueMergeSummaryCell: IssueCommentBaseCell, ListBindable { label.snp.makeConstraints { make in make.left.equalTo(imageView.snp.right).offset(Styles.Sizes.columnSpacing) make.centerY.equalToSuperview() - make.right.lessThanOrEqualToSuperview().offset(-Styles.Sizes.commentGutter) + make.right.lessThanOrEqualToSuperview() } } diff --git a/Classes/Issues/Milestone/IssueMilestoneCell.swift b/Classes/Issues/Milestone/IssueMilestoneCell.swift index b8a4a1a53..93615fb59 100644 --- a/Classes/Issues/Milestone/IssueMilestoneCell.swift +++ b/Classes/Issues/Milestone/IssueMilestoneCell.swift @@ -22,19 +22,19 @@ final class IssueMilestoneCell: UICollectionViewCell { titleLabel.backgroundColor = .clear contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(contentView) + make.centerY.equalToSuperview() + make.left.equalToSuperview() } progress.progressTintColor = Styles.Colors.Green.medium.color progress.trackTintColor = Styles.Colors.Gray.border.color contentView.addSubview(progress) progress.snp.makeConstraints { make in - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() make.height.equalTo(6) // fit to gutter on all iphones, cap in landscape or ipad make.width.lessThanOrEqualTo(300).priority(.required) - make.right.equalTo(contentView) + make.right.equalToSuperview() make.left.equalTo(titleLabel.snp.right).offset(Styles.Sizes.rowSpacing) } } diff --git a/Classes/Issues/MilestoneEvent/IssueMilestoneEventModel.swift b/Classes/Issues/MilestoneEvent/IssueMilestoneEventModel.swift index 916a8d65c..b009d3b11 100644 --- a/Classes/Issues/MilestoneEvent/IssueMilestoneEventModel.swift +++ b/Classes/Issues/MilestoneEvent/IssueMilestoneEventModel.swift @@ -70,11 +70,10 @@ final class IssueMilestoneEventModel: ListDiffable { contentSizeCategory: contentSizeCategory, inset: UIEdgeInsets( top: Styles.Sizes.inlineSpacing, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.inlineSpacing, - right: Styles.Sizes.eventGutter - ), - backgroundColor: Styles.Colors.background + right: 0 + ) ).warm(width: width) } diff --git a/Classes/Issues/NeckLoad/IssueNeckLoadCell.swift b/Classes/Issues/NeckLoad/IssueNeckLoadCell.swift index a62bb68c0..f1817c6ef 100644 --- a/Classes/Issues/NeckLoad/IssueNeckLoadCell.swift +++ b/Classes/Issues/NeckLoad/IssueNeckLoadCell.swift @@ -22,13 +22,13 @@ final class IssueNeckLoadCell: SelectableCell { label.font = Styles.Text.secondaryBold.preferredFont contentView.addSubview(label) label.snp.makeConstraints { make in - make.center.equalTo(contentView) + make.center.equalToSuperview() } activity.hidesWhenStopped = true contentView.addSubview(activity) activity.snp.makeConstraints { make in - make.center.equalTo(contentView) + make.center.equalToSuperview() } } diff --git a/Classes/Issues/Preview/IssuePreviewViewController.swift b/Classes/Issues/Preview/IssuePreviewViewController.swift index ec61200aa..bca0f0f5f 100644 --- a/Classes/Issues/Preview/IssuePreviewViewController.swift +++ b/Classes/Issues/Preview/IssuePreviewViewController.swift @@ -44,7 +44,8 @@ final class IssuePreviewViewController: UIViewController, ListAdapterDataSource repo: repo, width: view.bounds.width, viewerCanUpdate: false, - contentSizeCategory: UIApplication.shared.preferredContentSizeCategory + contentSizeCategory: UIApplication.shared.preferredContentSizeCategory, + isRoot: false ) model = IssuePreviewModel(models: viewModels) diff --git a/Classes/Issues/PullRequest+IssueType.swift b/Classes/Issues/PullRequest+IssueType.swift index da74fb04b..4f0b71959 100644 --- a/Classes/Issues/PullRequest+IssueType.swift +++ b/Classes/Issues/PullRequest+IssueType.swift @@ -243,7 +243,8 @@ extension IssueOrPullRequestQuery.Data.Repository.IssueOrPullRequest.AsPullReque repo: repo, width: width, viewerCanUpdate: viewerCanUpdate, - contentSizeCategory: contentSizeCategory + contentSizeCategory: contentSizeCategory, + isRoot: false ) let model = IssueReviewModel( id: review.fragments.nodeFields.id, diff --git a/Classes/Issues/Referenced/IssueReferencedCell.swift b/Classes/Issues/Referenced/IssueReferencedCell.swift index 7f84a943d..defd2c6ab 100644 --- a/Classes/Issues/Referenced/IssueReferencedCell.swift +++ b/Classes/Issues/Referenced/IssueReferencedCell.swift @@ -13,9 +13,9 @@ final class IssueReferencedCell: StyledTextViewCell { static let inset = UIEdgeInsets( top: Styles.Sizes.inlineSpacing, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.inlineSpacing, - right: Styles.Sizes.eventGutter + Styles.Sizes.icon.width + Styles.Sizes.rowSpacing + right: Styles.Sizes.icon.width + Styles.Sizes.rowSpacing ) let statusButton = UIButton() @@ -27,9 +27,9 @@ final class IssueReferencedCell: StyledTextViewCell { statusButton.isUserInteractionEnabled = false contentView.addSubview(statusButton) statusButton.snp.makeConstraints { make in - make.right.equalTo(contentView).offset(-Styles.Sizes.eventGutter) + make.right.equalToSuperview() make.size.equalTo(Styles.Sizes.icon) - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Referenced/IssueReferencedModel.swift b/Classes/Issues/Referenced/IssueReferencedModel.swift index 383cf5388..4d7a480f1 100644 --- a/Classes/Issues/Referenced/IssueReferencedModel.swift +++ b/Classes/Issues/Referenced/IssueReferencedModel.swift @@ -76,8 +76,7 @@ final class IssueReferencedModel: ListDiffable { self.string = StyledTextRenderer( string: builder.build(), contentSizeCategory: contentSizeCategory, - inset: IssueReferencedCell.inset, - backgroundColor: Styles.Colors.background + inset: IssueReferencedCell.inset ).warm(width: width) } diff --git a/Classes/Issues/ReferencedCommit/IssueReferencedCommitModel.swift b/Classes/Issues/ReferencedCommit/IssueReferencedCommitModel.swift index e862e3fdc..48908b23f 100644 --- a/Classes/Issues/ReferencedCommit/IssueReferencedCommitModel.swift +++ b/Classes/Issues/ReferencedCommit/IssueReferencedCommitModel.swift @@ -66,11 +66,10 @@ final class IssueReferencedCommitModel: ListDiffable { contentSizeCategory: contentSizeCategory, inset: UIEdgeInsets( top: Styles.Sizes.inlineSpacing, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.inlineSpacing, - right: Styles.Sizes.eventGutter - ), - backgroundColor: Styles.Colors.background + right: 0 + ) ).warm(width: width) } diff --git a/Classes/Issues/Renamed/IssueRenamedCell.swift b/Classes/Issues/Renamed/IssueRenamedCell.swift index 19a741d59..83bc0e670 100644 --- a/Classes/Issues/Renamed/IssueRenamedCell.swift +++ b/Classes/Issues/Renamed/IssueRenamedCell.swift @@ -18,9 +18,9 @@ final class IssueRenamedCell: UICollectionViewCell { static let titleInset = UIEdgeInsets( top: Styles.Sizes.labelEventHeight, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.eventGutter + right: 0 ) weak var delegate: IssueRenamedCellDelegate? diff --git a/Classes/Issues/Request/IssueRequestModel.swift b/Classes/Issues/Request/IssueRequestModel.swift index d4ba71e54..34a3d18b3 100644 --- a/Classes/Issues/Request/IssueRequestModel.swift +++ b/Classes/Issues/Request/IssueRequestModel.swift @@ -71,11 +71,10 @@ final class IssueRequestModel: ListDiffable { contentSizeCategory: contentSizeCategory, inset: UIEdgeInsets( top: Styles.Sizes.inlineSpacing, - left: Styles.Sizes.eventGutter, + left: 0, bottom: Styles.Sizes.inlineSpacing, - right: Styles.Sizes.eventGutter - ), - backgroundColor: Styles.Colors.background + right: 0 + ) ).warm(width: width) } diff --git a/Classes/Issues/Review/IssueReviewDetailsCell.swift b/Classes/Issues/Review/IssueReviewDetailsCell.swift index dc142e514..21c85687b 100644 --- a/Classes/Issues/Review/IssueReviewDetailsCell.swift +++ b/Classes/Issues/Review/IssueReviewDetailsCell.swift @@ -32,8 +32,8 @@ final class IssueReviewDetailsCell: IssueCommentBaseCell, ListBindable { contentView.addSubview(icon) icon.snp.makeConstraints { make in make.size.equalTo(iconSize) - make.centerY.equalTo(contentView) - make.left.equalTo(Styles.Sizes.commentGutter) + make.centerY.equalToSuperview() + make.left.equalToSuperview() } actorButton.addTarget(self, action: #selector(IssueReviewDetailsCell.onActorTapped), for: .touchUpInside) @@ -50,8 +50,6 @@ final class IssueReviewDetailsCell: IssueCommentBaseCell, ListBindable { make.centerY.equalTo(actorButton) make.left.equalTo(actorButton.snp.right).offset(Styles.Sizes.columnSpacing/2) } - - border = .head } required init?(coder aDecoder: NSCoder) { diff --git a/Classes/Issues/Review/IssueReviewEmptyTailCell.swift b/Classes/Issues/Review/IssueReviewEmptyTailCell.swift index 39af023bc..158002027 100644 --- a/Classes/Issues/Review/IssueReviewEmptyTailCell.swift +++ b/Classes/Issues/Review/IssueReviewEmptyTailCell.swift @@ -11,15 +11,6 @@ import IGListKit final class IssueReviewEmptyTailCell: IssueCommentBaseCell, ListBindable { - override init(frame: CGRect) { - super.init(frame: frame) - border = .tail - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - // MARK: ListBindable func bindViewModel(_ viewModel: Any) {} diff --git a/Classes/Issues/Review/IssueReviewSectionController.swift b/Classes/Issues/Review/IssueReviewSectionController.swift index 6183dab94..42282c33c 100644 --- a/Classes/Issues/Review/IssueReviewSectionController.swift +++ b/Classes/Issues/Review/IssueReviewSectionController.swift @@ -36,8 +36,6 @@ MarkdownStyledTextViewDelegate { self.client = client self.autocomplete = autocomplete super.init() - let rowSpacing = Styles.Sizes.rowSpacing - self.inset = UIEdgeInsets(top: rowSpacing, left: 0, bottom: rowSpacing, right: 0) self.dataSource = self } diff --git a/Classes/Issues/Review/IssueReviewViewCommentsCell.swift b/Classes/Issues/Review/IssueReviewViewCommentsCell.swift index a259d7aea..a12c89be2 100644 --- a/Classes/Issues/Review/IssueReviewViewCommentsCell.swift +++ b/Classes/Issues/Review/IssueReviewViewCommentsCell.swift @@ -29,11 +29,9 @@ final class IssueReviewViewCommentsCell: IssueCommentBaseCell, ListBindable { button.titleLabel?.font = Styles.Text.body.preferredFont contentView.addSubview(button) button.snp.makeConstraints { make in - make.left.equalTo(Styles.Sizes.commentGutter) - make.centerY.equalTo(contentView) + make.left.equalToSuperview() + make.centerY.equalToSuperview() } - - border = .tail } required init?(coder aDecoder: NSCoder) { diff --git a/Classes/Issues/Spacer/SpacerCell.swift b/Classes/Issues/Spacer/SpacerCell.swift new file mode 100644 index 000000000..3bf671928 --- /dev/null +++ b/Classes/Issues/Spacer/SpacerCell.swift @@ -0,0 +1,32 @@ +// +// SpacerCell.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit +import SnapKit + +final class SpacerCell: UICollectionViewCell { + + private let spaceView = UIView() + + override init(frame: CGRect) { + super.init(frame: frame) + + spaceView.backgroundColor = Styles.Colors.Gray.border.color + contentView.addSubview(spaceView) + spaceView.snp.makeConstraints { make in + make.height.equalTo(1/UIScreen.main.scale) + make.centerY.equalToSuperview() + make.left.right.equalToSuperview() + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Classes/Issues/Spacer/SpacerModel.swift b/Classes/Issues/Spacer/SpacerModel.swift new file mode 100644 index 000000000..405841da7 --- /dev/null +++ b/Classes/Issues/Spacer/SpacerModel.swift @@ -0,0 +1,31 @@ +// +// SpacerModel.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import IGListKit + +final class SpacerModel: NSObject, ListDiffable { + + private let _diffIdentifier: String + + init(position: Int) { + _diffIdentifier = "vertical-spacer-\(position)" + } + + // MARK: ListDiffable + + func diffIdentifier() -> NSObjectProtocol { + return _diffIdentifier as NSObjectProtocol + } + + func isEqual(toDiffableObject object: ListDiffable?) -> Bool { + if self === object { return true } + return object is SpacerModel + } + +} diff --git a/Classes/Issues/Spacer/SpacerSectionController.swift b/Classes/Issues/Spacer/SpacerSectionController.swift new file mode 100644 index 000000000..d01cf2769 --- /dev/null +++ b/Classes/Issues/Spacer/SpacerSectionController.swift @@ -0,0 +1,27 @@ +// +// SpacerSectionController.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import IGListKit + +final class SpacerSectionController: ListSectionController { + + override func sizeForItem(at index: Int) -> CGSize { + return CGSize( + width: collectionContext?.insetContainerSize.width ?? 0, + height: Styles.Sizes.tableCellHeight/2) + } + + override func cellForItem(at index: Int) -> UICollectionViewCell { + guard let cell = collectionContext?.dequeueReusableCell(of: SpacerCell.self, for: self, at: index) else { + fatalError() + } + return cell + } + +} diff --git a/Classes/Issues/Status/IssueStatusCell.swift b/Classes/Issues/Status/IssueStatusCell.swift index 330afd7ff..0f619ae67 100644 --- a/Classes/Issues/Status/IssueStatusCell.swift +++ b/Classes/Issues/Status/IssueStatusCell.swift @@ -21,8 +21,8 @@ final class IssueStatusCell: UICollectionViewCell, ListBindable { button.setupAsLabel() contentView.addSubview(button) button.snp.makeConstraints { make in - make.centerY.equalTo(contentView) - make.left.equalTo(contentView) + make.centerY.equalToSuperview() + make.left.equalToSuperview() } lockedButton.setTitle(Constants.Strings.locked, for: .normal) diff --git a/Classes/Issues/StatusEvent/IssueStatusEventCell.swift b/Classes/Issues/StatusEvent/IssueStatusEventCell.swift index 6904a4bb9..283038e72 100644 --- a/Classes/Issues/StatusEvent/IssueStatusEventCell.swift +++ b/Classes/Issues/StatusEvent/IssueStatusEventCell.swift @@ -32,15 +32,15 @@ final class IssueStatusEventCell: UICollectionViewCell { actorButton.addTarget(self, action: #selector(IssueStatusEventCell.onActor), for: .touchUpInside) contentView.addSubview(actorButton) actorButton.snp.makeConstraints { make in - make.left.equalTo(Styles.Sizes.eventGutter) - make.centerY.equalTo(contentView) + make.left.equalToSuperview() + make.centerY.equalToSuperview() } statusButton.setupAsLabel() contentView.addSubview(statusButton) statusButton.snp.makeConstraints { make in make.left.equalTo(actorButton.snp.right).offset(Styles.Sizes.inlineSpacing) - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() } // courier has a little bit of a different kerning, manually adjust @@ -50,7 +50,7 @@ final class IssueStatusEventCell: UICollectionViewCell { contentView.addSubview(hashButton) hashButton.snp.makeConstraints { make in make.left.equalTo(statusButton.snp.right).offset(Styles.Sizes.inlineSpacing) - make.centerY.equalTo(contentView).offset(1) + make.centerY.equalToSuperview().offset(1) } dateLabel.font = Styles.Text.secondary.preferredFont @@ -59,7 +59,7 @@ final class IssueStatusEventCell: UICollectionViewCell { contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in self.dateConstraint = make.left.equalTo(hashButton.snp.right).offset(Styles.Sizes.inlineSpacing).constraint - make.centerY.equalTo(contentView) + make.centerY.equalToSuperview() } } diff --git a/Classes/Issues/Title/IssueTitleModel.swift b/Classes/Issues/Title/IssueTitleModel.swift index 5ad63dcf5..23bd1c54a 100644 --- a/Classes/Issues/Title/IssueTitleModel.swift +++ b/Classes/Issues/Title/IssueTitleModel.swift @@ -13,11 +13,9 @@ import StyledTextKit final class IssueTitleModel: ListDiffable { let string: StyledTextRenderer - let trailingMetadata: Bool - init(string: StyledTextRenderer, trailingMetadata: Bool) { + init(string: StyledTextRenderer) { self.string = string - self.trailingMetadata = trailingMetadata } // MARK: ListDiffable @@ -28,8 +26,8 @@ final class IssueTitleModel: ListDiffable { func isEqual(toDiffableObject object: ListDiffable?) -> Bool { if self === object { return true } - guard let object = object as? IssueTitleModel else { return false } - return trailingMetadata == object.trailingMetadata + guard object is IssueTitleModel else { return false } + return true } } diff --git a/Classes/Issues/Title/IssueTitleSectionController.swift b/Classes/Issues/Title/IssueTitleSectionController.swift index 7f26b7543..7af3c05cf 100644 --- a/Classes/Issues/Title/IssueTitleSectionController.swift +++ b/Classes/Issues/Title/IssueTitleSectionController.swift @@ -13,16 +13,14 @@ final class IssueTitleSectionController: ListSectionController { var object: IssueTitleModel? + override init() { + super.init() + inset = UIEdgeInsets(top: Styles.Sizes.rowSpacing, left: 0, bottom: 0, right: 0) + } + override func didUpdate(to object: Any) { guard let object = object as? IssueTitleModel else { return } self.object = object - let rowSpacing = Styles.Sizes.rowSpacing - inset = UIEdgeInsets( - top: rowSpacing, - left: 0, - bottom: rowSpacing / 2 + (object.trailingMetadata ? rowSpacing : 0), - right: 0 - ) } override func sizeForItem(at index: Int) -> CGSize { diff --git a/Classes/Login/LoginSplashViewController.swift b/Classes/Login/LoginSplashViewController.swift index d39771e52..ccb02aaf9 100644 --- a/Classes/Login/LoginSplashViewController.swift +++ b/Classes/Login/LoginSplashViewController.swift @@ -60,7 +60,7 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener { super.viewDidLoad() state = .idle sessionManager.addListener(listener: self) - signInButton.layer.cornerRadius = Styles.Sizes.eventGutter + signInButton.layer.cornerRadius = Styles.Sizes.cardCornerRadius } // MARK: Public API diff --git a/Classes/PullRequestReviews/GithubClient+PullRequestReviewComments.swift b/Classes/PullRequestReviews/GithubClient+PullRequestReviewComments.swift index 8e27c2f8b..6b2003fa1 100644 --- a/Classes/PullRequestReviews/GithubClient+PullRequestReviewComments.swift +++ b/Classes/PullRequestReviews/GithubClient+PullRequestReviewComments.swift @@ -193,7 +193,8 @@ private func createReviewComment( repo: repo, width: width, viewerCanUpdate: false, - contentSizeCategory: contentSizeCategory + contentSizeCategory: contentSizeCategory, + isRoot: false ) return IssueCommentModel( diff --git a/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift b/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift index 41633409d..d34e5fc7f 100644 --- a/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift +++ b/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift @@ -28,6 +28,7 @@ final class PullRequestReviewCommentsViewController: MessageViewController, lazy private var feed: Feed = { let f = Feed(viewController: self, delegate: self, managesLayout: false) f.collectionView.contentInset = Styles.Sizes.threadInset + f.collectionView.backgroundColor = .white return f }() diff --git a/Classes/PullRequestReviews/PullRequestReviewReplyCell.swift b/Classes/PullRequestReviews/PullRequestReviewReplyCell.swift index 4bbe210cf..ba5e7c800 100644 --- a/Classes/PullRequestReviews/PullRequestReviewReplyCell.swift +++ b/Classes/PullRequestReviews/PullRequestReviewReplyCell.swift @@ -33,8 +33,6 @@ final class PullRequestReviewReplyCell: IssueCommentBaseCell { button.snp.makeConstraints { make in make.center.equalToSuperview() } - - border = .tail } required init?(coder aDecoder: NSCoder) { diff --git a/Classes/Repository/RepositoryOverviewViewController.swift b/Classes/Repository/RepositoryOverviewViewController.swift index a32592bea..46d7877f2 100644 --- a/Classes/Repository/RepositoryOverviewViewController.swift +++ b/Classes/Repository/RepositoryOverviewViewController.swift @@ -33,6 +33,7 @@ BaseListViewControllerDataSource { override func viewDidLoad() { super.viewDidLoad() + feed.collectionView.backgroundColor = .white makeBackBarItemEmpty() } @@ -64,6 +65,7 @@ BaseListViewControllerDataSource { width: width, viewerCanUpdate: false, contentSizeCategory: contentSizeCategory, + isRoot: false, branch: branch ) let model = RepositoryReadmeModel(models: models) diff --git a/Classes/Systems/ListAdapter+Scrolling.swift b/Classes/Systems/ListAdapter+Scrolling.swift index 1e52efb43..87ec134b1 100644 --- a/Classes/Systems/ListAdapter+Scrolling.swift +++ b/Classes/Systems/ListAdapter+Scrolling.swift @@ -27,7 +27,7 @@ extension ListAdapter { // make sure not already at the top guard paddedMaxY > viewportHeight else { return } - let offset = paddedMaxY - viewportHeight + let offset = paddedMaxY - viewportHeight + collectionView.contentInset.bottom collectionView.setContentOffset( CGPoint(x: collectionView.contentOffset.x, y: offset), animated: trueUnlessReduceMotionEnabled diff --git a/Classes/Utility/String+Size.swift b/Classes/Utility/String+Size.swift new file mode 100644 index 000000000..dcf4368aa --- /dev/null +++ b/Classes/Utility/String+Size.swift @@ -0,0 +1,29 @@ +// +// String+Size.swift +// Freetime +// +// Created by Ryan Nystrom on 7/26/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation + +extension String { + + func size( + font: UIFont, + xPadding: CGFloat = 0, + yPadding: CGFloat = 0 + ) -> CGSize { + return (self as NSString).size(withAttributes: [ + .font: font + ]).resized(inset: UIEdgeInsets( + top: yPadding, + left: xPadding, + bottom: yPadding, + right: xPadding + ) + ) + } + +} diff --git a/Classes/Views/CardCollectionViewCell.swift b/Classes/Views/CardCollectionViewCell.swift new file mode 100644 index 000000000..f1c11e47f --- /dev/null +++ b/Classes/Views/CardCollectionViewCell.swift @@ -0,0 +1,116 @@ +// +// CardCollectionViewCell.swift +// Freetime +// +// Created by Ryan Nystrom on 7/22/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit + +class CardCollectionViewCell: UICollectionViewCell, UIGestureRecognizerDelegate { + + enum BorderType { + case head + case neck + case tail + } + + var border: BorderType = .neck { + didSet { setNeedsLayout() } + } + + private let borderLayer = CAShapeLayer() + private let backgroundLayer = CAShapeLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + + contentView.clipsToBounds = true + + // insert above contentView layer + borderLayer.strokeColor = UIColor.red.cgColor + borderLayer.strokeColor = Styles.Colors.Gray.border.color.cgColor + borderLayer.lineWidth = 1 / UIScreen.main.scale + borderLayer.fillColor = nil + layer.addSublayer(borderLayer) + + // insert as base layer + backgroundLayer.strokeColor = nil + backgroundLayer.fillColor = UIColor.white.cgColor + layer.insertSublayer(backgroundLayer, at: 0) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + layoutContentViewForSafeAreaInsets() + + let bounds = contentView.frame + let inset = borderLayer.lineWidth / 2 + let pixelSnapBounds = bounds.insetBy(dx: inset, dy: inset) + let cornerRadius = Styles.Sizes.cardCornerRadius + + let borderPath = UIBezierPath() + let fillPath: UIBezierPath + + switch border { + case .head: + borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.maxY)) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.minY + cornerRadius)) + borderPath.addQuadCurve( + to: CGPoint(x: pixelSnapBounds.minX + cornerRadius, y: pixelSnapBounds.minY), + controlPoint: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.minY) + ) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX - cornerRadius, y: pixelSnapBounds.minY)) + borderPath.addQuadCurve( + to: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.minY + cornerRadius), + controlPoint: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.minY) + ) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.maxY)) + + fillPath = borderPath.copy() as! UIBezierPath + fillPath.close() + case .neck: + borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.minY)) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.maxY)) + borderPath.move(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.minY)) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.maxY)) + + fillPath = UIBezierPath(rect: bounds) + case .tail: + borderPath.move(to: CGPoint(x: pixelSnapBounds.minX, y: bounds.minY)) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.maxY - cornerRadius)) + borderPath.addQuadCurve( + to: CGPoint(x: pixelSnapBounds.minX + cornerRadius, y: pixelSnapBounds.maxY), + controlPoint: CGPoint(x: pixelSnapBounds.minX, y: pixelSnapBounds.maxY) + ) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX - cornerRadius, y: pixelSnapBounds.maxY)) + borderPath.addQuadCurve( + to: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.maxY - cornerRadius), + controlPoint: CGPoint(x: pixelSnapBounds.maxX, y: pixelSnapBounds.maxY) + ) + borderPath.addLine(to: CGPoint(x: pixelSnapBounds.maxX, y: bounds.minY)) + + fillPath = borderPath.copy() as! UIBezierPath + fillPath.close() + } + borderLayer.path = borderPath.cgPath + backgroundLayer.path = fillPath.cgPath + + borderLayer.frame = self.bounds + backgroundLayer.frame = self.bounds + } + + override var backgroundColor: UIColor? { + get { + guard let color = backgroundLayer.fillColor else { return nil } + return UIColor(cgColor: color) + } + set { backgroundLayer.fillColor = newValue?.cgColor} + } + +} diff --git a/Classes/Views/LabelListCell.swift b/Classes/Views/LabelListCell.swift index 32aeb333a..e1fd876aa 100644 --- a/Classes/Views/LabelListCell.swift +++ b/Classes/Views/LabelListCell.swift @@ -14,18 +14,13 @@ final class LabelListCell: UICollectionViewCell, ListBindable { static let reuse = "cell" static let font = Styles.Text.smallTitle.preferredFont - static func size(_ string: String) -> CGSize { - return (string as NSString).size(withAttributes: [ - .font: font - ]).resized(inset: UIEdgeInsets( - top: 1, - left: Styles.Sizes.labelTextPadding, - bottom: 1, - right: Styles.Sizes.labelTextPadding) - ) + static func size( + _ string: String + ) -> CGSize { + return string.size(font: font, xPadding: Styles.Sizes.labelTextPadding, yPadding: 3) } - let nameLabel = UILabel() + private let nameLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) @@ -33,9 +28,6 @@ final class LabelListCell: UICollectionViewCell, ListBindable { isAccessibilityElement = true accessibilityTraits |= UIAccessibilityTraitButton - layer.borderColor = Styles.Colors.Gray.border.color.cgColor - layer.borderWidth = 1 / UIScreen.main.scale - layer.cornerRadius = Styles.Sizes.labelCornerRadius contentView.layer.cornerRadius = Styles.Sizes.labelCornerRadius diff --git a/Classes/Views/Styles.swift b/Classes/Views/Styles.swift index d5b6ebb15..b774aa82b 100644 --- a/Classes/Views/Styles.swift +++ b/Classes/Views/Styles.swift @@ -13,8 +13,7 @@ enum Styles { enum Sizes { static let gutter: CGFloat = 15 - static let eventGutter: CGFloat = 8 // comment gutter 2x - static let commentGutter: CGFloat = 8 + static let cardGutter: CGFloat = 8 static let icon = CGSize(width: 20, height: 20) static let buttonMin = CGSize(width: 44, height: 44) static let buttonIcon = CGSize(width: 25, height: 25) @@ -35,21 +34,20 @@ enum Styles { static let listInsetLargeTail = UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0) static let listInsetTight = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0) static let textViewInset = UIEdgeInsets( - top: Styles.Sizes.rowSpacing, - left: Styles.Sizes.gutter, - bottom: Styles.Sizes.rowSpacing, - right: Styles.Sizes.gutter + top: rowSpacing, + left: gutter, + bottom: rowSpacing, + right: gutter ) static let labelEventHeight: CGFloat = 30 - static let labelRowHeight: CGFloat = 18 - static let labelSpacing: CGFloat = 4 + static let labelSpacing: CGFloat = 8 static let labelTextPadding: CGFloat = 4 static let cardCornerRadius: CGFloat = 6 static let threadInset = UIEdgeInsets( - top: Styles.Sizes.rowSpacing / 2, - left: Styles.Sizes.commentGutter, - bottom: 2 * Styles.Sizes.rowSpacing + Styles.Sizes.tableCellHeight, - right: Styles.Sizes.commentGutter + top: rowSpacing / 2, + left: gutter, + bottom: 2 * rowSpacing + tableCellHeight, + right: gutter ) static let maxImageHeight: CGFloat = 300 static let contextMenuSize: CGSize = CGSize(width: 280, height: 240) @@ -64,11 +62,13 @@ enum Styles { static let body = TextStyle(size: 16) static let bodyBold = TextStyle(font: .system(.bold), size: 16) + static let rootBody = TextStyle(size: 18) + static let commentBody = TextStyle(size: 15) static let secondary = TextStyle(size: 13) static let secondaryBold = TextStyle(font: .system(.bold), size: 13) static let title = TextStyle(font: .system(.bold), size: 14) static let button = TextStyle(size: 16) - static let headline = TextStyle(font: .system(.bold), size: 18) + static let headline = TextStyle(font: .system(.bold), size: 24) static let smallTitle = TextStyle(font: .system(.bold), size: 12) static let code = TextStyle(font: .name("Courier"), size: 16) static let codeBold = TextStyle(font: .name("Courier-Bold"), size: 16) diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index 7b27325b8..8b49d62b8 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -45,6 +45,9 @@ 291929611F3FD2960012067B /* DiffString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929601F3FD2960012067B /* DiffString.swift */; }; 291929631F3FF0DA0012067B /* StyledTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929621F3FF0DA0012067B /* StyledTableCell.swift */; }; 291929671F3FF9C50012067B /* BadgeNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929661F3FF9C50012067B /* BadgeNotifications.swift */; }; + 29242810210A50BE001F5980 /* SpacerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2924280F210A50BE001F5980 /* SpacerModel.swift */; }; + 29242812210A5148001F5980 /* SpacerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29242811210A5148001F5980 /* SpacerCell.swift */; }; + 29242814210A51B5001F5980 /* SpacerSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29242813210A51B5001F5980 /* SpacerSectionController.swift */; }; 2924C18120D5B29800FCFCFF /* MilestonesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2924C18020D5B29800FCFCFF /* MilestonesViewController.swift */; }; 2924C18320D5B2BF00FCFCFF /* MilestoneCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2924C18220D5B2BF00FCFCFF /* MilestoneCell.swift */; }; 2924C18820D5B2F200FCFCFF /* PeopleSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2924C18620D5B2F200FCFCFF /* PeopleSectionController.swift */; }; @@ -195,6 +198,7 @@ 295C31CF1F0AA67600521CED /* IssueStatus+ButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C31CE1F0AA67600521CED /* IssueStatus+ButtonState.swift */; }; 295C31D11F0AA72000521CED /* IssueStatusEvent+ButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C31D01F0AA72000521CED /* IssueStatusEvent+ButtonState.swift */; }; 295F52AA1EF1BB86000B53CF /* Test.md in Resources */ = {isa = PBXBuildFile; fileRef = 295F52A61EF1B9D2000B53CF /* Test.md */; }; + 29622B45210520E6000C428D /* CardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29622B44210520E6000C428D /* CardCollectionViewCell.swift */; }; 2963A9321EE1EBE20066509C /* UIMenuController+Reactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2963A9311EE1EBE20066509C /* UIMenuController+Reactions.swift */; }; 2963A9341EE2118E0066509C /* ResponderButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2963A9331EE2118E0066509C /* ResponderButton.swift */; }; 2963A93B1EE25F6F0066509C /* LabelableFields+IssueLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2963A93A1EE25F6F0066509C /* LabelableFields+IssueLabelModel.swift */; }; @@ -306,6 +310,9 @@ 29AF1E8E1F8ABC900008A0EF /* RepositoryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF1E8D1F8ABC900008A0EF /* RepositoryFile.swift */; }; 29B0EF871F93DF6C00870291 /* RepositoryCodeBlobViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B0EF861F93DF6C00870291 /* RepositoryCodeBlobViewController.swift */; }; 29B5D08B20D578DB003DFBE2 /* InboxType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5D08A20D578DB003DFBE2 /* InboxType.swift */; }; + 29B75AD9210A9A0300C28131 /* IssueLabelStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B75AD8210A9A0300C28131 /* IssueLabelStatusCell.swift */; }; + 29B75ADB210A9B3100C28131 /* IssueLabelStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B75ADA210A9B3100C28131 /* IssueLabelStatusModel.swift */; }; + 29B75ADD210AA14F00C28131 /* String+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B75ADC210AA14F00C28131 /* String+Size.swift */; }; 29B94E671FCB2D4600715D7E /* CodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B94E661FCB2D4600715D7E /* CodeView.swift */; }; 29B94E691FCB36A000715D7E /* File+ListDiffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B94E681FCB36A000715D7E /* File+ListDiffable.swift */; }; 29B94E6D1FCB472400715D7E /* IssueFileChangesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B94E6C1FCB472400715D7E /* IssueFileChangesModel.swift */; }; @@ -545,6 +552,9 @@ 291929601F3FD2960012067B /* DiffString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffString.swift; sourceTree = ""; }; 291929621F3FF0DA0012067B /* StyledTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledTableCell.swift; sourceTree = ""; }; 291929661F3FF9C50012067B /* BadgeNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeNotifications.swift; sourceTree = ""; }; + 2924280F210A50BE001F5980 /* SpacerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerModel.swift; sourceTree = ""; }; + 29242811210A5148001F5980 /* SpacerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerCell.swift; sourceTree = ""; }; + 29242813210A51B5001F5980 /* SpacerSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerSectionController.swift; sourceTree = ""; }; 292484B71F01CB5C0054FE20 /* SwipeCellKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwipeCellKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2924C18020D5B29800FCFCFF /* MilestonesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MilestonesViewController.swift; sourceTree = ""; }; 2924C18220D5B2BF00FCFCFF /* MilestoneCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MilestoneCell.swift; sourceTree = ""; }; @@ -696,6 +706,7 @@ 295C31D01F0AA72000521CED /* IssueStatusEvent+ButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IssueStatusEvent+ButtonState.swift"; sourceTree = ""; }; 295F52A61EF1B9D2000B53CF /* Test.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Test.md; sourceTree = ""; }; 295F52D91EF1C0C7000B53CF /* MMMarkdown.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMMarkdown.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 29622B44210520E6000C428D /* CardCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardCollectionViewCell.swift; sourceTree = ""; }; 2963A9311EE1EBE20066509C /* UIMenuController+Reactions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIMenuController+Reactions.swift"; sourceTree = ""; }; 2963A9331EE2118E0066509C /* ResponderButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponderButton.swift; sourceTree = ""; }; 2963A93A1EE25F6F0066509C /* LabelableFields+IssueLabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LabelableFields+IssueLabelModel.swift"; sourceTree = ""; }; @@ -814,6 +825,9 @@ 29AF1E8D1F8ABC900008A0EF /* RepositoryFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryFile.swift; sourceTree = ""; }; 29B0EF861F93DF6C00870291 /* RepositoryCodeBlobViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryCodeBlobViewController.swift; sourceTree = ""; }; 29B5D08A20D578DB003DFBE2 /* InboxType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxType.swift; sourceTree = ""; }; + 29B75AD8210A9A0300C28131 /* IssueLabelStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueLabelStatusCell.swift; sourceTree = ""; }; + 29B75ADA210A9B3100C28131 /* IssueLabelStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueLabelStatusModel.swift; sourceTree = ""; }; + 29B75ADC210AA14F00C28131 /* String+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Size.swift"; sourceTree = ""; }; 29B94E661FCB2D4600715D7E /* CodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeView.swift; sourceTree = ""; }; 29B94E681FCB36A000715D7E /* File+ListDiffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "File+ListDiffable.swift"; sourceTree = ""; }; 29B94E6C1FCB472400715D7E /* IssueFileChangesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueFileChangesModel.swift; sourceTree = ""; }; @@ -1053,6 +1067,16 @@ path = Files; sourceTree = ""; }; + 2924280E210A50B1001F5980 /* Spacer */ = { + isa = PBXGroup; + children = ( + 29242811210A5148001F5980 /* SpacerCell.swift */, + 2924280F210A50BE001F5980 /* SpacerModel.swift */, + 29242813210A51B5001F5980 /* SpacerSectionController.swift */, + ); + path = Spacer; + sourceTree = ""; + }; 2928C7861F15D7A30000D06D /* Renamed */ = { isa = PBXGroup; children = ( @@ -1137,11 +1161,11 @@ 292CD3C41F0C9EB200D3D57B /* IssueCommentModelHandling.swift */, 291929461F3EAB250012067B /* IssueDetailsModel.swift */, 2973E80E20FBDB620050233F /* IssueManageButton.swift */, + 2973E81020FBF73D0050233F /* IssueManagingContextController.swift */, 29F7F05B1F2A751B00F6075D /* IssueResult.swift */, 290D2A411F04D3470082E6CC /* IssueStatus.swift */, 295C31CE1F0AA67600521CED /* IssueStatus+ButtonState.swift */, 292FCAE91EDFCC510026635E /* IssuesViewController.swift */, - 2973E81020FBF73D0050233F /* IssueManagingContextController.swift */, 292FF8AF1F2FDC33009E63F7 /* IssueTextActionsView.swift */, 294563EF1EE5036A00DBCD35 /* IssueType.swift */, 292FCAEA1EDFCC510026635E /* IssueViewModels.swift */, @@ -1159,6 +1183,7 @@ 2928C7861F15D7A30000D06D /* Renamed */, 297A372A1F1700A30081C04E /* Request */, 292CD3C61F0DB31200D3D57B /* Review */, + 2924280E210A50B1001F5980 /* Spacer */, 294563E41EE4EE5B00DBCD35 /* Status */, 295840631EE89F19007723C6 /* StatusEvent */, 292FCAF31EDFCC510026635E /* Title */, @@ -1259,6 +1284,8 @@ 29EE1C111F37C51D0046A54D /* IssueLabelEditCell.swift */, 292FCAEF1EDFCC510026635E /* IssueLabelsModel.swift */, 292FCAF01EDFCC510026635E /* IssueLabelsSectionController.swift */, + 29B75AD8210A9A0300C28131 /* IssueLabelStatusCell.swift */, + 29B75ADA210A9B3100C28131 /* IssueLabelStatusModel.swift */, ); path = Labels; sourceTree = ""; @@ -1706,6 +1733,7 @@ 292FF8B11F302FE7009E63F7 /* UITextView+SelectedRange.swift */, 298BA08E1EC90FEE00B01946 /* UIView+BottomBorder.swift */, 299F63E1205DE1470015D901 /* UIView+DateDetails.swift */, + 29622B44210520E6000C428D /* CardCollectionViewCell.swift */, ); path = Views; sourceTree = ""; @@ -1997,11 +2025,12 @@ 29459A701FE7153500034A04 /* LogEnvironmentInformation.swift */, DC63393A1F9F65EE00402A8D /* RepositoryAttributedString.swift */, 7B7BBA3D1F8B752A00D6AEDA /* SearchQueryTokenizer.swift */, + 49FE18FC204B5D32001681E8 /* Sequence+Contains.swift */, DCA5ED111FAEE3AE0072F074 /* Store.swift */, + 29B75ADC210AA14F00C28131 /* String+Size.swift */, 754488B01F7ADF8D0032D08C /* UIAlertController+Action.swift */, 98835BCD1F1965E2005BA24F /* UIDevice+Model.swift */, 98B5A0851F6D0FFE000617D6 /* UINavigationController+Replace.swift */, - 49FE18FC204B5D32001681E8 /* Sequence+Contains.swift */, 299F63DF205DDF6B0015D901 /* UIViewController+Routing.swift */, ); path = Utility; @@ -2669,6 +2698,7 @@ 29EE44461F19D5C100B05ED3 /* GithubClient+Issues.swift in Sources */, 299F63E4205E1CAB0015D901 /* UIViewController+StyledTextViewCellDelegate.swift in Sources */, 29B94E6D1FCB472400715D7E /* IssueFileChangesModel.swift in Sources */, + 29242810210A50BE001F5980 /* SpacerModel.swift in Sources */, 986B87191F2B875800AAB55C /* GithubClient+Search.swift in Sources */, 29C0E7071ECBC6C50051D756 /* GithubClient.swift in Sources */, 2986B35A1FD30F0B00E3CFC6 /* IssueManagingModel.swift in Sources */, @@ -2785,6 +2815,7 @@ 292FF8B91F303DB0009E63F7 /* IssuePreviewModel.swift in Sources */, 2973E81120FBF73D0050233F /* IssueManagingContextController.swift in Sources */, 292FF8B51F303BD0009E63F7 /* IssuePreviewSectionController.swift in Sources */, + 29B75ADB210A9B3100C28131 /* IssueLabelStatusModel.swift in Sources */, 292FF8B71F303BD9009E63F7 /* IssuePreviewViewController.swift in Sources */, 292FCB211EDFCF870026635E /* IssueReactionCell.swift in Sources */, 29C53FC21F12EF4000A59ED5 /* IssueReferencedCell.swift in Sources */, @@ -2923,6 +2954,7 @@ 29351EA62079791800FF8C17 /* String+StripHTMLComments.swift in Sources */, 29DA1E8A1F5E2DEC0050C64B /* SearchRecentHeaderSectionController.swift in Sources */, 29AF1E841F8AAB4A0008A0EF /* UITextView+GitHawk.swift in Sources */, + 29B75AD9210A9A0300C28131 /* IssueLabelStatusCell.swift in Sources */, 299F63D4205DA24A0015D901 /* MarkdownAttributeHandling.swift in Sources */, 29DA1E861F5E26D30050C64B /* SearchRecentSectionController.swift in Sources */, 29DA1E821F5DF5CC0050C64B /* SearchRecentStore.swift in Sources */, @@ -2964,6 +2996,7 @@ 49AF91B4204B4B6A00DFF325 /* MergeHelper.swift in Sources */, 15245488205DC016005810A6 /* IssueBranchesSectionController.swift in Sources */, 2930F2731F8A27750082BA26 /* WidthCache.swift in Sources */, + 29242814210A51B5001F5980 /* SpacerSectionController.swift in Sources */, 2971722B1F069E6B005E43AC /* SpinnerSectionController.swift in Sources */, 2999972E203120E300995FFD /* IssueMergeButtonCell.swift in Sources */, 2950AB1B2082E47200C6F19A /* AppSplitViewController.swift in Sources */, @@ -2981,7 +3014,9 @@ 98F9F4001F9CCFFE005A0266 /* ImageUploadTableViewController.swift in Sources */, 29C167671ECA005500439D62 /* Constants.swift in Sources */, 291929631F3FF0DA0012067B /* StyledTableCell.swift in Sources */, + 29B75ADD210AA14F00C28131 /* String+Size.swift in Sources */, 29C9FDE11EC667AE00EE3A52 /* Styles.swift in Sources */, + 29622B45210520E6000C428D /* CardCollectionViewCell.swift in Sources */, 294B11201F7B07DD00E04F2D /* TabBarControllerDelegate.swift in Sources */, 294B111E1F7B07BB00E04F2D /* TabNavRootViewControllerType.swift in Sources */, 754488B11F7ADF8D0032D08C /* UIAlertController+Action.swift in Sources */, @@ -3001,6 +3036,7 @@ D8D876FA1FB6084F00A57E2B /* UIBarButtonItem+TightSpacing.swift in Sources */, 2977788820B0DAD500F2AFC2 /* ViewMarkdownViewController.swift in Sources */, 29CEA5CF1F84DCB3009827DB /* UIViewController+EmptyBackBar.swift in Sources */, + 29242812210A5148001F5980 /* SpacerCell.swift in Sources */, 292CD3D81F0DC52900D3D57B /* UIViewController+IssueCommentHtmlCellNavigationDelegate.swift in Sources */, 297AE8801EC0D5C200B44A1F /* UIViewController+LoadingIndicator.swift in Sources */, 292CD3D21F0DBEC000D3D57B /* UIViewController+Safari.swift in Sources */, diff --git a/FreetimeTests/IssueTests.swift b/FreetimeTests/IssueTests.swift index 3ef15732b..c5a055a45 100644 --- a/FreetimeTests/IssueTests.swift +++ b/FreetimeTests/IssueTests.swift @@ -39,7 +39,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 3) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is the first line\n") @@ -59,7 +60,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 2) XCTAssertEqual((models[0] as! IssueCommentImageModel).url.absoluteString, "https://apple.com") @@ -78,7 +80,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 2) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is the first line\nthen some more text\n") @@ -95,7 +98,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 1) XCTAssertEqual((models[0] as! IssueCommentImageModel).url.absoluteString, "https://apple.com") @@ -115,7 +119,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 5) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is the first line\n") @@ -139,7 +144,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 3) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is some text") @@ -162,7 +168,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 3) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is some text") @@ -187,7 +194,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 5) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "this is the first line") @@ -205,7 +213,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 1) } @@ -225,7 +234,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 5) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "line one") @@ -246,7 +256,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual(models.count, 1) @@ -267,7 +278,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual((models[1] as! StyledTextRenderer).string.allText, "See: [Pitch] Introducing the \"Unwrap or Die\" operator to the standard library") @@ -287,7 +299,8 @@ class IssueTests: XCTestCase { repo: "repo", width: 0, viewerCanUpdate: false, - contentSizeCategory: .large + contentSizeCategory: .large, + isRoot: false ) XCTAssertEqual((models[0] as! StyledTextRenderer).string.allText, "This is the implementation for apple/swift-evolution#793")