mirror of
https://github.com/zhigang1992/GitHawk.git
synced 2026-04-24 04:05:16 +08:00
New Inbox design (#1870)
* New Inbox design * rename ident to number * use ax animation * building with old notifications removed * remove "2" suffix * use latest IGLK+Swift * apply IGLK perf fixes * fix build * Update with IGLK binding bug fix
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
final class IssueAssigneeViewModel: ListDiffable, ListSwiftIdentifiable, ListSwiftEquatable {
|
||||
final class IssueAssigneeViewModel: ListDiffable, ListSwiftDiffable {
|
||||
let login: String
|
||||
let avatarURL: URL
|
||||
|
||||
@@ -28,14 +28,12 @@ final class IssueAssigneeViewModel: ListDiffable, ListSwiftIdentifiable, ListSwi
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: ListSwiftIdentifiable
|
||||
// MARK: ListSwiftDiffable
|
||||
|
||||
var identifier: String {
|
||||
return avatarURL.absoluteString
|
||||
}
|
||||
|
||||
// MARK: ListSwiftEquatable
|
||||
|
||||
func isEqual(to object: ListSwiftDiffable) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -231,25 +231,15 @@ final class IssuesViewController:
|
||||
bookmarkNavController?.configureNavigationItem(bookmarkItem)
|
||||
}
|
||||
|
||||
func viewOwnerAction() -> UIAlertAction? {
|
||||
weak var weakSelf = self
|
||||
return AlertAction(AlertActionBuilder { $0.rootViewController = weakSelf })
|
||||
.view(owner: model.owner)
|
||||
}
|
||||
|
||||
func viewRepoAction() -> UIAlertAction? {
|
||||
guard let result = result else { return nil }
|
||||
|
||||
let repo = RepositoryDetails(
|
||||
return action(
|
||||
owner: model.owner,
|
||||
name: model.repo,
|
||||
defaultBranch: result.defaultBranch,
|
||||
hasIssuesEnabled: result.hasIssuesEnabled
|
||||
repo: model.repo,
|
||||
branch: result.defaultBranch,
|
||||
issuesEnabled: result.hasIssuesEnabled,
|
||||
client: client
|
||||
)
|
||||
|
||||
weak var weakSelf = self
|
||||
return AlertAction(AlertActionBuilder { $0.rootViewController = weakSelf })
|
||||
.view(client: client, repo: repo)
|
||||
}
|
||||
|
||||
@objc func onMore(sender: UIBarButtonItem) {
|
||||
@@ -344,7 +334,7 @@ final class IssuesViewController:
|
||||
@objc func onNavigationTitle(sender: UIView) {
|
||||
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
|
||||
alert.addActions([
|
||||
viewOwnerAction(),
|
||||
action(owner: model.owner),
|
||||
viewRepoAction(),
|
||||
AlertAction.cancel()
|
||||
])
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import IGListKit
|
||||
|
||||
final class LabelsViewController: BaseListViewController2, BaseListViewController2DataSource {
|
||||
final class LabelsViewController: BaseListViewController2<String>, BaseListViewController2DataSource {
|
||||
|
||||
private let selectedLabels: Set<RepositoryLabel>
|
||||
private var labels = [RepositoryLabel]()
|
||||
|
||||
@@ -9,19 +9,17 @@
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
struct MilestoneViewModel: ListSwiftIdentifiable, ListSwiftEquatable {
|
||||
struct MilestoneViewModel: ListSwiftDiffable {
|
||||
let title: String
|
||||
let due: String
|
||||
let selected: Bool
|
||||
|
||||
// MARK: ListSwiftIdentifiable
|
||||
// MARK: ListSwiftDiffable
|
||||
|
||||
var identifier: String {
|
||||
return title
|
||||
}
|
||||
|
||||
// MARK: ListSwiftEquatable
|
||||
|
||||
func isEqual(to value: ListSwiftDiffable) -> Bool {
|
||||
guard let value = value as? MilestoneViewModel else { return false }
|
||||
return due == value.due
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import IGListKit
|
||||
|
||||
final class MilestonesViewController: BaseListViewController2,
|
||||
final class MilestonesViewController: BaseListViewController2<String>,
|
||||
BaseListViewController2DataSource,
|
||||
MilestoneSectionControllerDelegate {
|
||||
|
||||
|
||||
14
Classes/Models/Repository.swift
Normal file
14
Classes/Models/Repository.swift
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Repository.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/9/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Repository {
|
||||
var owner: String
|
||||
var name: String
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
final class RepositoryLabel: ListDiffable, Hashable, Equatable, ListSwiftIdentifiable, ListSwiftEquatable {
|
||||
final class RepositoryLabel: ListDiffable, Hashable, Equatable, ListSwiftDiffable {
|
||||
|
||||
let color: String
|
||||
let name: String
|
||||
@@ -46,14 +46,12 @@ final class RepositoryLabel: ListDiffable, Hashable, Equatable, ListSwiftIdentif
|
||||
&& lhs.name == rhs.name
|
||||
}
|
||||
|
||||
// MARK: ListSwiftIdentifiable
|
||||
// MARK: ListSwiftDiffable
|
||||
|
||||
var identifier: String {
|
||||
return name
|
||||
}
|
||||
|
||||
// MARK: ListSwiftEquatable
|
||||
|
||||
func isEqual(to object: ListSwiftDiffable) -> Bool {
|
||||
guard let object = object as? RepositoryLabel else { return false }
|
||||
return self == object
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
extension String: ListSwiftIdentifiable, ListSwiftEquatable {
|
||||
extension String: ListSwiftDiffable {
|
||||
|
||||
public var identifier: String {
|
||||
return self
|
||||
|
||||
24
Classes/Notifications/CreateNotificationTitle.swift
Normal file
24
Classes/Notifications/CreateNotificationTitle.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// CreateNotificationTitle.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/9/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import StyledTextKit
|
||||
|
||||
func CreateNotification(title: String, width: CGFloat, contentSizeCategory: UIContentSizeCategory) -> StyledTextRenderer {
|
||||
// TODO add owner/repo #
|
||||
let builder = StyledTextBuilder(styledText: StyledText(
|
||||
text: title,
|
||||
style: Styles.Text.body
|
||||
))
|
||||
.add(attributes: [.foregroundColor: Styles.Colors.Gray.dark.color])
|
||||
return StyledTextRenderer(
|
||||
string: builder.build(),
|
||||
contentSizeCategory: contentSizeCategory,
|
||||
inset: NotificationCell.inset
|
||||
).warm(width: width)
|
||||
}
|
||||
22
Classes/Notifications/InboxType.swift
Normal file
22
Classes/Notifications/InboxType.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// InboxType.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/16/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum InboxType {
|
||||
case unread
|
||||
case repo(Repository)
|
||||
case all
|
||||
|
||||
var showAll: Bool {
|
||||
switch self {
|
||||
case .all, .repo: return true
|
||||
case .unread: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,41 +7,50 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import GitHubAPI
|
||||
|
||||
func CreateViewModels(
|
||||
containerWidth: CGFloat,
|
||||
func CreateNotificationViewModels(
|
||||
width: CGFloat,
|
||||
contentSizeCategory: UIContentSizeCategory,
|
||||
v3notifications: [V3Notification]) -> [NotificationViewModel] {
|
||||
var viewModels = [NotificationViewModel]()
|
||||
v3notifications: [V3Notification],
|
||||
completion: @escaping ([NotificationViewModel]) -> Void
|
||||
) {
|
||||
DispatchQueue.global().async {
|
||||
var models = [NotificationViewModel]()
|
||||
|
||||
for notification in v3notifications {
|
||||
guard let type = NotificationType(rawValue: notification.subject.type.rawValue),
|
||||
let identifier = notification.subject.identifier
|
||||
else { continue }
|
||||
for notification in v3notifications {
|
||||
guard let type = NotificationType(rawValue: notification.subject.type.rawValue),
|
||||
let identifier = notification.subject.identifier
|
||||
else { continue }
|
||||
|
||||
let modelIdentifier: NotificationViewModel.Identifier
|
||||
switch identifier {
|
||||
case .hash(let h): modelIdentifier = .hash(h)
|
||||
case .number(let n): modelIdentifier = .number(n)
|
||||
case .release(let r): modelIdentifier = .release(r)
|
||||
let number: NotificationViewModel.Number
|
||||
switch identifier {
|
||||
case .hash(let h): number = .hash(h)
|
||||
case .number(let n): number = .number(n)
|
||||
case .release(let r): number = .release(r)
|
||||
}
|
||||
|
||||
models.append(NotificationViewModel(
|
||||
v3id: notification.id,
|
||||
repo: notification.repository.name,
|
||||
owner: notification.repository.owner.login,
|
||||
title: CreateNotification(title: notification.subject.title, width: width, contentSizeCategory: contentSizeCategory),
|
||||
number: number,
|
||||
state: .pending, // fetched later
|
||||
date: notification.updatedAt,
|
||||
ago: notification.updatedAt.agoString(.short),
|
||||
read: !notification.unread,
|
||||
comments: 0, // fetched later
|
||||
watching: true, // assumed based on receiving
|
||||
type: type,
|
||||
// TODO get from GQL notification request
|
||||
branch: "master",
|
||||
issuesEnabled: true
|
||||
))
|
||||
}
|
||||
|
||||
let model = NotificationViewModel(
|
||||
id: notification.id,
|
||||
title: notification.subject.title,
|
||||
type: type,
|
||||
date: notification.updatedAt,
|
||||
read: !notification.unread,
|
||||
owner: notification.repository.owner.login,
|
||||
repo: notification.repository.name,
|
||||
identifier: modelIdentifier,
|
||||
containerWidth: containerWidth,
|
||||
contentSizeCategory: contentSizeCategory
|
||||
)
|
||||
viewModels.append(model)
|
||||
DispatchQueue.main.async {
|
||||
completion(models)
|
||||
}
|
||||
}
|
||||
|
||||
return viewModels
|
||||
}
|
||||
|
||||
@@ -1,100 +1,132 @@
|
||||
//
|
||||
// NotificationDetailsCell.swift
|
||||
// NotificationCell.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/12/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
// Created by Ryan Nystrom on 6/8/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
import StyledTextKit
|
||||
|
||||
final class NotificationCell: SwipeSelectableCell {
|
||||
protocol NotificationCellDelegate: class {
|
||||
func didTapRead(cell: NotificationCell)
|
||||
func didTapWatch(cell: NotificationCell)
|
||||
func didTapMore(cell: NotificationCell, sender: UIView)
|
||||
}
|
||||
|
||||
static let labelInset = UIEdgeInsets(
|
||||
top: Styles.Text.title.preferredFont.lineHeight + 2*Styles.Sizes.rowSpacing,
|
||||
final class NotificationCell: SelectableCell {
|
||||
|
||||
public static let inset = UIEdgeInsets(
|
||||
top: NotificationCell.topInset + NotificationCell.headerHeight + Styles.Sizes.rowSpacing,
|
||||
left: Styles.Sizes.icon.width + 2*Styles.Sizes.columnSpacing,
|
||||
bottom: Styles.Sizes.rowSpacing,
|
||||
right: Styles.Sizes.gutter + Styles.Sizes.icon.width + Styles.Sizes.columnSpacing
|
||||
bottom: NotificationCell.actionsHeight,
|
||||
right: Styles.Sizes.gutter
|
||||
)
|
||||
public static let topInset = Styles.Sizes.rowSpacing
|
||||
public static let headerHeight = ceil(Styles.Text.secondary.preferredFont.lineHeight)
|
||||
public static let actionsHeight = Styles.Sizes.gutter + 4*Styles.Sizes.rowSpacing
|
||||
|
||||
static var minHeight: CGFloat {
|
||||
// comment icon
|
||||
return Styles.Sizes.icon.height
|
||||
// date, comment count labels
|
||||
+ 2 * Styles.Text.secondary.preferredFont.lineHeight
|
||||
// padding
|
||||
+ 3 * Styles.Sizes.rowSpacing
|
||||
}
|
||||
|
||||
private let reasonImageView = UIImageView()
|
||||
private weak var delegate: NotificationCellDelegate?
|
||||
private let iconImageView = UIImageView()
|
||||
private let dateLabel = ShowMoreDetailsLabel()
|
||||
private let titleLabel = UILabel()
|
||||
private let detailsLabel = UILabel()
|
||||
private let textView = StyledTextView()
|
||||
private let commentLabel = UILabel()
|
||||
private let commentImageView = UIImageView()
|
||||
private let stackView = UIStackView()
|
||||
private let commentButton = UIButton()
|
||||
private let readButton = UIButton()
|
||||
private let watchButton = UIButton()
|
||||
private let moreButton = UIButton()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
accessibilityTraits |= UIAccessibilityTraitButton
|
||||
isAccessibilityElement = true
|
||||
|
||||
backgroundColor = .white
|
||||
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(iconImageView)
|
||||
contentView.addSubview(detailsLabel)
|
||||
contentView.addSubview(dateLabel)
|
||||
contentView.addSubview(reasonImageView)
|
||||
contentView.addSubview(textView)
|
||||
contentView.addSubview(commentImageView)
|
||||
contentView.addSubview(commentLabel)
|
||||
contentView.addSubview(stackView)
|
||||
stackView.addArrangedSubview(commentButton)
|
||||
stackView.addArrangedSubview(watchButton)
|
||||
stackView.addArrangedSubview(readButton)
|
||||
stackView.addArrangedSubview(moreButton)
|
||||
|
||||
titleLabel.numberOfLines = 1
|
||||
titleLabel.font = Styles.Text.title.preferredFont
|
||||
titleLabel.textColor = Styles.Colors.Gray.light.color
|
||||
titleLabel.lineBreakMode = .byTruncatingMiddle
|
||||
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
let grey = Styles.Colors.Gray.light.color
|
||||
let font = Styles.Text.secondary.preferredFont
|
||||
let inset = NotificationCell.inset
|
||||
let actionsHeight = NotificationCell.actionsHeight
|
||||
|
||||
iconImageView.snp.makeConstraints { make in
|
||||
make.top.equalTo(inset.top)
|
||||
make.centerX.equalTo(inset.left / 2)
|
||||
}
|
||||
|
||||
dateLabel.font = font
|
||||
dateLabel.textColor = grey
|
||||
dateLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(NotificationCell.topInset)
|
||||
make.right.equalTo(-inset.right)
|
||||
}
|
||||
|
||||
detailsLabel.lineBreakMode = .byTruncatingMiddle
|
||||
detailsLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
detailsLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(Styles.Sizes.rowSpacing)
|
||||
make.left.equalTo(NotificationCell.labelInset.left)
|
||||
make.left.equalTo(NotificationCell.inset.left)
|
||||
make.right.lessThanOrEqualTo(dateLabel.snp.left).offset(-Styles.Sizes.columnSpacing)
|
||||
}
|
||||
|
||||
dateLabel.backgroundColor = .clear
|
||||
dateLabel.numberOfLines = 1
|
||||
dateLabel.font = Styles.Text.secondary.preferredFont
|
||||
dateLabel.textColor = Styles.Colors.Gray.light.color
|
||||
dateLabel.textAlignment = .right
|
||||
dateLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
dateLabel.snp.makeConstraints { make in
|
||||
make.right.equalTo(-Styles.Sizes.gutter)
|
||||
make.centerY.equalTo(titleLabel)
|
||||
stackView.alignment = .center
|
||||
stackView.distribution = .equalSpacing
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.left.equalTo(inset.left)
|
||||
make.right.equalTo(-inset.right)
|
||||
make.bottom.equalToSuperview()
|
||||
make.height.equalTo(actionsHeight)
|
||||
}
|
||||
|
||||
reasonImageView.backgroundColor = .clear
|
||||
reasonImageView.contentMode = .scaleAspectFit
|
||||
reasonImageView.snp.makeConstraints { make in
|
||||
make.size.equalTo(Styles.Sizes.icon)
|
||||
make.top.equalTo(NotificationCell.labelInset.top)
|
||||
make.left.equalTo(Styles.Sizes.rowSpacing)
|
||||
commentButton.titleLabel?.font = font
|
||||
commentButton.isUserInteractionEnabled = false
|
||||
commentButton.tintColor = grey
|
||||
commentButton.setTitleColor(grey, for: .normal)
|
||||
commentButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: -2, right: 0)
|
||||
commentButton.titleEdgeInsets = UIEdgeInsets(top: -4, left: 2, bottom: 0, right: 0)
|
||||
commentButton.setImage(UIImage(named: "comment-small")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
commentButton.contentHorizontalAlignment = .left
|
||||
commentButton.snp.makeConstraints { make in
|
||||
make.width.equalTo(actionsHeight)
|
||||
}
|
||||
|
||||
commentImageView.tintColor = dateLabel.textColor
|
||||
commentImageView.image = UIImage(named: "comment-small")?.withRenderingMode(.alwaysTemplate)
|
||||
commentImageView.backgroundColor = .clear
|
||||
commentImageView.snp.makeConstraints { make in
|
||||
make.right.equalTo(dateLabel)
|
||||
make.top.equalTo(dateLabel.snp.bottom).offset(Styles.Sizes.rowSpacing)
|
||||
watchButton.tintColor = grey
|
||||
watchButton.addTarget(self, action: #selector(onWatch(sender:)), for: .touchUpInside)
|
||||
watchButton.contentHorizontalAlignment = .center
|
||||
watchButton.snp.makeConstraints { make in
|
||||
make.width.equalTo(actionsHeight)
|
||||
}
|
||||
|
||||
commentLabel.font = dateLabel.font
|
||||
commentLabel.textColor = dateLabel.textColor
|
||||
commentLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(commentImageView.snp.bottom)
|
||||
make.centerX.equalTo(commentImageView)
|
||||
readButton.tintColor = grey
|
||||
readButton.setImage(UIImage(named: "check-small")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
readButton.addTarget(self, action: #selector(onRead(sender:)), for: .touchUpInside)
|
||||
readButton.contentHorizontalAlignment = .center
|
||||
readButton.snp.makeConstraints { make in
|
||||
make.width.equalTo(actionsHeight)
|
||||
}
|
||||
|
||||
contentView.addBorder(.bottom, left: NotificationCell.labelInset.left)
|
||||
moreButton.tintColor = grey
|
||||
moreButton.setImage(UIImage(named: "bullets-small")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
moreButton.addTarget(self, action: #selector(onMore(sender:)), for: .touchUpInside)
|
||||
moreButton.contentHorizontalAlignment = .right
|
||||
moreButton.snp.makeConstraints { make in
|
||||
make.width.equalTo(actionsHeight)
|
||||
}
|
||||
|
||||
contentView.addBorder(.bottom, left: inset.left)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
@@ -103,55 +135,65 @@ final class NotificationCell: SwipeSelectableCell {
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
layoutContentViewForSafeAreaInsets()
|
||||
textView.reposition(for: contentView.bounds.width)
|
||||
}
|
||||
|
||||
// MARK: Public API
|
||||
|
||||
var isRead = false {
|
||||
didSet {
|
||||
for view in [titleLabel, textView, reasonImageView] {
|
||||
view.alpha = isRead ? 0.5 : 1
|
||||
}
|
||||
}
|
||||
override var accessibilityLabel: String? {
|
||||
get { return AccessibilityHelper.generatedLabel(forCell: self)}
|
||||
set {}
|
||||
}
|
||||
|
||||
func configure(_ viewModel: NotificationViewModel) {
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func configure(with model: NotificationViewModel, delegate: NotificationCellDelegate?) {
|
||||
textView.configure(with: model.title, width: contentView.bounds.width)
|
||||
dateLabel.setText(date: model.date, format: .short)
|
||||
self.delegate = delegate
|
||||
|
||||
var titleAttributes = [
|
||||
NSAttributedStringKey.font: Styles.Text.title.preferredFont,
|
||||
NSAttributedStringKey.foregroundColor: Styles.Colors.Gray.light.color
|
||||
]
|
||||
let title = NSMutableAttributedString(string: "\(viewModel.owner)/\(viewModel.repo) ", attributes: titleAttributes)
|
||||
|
||||
let title = NSMutableAttributedString(string: "\(model.owner)/\(model.repo) ", attributes: titleAttributes)
|
||||
titleAttributes[NSAttributedStringKey.font] = Styles.Text.secondary.preferredFont
|
||||
switch viewModel.identifier {
|
||||
switch model.number {
|
||||
case .number(let number): title.append(NSAttributedString(string: "#\(number)", attributes: titleAttributes))
|
||||
default: break
|
||||
}
|
||||
titleLabel.attributedText = title
|
||||
|
||||
textView.configure(with: viewModel.title, width: contentView.bounds.width)
|
||||
|
||||
dateLabel.setText(date: viewModel.date)
|
||||
reasonImageView.image = viewModel.type.icon.withRenderingMode(.alwaysTemplate)
|
||||
accessibilityLabel = AccessibilityHelper
|
||||
.generatedLabel(forCell: self)
|
||||
.appending(".\n\(viewModel.type.localizedString)")
|
||||
detailsLabel.attributedText = title
|
||||
|
||||
let tintColor: UIColor
|
||||
switch viewModel.state {
|
||||
switch model.state {
|
||||
case .closed: tintColor = Styles.Colors.Red.medium.color
|
||||
case .merged: tintColor = Styles.Colors.purple.color
|
||||
case .open: tintColor = Styles.Colors.Green.medium.color
|
||||
case .pending: tintColor = Styles.Colors.Blue.medium.color
|
||||
}
|
||||
reasonImageView.tintColor = tintColor
|
||||
iconImageView.tintColor = tintColor
|
||||
iconImageView.image = model.type.icon.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
let commentHidden = viewModel.commentCount == 0
|
||||
commentLabel.isHidden = commentHidden
|
||||
commentImageView.isHidden = commentHidden
|
||||
commentLabel.text = viewModel.commentCount.abbreviated
|
||||
let hasComments = model.comments > 0
|
||||
commentButton.alpha = hasComments ? 1 : 0.3
|
||||
commentButton.setTitle(hasComments ? model.comments.abbreviated : "", for: .normal)
|
||||
|
||||
let watchingImageName = model.watching ? "mute" : "unmute"
|
||||
watchButton.setImage(UIImage(named: "\(watchingImageName)-small")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
|
||||
contentView.alpha = model.read ? 0.5 : 1
|
||||
}
|
||||
|
||||
@objc func onRead(sender: UIView) {
|
||||
delegate?.didTapRead(cell: self)
|
||||
}
|
||||
|
||||
@objc func onWatch(sender: UIView) {
|
||||
delegate?.didTapWatch(cell: self)
|
||||
}
|
||||
|
||||
@objc func onMore(sender: UIView) {
|
||||
delegate?.didTapMore(cell: self, sender: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
//
|
||||
// NotificationClient.swift
|
||||
// NotificationClient2.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/30/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
// Created by Ryan Nystrom on 6/9/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import GitHubAPI
|
||||
|
||||
// used to request states via graphQL
|
||||
extension NotificationViewModel {
|
||||
var stateAlias: (number: Int, key: String)? {
|
||||
switch identifier {
|
||||
switch number {
|
||||
case .hash, .release:
|
||||
// commits and releases don't have states, always "merged"
|
||||
return nil
|
||||
@@ -24,14 +23,7 @@ extension NotificationViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
final class NotificationClient {
|
||||
|
||||
struct NotificationRepository {
|
||||
let owner: String
|
||||
let name: String
|
||||
}
|
||||
|
||||
private var openedNotificationIDs = Set<String>()
|
||||
final class NotificationModelController {
|
||||
|
||||
let githubClient: GithubClient
|
||||
|
||||
@@ -43,7 +35,7 @@ final class NotificationClient {
|
||||
|
||||
static private let openOnReadKey = "com.freetime.NotificationClient.read-on-open"
|
||||
|
||||
static func readOnOpen() -> Bool {
|
||||
static var readOnOpen: Bool {
|
||||
return UserDefaults.standard.bool(forKey: openOnReadKey)
|
||||
}
|
||||
|
||||
@@ -53,23 +45,25 @@ final class NotificationClient {
|
||||
|
||||
// https://developer.github.com/v3/activity/notifications/#list-your-notifications
|
||||
func fetchNotifications(
|
||||
repo: NotificationRepository? = nil,
|
||||
repo: Repository? = nil,
|
||||
all: Bool = false,
|
||||
page: Int = 1,
|
||||
width: CGFloat,
|
||||
completion: @escaping (Result<([NotificationViewModel], Int?)>) -> Void
|
||||
) {
|
||||
let contentSizeCategory = UIApplication.shared.preferredContentSizeCategory
|
||||
// TODO move handling + parsing to a single method?
|
||||
if let repo = repo {
|
||||
githubClient.client.send(V3RepositoryNotificationRequest(all: all, owner: repo.owner, repo: repo.name)) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let viewModels = CreateViewModels(
|
||||
containerWidth: width,
|
||||
CreateNotificationViewModels(
|
||||
width: width,
|
||||
contentSizeCategory: contentSizeCategory,
|
||||
v3notifications: response.data
|
||||
)
|
||||
self.fetchStates(for: viewModels, page: response.next, completion: completion)
|
||||
) { [weak self] in
|
||||
self?.fetchStates(for: $0, page: response.next, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.error(error))
|
||||
}
|
||||
@@ -78,12 +72,13 @@ final class NotificationClient {
|
||||
githubClient.client.send(V3NotificationRequest(all: all, page: page)) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let viewModels = CreateViewModels(
|
||||
containerWidth: width,
|
||||
CreateNotificationViewModels(
|
||||
width: width,
|
||||
contentSizeCategory: contentSizeCategory,
|
||||
v3notifications: response.data
|
||||
)
|
||||
self.fetchStates(for: viewModels, page: response.next, completion: completion)
|
||||
) { [weak self] in
|
||||
self?.fetchStates(for: $0, page: response.next, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.error(error))
|
||||
}
|
||||
@@ -125,7 +120,10 @@ final class NotificationClient {
|
||||
let state = NotificationViewModel.State(rawValue: stateString),
|
||||
let commentsJSON = issueOrPullRequest["comments"] as? [String: Any],
|
||||
let commentCount = commentsJSON["totalCount"] as? Int {
|
||||
updatedNotifications.append(notification.updated(state: state, commentCount: commentCount))
|
||||
var newNotification = notification
|
||||
newNotification.state = state
|
||||
newNotification.comments = commentCount
|
||||
updatedNotifications.append(newNotification)
|
||||
} else {
|
||||
updatedNotifications.append(notification)
|
||||
}
|
||||
@@ -148,7 +146,7 @@ final class NotificationClient {
|
||||
}
|
||||
}
|
||||
|
||||
func markRepoNotifications(repo: NotificationRepository, completion: @escaping (Bool) -> Void) {
|
||||
func markRepoNotifications(repo: Repository, completion: @escaping (Bool) -> Void) {
|
||||
githubClient.client.send(V3MarkRepositoryNotificationsRequest(owner: repo.owner, repo: repo.name)) { result in
|
||||
switch result {
|
||||
case .success: completion(true)
|
||||
@@ -157,32 +155,37 @@ final class NotificationClient {
|
||||
}
|
||||
}
|
||||
|
||||
func notificationOpened(id: String) -> Bool {
|
||||
return openedNotificationIDs.contains(id)
|
||||
}
|
||||
func markNotificationRead(id: String) {
|
||||
let cache = githubClient.cache
|
||||
guard var model = cache.get(id: id) as NotificationViewModel?,
|
||||
!model.read
|
||||
else { return }
|
||||
|
||||
func markNotificationRead(id: String, isOpen: Bool) {
|
||||
let oldModel = githubClient.cache.get(id: id) as NotificationViewModel?
|
||||
model.read = true
|
||||
cache.set(value: model)
|
||||
|
||||
if isOpen {
|
||||
openedNotificationIDs.insert(id)
|
||||
} else {
|
||||
// optimistically set the model to read
|
||||
// if the request fails, replace this model w/ the old one.
|
||||
if let old = oldModel {
|
||||
githubClient.cache.set(value: old.updated(read: true))
|
||||
}
|
||||
}
|
||||
|
||||
githubClient.client.send(V3MarkThreadsRequest(id: id)) { [weak self] result in
|
||||
githubClient.client.send(V3MarkThreadsRequest(id: id)) { result in
|
||||
switch result {
|
||||
case .success: break
|
||||
case .failure:
|
||||
if isOpen {
|
||||
self?.openedNotificationIDs.remove(id)
|
||||
} else if let old = oldModel {
|
||||
self?.githubClient.cache.set(value: old)
|
||||
}
|
||||
model.read = false
|
||||
cache.set(value: model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toggleWatch(notification: NotificationViewModel) {
|
||||
let cache = githubClient.cache
|
||||
|
||||
var model = notification
|
||||
model.watching = !notification.watching
|
||||
cache.set(value: model)
|
||||
|
||||
githubClient.client.send(V3SubscribeThreadRequest(id: model.v3id, ignore: model.watching)) { result in
|
||||
switch result {
|
||||
case .success: break
|
||||
case .failure:
|
||||
cache.set(value: notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// NotificationNextPageCell.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 7/2/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
final class NotificationNextPageCell: UICollectionViewCell {
|
||||
|
||||
let label = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
accessibilityTraits |= UIAccessibilityTraitButton
|
||||
isAccessibilityElement = true
|
||||
label.font = Styles.Text.button.preferredFont
|
||||
label.textColor = Styles.Colors.Gray.light.color
|
||||
contentView.addSubview(label)
|
||||
label.snp.makeConstraints { make in
|
||||
make.center.equalTo(contentView)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
layoutContentViewForSafeAreaInsets()
|
||||
}
|
||||
|
||||
// MARK: Public API
|
||||
|
||||
func configure(page: Int) {
|
||||
let format = NSLocalizedString("Load page %i", comment: "")
|
||||
label.text = String(format: format, page)
|
||||
}
|
||||
|
||||
override var accessibilityLabel: String? {
|
||||
get {
|
||||
return AccessibilityHelper.generatedLabel(forCell: self)
|
||||
}
|
||||
set { }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// NotificationNextPageSectionController.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 7/2/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
|
||||
protocol NotificationNextPageSectionControllerDelegate: class {
|
||||
func didSelect(notificationSectionController: NotificationNextPageSectionController)
|
||||
}
|
||||
|
||||
final class NotificationNextPageSectionController: ListGenericSectionController<NSNumber> {
|
||||
|
||||
weak var delegate: NotificationNextPageSectionControllerDelegate?
|
||||
|
||||
init(delegate: NotificationNextPageSectionControllerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
override func sizeForItem(at index: Int) -> CGSize {
|
||||
guard let width = collectionContext?.containerSize.width else { fatalError("Missing context") }
|
||||
return CGSize(width: width, height: Styles.Sizes.tableCellHeight)
|
||||
}
|
||||
|
||||
override func cellForItem(at index: Int) -> UICollectionViewCell {
|
||||
guard let cell = collectionContext?.dequeueReusableCell(
|
||||
of: NotificationNextPageCell.self,
|
||||
for: self,
|
||||
at: index
|
||||
) as? NotificationNextPageCell,
|
||||
let object = self.object
|
||||
else { fatalError("Missing context, object, or cell is wrong type") }
|
||||
// add one so reads as loading the NEXT page
|
||||
cell.configure(page: object.intValue)
|
||||
return cell
|
||||
}
|
||||
|
||||
override func didSelectItem(at index: Int) {
|
||||
delegate?.didSelect(notificationSectionController: self)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// NotificationRepoCell.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/13/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
import SnapKit
|
||||
|
||||
final class NotificationRepoCell: UICollectionViewCell, ListBindable {
|
||||
|
||||
let label = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
contentView.backgroundColor = Styles.Colors.Gray.lighter.color
|
||||
|
||||
label.font = Styles.Text.title.preferredFont
|
||||
label.textColor = Styles.Colors.Gray.light.color
|
||||
contentView.addSubview(label)
|
||||
label.snp.makeConstraints { make in
|
||||
make.left.equalTo(Styles.Sizes.gutter)
|
||||
make.centerY.equalTo(self.contentView)
|
||||
}
|
||||
|
||||
contentView.addBorder(.bottom)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: ListBindable
|
||||
|
||||
func bindViewModel(_ viewModel: Any) {
|
||||
guard let viewModel = viewModel as? String else { return }
|
||||
label.text = viewModel
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +1,109 @@
|
||||
//
|
||||
// NotificationSectionControllerDelegate.swift
|
||||
// NotificationSectionController.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/13/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
// Created by Ryan Nystrom on 6/8/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
import SwipeCellKit
|
||||
import GitHubAPI
|
||||
|
||||
final class NotificationSectionController: ListGenericSectionController<NotificationViewModel>,
|
||||
SwipeCollectionViewCellDelegate {
|
||||
final class NotificationSectionController: ListSwiftSectionController<NotificationViewModel>, NotificationCellDelegate {
|
||||
|
||||
private let client: NotificationClient
|
||||
private let modelController: NotificationModelController
|
||||
private let generator = UIImpactFeedbackGenerator()
|
||||
|
||||
init(client: NotificationClient) {
|
||||
self.client = client
|
||||
init(modelController: NotificationModelController) {
|
||||
self.modelController = modelController
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func sizeForItem(at index: Int) -> CGSize {
|
||||
guard let width = collectionContext?.containerSize.width
|
||||
else { fatalError("Collection context must be set") }
|
||||
return CGSize(
|
||||
width: width,
|
||||
height: max(ceil(object?.title.viewSize(in: width).height ?? 0), NotificationCell.minHeight)
|
||||
)
|
||||
override func createBinders(from value: NotificationViewModel) -> [ListBinder] {
|
||||
return [
|
||||
binder(
|
||||
value,
|
||||
cellType: ListCellType.class(NotificationCell.self),
|
||||
size: {
|
||||
let width = $0.collection.containerSize.width
|
||||
return CGSize(
|
||||
width: width,
|
||||
height: $0.value.title.viewSize(in: width).height
|
||||
)
|
||||
},
|
||||
configure: { [weak self] in
|
||||
$0.configure(with: $1.value, delegate: self)
|
||||
},
|
||||
didSelect: { [weak self] context in
|
||||
self?.showIssue(model: context.value)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
override func cellForItem(at index: Int) -> UICollectionViewCell {
|
||||
guard let object = self.object,
|
||||
let cell = collectionContext?.dequeueReusableCell(of: NotificationCell.self, for: self, at: index) as? NotificationCell
|
||||
else { fatalError("Collection context must be set, missing object, or cell incorrect type") }
|
||||
|
||||
cell.delegate = self
|
||||
cell.configure(object)
|
||||
cell.isRead = considerObjectRead
|
||||
|
||||
return cell
|
||||
func didTapRead(cell: NotificationCell) {
|
||||
guard let id = value?.id else { return }
|
||||
generator.impactOccurred()
|
||||
modelController.markNotificationRead(id: id)
|
||||
}
|
||||
|
||||
override func didSelectItem(at index: Int) {
|
||||
guard let object = self.object,
|
||||
let cell = collectionContext?.cellForItem(at: index, sectionController: self) as? NotificationCell
|
||||
else { fatalError("Missing object, cell missing, or incorrect type") }
|
||||
func didTapWatch(cell: NotificationCell) {
|
||||
guard let value = self.value else { return }
|
||||
modelController.toggleWatch(notification: value)
|
||||
}
|
||||
|
||||
if NotificationClient.readOnOpen() {
|
||||
cell.isRead = true
|
||||
client.markNotificationRead(id: object.id, isOpen: true)
|
||||
func didTapMore(cell: NotificationCell, sender: UIView) {
|
||||
guard let value = self.value else { return }
|
||||
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
|
||||
alert.addActions([
|
||||
viewController?.action(owner: value.owner),
|
||||
viewController?.action(
|
||||
owner: value.owner,
|
||||
repo: value.repo,
|
||||
branch: value.branch,
|
||||
issuesEnabled: value.issuesEnabled,
|
||||
client: modelController.githubClient
|
||||
),
|
||||
AlertAction.cancel()
|
||||
])
|
||||
alert.popoverPresentationController?.setSourceView(sender)
|
||||
viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
private func showIssue(model: NotificationViewModel) {
|
||||
if NotificationModelController.readOnOpen {
|
||||
modelController.markNotificationRead(id: model.id)
|
||||
}
|
||||
|
||||
switch object.identifier {
|
||||
switch model.number {
|
||||
case .hash(let hash):
|
||||
viewController?.presentCommit(owner: object.owner, repo: object.repo, hash: hash)
|
||||
viewController?.presentCommit(owner: model.owner, repo: model.repo, hash: hash)
|
||||
case .number(let number):
|
||||
let model = IssueDetailsModel(owner: object.owner, repo: object.repo, number: number)
|
||||
let controller = IssuesViewController(
|
||||
client: client.githubClient,
|
||||
model: model,
|
||||
client: modelController.githubClient,
|
||||
model: IssueDetailsModel(owner: model.owner, repo: model.repo, number: number),
|
||||
scrollToBottom: true
|
||||
)
|
||||
let navigation = UINavigationController(rootViewController: controller)
|
||||
viewController?.showDetailViewController(navigation, sender: nil)
|
||||
case .release(let release):
|
||||
client.githubClient.client
|
||||
.send(V3ReleaseRequest(owner: object.owner, repo: object.repo, id: release)) { [weak self] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self?.viewController?.presentRelease(
|
||||
owner: object.owner,
|
||||
repo: object.repo,
|
||||
release: response.data.tagName
|
||||
)
|
||||
case .failure: ToastManager.showGenericError()
|
||||
}
|
||||
}
|
||||
showRelease(release, model: model)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
|
||||
func markRead() {
|
||||
guard let object = object else { fatalError("Should have an object") }
|
||||
client.markNotificationRead(id: object.id, isOpen: false)
|
||||
}
|
||||
|
||||
var considerObjectRead: Bool {
|
||||
guard let object = object else { fatalError("Should have an object") }
|
||||
return object.read || client.notificationOpened(id: object.id)
|
||||
}
|
||||
|
||||
// MARK: SwipeCollectionViewCellDelegate
|
||||
|
||||
func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
editActionsForRowAt indexPath: IndexPath,
|
||||
for orientation: SwipeActionsOrientation
|
||||
) -> [SwipeAction]? {
|
||||
guard orientation == .right, !considerObjectRead else { return nil }
|
||||
|
||||
let title = NSLocalizedString("Read", comment: "")
|
||||
let action = SwipeAction(style: .destructive, title: title) { [weak self] (_, _) in
|
||||
// swiping-read is an engaging action, system prompt on it
|
||||
RatingController.prompt(.system)
|
||||
|
||||
self?.markRead()
|
||||
private func showRelease(_ release: String, model: NotificationViewModel) {
|
||||
modelController.githubClient.client
|
||||
.send(V3ReleaseRequest(owner: model.owner, repo: model.repo, id: release)) { [weak self] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self?.viewController?.presentRelease(
|
||||
owner: model.owner,
|
||||
repo: model.repo,
|
||||
release: response.data.tagName
|
||||
)
|
||||
case .failure:
|
||||
ToastManager.showGenericError()
|
||||
}
|
||||
}
|
||||
|
||||
action.backgroundColor = Styles.Colors.Blue.medium.color
|
||||
action.image = UIImage(named: "check")?.withRenderingMode(.alwaysTemplate)
|
||||
action.textColor = .white
|
||||
action.tintColor = .white
|
||||
action.font = Styles.Text.button.preferredFont
|
||||
action.transitionDelegate = ScaleTransition.default
|
||||
return [action]
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
|
||||
var options = SwipeTableOptions()
|
||||
options.expansionStyle = .selection
|
||||
return options
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// NotificationViewModel+Filterable.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 10/14/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension NotificationViewModel: Filterable {
|
||||
|
||||
func match(query: String) -> Bool {
|
||||
// if query is a number and model is issue/PR, match on the number
|
||||
switch identifier {
|
||||
case .number(let id):
|
||||
if query.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil {
|
||||
let ticketId = String(id)
|
||||
return ticketId.contains(query)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
let lowerQuery = query.lowercased()
|
||||
if title.string.allText.lowercased().contains(lowerQuery) { return true }
|
||||
if owner.lowercased().contains(lowerQuery) { return true }
|
||||
if repo.lowercased().contains(lowerQuery) { return true }
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,19 +2,18 @@
|
||||
// NotificationViewModel.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/12/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
// Created by Ryan Nystrom on 6/8/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IGListKit
|
||||
import FlatCache
|
||||
import DateAgo
|
||||
import StyledTextKit
|
||||
|
||||
final class NotificationViewModel: ListDiffable, Cachable {
|
||||
struct NotificationViewModel: ListSwiftDiffable, Cachable {
|
||||
|
||||
enum Identifier {
|
||||
enum Number {
|
||||
case number(Int)
|
||||
case hash(String)
|
||||
case release(String)
|
||||
@@ -30,123 +29,44 @@ final class NotificationViewModel: ListDiffable, Cachable {
|
||||
case open = "OPEN"
|
||||
}
|
||||
|
||||
let id: String
|
||||
let title: StyledTextRenderer
|
||||
let type: NotificationType
|
||||
let date: Date
|
||||
let agoString: String
|
||||
let read: Bool
|
||||
let owner: String
|
||||
let repo: String
|
||||
let identifier: Identifier
|
||||
let state: State
|
||||
let commentCount: Int
|
||||
var v3id: String
|
||||
var repo: String
|
||||
var owner: String
|
||||
var title: StyledTextRenderer
|
||||
var number: Number
|
||||
var state: State
|
||||
var date: Date
|
||||
var ago: String // only used for diffing to capture "ago" string at model init
|
||||
var read: Bool
|
||||
var comments: Int
|
||||
var watching: Bool
|
||||
var type: NotificationType
|
||||
var branch: String
|
||||
var issuesEnabled: Bool
|
||||
|
||||
init(
|
||||
id: String,
|
||||
title: StyledTextRenderer,
|
||||
type: NotificationType,
|
||||
date: Date,
|
||||
read: Bool,
|
||||
owner: String,
|
||||
repo: String,
|
||||
identifier: Identifier,
|
||||
state: State,
|
||||
commentCount: Int
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.type = type
|
||||
self.date = date
|
||||
self.read = read
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
self.identifier = identifier
|
||||
self.state = state
|
||||
self.commentCount = commentCount
|
||||
self.agoString = date.agoString(.long)
|
||||
// MARK: Identifiable
|
||||
|
||||
var id: String {
|
||||
return v3id
|
||||
}
|
||||
|
||||
convenience init(
|
||||
id: String,
|
||||
title: String,
|
||||
type: NotificationType,
|
||||
date: Date,
|
||||
read: Bool,
|
||||
owner: String,
|
||||
repo: String,
|
||||
identifier: Identifier,
|
||||
containerWidth: CGFloat,
|
||||
contentSizeCategory: UIContentSizeCategory
|
||||
) {
|
||||
let builder = StyledTextBuilder(styledText: StyledText(
|
||||
text: title,
|
||||
style: Styles.Text.body
|
||||
))
|
||||
.add(attributes: [.foregroundColor: Styles.Colors.Gray.dark.color])
|
||||
let title = StyledTextRenderer(
|
||||
string: builder.build(),
|
||||
contentSizeCategory: contentSizeCategory,
|
||||
inset: NotificationCell.labelInset
|
||||
)
|
||||
self.init(
|
||||
id: id,
|
||||
title: title,
|
||||
type: type,
|
||||
date: date,
|
||||
read: read,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
identifier: identifier,
|
||||
state: .pending,
|
||||
commentCount: 0
|
||||
)
|
||||
// MARK: ListSwiftDiffable
|
||||
|
||||
var identifier: String {
|
||||
return v3id
|
||||
}
|
||||
|
||||
// MARK: Public API
|
||||
// MARK: ListSwiftEquatable
|
||||
|
||||
func updated(
|
||||
id: String? = nil,
|
||||
title: StyledTextRenderer? = nil,
|
||||
type: NotificationType? = nil,
|
||||
date: Date? = nil,
|
||||
read: Bool? = nil,
|
||||
owner: String? = nil,
|
||||
repo: String? = nil,
|
||||
identifier: Identifier? = nil,
|
||||
state: State? = nil,
|
||||
commentCount: Int? = nil
|
||||
) -> NotificationViewModel {
|
||||
return NotificationViewModel(
|
||||
id: id ?? self.id,
|
||||
title: title ?? self.title,
|
||||
type: type ?? self.type,
|
||||
date: date ?? self.date,
|
||||
read: read ?? self.read,
|
||||
owner: owner ?? self.owner,
|
||||
repo: repo ?? self.repo,
|
||||
identifier: identifier ?? self.identifier,
|
||||
state: state ?? self.state,
|
||||
commentCount: commentCount ?? self.commentCount
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: ListDiffable
|
||||
|
||||
func diffIdentifier() -> NSObjectProtocol {
|
||||
return id as NSObjectProtocol
|
||||
}
|
||||
|
||||
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
|
||||
if self === object { return true }
|
||||
guard let object = object as? NotificationViewModel else { return false }
|
||||
return read == object.read
|
||||
&& type == object.type
|
||||
&& agoString == object.agoString
|
||||
&& repo == object.repo
|
||||
&& owner == object.owner
|
||||
&& state == object.state
|
||||
&& title.string == object.title.string
|
||||
func isEqual(to value: ListSwiftDiffable) -> Bool {
|
||||
guard let value = value as? NotificationViewModel else { return false }
|
||||
// making assumptions that given the v3id, most things don't change
|
||||
return read == value.read
|
||||
&& comments == value.comments
|
||||
&& watching == value.watching
|
||||
&& state == value.state
|
||||
&& ago == value.ago
|
||||
&& title.string == value.title.string
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,54 +2,40 @@
|
||||
// NotificationsViewController.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/13/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
// Created by Ryan Nystrom on 6/8/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
import SnapKit
|
||||
import FlatCache
|
||||
|
||||
class NotificationsViewController: BaseListViewController<NSNumber>,
|
||||
ForegroundHandlerDelegate,
|
||||
RatingSectionControllerDelegate,
|
||||
PrimaryViewController,
|
||||
TabNavRootViewControllerType,
|
||||
BaseListViewControllerDataSource,
|
||||
FlatCacheListener {
|
||||
final class NotificationsViewController: BaseListViewController2<Int>,
|
||||
BaseListViewController2DataSource,
|
||||
ForegroundHandlerDelegate, FlatCacheListener {
|
||||
|
||||
enum InboxType {
|
||||
case unread
|
||||
case repo(NotificationClient.NotificationRepository)
|
||||
case all
|
||||
}
|
||||
|
||||
private let client: NotificationClient
|
||||
private let modelController: NotificationModelController
|
||||
private let foreground = ForegroundHandler(threshold: 5 * 60)
|
||||
private let inboxType: InboxType
|
||||
private var notificationIDs = [String]()
|
||||
|
||||
// set to nil and update to dismiss the rating control
|
||||
private var ratingToken: RatingToken? = RatingController.inFeedToken()
|
||||
private var notifications: [NotificationViewModel] {
|
||||
return notificationIDs.compactMap { modelController.githubClient.cache.get(id: $0) }
|
||||
}
|
||||
|
||||
init(client: NotificationClient, inboxType: InboxType) {
|
||||
self.client = client
|
||||
init(modelController: NotificationModelController, inboxType: InboxType) {
|
||||
self.modelController = modelController
|
||||
self.inboxType = inboxType
|
||||
|
||||
super.init(
|
||||
emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: "")
|
||||
)
|
||||
super.init(emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: ""))
|
||||
|
||||
self.dataSource = self
|
||||
self.foreground.delegate = self
|
||||
|
||||
switch inboxType {
|
||||
case .all:
|
||||
title = NSLocalizedString("All", comment: "")
|
||||
case .unread:
|
||||
title = NSLocalizedString("Inbox", comment: "")
|
||||
case .repo(let repo):
|
||||
title = repo.name
|
||||
case .all: title = NSLocalizedString("All", comment: "")
|
||||
case .unread: title = NSLocalizedString("Inbox", comment: "")
|
||||
case .repo(let repo): title = repo.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +65,67 @@ FlatCacheListener {
|
||||
navigationController?.tabBarItem.badgeColor = Styles.Colors.Red.medium.color
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
override func fetch(page: Int?) {
|
||||
let width = view.bounds.width
|
||||
let showAll = inboxType.showAll
|
||||
|
||||
let repo: Repository?
|
||||
switch inboxType {
|
||||
case .repo(let r): repo = r
|
||||
case .all, .unread: repo = nil
|
||||
}
|
||||
|
||||
if let page = page {
|
||||
modelController.fetchNotifications(repo: repo, all: showAll, page: page, width: width) { [weak self] result in
|
||||
self?.handle(result: result, append: true, animated: false, page: page)
|
||||
}
|
||||
} else {
|
||||
let first = 1
|
||||
modelController.fetchNotifications(repo: repo, all: showAll, page: first, width: width) { [weak self] result in
|
||||
self?.handle(result: result, append: false, animated: trueUnlessReduceMotionEnabled, page: first)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handle(result: Result<([NotificationViewModel], Int?)>, append: Bool, animated: Bool, page: Int) {
|
||||
switch result {
|
||||
case .success(let notifications, let next):
|
||||
var ids = [String]()
|
||||
notifications.forEach {
|
||||
modelController.githubClient.cache.add(listener: self, value: $0)
|
||||
ids.append($0.id)
|
||||
}
|
||||
rebuildAndUpdate(ids: ids, append: append, page: next, animated: animated)
|
||||
case .error:
|
||||
error(animated: trueUnlessReduceMotionEnabled)
|
||||
ToastManager.showNetworkError()
|
||||
}
|
||||
|
||||
// set after updating so self.models has already been changed
|
||||
updateUnreadState()
|
||||
}
|
||||
|
||||
private func updateUnreadState() {
|
||||
// don't update tab bar and badges when not showing only new notifications
|
||||
// prevents archives updating badge and tab #s
|
||||
switch inboxType {
|
||||
case .all, .repo: return
|
||||
case .unread: break
|
||||
}
|
||||
|
||||
var unread = 0
|
||||
for id in notificationIDs {
|
||||
guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?,
|
||||
!model.read
|
||||
else { continue }
|
||||
unread += 1
|
||||
}
|
||||
|
||||
let hasUnread = unread > 0
|
||||
navigationItem.rightBarButtonItem?.isEnabled = hasUnread
|
||||
navigationController?.tabBarItem.badgeValue = hasUnread ? "\(unread)" : nil
|
||||
BadgeNotifications.update(count: unread)
|
||||
}
|
||||
|
||||
@objc func onMore(sender: UIBarButtonItem) {
|
||||
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
|
||||
@@ -91,33 +137,36 @@ FlatCacheListener {
|
||||
self?.onViewAll()
|
||||
}))
|
||||
|
||||
let cache = client.githubClient.cache
|
||||
let cache = modelController.githubClient.cache
|
||||
var repoNames = Set<String>()
|
||||
for id in notificationIDs {
|
||||
guard let notification = cache.get(id: id) as NotificationViewModel?,
|
||||
!repoNames.contains(notification.repo)
|
||||
guard let model = cache.get(id: id) as NotificationViewModel?,
|
||||
!repoNames.contains(model.repo)
|
||||
else { continue }
|
||||
repoNames.insert(notification.repo)
|
||||
alert.add(action: UIAlertAction(title: notification.repo, style: .default, handler: { [weak self] _ in
|
||||
self?.pushRepoNotifications(owner: notification.owner, repo: notification.repo)
|
||||
repoNames.insert(model.repo)
|
||||
alert.add(action: UIAlertAction(title: model.repo, style: .default, handler: { [weak self] _ in
|
||||
self?.pushRepoNotifications(owner: model.owner, repo: model.repo)
|
||||
}))
|
||||
}
|
||||
|
||||
alert.add(action: AlertAction.cancel())
|
||||
|
||||
alert.popoverPresentationController?.barButtonItem = sender
|
||||
|
||||
present(alert, animated: true)
|
||||
present(alert, animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
func pushRepoNotifications(owner: String, repo: String) {
|
||||
let model = NotificationClient.NotificationRepository(owner: owner, name: repo)
|
||||
let controller = NotificationsViewController(client: client, inboxType: .repo(model))
|
||||
let controller = NotificationsViewController(
|
||||
modelController: modelController,
|
||||
inboxType: .repo(Repository(owner: owner, name: repo))
|
||||
)
|
||||
navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
func onViewAll() {
|
||||
let controller = NotificationsViewController(client: client, inboxType: .all)
|
||||
let controller = NotificationsViewController(
|
||||
modelController: modelController,
|
||||
inboxType: .all
|
||||
)
|
||||
navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
@@ -131,50 +180,7 @@ FlatCacheListener {
|
||||
item.accessibilityLabel = NSLocalizedString("Mark notifications read", comment: "")
|
||||
navigationItem.rightBarButtonItem = item
|
||||
if updateState {
|
||||
updateUnreadState(count: notificationIDs.count)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateUnreadState(count: Int) {
|
||||
// don't update tab bar and badges when not showing only new notifications
|
||||
// prevents archives updating badge and tab #s
|
||||
switch inboxType {
|
||||
case .all, .repo: return
|
||||
case .unread: break
|
||||
}
|
||||
|
||||
let hasUnread = count > 0
|
||||
navigationItem.rightBarButtonItem?.isEnabled = hasUnread
|
||||
navigationController?.tabBarItem.badgeValue = hasUnread ? "\(count)" : nil
|
||||
BadgeNotifications.update(count: count)
|
||||
}
|
||||
|
||||
private func markRead() {
|
||||
self.setRightBarItemSpinning()
|
||||
|
||||
let block: (Bool) -> Void = { success in
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
if success {
|
||||
generator.notificationOccurred(.success)
|
||||
|
||||
// clear all badges
|
||||
BadgeNotifications.update(count: 0)
|
||||
|
||||
// change the spinner to the mark all item
|
||||
// don't update state here; it is managed by `fetch`
|
||||
self.resetRightBarItem(updatingState: false)
|
||||
} else {
|
||||
generator.notificationOccurred(.error)
|
||||
}
|
||||
self.fetch(page: nil)
|
||||
|
||||
// "mark all" is an engaging action, system prompt on it
|
||||
RatingController.prompt(.system)
|
||||
}
|
||||
|
||||
switch inboxType {
|
||||
case .all, .unread: client.markAllNotifications(completion: block)
|
||||
case .repo(let repo): client.markRepoNotifications(repo: repo, completion: block)
|
||||
updateUnreadState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,33 +208,44 @@ FlatCacheListener {
|
||||
self?.markRead()
|
||||
}),
|
||||
AlertAction.cancel()
|
||||
])
|
||||
])
|
||||
|
||||
present(alert, animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
private func handle(result: Result<([NotificationViewModel], Int?)>, append: Bool, animated: Bool, page: Int) {
|
||||
switch result {
|
||||
case .success(let notifications, let next):
|
||||
var ids = [String]()
|
||||
notifications.forEach {
|
||||
client.githubClient.cache.add(listener: self, value: $0)
|
||||
ids.append($0.id)
|
||||
private func markRead() {
|
||||
self.setRightBarItemSpinning()
|
||||
|
||||
let block: (Bool) -> Void = { success in
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
if success {
|
||||
generator.notificationOccurred(.success)
|
||||
|
||||
// clear all badges
|
||||
BadgeNotifications.update(count: 0)
|
||||
|
||||
// change the spinner to the mark all item
|
||||
// don't update state here; it is managed by `fetch`
|
||||
self.resetRightBarItem(updatingState: false)
|
||||
} else {
|
||||
generator.notificationOccurred(.error)
|
||||
}
|
||||
rebuildAndUpdate(ids: ids, append: append, page: next as NSNumber?, animated: animated)
|
||||
case .error:
|
||||
error(animated: trueUnlessReduceMotionEnabled)
|
||||
ToastManager.showNetworkError()
|
||||
self.fetch(page: nil)
|
||||
|
||||
// "mark all" is an engaging action, system prompt on it
|
||||
RatingController.prompt(.system)
|
||||
}
|
||||
|
||||
// set after updating so self.models has already been changed
|
||||
updateUnreadState(count: notificationIDs.count)
|
||||
switch inboxType {
|
||||
case .all, .unread: modelController.markAllNotifications(completion: block)
|
||||
case .repo(let repo): modelController.markRepoNotifications(repo: repo, completion: block)
|
||||
}
|
||||
}
|
||||
|
||||
private func rebuildAndUpdate(
|
||||
ids: [String],
|
||||
append: Bool,
|
||||
page: NSNumber?,
|
||||
page: Int?,
|
||||
animated: Bool
|
||||
) {
|
||||
if append {
|
||||
@@ -239,101 +256,29 @@ FlatCacheListener {
|
||||
update(page: page, animated: animated)
|
||||
}
|
||||
|
||||
private var showAll: Bool {
|
||||
switch inboxType {
|
||||
case .all, .repo: return true
|
||||
case .unread: return false
|
||||
}
|
||||
}
|
||||
// MARK: BaseListViewController2DataSource
|
||||
|
||||
// MARK: Overrides
|
||||
|
||||
override func fetch(page: NSNumber?) {
|
||||
let width = view.bounds.width
|
||||
|
||||
let repo: NotificationClient.NotificationRepository?
|
||||
switch inboxType {
|
||||
case .repo(let r): repo = r
|
||||
case .all, .unread: repo = nil
|
||||
}
|
||||
|
||||
if let page = page?.intValue {
|
||||
client.fetchNotifications(repo: repo, all: showAll, page: page, width: width) { [weak self] result in
|
||||
self?.handle(result: result, append: true, animated: false, page: page)
|
||||
}
|
||||
} else {
|
||||
let first = 1
|
||||
client.fetchNotifications(repo: repo, all: showAll, page: first, width: width) { [weak self] result in
|
||||
self?.handle(result: result, append: false, animated: trueUnlessReduceMotionEnabled, page: first)
|
||||
func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
|
||||
return notificationIDs.compactMap { id in
|
||||
guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?
|
||||
else { return nil }
|
||||
return ListSwiftPair.pair(model) { [modelController] in
|
||||
NotificationSectionController(modelController: modelController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: BaseListViewControllerDataSource
|
||||
|
||||
func headModels(listAdapter: ListAdapter) -> [ListDiffable] {
|
||||
return []
|
||||
}
|
||||
|
||||
func models(listAdapter: ListAdapter) -> [ListDiffable] {
|
||||
var models = [NotificationViewModel]()
|
||||
|
||||
let showAll = self.showAll
|
||||
for id in notificationIDs {
|
||||
if let model = client.githubClient.cache.get(id: id) as NotificationViewModel?,
|
||||
(showAll || !model.read) {
|
||||
// swap the model if not read, otherwise exclude it
|
||||
// this powers the "swipe to archive" feature deleting the cell
|
||||
models.append(model)
|
||||
}
|
||||
}
|
||||
|
||||
// every time the list is updated, update bar items and badges
|
||||
updateUnreadState(count: models.count)
|
||||
|
||||
return models
|
||||
}
|
||||
|
||||
func sectionController(model: Any, listAdapter: ListAdapter) -> ListSectionController {
|
||||
switch model {
|
||||
case is NotificationViewModel: return NotificationSectionController(client: client)
|
||||
case is RatingToken: return RatingSectionController(delegate: self)
|
||||
default: fatalError("Unhandled object: \(model)")
|
||||
}
|
||||
}
|
||||
|
||||
func emptySectionController(listAdapter: ListAdapter) -> ListSectionController {
|
||||
return NoNewNotificationSectionController(
|
||||
topInset: 0,
|
||||
layoutInsets: view.safeAreaInsets
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: ForegroundHandlerDelegate
|
||||
|
||||
func didForeground(handler: ForegroundHandler) {
|
||||
feed.refreshHead()
|
||||
}
|
||||
|
||||
// MARK: RatingSectionControllerDelegate
|
||||
|
||||
func ratingNeedsDismiss(sectionController: RatingSectionController) {
|
||||
ratingToken = nil
|
||||
update(animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
// MARK: TabNavRootViewControllerType
|
||||
|
||||
func didSingleTapTab() {
|
||||
feed.collectionView.scrollToTop(animated: trueUnlessReduceMotionEnabled)
|
||||
}
|
||||
|
||||
func didDoubleTapTab() {}
|
||||
|
||||
// MARK: FlatCacheListener
|
||||
|
||||
func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) {
|
||||
self.update(animated: trueUnlessReduceMotionEnabled)
|
||||
updateUnreadState()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// SegmentedControlModel+Notifications.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 7/6/17.
|
||||
// Copyright © 2017 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension SegmentedControlModel {
|
||||
|
||||
static func forNotifications() -> SegmentedControlModel {
|
||||
return SegmentedControlModel(items: [Constants.Strings.unread, Constants.Strings.all])
|
||||
}
|
||||
|
||||
var unreadSelected: Bool {
|
||||
return items[selectedIndex] == Constants.Strings.unread
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import IGListKit
|
||||
import GitHubAPI
|
||||
|
||||
final class PeopleViewController: BaseListViewController2,
|
||||
final class PeopleViewController: BaseListViewController2<String>,
|
||||
BaseListViewController2DataSource,
|
||||
PeopleSectionControllerDelegate {
|
||||
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
import UIKit
|
||||
import IGListKit
|
||||
|
||||
protocol LoadMoreSectionController2Delegate: class {
|
||||
func didSelect(controller: LoadMoreSectionController2)
|
||||
}
|
||||
|
||||
final class LoadMoreSectionController2: ListSwiftSectionController<String> {
|
||||
|
||||
weak var delegate: LoadMoreSectionController2Delegate?
|
||||
|
||||
override func createBinders(from value: String) -> [ListBinder] {
|
||||
return [
|
||||
binder(value, cellType: .class(LoadMoreCell.self), size: {
|
||||
@@ -18,6 +24,9 @@ final class LoadMoreSectionController2: ListSwiftSectionController<String> {
|
||||
width: $0.collection.containerSize.width,
|
||||
height: Styles.Sizes.tableCellHeight
|
||||
)
|
||||
}, didSelect: { [weak self] context in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.delegate?.didSelect(controller: strongSelf)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ NewIssueTableViewControllerDelegate {
|
||||
super.viewDidLoad()
|
||||
|
||||
versionLabel.text = Bundle.main.prettyVersionString
|
||||
markReadSwitch.isOn = NotificationClient.readOnOpen()
|
||||
markReadSwitch.isOn = NotificationModelController.readOnOpen
|
||||
apiStatusView.layer.cornerRadius = 7
|
||||
signatureSwitch.isOn = Signature.enabled
|
||||
|
||||
@@ -227,7 +227,7 @@ NewIssueTableViewControllerDelegate {
|
||||
}
|
||||
|
||||
@IBAction func onMarkRead(_ sender: Any) {
|
||||
NotificationClient.setReadOnOpen(open: markReadSwitch.isOn)
|
||||
NotificationModelController.setReadOnOpen(open: markReadSwitch.isOn)
|
||||
}
|
||||
|
||||
private func style() {
|
||||
|
||||
@@ -13,15 +13,16 @@ protocol BaseListViewController2DataSource: class {
|
||||
func models(adapter: ListSwiftAdapter) -> [ListSwiftPair]
|
||||
}
|
||||
|
||||
class BaseListViewController2: UIViewController,
|
||||
class BaseListViewController2<PageType: CustomStringConvertible>: UIViewController,
|
||||
ListSwiftAdapterDataSource,
|
||||
FeedDelegate {
|
||||
FeedDelegate,
|
||||
LoadMoreSectionController2Delegate {
|
||||
|
||||
private let emptyErrorMessage: String
|
||||
public weak var dataSource: BaseListViewController2DataSource?
|
||||
|
||||
public private(set) lazy var feed: Feed = { Feed(viewController: self, delegate: self) }()
|
||||
private var page: String?
|
||||
private var page: PageType?
|
||||
private var hasError = false
|
||||
private let emptyKey: ListDiffable = "emptyKey" as ListDiffable
|
||||
|
||||
@@ -52,7 +53,7 @@ FeedDelegate {
|
||||
|
||||
// MARK: Overridable API
|
||||
|
||||
func fetch(page: String?) {}
|
||||
func fetch(page: PageType?) {}
|
||||
|
||||
// MARK: Public API
|
||||
|
||||
@@ -64,7 +65,7 @@ FeedDelegate {
|
||||
}
|
||||
|
||||
final func update(
|
||||
page: String?,
|
||||
page: PageType?,
|
||||
animated: Bool,
|
||||
completion: (() -> Void)? = nil
|
||||
) {
|
||||
@@ -117,12 +118,22 @@ FeedDelegate {
|
||||
return []
|
||||
}
|
||||
|
||||
if let page = self.page {
|
||||
let pagePair = ListSwiftPair.pair(page) { LoadMoreSectionController2() }
|
||||
if let page = self.page?.description {
|
||||
let pagePair = ListSwiftPair.pair(page) { [weak self] in
|
||||
let controller = LoadMoreSectionController2()
|
||||
controller.delegate = self
|
||||
return controller
|
||||
}
|
||||
return models + [pagePair]
|
||||
} else {
|
||||
return models
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: LoadMoreSectionController2Delegate
|
||||
|
||||
func didSelect(controller: LoadMoreSectionController2) {
|
||||
fetch(page: page)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ func newSettingsRootViewController(
|
||||
}
|
||||
|
||||
func newNotificationsRootViewController(client: GithubClient) -> UIViewController {
|
||||
let controller = NotificationsViewController(client: NotificationClient(githubClient: client), inboxType: .unread)
|
||||
let controller = NotificationsViewController(
|
||||
modelController: NotificationModelController(githubClient: client),
|
||||
inboxType: .unread
|
||||
)
|
||||
let title = NSLocalizedString("Inbox", comment: "")
|
||||
controller.title = title
|
||||
let nav = UINavigationController(rootViewController: controller)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// UIViewController+CommonActionItems.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/10/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func action(owner: String) -> UIAlertAction? {
|
||||
weak var weakSelf = self
|
||||
return AlertAction(AlertActionBuilder { $0.rootViewController = weakSelf })
|
||||
.view(owner: owner)
|
||||
}
|
||||
|
||||
func action(
|
||||
owner: String,
|
||||
repo: String,
|
||||
branch: String,
|
||||
issuesEnabled: Bool,
|
||||
client: GithubClient
|
||||
) -> UIAlertAction? {
|
||||
let repo = RepositoryDetails(
|
||||
owner: owner,
|
||||
name: repo,
|
||||
defaultBranch: branch,
|
||||
hasIssuesEnabled: issuesEnabled
|
||||
)
|
||||
weak var weakSelf = self
|
||||
return AlertAction(AlertActionBuilder { $0.rootViewController = weakSelf })
|
||||
.view(client: client, repo: repo)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import DateAgo
|
||||
|
||||
extension ShowMoreDetailsLabel {
|
||||
|
||||
func setText(date: Date) {
|
||||
text = date.agoString(.long)
|
||||
func setText(date: Date, format: Date.AgoFormat = .long) {
|
||||
text = date.agoString(format)
|
||||
detailText = DateDetailsFormatter().string(from: date)
|
||||
}
|
||||
|
||||
|
||||
@@ -191,8 +191,6 @@
|
||||
295A77BE1F75C1CC007BC403 /* RepositoryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295A77BD1F75C1CC007BC403 /* RepositoryDetails.swift */; };
|
||||
295B51421FC26B8100C3993B /* PeopleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295B51411FC26B8100C3993B /* PeopleCell.swift */; };
|
||||
295B51481FC26F7F00C3993B /* UIImageView+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295B51471FC26F7F00C3993B /* UIImageView+Avatar.swift */; };
|
||||
295C31C71F09E62600521CED /* NotificationNextPageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C31C61F09E62600521CED /* NotificationNextPageCell.swift */; };
|
||||
295C31C91F09E72D00521CED /* NotificationNextPageSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C31C81F09E72D00521CED /* NotificationNextPageSectionController.swift */; };
|
||||
295C31CD1F0AA55400521CED /* IssueStatusEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C31CC1F0AA55400521CED /* IssueStatusEvent.swift */; };
|
||||
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 */; };
|
||||
@@ -213,7 +211,6 @@
|
||||
297403D91F18545A00ABA95A /* IssueAssigneesSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297403D81F18545A00ABA95A /* IssueAssigneesSectionController.swift */; };
|
||||
297403DB1F18550200ABA95A /* IssueAssigneeSummaryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297403DA1F18550200ABA95A /* IssueAssigneeSummaryModel.swift */; };
|
||||
297403DD1F185A8700ABA95A /* IssueAssigneeUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297403DC1F185A8700ABA95A /* IssueAssigneeUserCell.swift */; };
|
||||
297406981F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297406971F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift */; };
|
||||
2974069B1F0EDC7C003A6BFB /* IssueCommentTableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2974069A1F0EDC7C003A6BFB /* IssueCommentTableModel.swift */; };
|
||||
2974069D1F0EDEAD003A6BFB /* IssueCommentTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2974069C1F0EDEAD003A6BFB /* IssueCommentTableCell.swift */; };
|
||||
2974069F1F0EDED3003A6BFB /* IssueCommentTableCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2974069E1F0EDED3003A6BFB /* IssueCommentTableCollectionCell.swift */; };
|
||||
@@ -244,7 +241,6 @@
|
||||
2980033C1F51E82400BE90F4 /* RatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980033B1F51E82400BE90F4 /* RatingController.swift */; };
|
||||
2980033E1F51E93500BE90F4 /* RatingSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980033D1F51E93500BE90F4 /* RatingSectionController.swift */; };
|
||||
298003401F51E93B00BE90F4 /* RatingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980033F1F51E93B00BE90F4 /* RatingCell.swift */; };
|
||||
2980E0921F073E8B000E02C6 /* NotificationSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980E0911F073E8B000E02C6 /* NotificationSectionController.swift */; };
|
||||
2981A8A41EFE9FC700E25EF1 /* GithubEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2981A8A31EFE9FC700E25EF1 /* GithubEmoji.swift */; };
|
||||
2982ED7A1F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2982ED791F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift */; };
|
||||
2986B3581FD30EC400E3CFC6 /* IssueManagingExpansionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2986B3571FD30EC400E3CFC6 /* IssueManagingExpansionModel.swift */; };
|
||||
@@ -291,10 +287,8 @@
|
||||
29A195081EC7602500C3E289 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 29A195061EC7601000C3E289 /* Localizable.stringsdict */; };
|
||||
29A1950A1EC78B4800C3E289 /* NotificationType+Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A195091EC78B4800C3E289 /* NotificationType+Icon.swift */; };
|
||||
29A1950C1EC7901400C3E289 /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A1950B1EC7901400C3E289 /* NotificationType.swift */; };
|
||||
29A195111EC7AC9500C3E289 /* NotificationRepoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A195101EC7AC9500C3E289 /* NotificationRepoCell.swift */; };
|
||||
29A4768E1ED07A23005D0953 /* DateDetailsFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A4768D1ED07A23005D0953 /* DateDetailsFormatter.swift */; };
|
||||
29A476A01ED0E6C6005D0953 /* UIColor+Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A4769F1ED0E6C6005D0953 /* UIColor+Overlay.swift */; };
|
||||
29A5AF3F1F9266370065D529 /* NotificationViewModel+Filterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A5AF3E1F9266370065D529 /* NotificationViewModel+Filterable.swift */; };
|
||||
29A5AF411F92677D0065D529 /* RepositoryIssueSummaryModel+Filterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A5AF401F92677D0065D529 /* RepositoryIssueSummaryModel+Filterable.swift */; };
|
||||
29A5AF431F926C600065D529 /* Filterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A5AF421F926C600065D529 /* Filterable.swift */; };
|
||||
29A5AF451F9298360065D529 /* GitHubClient+Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A5AF441F9298360065D529 /* GitHubClient+Repository.swift */; };
|
||||
@@ -308,10 +302,14 @@
|
||||
29AF1E8C1F8ABC5A0008A0EF /* RepositoryCodeDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF1E8B1F8ABC5A0008A0EF /* RepositoryCodeDirectoryViewController.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
29B94E6F1FCB743900715D7E /* RepositoryFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B94E6E1FCB743900715D7E /* RepositoryFileCell.swift */; };
|
||||
29BBD82920CAC7D5004D62FE /* NotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BBD82820CAC7D5004D62FE /* NotificationViewModel.swift */; };
|
||||
29BBD82B20CACB2F004D62FE /* NotificationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BBD82A20CACB2F004D62FE /* NotificationCell.swift */; };
|
||||
29BBD82D20CACDFF004D62FE /* NotificationSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BBD82C20CACDFF004D62FE /* NotificationSectionController.swift */; };
|
||||
29BE40D32070786400A79C86 /* CMarkParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE40D22070786400A79C86 /* CMarkParsing.swift */; };
|
||||
29C0E7071ECBC6C50051D756 /* GithubClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0E7061ECBC6C50051D756 /* GithubClient.swift */; };
|
||||
29C167671ECA005500439D62 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C167661ECA005500439D62 /* Constants.swift */; };
|
||||
@@ -328,8 +326,6 @@
|
||||
29C8F9AD208C02860075931C /* LoadMoreSectionController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8F9AC208C02860075931C /* LoadMoreSectionController2.swift */; };
|
||||
29C8F9AF208C02BE0075931C /* String+ListSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8F9AE208C02BE0075931C /* String+ListSwift.swift */; };
|
||||
29C8F9B5208C081D0075931C /* LabelSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8F9B4208C081D0075931C /* LabelSectionController.swift */; };
|
||||
29C9FDDB1EC6627200EE3A52 /* NotificationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9FDDA1EC6627200EE3A52 /* NotificationCell.swift */; };
|
||||
29C9FDDD1EC6628200EE3A52 /* NotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9FDDC1EC6628200EE3A52 /* NotificationViewModel.swift */; };
|
||||
29C9FDE11EC667AE00EE3A52 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9FDE01EC667AE00EE3A52 /* Styles.swift */; };
|
||||
29CC29301FF421DC006B6DE7 /* GithubClient+PullRequestReviewComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CC292E1FF421DC006B6DE7 /* GithubClient+PullRequestReviewComments.swift */; };
|
||||
29CC29311FF421DC006B6DE7 /* PullRequestReviewCommentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CC292F1FF421DC006B6DE7 /* PullRequestReviewCommentsViewController.swift */; };
|
||||
@@ -363,7 +359,11 @@
|
||||
29EE1C1D1F3A33890046A54D /* RepositoryLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EE1C1C1F3A33890046A54D /* RepositoryLabel.swift */; };
|
||||
29EE44461F19D5C100B05ED3 /* GithubClient+Issues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EE44451F19D5C100B05ED3 /* GithubClient+Issues.swift */; };
|
||||
29EE444A1F19D85800B05ED3 /* ShowErrorStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EE44491F19D85800B05ED3 /* ShowErrorStatusBar.swift */; };
|
||||
29EEB22D1F9BF65B00AB237B /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EEB22C1F9BF65B00AB237B /* NotificationsViewController.swift */; };
|
||||
29F3A18420CADA3A00645CB7 /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18320CADA3A00645CB7 /* NotificationsViewController.swift */; };
|
||||
29F3A18620CBF99E00645CB7 /* NotificationModelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18520CBF99E00645CB7 /* NotificationModelController.swift */; };
|
||||
29F3A18820CBFF8700645CB7 /* CreateNotificationTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18720CBFF8700645CB7 /* CreateNotificationTitle.swift */; };
|
||||
29F3A18A20CC017700645CB7 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18920CC017700645CB7 /* Repository.swift */; };
|
||||
29F3A18C20CD790F00645CB7 /* UIViewController+CommonActionItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18B20CD790F00645CB7 /* UIViewController+CommonActionItems.swift */; };
|
||||
29F7F05C1F2A751B00F6075D /* IssueResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F7F05B1F2A751B00F6075D /* IssueResult.swift */; };
|
||||
29F7F05F1F2A839100F6075D /* IssueNeckLoadSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F7F05E1F2A839100F6075D /* IssueNeckLoadSectionController.swift */; };
|
||||
29F7F0611F2A83AA00F6075D /* IssueNeckLoadCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F7F0601F2A83AA00F6075D /* IssueNeckLoadCell.swift */; };
|
||||
@@ -688,8 +688,6 @@
|
||||
295A77BD1F75C1CC007BC403 /* RepositoryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryDetails.swift; sourceTree = "<group>"; };
|
||||
295B51411FC26B8100C3993B /* PeopleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleCell.swift; sourceTree = "<group>"; };
|
||||
295B51471FC26F7F00C3993B /* UIImageView+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Avatar.swift"; sourceTree = "<group>"; };
|
||||
295C31C61F09E62600521CED /* NotificationNextPageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNextPageCell.swift; sourceTree = "<group>"; };
|
||||
295C31C81F09E72D00521CED /* NotificationNextPageSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNextPageSectionController.swift; sourceTree = "<group>"; };
|
||||
295C31CC1F0AA55400521CED /* IssueStatusEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueStatusEvent.swift; sourceTree = "<group>"; };
|
||||
295C31CE1F0AA67600521CED /* IssueStatus+ButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IssueStatus+ButtonState.swift"; sourceTree = "<group>"; };
|
||||
295C31D01F0AA72000521CED /* IssueStatusEvent+ButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IssueStatusEvent+ButtonState.swift"; sourceTree = "<group>"; };
|
||||
@@ -712,7 +710,6 @@
|
||||
297403D81F18545A00ABA95A /* IssueAssigneesSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueAssigneesSectionController.swift; sourceTree = "<group>"; };
|
||||
297403DA1F18550200ABA95A /* IssueAssigneeSummaryModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueAssigneeSummaryModel.swift; sourceTree = "<group>"; };
|
||||
297403DC1F185A8700ABA95A /* IssueAssigneeUserCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueAssigneeUserCell.swift; sourceTree = "<group>"; };
|
||||
297406971F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SegmentedControlModel+Notifications.swift"; sourceTree = "<group>"; };
|
||||
2974069A1F0EDC7C003A6BFB /* IssueCommentTableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueCommentTableModel.swift; sourceTree = "<group>"; };
|
||||
2974069C1F0EDEAD003A6BFB /* IssueCommentTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueCommentTableCell.swift; sourceTree = "<group>"; };
|
||||
2974069E1F0EDED3003A6BFB /* IssueCommentTableCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueCommentTableCollectionCell.swift; sourceTree = "<group>"; };
|
||||
@@ -747,7 +744,6 @@
|
||||
2980033B1F51E82400BE90F4 /* RatingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingController.swift; sourceTree = "<group>"; };
|
||||
2980033D1F51E93500BE90F4 /* RatingSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingSectionController.swift; sourceTree = "<group>"; };
|
||||
2980033F1F51E93B00BE90F4 /* RatingCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingCell.swift; sourceTree = "<group>"; };
|
||||
2980E0911F073E8B000E02C6 /* NotificationSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSectionController.swift; sourceTree = "<group>"; };
|
||||
2981A8A31EFE9FC700E25EF1 /* GithubEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubEmoji.swift; sourceTree = "<group>"; };
|
||||
2981A8A61EFEBEF900E25EF1 /* EmojiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTests.swift; sourceTree = "<group>"; };
|
||||
2982ED791F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewCell+SafeAreaContentView.swift"; sourceTree = "<group>"; };
|
||||
@@ -795,11 +791,9 @@
|
||||
29A195061EC7601000C3E289 /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
29A195091EC78B4800C3E289 /* NotificationType+Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NotificationType+Icon.swift"; sourceTree = "<group>"; };
|
||||
29A1950B1EC7901400C3E289 /* NotificationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = "<group>"; };
|
||||
29A195101EC7AC9500C3E289 /* NotificationRepoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRepoCell.swift; sourceTree = "<group>"; };
|
||||
29A4768D1ED07A23005D0953 /* DateDetailsFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateDetailsFormatter.swift; sourceTree = "<group>"; };
|
||||
29A4769F1ED0E6C6005D0953 /* UIColor+Overlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Overlay.swift"; sourceTree = "<group>"; };
|
||||
29A476B11ED24D99005D0953 /* IssueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueTests.swift; sourceTree = "<group>"; };
|
||||
29A5AF3E1F9266370065D529 /* NotificationViewModel+Filterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewModel+Filterable.swift"; sourceTree = "<group>"; };
|
||||
29A5AF401F92677D0065D529 /* RepositoryIssueSummaryModel+Filterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RepositoryIssueSummaryModel+Filterable.swift"; sourceTree = "<group>"; };
|
||||
29A5AF421F926C600065D529 /* Filterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filterable.swift; sourceTree = "<group>"; };
|
||||
29A5AF441F9298360065D529 /* GitHubClient+Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitHubClient+Repository.swift"; sourceTree = "<group>"; };
|
||||
@@ -813,10 +807,14 @@
|
||||
29AF1E8B1F8ABC5A0008A0EF /* RepositoryCodeDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryCodeDirectoryViewController.swift; sourceTree = "<group>"; };
|
||||
29AF1E8D1F8ABC900008A0EF /* RepositoryFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryFile.swift; sourceTree = "<group>"; };
|
||||
29B0EF861F93DF6C00870291 /* RepositoryCodeBlobViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryCodeBlobViewController.swift; sourceTree = "<group>"; };
|
||||
29B5D08A20D578DB003DFBE2 /* InboxType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxType.swift; sourceTree = "<group>"; };
|
||||
29B94E661FCB2D4600715D7E /* CodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeView.swift; sourceTree = "<group>"; };
|
||||
29B94E681FCB36A000715D7E /* File+ListDiffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "File+ListDiffable.swift"; sourceTree = "<group>"; };
|
||||
29B94E6C1FCB472400715D7E /* IssueFileChangesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueFileChangesModel.swift; sourceTree = "<group>"; };
|
||||
29B94E6E1FCB743900715D7E /* RepositoryFileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryFileCell.swift; sourceTree = "<group>"; };
|
||||
29BBD82820CAC7D5004D62FE /* NotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewModel.swift; sourceTree = "<group>"; };
|
||||
29BBD82A20CACB2F004D62FE /* NotificationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCell.swift; sourceTree = "<group>"; };
|
||||
29BBD82C20CACDFF004D62FE /* NotificationSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSectionController.swift; sourceTree = "<group>"; };
|
||||
29BE40D22070786400A79C86 /* CMarkParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMarkParsing.swift; sourceTree = "<group>"; };
|
||||
29C0E7061ECBC6C50051D756 /* GithubClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GithubClient.swift; sourceTree = "<group>"; };
|
||||
29C167661ECA005500439D62 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
@@ -835,8 +833,6 @@
|
||||
29C8F9AC208C02860075931C /* LoadMoreSectionController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreSectionController2.swift; sourceTree = "<group>"; };
|
||||
29C8F9AE208C02BE0075931C /* String+ListSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ListSwift.swift"; sourceTree = "<group>"; };
|
||||
29C8F9B4208C081D0075931C /* LabelSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSectionController.swift; sourceTree = "<group>"; };
|
||||
29C9FDDA1EC6627200EE3A52 /* NotificationCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCell.swift; sourceTree = "<group>"; };
|
||||
29C9FDDC1EC6628200EE3A52 /* NotificationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationViewModel.swift; sourceTree = "<group>"; };
|
||||
29C9FDE01EC667AE00EE3A52 /* Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Styles.swift; sourceTree = "<group>"; };
|
||||
29CC292E1FF421DC006B6DE7 /* GithubClient+PullRequestReviewComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GithubClient+PullRequestReviewComments.swift"; sourceTree = "<group>"; };
|
||||
29CC292F1FF421DC006B6DE7 /* PullRequestReviewCommentsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PullRequestReviewCommentsViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -870,7 +866,11 @@
|
||||
29EE1C1C1F3A33890046A54D /* RepositoryLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryLabel.swift; sourceTree = "<group>"; };
|
||||
29EE44451F19D5C100B05ED3 /* GithubClient+Issues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GithubClient+Issues.swift"; sourceTree = "<group>"; };
|
||||
29EE44491F19D85800B05ED3 /* ShowErrorStatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowErrorStatusBar.swift; sourceTree = "<group>"; };
|
||||
29EEB22C1F9BF65B00AB237B /* NotificationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = "<group>"; };
|
||||
29F3A18320CADA3A00645CB7 /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = "<group>"; };
|
||||
29F3A18520CBF99E00645CB7 /* NotificationModelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModelController.swift; sourceTree = "<group>"; };
|
||||
29F3A18720CBFF8700645CB7 /* CreateNotificationTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNotificationTitle.swift; sourceTree = "<group>"; };
|
||||
29F3A18920CC017700645CB7 /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = "<group>"; };
|
||||
29F3A18B20CD790F00645CB7 /* UIViewController+CommonActionItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+CommonActionItems.swift"; sourceTree = "<group>"; };
|
||||
29F7F05B1F2A751B00F6075D /* IssueResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueResult.swift; sourceTree = "<group>"; };
|
||||
29F7F05E1F2A839100F6075D /* IssueNeckLoadSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueNeckLoadSectionController.swift; sourceTree = "<group>"; };
|
||||
29F7F0601F2A83AA00F6075D /* IssueNeckLoadCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueNeckLoadCell.swift; sourceTree = "<group>"; };
|
||||
@@ -1558,6 +1558,7 @@
|
||||
98003D8C1FCAD7FC00755C17 /* LabelDetails.swift */,
|
||||
29EE1C1C1F3A33890046A54D /* RepositoryLabel.swift */,
|
||||
29C8F9AE208C02BE0075931C /* String+ListSwift.swift */,
|
||||
29F3A18920CC017700645CB7 /* Repository.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -1628,6 +1629,7 @@
|
||||
D8D876F71FB6083200A57E2B /* UIPopoverPresentationController+SourceView.swift */,
|
||||
297AE8691EC0D5C200B44A1F /* UIViewController+Alerts.swift */,
|
||||
29AF1E851F8AADD00008A0EF /* UIViewController+CancelAction.swift */,
|
||||
29F3A18B20CD790F00645CB7 /* UIViewController+CommonActionItems.swift */,
|
||||
29CEA5CE1F84DCB3009827DB /* UIViewController+EmptyBackBar.swift */,
|
||||
29136BE2200AAA5A007317BE /* UIViewController+FilePathTitle.swift */,
|
||||
292CD3D71F0DC52900D3D57B /* UIViewController+IssueCommentHtmlCellNavigationDelegate.swift */,
|
||||
@@ -1808,22 +1810,19 @@
|
||||
29C9FDD91EC6613F00EE3A52 /* Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
29F3A18720CBFF8700645CB7 /* CreateNotificationTitle.swift */,
|
||||
290EF5751F06BA06006A2160 /* NoNewNotificationsCell.swift */,
|
||||
290EF5781F06BAF4006A2160 /* NoNewNotificationsSectionController.swift */,
|
||||
290EF5691F06A7E1006A2160 /* Notification+NotificationViewModel.swift */,
|
||||
29C9FDDA1EC6627200EE3A52 /* NotificationCell.swift */,
|
||||
297DD5E21F06922A006E7E63 /* NotificationClient.swift */,
|
||||
29B5D08A20D578DB003DFBE2 /* InboxType.swift */,
|
||||
29BBD82A20CACB2F004D62FE /* NotificationCell.swift */,
|
||||
29CF01D21FDDA1EE0084B66F /* NotificationEmptyMessageClient.swift */,
|
||||
295C31C61F09E62600521CED /* NotificationNextPageCell.swift */,
|
||||
295C31C81F09E72D00521CED /* NotificationNextPageSectionController.swift */,
|
||||
29A195101EC7AC9500C3E289 /* NotificationRepoCell.swift */,
|
||||
2980E0911F073E8B000E02C6 /* NotificationSectionController.swift */,
|
||||
29EEB22C1F9BF65B00AB237B /* NotificationsViewController.swift */,
|
||||
29F3A18520CBF99E00645CB7 /* NotificationModelController.swift */,
|
||||
29BBD82C20CACDFF004D62FE /* NotificationSectionController.swift */,
|
||||
29F3A18320CADA3A00645CB7 /* NotificationsViewController.swift */,
|
||||
29A1950B1EC7901400C3E289 /* NotificationType.swift */,
|
||||
29A195091EC78B4800C3E289 /* NotificationType+Icon.swift */,
|
||||
29C9FDDC1EC6628200EE3A52 /* NotificationViewModel.swift */,
|
||||
29A5AF3E1F9266370065D529 /* NotificationViewModel+Filterable.swift */,
|
||||
297406971F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift */,
|
||||
29BBD82820CAC7D5004D62FE /* NotificationViewModel.swift */,
|
||||
);
|
||||
path = Notifications;
|
||||
sourceTree = "<group>";
|
||||
@@ -2638,9 +2637,9 @@
|
||||
29A4768E1ED07A23005D0953 /* DateDetailsFormatter.swift in Sources */,
|
||||
29AF1E8C1F8ABC5A0008A0EF /* RepositoryCodeDirectoryViewController.swift in Sources */,
|
||||
291929611F3FD2960012067B /* DiffString.swift in Sources */,
|
||||
29EEB22D1F9BF65B00AB237B /* NotificationsViewController.swift in Sources */,
|
||||
290744B51F250A6800FD9E48 /* EmojiAutocomplete.swift in Sources */,
|
||||
29C167691ECA016500439D62 /* EmptyView.swift in Sources */,
|
||||
29F3A18420CADA3A00645CB7 /* NotificationsViewController.swift in Sources */,
|
||||
2957777B200129EB00DDD785 /* Int+Abbreviated.swift in Sources */,
|
||||
29C1677A1ECA14F700439D62 /* Feed.swift in Sources */,
|
||||
291929551F3FAADF0012067B /* FeedRefresh.swift in Sources */,
|
||||
@@ -2649,6 +2648,7 @@
|
||||
49FE18FD204B5D32001681E8 /* Sequence+Contains.swift in Sources */,
|
||||
291929421F3EA8CD0012067B /* File.swift in Sources */,
|
||||
299E86491EFD9DBB00E5FE70 /* FlexController.m in Sources */,
|
||||
29F3A18620CBF99E00645CB7 /* NotificationModelController.swift in Sources */,
|
||||
29EB1EEF1F425E5100A200B4 /* ForegroundHandler.swift in Sources */,
|
||||
29C167741ECA0DBB00439D62 /* GithubAPIDateFormatter.swift in Sources */,
|
||||
294434E11FB1F2DA00050C06 /* BookmarkNavigationController.swift in Sources */,
|
||||
@@ -2751,11 +2751,13 @@
|
||||
2908C5891F6F3EB00071C39D /* IssueLocalReaction.swift in Sources */,
|
||||
2931892B1F5397E400EF0911 /* IssueMilestoneCell.swift in Sources */,
|
||||
292EB08F1F1FF5EC0046865D /* IssueMilestoneEventCell.swift in Sources */,
|
||||
29F3A18820CBFF8700645CB7 /* CreateNotificationTitle.swift in Sources */,
|
||||
292EB08D1F1FF58D0046865D /* IssueMilestoneEventModel.swift in Sources */,
|
||||
292EB0911F1FF72D0046865D /* IssueMilestoneEventSectionController.swift in Sources */,
|
||||
DCA5ED141FAEE8030072F074 /* Bookmark.swift in Sources */,
|
||||
299F4A89204CEDDC004BA4F0 /* Client+AccessToken.swift in Sources */,
|
||||
2931892F1F539C0E00EF0911 /* IssueMilestoneSectionController.swift in Sources */,
|
||||
29BBD82920CAC7D5004D62FE /* NotificationViewModel.swift in Sources */,
|
||||
299F63E2205DE1470015D901 /* UIView+DateDetails.swift in Sources */,
|
||||
9870B9031FC73EE70009719C /* Secrets.swift in Sources */,
|
||||
29F7F0611F2A83AA00F6075D /* IssueNeckLoadCell.swift in Sources */,
|
||||
@@ -2790,6 +2792,7 @@
|
||||
29136BE1200A7D3D007317BE /* NavigationTitleDropdownView.swift in Sources */,
|
||||
299C06D81F0DD17B00C2828E /* IssueReviewEmptyTailCell.swift in Sources */,
|
||||
292CD3CE1F0DB8E700D3D57B /* IssueReviewModel.swift in Sources */,
|
||||
29F3A18A20CC017700645CB7 /* Repository.swift in Sources */,
|
||||
292CD3CC1F0DB89600D3D57B /* IssueReviewSectionController.swift in Sources */,
|
||||
295C31CF1F0AA67600521CED /* IssueStatus+ButtonState.swift in Sources */,
|
||||
290D2A421F04D3470082E6CC /* IssueStatus.swift in Sources */,
|
||||
@@ -2803,9 +2806,11 @@
|
||||
65A315292044369D0074E3B6 /* TabBarController.swift in Sources */,
|
||||
295840651EE89F28007723C6 /* IssueStatusEventModel.swift in Sources */,
|
||||
295840691EE8A328007723C6 /* IssueStatusEventSectionController.swift in Sources */,
|
||||
29F3A18C20CD790F00645CB7 /* UIViewController+CommonActionItems.swift in Sources */,
|
||||
294563E61EE4EE6F00DBCD35 /* IssueStatusModel.swift in Sources */,
|
||||
294563E81EE4EED200DBCD35 /* IssueStatusSectionController.swift in Sources */,
|
||||
292FCB0F1EDFCC510026635E /* IssuesViewController.swift in Sources */,
|
||||
29BBD82D20CACDFF004D62FE /* NotificationSectionController.swift in Sources */,
|
||||
292FF8B01F2FDC33009E63F7 /* IssueTextActionsView.swift in Sources */,
|
||||
292FCB181EDFCC510026635E /* IssueTitleCell.swift in Sources */,
|
||||
292FCB191EDFCC510026635E /* IssueTitleSectionController.swift in Sources */,
|
||||
@@ -2818,6 +2823,8 @@
|
||||
98835BD21F1A158D005BA24F /* LabelCell.swift in Sources */,
|
||||
29C8F9B5208C081D0075931C /* LabelSectionController.swift in Sources */,
|
||||
29136BE3200AAA5A007317BE /* UIViewController+FilePathTitle.swift in Sources */,
|
||||
29B5D08B20D578DB003DFBE2 /* InboxType.swift in Sources */,
|
||||
29EE1C191F3A2EDB0046A54D /* LabelTableCell.swift in Sources */,
|
||||
295B51481FC26F7F00C3993B /* UIImageView+Avatar.swift in Sources */,
|
||||
297B062A1FB9239E0026FA23 /* IGListCollectionViewLayout+GitHawk.swift in Sources */,
|
||||
DCA5ED1D1FAEF9EE0072F074 /* BookmarkCell.swift in Sources */,
|
||||
@@ -2835,14 +2842,8 @@
|
||||
290EF5791F06BAF4006A2160 /* NoNewNotificationsSectionController.swift in Sources */,
|
||||
290EF56A1F06A821006A2160 /* Notification+NotificationViewModel.swift in Sources */,
|
||||
29792B1B1FFB21AD007A0C57 /* AutocompleteController.swift in Sources */,
|
||||
29C9FDDB1EC6627200EE3A52 /* NotificationCell.swift in Sources */,
|
||||
297DD5E31F06922A006E7E63 /* NotificationClient.swift in Sources */,
|
||||
295C31C71F09E62600521CED /* NotificationNextPageCell.swift in Sources */,
|
||||
295C31C91F09E72D00521CED /* NotificationNextPageSectionController.swift in Sources */,
|
||||
29A195111EC7AC9500C3E289 /* NotificationRepoCell.swift in Sources */,
|
||||
29D548CB1FA27FE900F8E46F /* UINavigationItem+TitleSubtitle.swift in Sources */,
|
||||
299A04A11FAE86B0003C2450 /* IssueReviewViewCommentsCell.swift in Sources */,
|
||||
2980E0921F073E8B000E02C6 /* NotificationSectionController.swift in Sources */,
|
||||
7BF2239D1F91056C006CC9A2 /* File+Filename.swift in Sources */,
|
||||
2924C18120D5B29800FCFCFF /* MilestonesViewController.swift in Sources */,
|
||||
29A1950A1EC78B4800C3E289 /* NotificationType+Icon.swift in Sources */,
|
||||
@@ -2862,7 +2863,6 @@
|
||||
29FF85A51EE1EA7A007B8762 /* ReactionContent+ReactionType.swift in Sources */,
|
||||
292FCB1D1EDFCD3D0026635E /* ReactionViewModel.swift in Sources */,
|
||||
29DAA7AF20202BEA0029277A /* PullRequestReviewReplyModel.swift in Sources */,
|
||||
29A5AF3F1F9266370065D529 /* NotificationViewModel+Filterable.swift in Sources */,
|
||||
29B94E691FCB36A000715D7E /* File+ListDiffable.swift in Sources */,
|
||||
986B873E1F2E1CE400AAB55C /* RepositoryClient.swift in Sources */,
|
||||
295A77BE1F75C1CC007BC403 /* RepositoryDetails.swift in Sources */,
|
||||
@@ -2918,7 +2918,6 @@
|
||||
986B871D1F2B8FCD00AAB55C /* SearchViewController.swift in Sources */,
|
||||
298BA0971EC947F100B01946 /* SegmentedControlCell.swift in Sources */,
|
||||
297B062C1FB92DCA0026FA23 /* UICollectionViewLayout+Orientation.swift in Sources */,
|
||||
297406981F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift in Sources */,
|
||||
298BA09A1EC947FC00B01946 /* SegmentedControlModel.swift in Sources */,
|
||||
299F63EA20603FB80015D901 /* MarkdownStyledTextView.swift in Sources */,
|
||||
DCA5ED0E1FAED91F0072F074 /* BookmarkViewController.swift in Sources */,
|
||||
@@ -2938,6 +2937,7 @@
|
||||
295840711EE9F4D3007723C6 /* ShowMoreDetailsLabel+Date.swift in Sources */,
|
||||
29CF01D31FDDA1EE0084B66F /* NotificationEmptyMessageClient.swift in Sources */,
|
||||
29CCB28B1FDDFFA200E23FA0 /* String+GithubDate.swift in Sources */,
|
||||
29BBD82B20CACB2F004D62FE /* NotificationCell.swift in Sources */,
|
||||
29C295111EC7B83200D46CD2 /* ShowMoreDetailsLabel.swift in Sources */,
|
||||
DC6339371F9F567000402A8D /* DeleteSwipeAction.swift in Sources */,
|
||||
DCA5ED121FAEE3AE0072F074 /* Store.swift in Sources */,
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
29C1A9EE204B22A200CB6995 /* GitHubAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C1A9ED204B22A200CB6995 /* GitHubAPITests.swift */; };
|
||||
29C1A9F0204B22A200CB6995 /* GitHubAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C1A9C8204B04E700CB6995 /* GitHubAPI.framework */; };
|
||||
29C1A9F7204B497A00CB6995 /* V3Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C1A9F6204B497A00CB6995 /* V3Request.swift */; };
|
||||
29F3A18E20CD7DFC00645CB7 /* V3SubscribeThreadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F3A18D20CD7DFC00645CB7 /* V3SubscribeThreadRequest.swift */; };
|
||||
29F8BABC204B545F00E5CA32 /* V3User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F8BABB204B545F00E5CA32 /* V3User.swift */; };
|
||||
29F8BABE204B56BC00E5CA32 /* V3Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F8BABD204B56BC00E5CA32 /* V3Repository.swift */; };
|
||||
29F8BAC0204B577800E5CA32 /* V3NotificationSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F8BABF204B577800E5CA32 /* V3NotificationSubject.swift */; };
|
||||
@@ -130,6 +131,7 @@
|
||||
29C1A9ED204B22A200CB6995 /* GitHubAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubAPITests.swift; sourceTree = "<group>"; };
|
||||
29C1A9EF204B22A200CB6995 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
29C1A9F6204B497A00CB6995 /* V3Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V3Request.swift; sourceTree = "<group>"; };
|
||||
29F3A18D20CD7DFC00645CB7 /* V3SubscribeThreadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V3SubscribeThreadRequest.swift; sourceTree = "<group>"; };
|
||||
29F8BABB204B545F00E5CA32 /* V3User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V3User.swift; sourceTree = "<group>"; };
|
||||
29F8BABD204B56BC00E5CA32 /* V3Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V3Repository.swift; sourceTree = "<group>"; };
|
||||
29F8BABF204B577800E5CA32 /* V3NotificationSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V3NotificationSubject.swift; sourceTree = "<group>"; };
|
||||
@@ -195,6 +197,7 @@
|
||||
290890A7204BC4B400D088BC /* GitHubAPIStatusRequest.swift */,
|
||||
29580512204B62BE009CFD51 /* HTTPRequest.swift */,
|
||||
29C1A9CC204B04E700CB6995 /* Info.plist */,
|
||||
290890C1204BDE2A00D088BC /* JSONResponse.swift */,
|
||||
2958053C204BAD77009CFD51 /* ManualGraphQLRequest.swift */,
|
||||
29580510204B629B009CFD51 /* Processing.swift */,
|
||||
29C1A9D3204B05AE00CB6995 /* Request.swift */,
|
||||
@@ -207,6 +210,7 @@
|
||||
290890A9204BC58A00D088BC /* V3CreateIssueRequest.swift */,
|
||||
29580522204B713C009CFD51 /* V3DataResponse.swift */,
|
||||
2958052C204B7AB2009CFD51 /* V3DeleteCommentRequest.swift */,
|
||||
290890C3204BDF9E00D088BC /* V3EditCommentRequest.swift */,
|
||||
290890B3204BCC3100D088BC /* V3File.swift */,
|
||||
2958052E204B7B5D009CFD51 /* V3LockIssueRequest.swift */,
|
||||
29580536204BAAE6009CFD51 /* V3MarkNotificationsRequest.swift */,
|
||||
@@ -220,7 +224,6 @@
|
||||
29F8BABF204B577800E5CA32 /* V3NotificationSubject.swift */,
|
||||
290890BF204BDDD900D088BC /* V3PullRequestCommentsRequest.swift */,
|
||||
290890B1204BCBD200D088BC /* V3PullRequestFilesRequest.swift */,
|
||||
290890C1204BDE2A00D088BC /* JSONResponse.swift */,
|
||||
29580526204B778B009CFD51 /* V3Release.swift */,
|
||||
29580528204B77B1009CFD51 /* V3ReleaseRequest.swift */,
|
||||
29F8BABD204B56BC00E5CA32 /* V3Repository.swift */,
|
||||
@@ -228,12 +231,12 @@
|
||||
290890A5204BC0ED00D088BC /* V3RepositoryReadmeRequest.swift */,
|
||||
29C1A9F6204B497A00CB6995 /* V3Request.swift */,
|
||||
290890BD204BDA8F00D088BC /* V3SendPullRequestCommentRequest.swift */,
|
||||
290890C3204BDF9E00D088BC /* V3EditCommentRequest.swift */,
|
||||
290890AF204BCB2100D088BC /* V3SetIssueStatusRequest.swift */,
|
||||
290890B7204BCD2600D088BC /* V3SetMilestonesRequest.swift */,
|
||||
2908909D204BB57700D088BC /* V3SetRepositoryLabelsRequest.swift */,
|
||||
2958053A204BAB1A009CFD51 /* V3StatusCode205.swift */,
|
||||
2958052A204B785F009CFD51 /* V3StatusCodeResponse.swift */,
|
||||
29F3A18D20CD7DFC00645CB7 /* V3SubscribeThreadRequest.swift */,
|
||||
29F8BABB204B545F00E5CA32 /* V3User.swift */,
|
||||
290890AB204BC7D400D088BC /* V3VerifyPersonalAccessTokenRequest.swift */,
|
||||
29580530204B7BCD009CFD51 /* V3ViewerIsCollaboratorRequest.swift */,
|
||||
@@ -509,6 +512,7 @@
|
||||
2958052F204B7B5D009CFD51 /* V3LockIssueRequest.swift in Sources */,
|
||||
29580515204B62DC009CFD51 /* Response.swift in Sources */,
|
||||
29F8BABC204B545F00E5CA32 /* V3User.swift in Sources */,
|
||||
29F3A18E20CD7DFC00645CB7 /* V3SubscribeThreadRequest.swift in Sources */,
|
||||
29580511204B629B009CFD51 /* Processing.swift in Sources */,
|
||||
29C1A9E4204B182200CB6995 /* Alamofire+GitHubAPI.swift in Sources */,
|
||||
290890B2204BCBD200D088BC /* V3PullRequestFilesRequest.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// V3SubscribeThreadRequest.swift
|
||||
// GitHubAPI
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/10/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct V3SubscribeThreadRequest: V3Request {
|
||||
public typealias ResponseType = V3StatusCodeResponse<V3StatusCode200>
|
||||
public var pathComponents: [String] {
|
||||
return ["notifications", "threads", id, "subscription"]
|
||||
}
|
||||
public var method: HTTPMethod { return .put }
|
||||
public var parameters: [String : Any]? {
|
||||
return [
|
||||
"ignored": ignore ? "true" : "false"
|
||||
]
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let ignore: Bool
|
||||
|
||||
public init(id: String, ignore: Bool) {
|
||||
self.id = id
|
||||
self.ignore = ignore
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ CHECKOUT OPTIONS:
|
||||
:commit: 4f7e90477619b8dc4b9e641efd10952c22150c5c
|
||||
:git: https://github.com/GitHawkApp/Highlightr.git
|
||||
IGListKit:
|
||||
:commit: 49413f2c37ffb8833d0810c7e641c85e91ffe037
|
||||
:commit: 5c1c92b600f0d8094f22fff48e26ded323568194
|
||||
:git: https://github.com/Instagram/IGListKit.git
|
||||
MessageViewController:
|
||||
:commit: b39c89ea688b79cc8daeb29c214a925c9a1c4396
|
||||
|
||||
@@ -39,7 +39,7 @@ public extension ListAdapter {
|
||||
@return The `ListSectionController`, if the value is a member of the `IGListAdapter`.
|
||||
*/
|
||||
public func sectionController(for value: ListSwiftDiffable) -> ListSectionController? {
|
||||
return sectionController(for: value.boxed)
|
||||
return sectionController(for: value.sectionBox)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ public extension ListAdapter {
|
||||
@return The section, if the value is a member of the adapter.
|
||||
*/
|
||||
public func section(for value: ListSwiftDiffable) -> Int? {
|
||||
let section = self.section(for: value.boxed)
|
||||
let section = self.section(for: value.sectionBox)
|
||||
return section == NSNotFound ? nil : section
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public extension ListAdapter {
|
||||
@return All currently visible cells in the `UICollectionView`. Array is empty if no cells are visible.
|
||||
*/
|
||||
public func visibleCells(for value: ListSwiftDiffable) -> [UICollectionViewCell] {
|
||||
return visibleCells(for: value.boxed)
|
||||
return visibleCells(for: value.sectionBox)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ public extension ListAdapter {
|
||||
animated: Bool = true
|
||||
) {
|
||||
scroll(
|
||||
to: value.boxed,
|
||||
to: value.sectionBox,
|
||||
supplementaryKinds: supplementaryKinds,
|
||||
scrollDirection: scrollDirection,
|
||||
scrollPosition: scrollPosition,
|
||||
|
||||
21
Pods/IGListKit/Source/Swift/ListDiffableBox.swift
generated
21
Pods/IGListKit/Source/Swift/ListDiffableBox.swift
generated
@@ -22,13 +22,22 @@ internal final class ListDiffableBox: ListDiffable {
|
||||
*/
|
||||
let value: ListSwiftDiffable
|
||||
|
||||
/**
|
||||
*/
|
||||
let boxesSectionValue: Bool
|
||||
|
||||
private let _diffIdentifier: NSObjectProtocol
|
||||
|
||||
/**
|
||||
Initialize a new `ListDiffableBox` object.
|
||||
|
||||
@param value The value to be boxed.
|
||||
*/
|
||||
init(value: ListSwiftDiffable) {
|
||||
init(value: ListSwiftDiffable, boxesSectionValue: Bool) {
|
||||
self.value = value
|
||||
self.boxesSectionValue = boxesSectionValue
|
||||
// namespace the identifier with the value type to help prevent collisions
|
||||
self._diffIdentifier = "\(type(of: value))\(value.identifier)" as NSObjectProtocol
|
||||
}
|
||||
|
||||
// MARK: ListDiffable
|
||||
@@ -37,16 +46,18 @@ internal final class ListDiffableBox: ListDiffable {
|
||||
:nodoc:
|
||||
*/
|
||||
func diffIdentifier() -> NSObjectProtocol {
|
||||
// namespace the identifier with the value type to help prevent collisions
|
||||
return "\(value.self)\(value.identifier)" as NSObjectProtocol
|
||||
return _diffIdentifier
|
||||
}
|
||||
|
||||
/**
|
||||
:nodoc:
|
||||
*/
|
||||
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
|
||||
// always true since objects are updated with ListSwiftSectionController which handles updates at the cell level
|
||||
return true
|
||||
// always true when using section models since ListSwiftSectionController handles updates at the cell level
|
||||
guard boxesSectionValue == false,
|
||||
let box = object as? ListDiffableBox
|
||||
else { return true }
|
||||
return value.isEqual(to: box.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class ListSwiftAdapter: NSObject, ListAdapterDataSource {
|
||||
guard let dataSource = self.dataSource else { return [] }
|
||||
|
||||
return dataSource.values(adapter: self).map {
|
||||
let box = ListDiffableBox(value: $0.value)
|
||||
let box = $0.value.sectionBox
|
||||
// side effect: store the function for use in listAdapter(:, sectionControllerFor object:)
|
||||
map[box.functionLookupHash] = $0.constructor
|
||||
return box
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
*/
|
||||
|
||||
// Not very clean, but it's not possible to write extensions on a composed Protocols.
|
||||
internal extension ListSwiftIdentifiable where Self: ListSwiftEquatable {
|
||||
internal extension ListSwiftDiffable {
|
||||
|
||||
var boxed: ListDiffable {
|
||||
return ListDiffableBox(value: self)
|
||||
var sectionBox: ListDiffable {
|
||||
return ListDiffableBox(value: self, boxesSectionValue: true)
|
||||
}
|
||||
|
||||
var viewModelBox: ListDiffable {
|
||||
return ListDiffableBox(value: self, boxesSectionValue: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
public protocol ListSwiftIdentifiable {
|
||||
public protocol ListSwiftDiffable {
|
||||
var identifier: String { get }
|
||||
}
|
||||
|
||||
public protocol ListSwiftEquatable {
|
||||
func isEqual(to value: ListSwiftDiffable) -> Bool
|
||||
}
|
||||
|
||||
public typealias ListSwiftDiffable = ListSwiftIdentifiable & ListSwiftEquatable
|
||||
|
||||
@@ -11,7 +11,7 @@ import UIKit
|
||||
|
||||
public enum ListCellType<T: UICollectionViewCell> {
|
||||
case `class`(T.Type)
|
||||
case storyboard(T.Type, String, Bundle?)
|
||||
case storyboard(T.Type, String)
|
||||
case nib(T.Type, String, Bundle?)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public struct ListBinder {
|
||||
|
||||
enum CellType {
|
||||
case cellClass(UICollectionViewCell.Type)
|
||||
case storyboard(String, Bundle?)
|
||||
case storyboard(String)
|
||||
case nib(String, Bundle?)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ open class ListSwiftSectionController<T: ListSwiftDiffable>: ListSectionControll
|
||||
let nestedCellType: ListBinder.CellType
|
||||
switch cellType {
|
||||
case let .class(type): nestedCellType = .cellClass(type)
|
||||
case let .storyboard(_, type, bundle): nestedCellType = .storyboard(type, bundle)
|
||||
case let .storyboard(_, type): nestedCellType = .storyboard(type)
|
||||
case let .nib(_, type, bundle): nestedCellType = .nib(type, bundle)
|
||||
}
|
||||
|
||||
@@ -146,15 +146,17 @@ open class ListSwiftSectionController<T: ListSwiftDiffable>: ListSectionControll
|
||||
let value = strongSelf.value
|
||||
else { return }
|
||||
|
||||
let fromBoxed = strongSelf.binders.map { $0.value.boxed }
|
||||
let fromBoxed = strongSelf.binders.map { $0.value.viewModelBox }
|
||||
let to = strongSelf.createBinders(from: value)
|
||||
let toBoxed = to.map { $0.value.boxed }
|
||||
let toBoxed = to.map { $0.value.viewModelBox }
|
||||
let result = ListDiff(
|
||||
oldArray: fromBoxed,
|
||||
newArray: toBoxed,
|
||||
option: .equality
|
||||
)
|
||||
|
||||
strongSelf.binders = to
|
||||
|
||||
for (i, _) in result.updates.enumerated() {
|
||||
let identifier = fromBoxed[i].diffIdentifier()
|
||||
let toIndex = result.newIndex(forIdentifier: identifier)
|
||||
@@ -210,7 +212,7 @@ open class ListSwiftSectionController<T: ListSwiftDiffable>: ListSectionControll
|
||||
rawCell = collectionContext.dequeueReusableCell(of: type, for: self, at: index)
|
||||
case let .nib(type, bundle):
|
||||
rawCell = collectionContext.dequeueReusableCell(withNibName: type, bundle: bundle, for: self, at: index)
|
||||
case let .storyboard(type, bundle):
|
||||
case let .storyboard(type):
|
||||
rawCell = collectionContext.dequeueReusableCellFromStoryboard(withIdentifier: type, for: self, at: index)
|
||||
}
|
||||
|
||||
|
||||
2
Pods/Manifest.lock
generated
2
Pods/Manifest.lock
generated
@@ -185,7 +185,7 @@ CHECKOUT OPTIONS:
|
||||
:commit: 4f7e90477619b8dc4b9e641efd10952c22150c5c
|
||||
:git: https://github.com/GitHawkApp/Highlightr.git
|
||||
IGListKit:
|
||||
:commit: 49413f2c37ffb8833d0810c7e641c85e91ffe037
|
||||
:commit: 5c1c92b600f0d8094f22fff48e26ded323568194
|
||||
:git: https://github.com/Instagram/IGListKit.git
|
||||
MessageViewController:
|
||||
:commit: b39c89ea688b79cc8daeb29c214a925c9a1c4396
|
||||
|
||||
2432
Pods/Pods.xcodeproj/project.pbxproj
generated
2432
Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
22
Resources/Assets.xcassets/bullets-small.imageset/Contents.json
vendored
Normal file
22
Resources/Assets.xcassets/bullets-small.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "bullets-small.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "bullets-small@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Resources/Assets.xcassets/bullets-small.imageset/bullets-small.png
vendored
Normal file
BIN
Resources/Assets.xcassets/bullets-small.imageset/bullets-small.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 491 B |
BIN
Resources/Assets.xcassets/bullets-small.imageset/bullets-small@3x.png
vendored
Normal file
BIN
Resources/Assets.xcassets/bullets-small.imageset/bullets-small@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 681 B |
22
Resources/Assets.xcassets/mute-small.imageset/Contents.json
vendored
Normal file
22
Resources/Assets.xcassets/mute-small.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "mute-small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "mute-small@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Resources/Assets.xcassets/mute-small.imageset/mute-small@2x.png
vendored
Normal file
BIN
Resources/Assets.xcassets/mute-small.imageset/mute-small@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 343 B |
BIN
Resources/Assets.xcassets/mute-small.imageset/mute-small@3x.png
vendored
Normal file
BIN
Resources/Assets.xcassets/mute-small.imageset/mute-small@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 571 B |
22
Resources/Assets.xcassets/unmute-small.imageset/Contents.json
vendored
Normal file
22
Resources/Assets.xcassets/unmute-small.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "unmute-small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "unmute-small@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Resources/Assets.xcassets/unmute-small.imageset/unmute-small@2x.png
vendored
Normal file
BIN
Resources/Assets.xcassets/unmute-small.imageset/unmute-small@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 651 B |
BIN
Resources/Assets.xcassets/unmute-small.imageset/unmute-small@3x.png
vendored
Normal file
BIN
Resources/Assets.xcassets/unmute-small.imageset/unmute-small@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 991 B |
@@ -232,7 +232,7 @@
|
||||
<key>File</key>
|
||||
<string>com.mono0926.LicensePlist/StyledTextKit</string>
|
||||
<key>Title</key>
|
||||
<string>StyledTextKit (0.1.0)</string>
|
||||
<string>StyledTextKit (0.1.1)</string>
|
||||
<key>Type</key>
|
||||
<string>PSChildPaneSpecifier</string>
|
||||
</dict>
|
||||
|
||||
Reference in New Issue
Block a user