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

Allow for quick toggle between open/closed issues #2687

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
21 changes: 18 additions & 3 deletions Classes/Repository/RepositoryIssuesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ IndicatorInfoProvider {
private let type: RepositoryIssuesType
private let searchKey: ListDiffable = "searchKey" as ListDiffable
private let debouncer = Debouncer()
private var previousSearchString = "is:open "
private var previousSearchString = ""
private var prefix = "is:open "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the GitHub highlighter think prefix is a keyword? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 Problem?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No should be good 😊

private var label: String?

init(client: GithubClient, owner: String, repo: String, type: RepositoryIssuesType, label: String? = nil) {
Expand Down Expand Up @@ -103,14 +104,23 @@ IndicatorInfoProvider {
debouncer.action = { [weak self] in self?.fetch(page: nil) }
}

func didChangeSegment(sectionController: SearchBarSectionController, index: Int) {
switch index {
case 0: updateState(isOpen: true)
case 1: updateState(isOpen: false)
default: break
}
}

// MARK: BaseListViewControllerHeaderDataSource

func headerModel(for adapter: ListSwiftAdapter) -> ListSwiftPair {
return ListSwiftPair.pair("header", { [weak self, previousSearchString] in
SearchBarSectionController(
placeholder: Constants.Strings.search,
delegate: self,
query: previousSearchString
query: previousSearchString,
items: ["Open", "Closed"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localization? Also... what would this look like if these would be awfully long? Not sure if that would scale...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well localization of those would never be done here, it would be done before adding to the segment control. Aka, user passes in array of strings, we localize and add to control.

)
})
}
Expand Down Expand Up @@ -151,7 +161,12 @@ IndicatorInfoProvider {
case .issues: typeQuery = "is:issue"
case .pullRequests: typeQuery = "is:pr"
}
return "repo:\(owner)/\(repo) \(typeQuery) \(previousSearchString)".lowercased()
return "repo:\(owner)/\(repo) \(typeQuery) \(prefix + previousSearchString)".lowercased()
}

func updateState(isOpen: Bool = true) {
prefix = isOpen ? "is:open " : "is:closed "
debouncer.action = { [weak self] in self?.fetch(page: nil) }
}

// MARK: IndicatorInfoProvider
Expand Down
8 changes: 6 additions & 2 deletions Classes/Section Controllers/SearchBar/SearchBarCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import UIKit
import SnapKit

protocol SearchableCell: class {}

protocol SearchBarCellDelegate: class {
func didChangeSearchText(cell: SearchBarCell, query: String)
func didChangeSearchText(cell: SearchableCell, query: String)
func didChangeSegment(cell: SearchableCell, index: Int)
}

final class SearchBarCell: UICollectionViewCell, UISearchBarDelegate {

final class SearchBarCell: UICollectionViewCell, SearchableCell, UISearchBarDelegate {

weak var delegate: SearchBarCellDelegate?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,64 @@ import IGListKit

protocol SearchBarSectionControllerDelegate: class {
func didChangeSelection(sectionController: SearchBarSectionController, query: String)
func didChangeSegment(sectionController: SearchBarSectionController, index: Int)
}

final class SearchBarSectionController: ListSwiftSectionController<String>, SearchBarCellDelegate {

public private(set) var query: String
public private(set) var index: Int
public private(set) var items: [String]?

private weak var delegate: SearchBarSectionControllerDelegate?
private let placeholder: String

init(placeholder: String, delegate: SearchBarSectionControllerDelegate?, query: String = "") {
init(placeholder: String, delegate: SearchBarSectionControllerDelegate?, query: String = "", index: Int = 0, items: [String]? = nil) {
self.delegate = delegate
self.placeholder = placeholder
self.query = query
self.index = index
self.items = items
super.init()
}

override func createBinders(from value: String) -> [ListBinder] {
return [
binder(value, cellType: ListCellType.class(SearchBarCell.self), size: {
return $0.collection.cellSize(with: 56)
}, configure: { [weak self] (cell, _) in
guard let `self` = self else { return }
cell.delegate = self
cell.configure(query: self.query, placeholder: self.placeholder)
})
]

if let items = items {
return [
binder(value, cellType: ListCellType.class(SearchSegmentBarCell.self), size: {
return $0.collection.cellSize(with: 56)
}, configure: { [weak self] (cell, _) in
guard let `self` = self else { return }
cell.set(items: items)
cell.delegate = self
cell.configure(query: self.query, placeholder: self.placeholder)
})
]
}
else {
return [
binder(value, cellType: ListCellType.class(SearchBarCell.self), size: {
return $0.collection.cellSize(with: 56)
}, configure: { [weak self] (cell, _) in
guard let `self` = self else { return }
cell.delegate = self
cell.configure(query: self.query, placeholder: self.placeholder)
})
]
}
}

// MARK: SearchBarSectionControllerDelegate

func didChangeSearchText(cell: SearchBarCell, query: String) {
func didChangeSearchText(cell: SearchableCell, query: String) {
self.query = query
self.delegate?.didChangeSelection(sectionController: self, query: query)
}

func didChangeSegment(cell: SearchableCell, index: Int) {
self.index = index
self.delegate?.didChangeSegment(sectionController: self, index: index)
}

}
126 changes: 126 additions & 0 deletions Classes/Section Controllers/SearchBar/SearchSegmentBarCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// SearchSegementBarCell.swift
// Freetime
//
// Created by Ehud Adler on 3/12/19.
// Copyright © 2019 Ryan Nystrom. All rights reserved.
//

import UIKit
import SnapKit

final class SearchSegmentBarCell: UICollectionViewCell, SearchableCell, UISearchBarDelegate {

weak var delegate: SearchBarCellDelegate?

private let searchBar = UISearchBar(frame: .zero)
private let segmentControl = UISegmentedControl(frame: .zero)
var segmentControlLeadingConstraint: Constraint!

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white

searchBar.returnKeyType = .search
searchBar.enablesReturnKeyAutomatically = false
searchBar.searchBarStyle = .minimal
searchBar.delegate = self

contentView.addSubview(searchBar)
contentView.addSubview(segmentControl)

searchBar.snp.makeConstraints { make in
make.leading.centerY.equalTo(contentView)
make.trailing.equalTo(segmentControl.snp.leading)
}

segmentControl.snp.makeConstraints { make in
segmentControlLeadingConstraint = make.leading.equalTo(contentView.snp.trailing).constraint
make.trailing.equalTo(contentView.safeAreaLayoutGuide).inset(15)
make.top.bottom.equalTo(contentView)

}

segmentControl.addTarget(self, action: #selector(updateSegement), for: .valueChanged)

setSegmentControl()
segmentControlLeadingConstraint.deactivate()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you need this constraint for if you deactivate it before the init returns?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deactivated on start due to the search bar not being in editing mode. Once you start editing the constraint becomes activated

searchBar.resignWhenKeyboardHides()
segmentControl.removeBorders()
}

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

override func layoutSubviews() {
super.layoutSubviews()
layoutContentView()
}

// MARK: Public API

func set(items: [String], selectedIndex: Int = 0) {
for title in items {
segmentControl.insertSegment(
withTitle: title,
at: segmentControl.numberOfSegments,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reading this as it will always insert it at the end as the numberOfSegments is updated every time this is called?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes correct, the order in which you pass in the array is the order it will appear.

animated: true
)
}
segmentControl.selectedSegmentIndex = selectedIndex
}

func configure(query: String, placeholder: String) {
searchBar.text = query
searchBar.placeholder = placeholder
}

// MARK: Private API

func setSegmentControl() {

segmentControl.backgroundColor = .clear
segmentControl.tintColor = .clear

let normalFont: [AnyHashable: Any] = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use [NSAttributedStringKey: Any] here instead and can then infer the type going forward. :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing!

NSAttributedStringKey.foregroundColor: Styles.Colors.Gray.dark.color,
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15)
]
let selectedFont: [AnyHashable: Any] = [
NSAttributedStringKey.foregroundColor: Styles.Colors.Blue.medium.color,
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15)
]
segmentControl.setTitleTextAttributes(normalFont, for: .normal)
segmentControl.setTitleTextAttributes(selectedFont, for: .selected)
}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
delegate?.didChangeSearchText(cell: self, query: searchText)
}

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}

func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
UIView.animate(withDuration: 0.3) {
self.segmentControlLeadingConstraint.deactivate()
self.segmentControl.alpha = 1
self.layoutIfNeeded()
}
}

func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
UIView.animate(withDuration: 0.3) {
self.segmentControlLeadingConstraint.activate()
self.segmentControl.alpha = 0
self.layoutIfNeeded()
}

}

