Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

New inbox header to select inbox type (all, mentioned, repo, etc) #2524

Merged
merged 10 commits into from
Dec 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// GithubClient+InboxFilterController.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import Foundation
import GitHubAPI

extension GithubClient: InboxFilterControllerClient {

func fetchSubscriptions(completion: @escaping (Result<[RepositoryDetails]>) -> Void) {
guard let username = userSession?.username else {
completion(.error(nil))
return
}
client.send(V3SubscriptionsRequest(username: username)) { result in
switch result {
case .failure(let error):
completion(.error(error))
case .success(let data):
let repos = data.data.map {
RepositoryDetails(owner: $0.owner.login, name: $0.name)
}
completion(.success(repos))
}
}
}

}
102 changes: 102 additions & 0 deletions Classes/Notifications/Filter/InboxFilterController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// InboxFilterController.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import Foundation
import ContextMenu

protocol InboxFilterControllerClient {
func fetchSubscriptions(completion: @escaping (Result<[RepositoryDetails]>) -> Void)
}

protocol InboxFilterControllerListener: class {
func didUpdateSelectedFilter(for controller: InboxFilterController)
}

final class InboxFilterController {

let client: InboxFilterControllerClient
let announcer = Announcer<InboxFilterControllerListener>()

private static let filters = [
InboxFilterModel(type: .unread),
InboxFilterModel(type: .all),
InboxFilterModel(type: .mentioned),
InboxFilterModel(type: .assigned),
InboxFilterModel(type: .created)
]

private(set) var selected: InboxFilterModel = InboxFilterController.filters[0] {
didSet {
announcer.enumerate { $0.didUpdateSelectedFilter(for: self) }
}
}
private var fetchedFilters = [InboxFilterModel]()

init(client: InboxFilterControllerClient) {
self.client = client
}

func update(selection: InboxFilterModel) {
selected = selection
}

private func selected(model: InboxFilterModel) {
selected = model
}

private func showRepos(from viewController: UIViewController?) {
guard let viewController = viewController else { return }
ContextMenu.shared.show(
sourceViewController: viewController,
viewController: InboxFilterReposViewController(inboxFilterController: self),
options: ContextMenu.Options(
containerStyle: ContextMenu.ContainerStyle(
backgroundColor: Styles.Colors.menuBackgroundColor.color
)
)
)
}

func showMenu(from viewController: UIViewController) {
var items: [ContrastContextMenuItem] = InboxFilterController.filters.map { model in
ContrastContextMenuItem(
title: model.type.title,
iconName: model.type.iconName,
iconColor: Styles.Colors.Blue.medium.color,
separator: false,
action: { [weak self] menu in
menu.dismiss(animated: trueUnlessReduceMotionEnabled)
self?.selected(model: model)
})
}
items.append(ContrastContextMenuItem(
title: NSLocalizedString("Repos", comment: ""),
iconName: "repo",
iconColor: Styles.Colors.Blue.medium.color,
separator: true,
action: { [weak self, weak viewController] menu in
menu.dismiss(animated: trueUnlessReduceMotionEnabled)
self?.showRepos(from: viewController)
}))

ContextMenu.shared.show(
sourceViewController: viewController,
viewController: ContrastContextMenu(items: items),
options: ContextMenu.Options(
containerStyle: ContextMenu.ContainerStyle(
backgroundColor: Styles.Colors.menuBackgroundColor.color
),
menuStyle: .minimal,
hapticsStyle: .medium,
position: .centerX
),
sourceView: viewController.navigationItem.titleView
)
}

}
72 changes: 72 additions & 0 deletions Classes/Notifications/Filter/InboxFilterModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// InboxFilterModel.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import Foundation
import IGListKit

