mirror of
https://github.com/zhigang1992/GitHawk.git
synced 2026-04-28 12:04:59 +08:00
People menu in IGListKit+Swift (#1851)
* Create new people with IGLK+Swift, fix AX+l18n, fix bug where cant remove people * smaller font for people cell
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
final class IssueAssigneeViewModel: ListDiffable {
|
||||
final class IssueAssigneeViewModel: ListDiffable, ListSwiftIdentifiable, ListSwiftEquatable {
|
||||
let login: String
|
||||
let avatarURL: URL
|
||||
|
||||
@@ -28,4 +28,16 @@ final class IssueAssigneeViewModel: ListDiffable {
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: ListSwiftIdentifiable
|
||||
|
||||
var identifier: String {
|
||||
return avatarURL.absoluteString
|
||||
}
|
||||
|
||||
// MARK: ListSwiftEquatable
|
||||
|
||||
func isEqual(to object: ListSwiftDiffable) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ extension GithubClient {
|
||||
number: number,
|
||||
type: type,
|
||||
add: false,
|
||||
people: added)
|
||||
people: removed)
|
||||
) { result in
|
||||
switch result {
|
||||
case .success: break
|
||||
|
||||
@@ -107,13 +107,13 @@ ContextMenuDelegate {
|
||||
)
|
||||
}
|
||||
|
||||
func newPeopleController(type: PeopleViewController.PeopleType) -> UIViewController {
|
||||
func newPeopleController(type: PeopleViewController2.PeopleType) -> UIViewController {
|
||||
let selections: [String]
|
||||
switch type {
|
||||
case .assignee: selections = issueResult?.assignee.users.map { $0.login } ?? []
|
||||
case .reviewer: selections = issueResult?.reviewers?.users.map { $0.login } ?? []
|
||||
}
|
||||
return PeopleViewController(
|
||||
return PeopleViewController2(
|
||||
selections: selections,
|
||||
type: type,
|
||||
client: client,
|
||||
@@ -284,12 +284,8 @@ ContextMenuDelegate {
|
||||
|
||||
// MARK: PeopleViewControllerDelegate
|
||||
|
||||
func didDismiss(controller: PeopleViewController) {
|
||||
func didDismiss(controller: PeopleViewController2) {
|
||||
guard let previous = issueResult else { return }
|
||||
var assignees = [IssueAssigneeViewModel]()
|
||||
for user in controller.selectedUsers {
|
||||
assignees.append(IssueAssigneeViewModel(login: user.login, avatarURL: user.avatarUrl))
|
||||
}
|
||||
|
||||
let mutationType: V3AddPeopleRequest.PeopleType
|
||||
switch controller.type {
|
||||
@@ -303,7 +299,7 @@ ContextMenuDelegate {
|
||||
owner: model.owner,
|
||||
repo: model.repo,
|
||||
number: model.number,
|
||||
people: assignees
|
||||
people: controller.selected
|
||||
)
|
||||
}
|
||||
|
||||
@@ -314,7 +310,7 @@ ContextMenuDelegate {
|
||||
didDismiss(selected: labels.selected)
|
||||
} else if let milestones = viewController as? MilestonesViewController2 {
|
||||
didDismiss(controller: milestones)
|
||||
} else if let people = viewController as? PeopleViewController {
|
||||
} else if let people = viewController as? PeopleViewController2 {
|
||||
didDismiss(controller: people)
|
||||
} else if let labels = viewController as? LabelsViewController2 {
|
||||
didDismiss(selected: labels.selected)
|
||||
|
||||
@@ -10,14 +10,17 @@ import UIKit
|
||||
import SDWebImage
|
||||
import SnapKit
|
||||
|
||||
final class PeopleCell: UICollectionViewCell {
|
||||
final class PeopleCell: SelectableCell {
|
||||
|
||||
let avatarImageView = UIImageView()
|
||||
let usernameLabel = UILabel()
|
||||
let checkmarkImageView = UIImageView()
|
||||
private let avatarImageView = UIImageView()
|
||||
private let usernameLabel = UILabel()
|
||||
private let checkmarkImageView = UIImageView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
backgroundColor = .white
|
||||
|
||||
contentView.addSubview(avatarImageView)
|
||||
contentView.addSubview(usernameLabel)
|
||||
contentView.addSubview(checkmarkImageView)
|
||||
@@ -28,12 +31,13 @@ final class PeopleCell: UICollectionViewCell {
|
||||
make.size.equalTo(Styles.Sizes.avatar)
|
||||
}
|
||||
|
||||
usernameLabel.font = Styles.Text.secondary.preferredFont
|
||||
usernameLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(self.contentView)
|
||||
make.left.equalTo(avatarImageView.snp.right).offset(Styles.Sizes.gutter)
|
||||
}
|
||||
|
||||
checkmarkImageView.image = UIImage(named: "check")?.withRenderingMode(.alwaysTemplate)
|
||||
checkmarkImageView.image = UIImage(named: "check-small")?.withRenderingMode(.alwaysTemplate)
|
||||
checkmarkImageView.tintColor = Styles.Colors.Blue.medium.color
|
||||
checkmarkImageView.contentMode = .scaleAspectFit
|
||||
|
||||
|
||||
50
Classes/People/PeopleSectionController2.swift
Normal file
50
Classes/People/PeopleSectionController2.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// PeopleSectionController2.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/2/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import IGListKit
|
||||
|
||||
protocol PeopleSectionController2Delegate: class {
|
||||
func didSelect(controller: PeopleSectionController2)
|
||||
}
|
||||
|
||||
final class PeopleSectionController2: ListSwiftSectionController<IssueAssigneeViewModel> {
|
||||
|
||||
public weak var delegate: PeopleSectionController2Delegate?
|
||||
|
||||
public private(set) var selected: Bool
|
||||
|
||||
init(selected: Bool) {
|
||||
self.selected = selected
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func createBinders(from value: IssueAssigneeViewModel) -> [ListBinder] {
|
||||
return [
|
||||
binder(
|
||||
value,
|
||||
cellType: ListCellType.class(PeopleCell.self),
|
||||
size: {
|
||||
return CGSize(
|
||||
width: $0.collection.containerSize.width,
|
||||
height: Styles.Sizes.tableCellHeight
|
||||
)
|
||||
},
|
||||
configure: { [selected] in
|
||||
$0.configure(avatarURL: $1.value.avatarURL, username: $1.value.login, showCheckmark: selected)
|
||||
},
|
||||
didSelect: { [weak self] context in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.selected = !strongSelf.selected
|
||||
context.deselect()
|
||||
context.cell?.setCellState(selected: strongSelf.selected)
|
||||
strongSelf.delegate?.didSelect(controller: strongSelf)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
139
Classes/People/PeopleViewController2.swift
Normal file
139
Classes/People/PeopleViewController2.swift
Normal file
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// PeopleViewController2.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 6/2/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IGListKit
|
||||
import GitHubAPI
|
||||
|
||||
final class PeopleViewController2: BaseListViewController2,
|
||||
BaseListViewController2DataSource,
|
||||
PeopleSectionController2Delegate {
|
||||
|
||||
enum PeopleType {
|
||||
case assignee
|
||||
case reviewer
|
||||
}
|
||||
|
||||
public let type: PeopleType
|
||||
|
||||
private let selections: Set<String>
|
||||
private let selectionLimit = 10
|
||||
private var users = [IssueAssigneeViewModel]()
|
||||
private let client: GithubClient
|
||||
private var owner: String
|
||||
private var repo: String
|
||||
|
||||
init(
|
||||
selections: [String],
|
||||
type: PeopleType,
|
||||
client: GithubClient,
|
||||
owner: String,
|
||||
repo: String
|
||||
) {
|
||||
self.selections = Set<String>(selections)
|
||||
self.type = type
|
||||
self.client = client
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
|
||||
super.init(emptyErrorMessage: NSLocalizedString("Cannot load users.", comment: ""))
|
||||
|
||||
self.dataSource = self
|
||||
|
||||
switch type {
|
||||
case .assignee: title = NSLocalizedString("Assignees", comment: "")
|
||||
case .reviewer: title = NSLocalizedString("Reviewers", comment: "")
|
||||
}
|
||||
|
||||
preferredContentSize = CGSize(width: 280, height: 240)
|
||||
updateSelectionCount()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: Public API
|
||||
|
||||
var selected: [IssueAssigneeViewModel] {
|
||||
return users.filter {
|
||||
if let sectionController: PeopleSectionController2 = feed.swiftAdapter.sectionController(for: $0) {
|
||||
return sectionController.selected
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
|
||||
func updateSelectionCount() {
|
||||
let label = UILabel()
|
||||
label.font = Styles.Text.body.preferredFont
|
||||
label.backgroundColor = .clear
|
||||
label.textColor = Styles.Colors.Gray.light.color
|
||||
label.text = "\(selected.count)/\(selectionLimit)"
|
||||
label.sizeToFit()
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: label)
|
||||
}
|
||||
|
||||
// MARK: Overrides
|
||||
|
||||
override func fetch(page: String?) {
|
||||
|
||||
client.client.send(
|
||||
V3AssigneesRequest(
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
page: (page as NSString?)?.integerValue ?? 1
|
||||
)
|
||||
) { [weak self] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let sortedUsers = response.data.sorted {
|
||||
$0.login.caseInsensitiveCompare($1.login) == .orderedAscending
|
||||
}
|
||||
let users = sortedUsers.map { IssueAssigneeViewModel(login: $0.login, avatarURL: $0.avatarUrl) }
|
||||
if page != nil {
|
||||
self?.users += users
|
||||
} else {
|
||||
self?.users = users
|
||||
}
|
||||
self?.update(animated: true)
|
||||
|
||||
let nextPage: String?
|
||||
if let next = response.next {
|
||||
nextPage = "\(next)"
|
||||
} else {
|
||||
nextPage = nil
|
||||
}
|
||||
self?.update(page: nextPage, animated: true)
|
||||
case .failure:
|
||||
ToastManager.showGenericError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: BaseListViewController2DataSource
|
||||
|
||||
func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
|
||||
return users.map { [selections] user in
|
||||
return ListSwiftPair.pair(user) { [weak self] in
|
||||
let controller = PeopleSectionController2(selected: selections.contains(user.login))
|
||||
controller.delegate = self
|
||||
return controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: PeopleSectionController2Delegate
|
||||
|
||||
func didSelect(controller: PeopleSectionController2) {
|
||||
updateSelectionCount()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,7 +57,10 @@ FeedDelegate {
|
||||
// MARK: Public API
|
||||
|
||||
final func update(animated: Bool) {
|
||||
self.feed.finishLoading(dismissRefresh: true, animated: animated)
|
||||
self.feed.finishLoading(
|
||||
dismissRefresh: true,
|
||||
animated: animated && trueUnlessReduceMotionEnabled
|
||||
)
|
||||
}
|
||||
|
||||
final func update(
|
||||
@@ -69,7 +72,11 @@ FeedDelegate {
|
||||
|
||||
self.hasError = false
|
||||
self.page = page
|
||||
self.feed.finishLoading(dismissRefresh: true, animated: animated, completion: completion)
|
||||
self.feed.finishLoading(
|
||||
dismissRefresh: true,
|
||||
animated: animated && trueUnlessReduceMotionEnabled,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
final func error(
|
||||
@@ -78,7 +85,11 @@ FeedDelegate {
|
||||
) {
|
||||
assert(Thread.isMainThread)
|
||||
hasError = true
|
||||
feed.finishLoading(dismissRefresh: true, animated: animated, completion: completion)
|
||||
feed.finishLoading(
|
||||
dismissRefresh: true,
|
||||
animated: animated && trueUnlessReduceMotionEnabled,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: FeedDelegate
|
||||
|
||||
@@ -241,6 +241,8 @@
|
||||
297EC91B20C4744800B6E42C /* MilestonesViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297EC91A20C4744800B6E42C /* MilestonesViewController2.swift */; };
|
||||
297EC91D20C4765000B6E42C /* MilestoneSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297EC91C20C4765000B6E42C /* MilestoneSectionController.swift */; };
|
||||
297EC91F20C4768600B6E42C /* MilestoneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297EC91E20C4768600B6E42C /* MilestoneViewModel.swift */; };
|
||||
297EC91520C3953300B6E42C /* PeopleSectionController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297EC91420C3953300B6E42C /* PeopleSectionController2.swift */; };
|
||||
297EC91720C3965E00B6E42C /* PeopleViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297EC91620C3965E00B6E42C /* PeopleViewController2.swift */; };
|
||||
297FB7781F51128A00F2E618 /* SettingsAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297FB7771F51128A00F2E618 /* SettingsAccountsViewController.swift */; };
|
||||
2980033C1F51E82400BE90F4 /* RatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980033B1F51E82400BE90F4 /* RatingController.swift */; };
|
||||
2980033E1F51E93500BE90F4 /* RatingSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980033D1F51E93500BE90F4 /* RatingSectionController.swift */; };
|
||||
@@ -750,6 +752,8 @@
|
||||
297EC91A20C4744800B6E42C /* MilestonesViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MilestonesViewController2.swift; sourceTree = "<group>"; };
|
||||
297EC91C20C4765000B6E42C /* MilestoneSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MilestoneSectionController.swift; sourceTree = "<group>"; };
|
||||
297EC91E20C4768600B6E42C /* MilestoneViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MilestoneViewModel.swift; sourceTree = "<group>"; };
|
||||
297EC91420C3953300B6E42C /* PeopleSectionController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleSectionController2.swift; sourceTree = "<group>"; };
|
||||
297EC91620C3965E00B6E42C /* PeopleViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleViewController2.swift; sourceTree = "<group>"; };
|
||||
297FB7771F51128A00F2E618 /* SettingsAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountsViewController.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -1411,8 +1415,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
295B51411FC26B8100C3993B /* PeopleCell.swift */,
|
||||
297EC91620C3965E00B6E42C /* PeopleViewController2.swift */,
|
||||
295B51431FC26C5400C3993B /* PeopleViewController.swift */,
|
||||
1F9D00F92051089C00DF5FF6 /* PeopleSectionController.swift */,
|
||||
297EC91420C3953300B6E42C /* PeopleSectionController2.swift */,
|
||||
);
|
||||
path = People;
|
||||
sourceTree = "<group>";
|
||||
@@ -2890,6 +2896,7 @@
|
||||
986B873E1F2E1CE400AAB55C /* RepositoryClient.swift in Sources */,
|
||||
295A77BE1F75C1CC007BC403 /* RepositoryDetails.swift in Sources */,
|
||||
292ACE1E1F5CB02400C9A02C /* RepositoryEmptyResultsCell.swift in Sources */,
|
||||
297EC91720C3965E00B6E42C /* PeopleViewController2.swift in Sources */,
|
||||
292ACE1F1F5CB02400C9A02C /* RepositoryEmptyResultsSectionController.swift in Sources */,
|
||||
292ACE1B1F5CAFAD00C9A02C /* RepositoryEmptyResultsType.swift in Sources */,
|
||||
29AF1E881F8AB0AA0008A0EF /* IssueTextActionsView+Markdown.swift in Sources */,
|
||||
@@ -2938,6 +2945,7 @@
|
||||
986B87251F2B990A00AAB55C /* SearchRepoResultCell.swift in Sources */,
|
||||
986B87231F2B98AD00AAB55C /* SearchResultSectionController.swift in Sources */,
|
||||
29AF1E8E1F8ABC900008A0EF /* RepositoryFile.swift in Sources */,
|
||||
297EC91520C3953300B6E42C /* PeopleSectionController2.swift in Sources */,
|
||||
986B871D1F2B8FCD00AAB55C /* SearchViewController.swift in Sources */,
|
||||
298BA0971EC947F100B01946 /* SegmentedControlCell.swift in Sources */,
|
||||
297B062C1FB92DCA0026FA23 /* UICollectionViewLayout+Orientation.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user