@objc func updateSegement() {
delegate?.didChangeSegment(cell: self, index: self.segmentControl.selectedSegmentIndex)
}
}
30 changes: 30 additions & 0 deletions Classes/Utility/SegmentControl+Border.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// SegmentControl+Border.swift
// Freetime
//
// Created by Ehud Adler on 3/12/19.
// Copyright © 2019 Ryan Nystrom. All rights reserved.
//

import Foundation

extension UISegmentedControl {
func removeBorders() {
setBackgroundImage(imageWithColor(color: backgroundColor ?? .white), for: .normal, barMetrics: .default)
setBackgroundImage(imageWithColor(color: tintColor ?? .white), for: .selected, barMetrics: .default)
setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
}

// create a 1x1 image with this color
private func imageWithColor(color: UIColor) -> UIImage {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have this somewhere else already by chance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill search

let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
UIGraphicsBeginImageContext(rect.size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to use UIGraphicsImageRenderer here instead, which removes the need to have to deal with optionals.

if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.fill(rect)
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
8 changes: 8 additions & 0 deletions Freetime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@
BDB6AA762165B8EA009BB73C /* SwitchBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */; };
C0E3CD4B21BAE49B00185B57 /* NSRegularExpression+StaticString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E3CD4A21BAE49B00185B57 /* NSRegularExpression+StaticString.swift */; };
C0E3CD4D21BAE65000185B57 /* UIImage+StaticString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E3CD4C21BAE65000185B57 /* UIImage+StaticString.swift */; };
C0F0707A22386CC900C90A31 /* SearchSegmentBarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F0707922386CC900C90A31 /* SearchSegmentBarCell.swift */; };
C0F0707C223882DD00C90A31 /* SegmentControl+Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F0707B223882DD00C90A31 /* SegmentControl+Border.swift */; };
D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */; };
D8BAD0641FDF221900C41071 /* LabelListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0631FDF221900C41071 /* LabelListView.swift */; };
D8BAD0661FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0651FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift */; };
Expand Down Expand Up @@ -1129,6 +1131,8 @@
BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchBranches.swift; sourceTree = "<group>"; };
C0E3CD4A21BAE49B00185B57 /* NSRegularExpression+StaticString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+StaticString.swift"; sourceTree = "<group>"; };
C0E3CD4C21BAE65000185B57 /* UIImage+StaticString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+StaticString.swift"; sourceTree = "<group>"; };
C0F0707922386CC900C90A31 /* SearchSegmentBarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSegmentBarCell.swift; sourceTree = "<group>"; };
C0F0707B223882DD00C90A31 /* SegmentControl+Border.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SegmentControl+Border.swift"; sourceTree = "<group>"; };
D396E0DA66FED629384A84BC /* Pods_FreetimeWatch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FreetimeWatch.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelListCell.swift; sourceTree = "<group>"; };
D8BAD0631FDF221900C41071 /* LabelListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelListView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2213,6 +2217,7 @@
children = (
7BBFEE561F8A8A0400C68E47 /* SearchBarCell.swift */,
7BBFEE581F8A8A0400C68E47 /* SearchBarSectionController.swift */,
C0F0707922386CC900C90A31 /* SearchSegmentBarCell.swift */,
);
path = SearchBar;
sourceTree = "<group>";
Expand Down Expand Up @@ -2303,6 +2308,7 @@
298C7E2721D80BAF00DD2A60 /* Error+GraphQLForbidden.swift */,
031E0240220B433C00A329F1 /* UIImage+Color.swift */,
03E8D823221D339200EB792A /* GithubURL.swift */,
C0F0707B223882DD00C90A31 /* SegmentControl+Border.swift */,
);
path = Utility;
sourceTree = "<group>";
Expand Down Expand Up @@ -3028,6 +3034,7 @@
292FCAF91EDFCC510026635E /* IssueCommentDetailCell.swift in Sources */,
DC3238931F9BA29D007DD924 /* SearchQuery.swift in Sources */,
2967DC56211751CB00FD3683 /* UIContentSizeCategory+Preferred.swift in Sources */,
C0F0707C223882DD00C90A31 /* SegmentControl+Border.swift in Sources */,
7BBFEE5B1F8A8A0400C68E47 /* SearchBarSectionController.swift in Sources */,
292FCAFA1EDFCC510026635E /* IssueCommentDetailsViewModel.swift in Sources */,
29A10541216D912F004734A0 /* IssueRoute+RoutePerformable.swift in Sources */,
Expand Down Expand Up @@ -3280,6 +3287,7 @@
290CA778216AFAE600DE04F8 /* RoutePerformable.swift in Sources */,
29FE635B21AB86B700A07A86 /* BookmarkRepoCell.swift in Sources */,
295B51421FC26B8100C3993B /* PeopleCell.swift in Sources */,
C0F0707A22386CC900C90A31 /* SearchSegmentBarCell.swift in Sources */,
29316DBF1ECC95DB007CAE3F /* RootViewControllers.swift in Sources */,
29DA1E791F5DEE8F0050C64B /* SearchLoadingView.swift in Sources */,
290CA76A216AC82700DE04F8 /* SearchShortcutRoute+RoutePerformable.swift in Sources */,
Expand Down