struct InboxFilterModel: ListSwiftDiffable {

enum FilterType: Equatable {
case unread
case all
case assigned
case created
case mentioned
case repo(owner: String, name: String)

var title: String {
switch self {
case .unread: return Constants.Strings.inbox
case .all: return NSLocalizedString("All", comment: "")
case .assigned: return NSLocalizedString("Assigned", comment: "")
case .created: return NSLocalizedString("Created", comment: "")
case .mentioned: return NSLocalizedString("Mentioned", comment: "")
case let .repo(_, name): return name
}
}

var subtitle: String? {
switch self {
case .unread, .all, .assigned, .created, .mentioned: return nil
case let .repo(owner, _): return owner
}
}

var iconName: String? {
switch self {
case .unread: return "inbox"
case .all: return "bell"
case .assigned: return "person"
case .created: return "plus"
case .mentioned: return "mention"
case .repo: return nil
}
}

// MARK: Equatable

static func == (lhs: FilterType, rhs: FilterType) -> Bool {
return lhs.title == rhs.title
&& lhs.subtitle == rhs.subtitle
}

}

let type: FilterType

// MARK: ListSwiftDiffable

var identifier: String {
return "\(type.title).\(type.subtitle ?? "")"
}

func isEqual(to value: ListSwiftDiffable) -> Bool {
return value is InboxFilterModel
}

}
50 changes: 50 additions & 0 deletions Classes/Notifications/Filter/InboxFilterRepoCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// InboxFilterRepoCell.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import UIKit
import SnapKit

final class InboxFilterRepoCell: SelectableCell {

private let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)

accessibilityIdentifier = "inbox-filter-repo-cell"

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.7
contentView.addSubview(label)

label.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(Styles.Sizes.gutter)
make.right.lessThanOrEqualTo(-Styles.Sizes.gutter)
}

addBorder(.bottom, left: Styles.Sizes.gutter)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(owner: String, name: String) {
let text = NSMutableAttributedString(string: "\(owner)/", attributes: [
.foregroundColor: Styles.Colors.Gray.light.color,
.font: Styles.Text.body.preferredFont
])
text.append(NSAttributedString(string: "\(name)", attributes: [
.foregroundColor: UIColor.white,
.font: Styles.Text.bodyBold.preferredFont
]))
label.attributedText = text
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// InboxFilterRepoSectionController.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import IGListKit

final class InboxFilterRepoSectionController: ListSwiftSectionController<RepositoryDetails> {

private let inboxFilterController: InboxFilterController

init(inboxFilterController: InboxFilterController) {
self.inboxFilterController = inboxFilterController
super.init()
}

override func createBinders(from value: RepositoryDetails) -> [ListBinder] {
return [
binder(
value,
cellType: ListCellType.class(InboxFilterRepoCell.self),
size: {
return $0.collection.cellSize(with: Styles.Sizes.tableCellHeight)
}, configure: {
$0.configure(
owner: $1.value.owner,
name: $1.value.name
)
}, didSelect: { [weak self] context in
self?.didSelect(value: context.value)
})
]
}

private func didSelect(value: RepositoryDetails) {
viewController?.dismiss(animated: trueUnlessReduceMotionEnabled)
inboxFilterController.update(selection: InboxFilterModel(
type: .repo(owner: value.owner, name: value.name))
)
}

}
62 changes: 62 additions & 0 deletions Classes/Notifications/Filter/InboxFilterReposViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// InboxFilterReposViewController.swift
// Freetime
//
// Created by Ryan Nystrom on 12/2/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import UIKit
import IGListKit
import Squawk

final class InboxFilterReposViewController: BaseListViewController<String>,
BaseListViewControllerDataSource {

private let inboxFilterController: InboxFilterController
private var repos = [RepositoryDetails]()

init(inboxFilterController: InboxFilterController) {
self.inboxFilterController = inboxFilterController
super.init(emptyErrorMessage: NSLocalizedString("Error loading repos", comment: ""))
dataSource = self
title = NSLocalizedString("Watched Repos", comment: "")
preferredContentSize = Styles.Sizes.contextMenuSize
feed.collectionView.backgroundColor = Styles.Colors.menuBackgroundColor.color
feed.setLoadingSpinnerColor(to: .white)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.titleTextAttributes = [
.foregroundColor: UIColor.white
]
}

override func fetch(page: String?) {
inboxFilterController.client.fetchSubscriptions { [weak self] result in
switch result {
case .error(let error):
Squawk.show(error: error)
case .success(let repos):
self?.repos = repos
self?.update()
}
}
}

// MARK: BaseListViewControllerDataSource

func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
return repos.map { [inboxFilterController] model in
ListSwiftPair.pair(model, {
InboxFilterRepoSectionController(inboxFilterController: inboxFilterController)
})
}
}

}
Loading