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

Commit 38a2f92

Browse files
author
Ryan Nystrom
committed
edit labels
1 parent 03c445b commit 38a2f92

13 files changed

+403
-31
lines changed

Classes/Issues/IssuesViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ IssueTextActionsViewDelegate {
287287
switch object {
288288
case is NSAttributedStringSizing: return IssueTitleSectionController()
289289
case is IssueCommentModel: return IssueCommentSectionController(client: client)
290-
case is IssueLabelsModel: return IssueLabelsSectionController()
290+
case is IssueLabelsModel: return IssueLabelsSectionController(owner: owner, repo: repo, number: number, client: client)
291291
case is IssueStatusModel: return IssueStatusSectionController()
292292
case is IssueLabeledModel: return IssueLabeledSectionController(owner: owner, repo: repo)
293293
case is IssueStatusEventModel: return IssueStatusEventSectionController(owner: owner, repo: repo)

Classes/Issues/Labels/IssueLabelCell.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class IssueLabelCell: UICollectionViewCell, ListBindable {
4242
// MARK: ListBindable
4343

4444
func bindViewModel(_ viewModel: Any) {
45-
guard let viewModel = viewModel as? IssueLabelModel else { return }
45+
guard let viewModel = viewModel as? RepositoryLabel else { return }
4646
let color = UIColor.fromHex(viewModel.color)
4747
background.backgroundColor = color
4848
label.text = viewModel.name

Classes/Issues/Labels/IssueLabelsModel.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import IGListKit
1212
final class IssueLabelsModel: ListDiffable {
1313

1414
let viewerCanUpdate: Bool
15-
let labels: [IssueLabelModel]
15+
let labels: [RepositoryLabel]
1616

17-
init(viewerCanUpdate: Bool, labels: [IssueLabelModel]) {
17+
init(viewerCanUpdate: Bool, labels: [RepositoryLabel]) {
1818
self.viewerCanUpdate = viewerCanUpdate
1919
self.labels = labels
2020
}

Classes/Issues/Labels/IssueLabelsSectionController.swift

+56-15
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@ import IGListKit
1111

1212
final class IssueLabelsSectionController: ListBindingSectionController<IssueLabelsModel>,
1313
ListBindingSectionControllerDataSource,
14-
ListBindingSectionControllerSelectionDelegate {
15-
16-
var expanded = false
17-
18-
override init() {
14+
ListBindingSectionControllerSelectionDelegate,
15+
LabelsViewControllerDelegate {
16+
17+
private var expanded = false
18+
private var owner: String
19+
private var repo: String
20+
private var number: Int
21+
private var client: GithubClient
22+
private var labelsOverride: [RepositoryLabel]? = nil
23+
24+
init(owner: String, repo: String, number: Int, client: GithubClient) {
25+
self.owner = owner
26+
self.repo = repo
27+
self.number = number
28+
self.client = client
1929
super.init()
2030
selectionDelegate = self
2131
dataSource = self
@@ -24,16 +34,20 @@ ListBindingSectionControllerSelectionDelegate {
2434
// MARK: ListBindingSectionControllerDataSource
2535

2636
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] {
27-
guard let object = self.object,
28-
object.labels.count > 0
29-
else { return [] }
30-
let colors = object.labels.map { UIColor.fromHex($0.color) }
37+
var viewModels = [ListDiffable]()
3138

32-
var viewModels: [ListDiffable] = [IssueLabelSummaryModel(colors: colors)]
39+
// use override labels when available
40+
let labels = (labelsOverride ?? self.object?.labels ?? [])
41+
let colors = labels.map { UIColor.fromHex($0.color) }
42+
43+
// avoid an empty summary cell
44+
if labels.count > 0 {
45+
viewModels.append(IssueLabelSummaryModel(colors: colors))
46+
}
3347
if expanded {
34-
viewModels += object.labels as [ListDiffable]
48+
viewModels += labels as [ListDiffable]
3549
}
36-
if object.viewerCanUpdate {
50+
if self.object?.viewerCanUpdate == true {
3751
viewModels.append("edit" as ListDiffable)
3852
}
3953

@@ -53,7 +67,7 @@ ListBindingSectionControllerSelectionDelegate {
5367
let cellClass: AnyClass
5468
switch viewModel {
5569
case is IssueLabelSummaryModel: cellClass = IssueLabelSummaryCell.self
56-
case is IssueLabelModel: cellClass = IssueLabelCell.self
70+
case is RepositoryLabel: cellClass = IssueLabelCell.self
5771
default: cellClass = IssueLabelEditCell.self
5872
}
5973

@@ -66,12 +80,39 @@ ListBindingSectionControllerSelectionDelegate {
6680
collectionContext?.deselectItem(at: index, sectionController: self, animated: true)
6781

6882
if collectionContext?.cellForItem(at: index, sectionController: self) is IssueLabelEditCell {
69-
// TODO
83+
guard let controller = UIStoryboard(name: "Labels", bundle: nil).instantiateInitialViewController() as? LabelsViewController
84+
else { fatalError("Missing labels view controller") }
85+
controller.configure(
86+
selected: labelsOverride ?? self.object?.labels ?? [],
87+
client: client,
88+
owner: owner,
89+
repo: repo,
90+
delegate: self
91+
)
92+
viewController?.present(UINavigationController(rootViewController: controller), animated: true)
7093
} else {
7194
expanded = !expanded
7295
update(animated: true)
7396
}
7497
}
7598

76-
}
99+
// MARK: LabelsViewControllerDelegate
100+
101+
func didDismiss(controller: LabelsViewController, selectedLabels: [RepositoryLabel]) {
102+
labelsOverride = selectedLabels
103+
update(animated: false)
104+
105+
let request = GithubClient.Request(
106+
path: "repos/\(owner)/\(repo)/issues/\(number)",
107+
method: .patch,
108+
parameters: ["labels": selectedLabels.map { $0.name }]
109+
) { [weak self] (response, _) in
110+
if response.response?.statusCode != 200 {
111+
self?.labelsOverride = nil
112+
self?.update(animated: true)
113+
}
114+
}
115+
client.request(request)
116+
}
77117

118+
}

Classes/Labels/LabelTableCell.swift

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// LabelTableCell.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 8/8/17.
6+
// Copyright © 2017 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
final class LabelTableCell: UITableViewCell {
12+
13+
@IBOutlet weak var button: UIButton!
14+
15+
override func awakeFromNib() {
16+
super.awakeFromNib()
17+
button.layer.cornerRadius = Styles.Sizes.avatarCornerRadius
18+
button.layer.borderColor = Styles.Colors.Gray.border.color.cgColor
19+
button.layer.borderWidth = 1 / UIScreen.main.scale
20+
button.clipsToBounds = true
21+
button.contentEdgeInsets = UIEdgeInsets(
22+
top: Styles.Sizes.inlineSpacing,
23+
left: Styles.Sizes.columnSpacing,
24+
bottom: Styles.Sizes.inlineSpacing,
25+
right: Styles.Sizes.columnSpacing
26+
)
27+
}
28+
29+
// MARK: Public API
30+
31+
func configure(label: String, color: UIColor, selected: Bool) {
32+
button.setTitle(label, for: .normal)
33+
button.setTitleColor(color.textOverlayColor, for: .normal)
34+
button.backgroundColor = color
35+
accessoryType = selected ? .checkmark : .none
36+
}
37+
38+
}

Classes/Labels/Labels.storyboard

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="i6k-yB-d8o">
3+
<device id="retina4_7" orientation="portrait">
4+
<adaptation id="fullscreen"/>
5+
</device>
6+
<dependencies>
7+
<deployment identifier="iOS"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
9+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+
</dependencies>
11+
<scenes>
12+
<!--Labels-->
13+
<scene sceneID="Dgg-e1-cC6">
14+
<objects>
15+
<tableViewController id="i6k-yB-d8o" customClass="LabelsViewController" customModule="Freetime" customModuleProvider="target" sceneMemberID="viewController">
16+
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="XYQ-74-fts">
17+
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
18+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19+
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
20+
<view key="tableFooterView" contentMode="scaleToFill" id="ZbV-t2-OLe">
21+
<rect key="frame" x="0.0" y="72" width="375" height="1"/>
22+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
23+
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
24+
</view>
25+
<prototypes>
26+
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="checkmark" indentationWidth="10" reuseIdentifier="cell" id="3Ru-pe-jei" customClass="LabelTableCell" customModule="Freetime" customModuleProvider="target">
27+
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
28+
<autoresizingMask key="autoresizingMask"/>
29+
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3Ru-pe-jei" id="JgS-eS-vpS">
30+
<rect key="frame" x="0.0" y="0.0" width="336" height="43.5"/>
31+
<autoresizingMask key="autoresizingMask"/>
32+
<subviews>
33+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6I8-5k-y6W">
34+
<rect key="frame" x="16" y="4.5" width="90" height="33"/>
35+
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
36+
<state key="normal" title="label-name"/>
37+
</button>
38+
</subviews>
39+
<constraints>
40+
<constraint firstItem="6I8-5k-y6W" firstAttribute="centerY" secondItem="JgS-eS-vpS" secondAttribute="centerY" id="M8R-jF-F8T"/>
41+
<constraint firstItem="6I8-5k-y6W" firstAttribute="leading" secondItem="JgS-eS-vpS" secondAttribute="leading" constant="16" id="Ogm-Lo-xHa"/>
42+
</constraints>
43+
</tableViewCellContentView>
44+
<color key="tintColor" red="0.011764705882352941" green="0.40000000000000002" blue="0.83921568627450982" alpha="1" colorSpace="calibratedRGB"/>
45+
<connections>
46+
<outlet property="button" destination="6I8-5k-y6W" id="uB1-Wo-2rN"/>
47+
</connections>
48+
</tableViewCell>
49+
</prototypes>
50+
<connections>
51+
<outlet property="dataSource" destination="i6k-yB-d8o" id="wzV-i4-8qH"/>
52+
<outlet property="delegate" destination="i6k-yB-d8o" id="5JQ-ro-yaz"/>
53+
</connections>
54+
</tableView>
55+
<navigationItem key="navigationItem" title="Labels" id="aTv-cp-0zu">
56+
<barButtonItem key="rightBarButtonItem" systemItem="done" id="VEL-fa-t0u">
57+
<connections>
58+
<action selector="onDone" destination="i6k-yB-d8o" id="EoD-LI-SRg"/>
59+
</connections>
60+
</barButtonItem>
61+
</navigationItem>
62+
</tableViewController>
63+
<placeholder placeholderIdentifier="IBFirstResponder" id="mMU-3e-31L" userLabel="First Responder" sceneMemberID="firstResponder"/>
64+
</objects>
65+
<point key="canvasLocation" x="-262" y="163"/>
66+
</scene>
67+
</scenes>
68+
</document>
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//
2+
// LabelsViewController.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 8/8/17.
6+
// Copyright © 2017 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
protocol LabelsViewControllerDelegate: class {
12+
func didDismiss(controller: LabelsViewController, selectedLabels: [RepositoryLabel])
13+
}
14+
15+
final class LabelsViewController: UITableViewController {
16+
17+
private weak var delegate: LabelsViewControllerDelegate? = nil
18+
private var labels = [RepositoryLabel]()
19+
private var selectedLabels = Set<String>()
20+
private var performedInitialLoad = false
21+
private var client: GithubClient!
22+
private var request: RepositoryLabelsQuery!
23+
24+
override func viewDidLoad() {
25+
super.viewDidLoad()
26+
27+
tableView.refreshControl = UIRefreshControl()
28+
tableView.refreshControl?.addTarget(self, action: #selector(LabelsViewController.onRefresh), for: .valueChanged)
29+
}
30+
31+
override func viewDidAppear(_ animated: Bool) {
32+
super.viewDidAppear(animated)
33+
34+
if !performedInitialLoad {
35+
tableView.refreshControl?.beginRefreshing()
36+
fetch()
37+
}
38+
}
39+
40+
// MARK: Private API
41+
42+
func onRefresh() {
43+
fetch()
44+
}
45+
46+
func fetch() {
47+
performedInitialLoad = true
48+
client.apollo.fetch(query: request, cachePolicy: .fetchIgnoringCacheData) { (result, error) in
49+
self.tableView.refreshControl?.endRefreshing()
50+
if let nodes = result?.data?.repository?.labels?.nodes {
51+
var labels = [RepositoryLabel]()
52+
for node in nodes {
53+
guard let node = node else { continue }
54+
labels.append(RepositoryLabel(color: node.color, name: node.name))
55+
}
56+
self.labels = labels
57+
self.tableView.reloadData()
58+
} else {
59+
StatusBar.showGenericError()
60+
}
61+
}
62+
}
63+
64+
@IBAction func onDone() {
65+
var selected = [RepositoryLabel]()
66+
for label in labels {
67+
if selectedLabels.contains(label.name) {
68+
selected.append(label)
69+
}
70+
}
71+
delegate?.didDismiss(controller: self, selectedLabels: selected)
72+
dismiss(animated: true)
73+
}
74+
75+
// MARK: UITableViewDataSource
76+
77+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
78+
return labels.count
79+
}
80+
81+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
82+
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? LabelTableCell
83+
else { fatalError("Wrong cell type") }
84+
let label = labels[indexPath.row]
85+
cell.configure(label: label.name, color: label.color.color, selected: selectedLabels.contains(label.name))
86+
return cell
87+
}
88+
89+
// MARK: UITableViewDelegate
90+
91+
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
92+
tableView.deselectRow(at: indexPath, animated: true)
93+
let name = labels[indexPath.row].name
94+
if selectedLabels.contains(name) {
95+
selectedLabels.remove(name)
96+
} else {
97+
selectedLabels.insert(name)
98+
}
99+
tableView.reloadData()
100+
}
101+
102+
// MARK: Public API
103+
104+
func configure(
105+
selected: [RepositoryLabel],
106+
client: GithubClient,
107+
owner: String,
108+
repo: String,
109+
delegate: LabelsViewControllerDelegate
110+
) {
111+
var set = Set<String>()
112+
for l in selected {
113+
set.insert(l.name)
114+
}
115+
self.selectedLabels = set
116+
self.client = client
117+
self.request = RepositoryLabelsQuery(owner: owner, repo: repo)
118+
self.delegate = delegate
119+
}
120+
121+
}

Classes/Models/LabelableFields+IssueLabelModel.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import Foundation
1010

1111
extension LabelableFields {
1212

13-
var issueLabelModels: [IssueLabelModel] {
14-
var models = [IssueLabelModel]()
13+
var issueLabelModels: [RepositoryLabel] {
14+
var models = [RepositoryLabel]()
1515
for node in labels?.nodes ?? [] {
1616
guard let node = node else { continue }
17-
models.append(IssueLabelModel(color: node.color, name: node.name))
17+
models.append(RepositoryLabel(color: node.color, name: node.name))
1818
}
1919
return models
2020
}

0 commit comments

Comments
 (0)