diff --git a/iOS/IssueTracker/IssueTracker/Extension/NotificationName.swift b/iOS/IssueTracker/IssueTracker/Extension/NotificationName.swift index 4ec39b04..0e534532 100644 --- a/iOS/IssueTracker/IssueTracker/Extension/NotificationName.swift +++ b/iOS/IssueTracker/IssueTracker/Extension/NotificationName.swift @@ -10,4 +10,5 @@ extension Notification.Name { static let issueDidChange = Notification.Name("issueDidChange") static let labelDidChange = Notification.Name("labelDidChange") static let milestoneDidChange = Notification.Name("milestoneDidChange") + static let issueFilterDidChange = Notification.Name("issueFilterDidChange") } diff --git a/iOS/IssueTracker/IssueTracker/Issue/View/FilterTableViewCell.swift b/iOS/IssueTracker/IssueTracker/Issue/View/FilterTableViewCell.swift index 0919e28d..be2af9e6 100644 --- a/iOS/IssueTracker/IssueTracker/Issue/View/FilterTableViewCell.swift +++ b/iOS/IssueTracker/IssueTracker/Issue/View/FilterTableViewCell.swift @@ -13,28 +13,35 @@ class FilterTableViewCell: UITableViewCell { @IBOutlet weak var accessoryImageView: UIImageView! private var section = 0 - private var hasChild = false + private var isChild = false override func awakeFromNib() { super.awakeFromNib() - if section == 1 && hasChild { + if section == 1 { + if isChild { + backgroundColor = UIColor.systemGray6 + } accessoryImageView.image = UIImage(systemName: "chevron.right") } } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - if section == 1 && hasChild { - accessoryImageView.image = selected ? UIImage(systemName: "chevron.down") : UIImage(systemName: "chevron.right") - } else { - accessoryImageView.image = selected ? UIImage(systemName: "checkmark") : nil + if section == 1 && !isChild { + DispatchQueue.main.async { [weak self] in + self?.accessoryImageView.image = selected ? UIImage(systemName: "chevron.down") : UIImage(systemName: "chevron.right") + } + } else { + DispatchQueue.main.async { [weak self] in + self?.accessoryImageView.image = selected ? UIImage(systemName: "checkmark") : nil + } } } func initCell(filter: Filter, section: Int) { self.titleLabel.text = filter.description self.section = section - self.hasChild = filter.hasChild + self.isChild = filter.isChild } } diff --git a/iOS/IssueTracker/IssueTracker/Issue/View/IssueListCollectionViewCell.xib b/iOS/IssueTracker/IssueTracker/Issue/View/IssueListCollectionViewCell.xib index 4311a543..abf59e7a 100644 --- a/iOS/IssueTracker/IssueTracker/Issue/View/IssueListCollectionViewCell.xib +++ b/iOS/IssueTracker/IssueTracker/Issue/View/IssueListCollectionViewCell.xib @@ -125,10 +125,10 @@ - + - + diff --git a/iOS/IssueTracker/IssueTracker/Issue/ViewController/IssueFilterViewController.swift b/iOS/IssueTracker/IssueTracker/Issue/ViewController/IssueFilterViewController.swift index ce518242..688fad29 100644 --- a/iOS/IssueTracker/IssueTracker/Issue/ViewController/IssueFilterViewController.swift +++ b/iOS/IssueTracker/IssueTracker/Issue/ViewController/IssueFilterViewController.swift @@ -8,13 +8,17 @@ import UIKit class IssueFilterViewController: UIViewController { - + @IBOutlet weak var filterTableView: UITableView! typealias FilterDataSource = UITableViewDiffableDataSource private var mainFilterContents = MainFilters().contents private var detailFilterContents = DetailFilters().contents private lazy var dataSource = createDataSource() + private var labelFilters = [Filter]() + private var milestonFilters = [Filter]() + private var writerFilters = [Filter]() + private var assigneeFilters = [Filter]() enum Section: Int, CustomStringConvertible { case main @@ -31,6 +35,15 @@ class IssueFilterViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() filterTableView.delegate = self + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reloadWriterFilters() + reloadLabelFilters() + reloadMilestoneFilters() + reloadAssigneeFilters() applySnapshot() } @@ -45,7 +58,6 @@ class IssueFilterViewController: UIViewController { } func applySnapshot() { - var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main, .detail]) snapshot.appendItems(mainFilterContents, toSection: .main) @@ -53,15 +65,65 @@ class IssueFilterViewController: UIViewController { self.dataSource.apply(snapshot) } + func reloadWriterFilters() { + NetworkManager.shared.getRequest(url: .user, type: UserArray.self) { result in + guard let userArray = result else { return } + self.writerFilters = userArray.userArray.map { + Filter(criteria: WriterCriteria(writer: $0.userName), description: $0.userName, isChild: true) + } + } + } + + func reloadLabelFilters() { + NetworkManager.shared.getRequest(url: .label, type: LabelArray.self) { result in + guard let labelArray = result else { return } + self.labelFilters = labelArray.labelArray.map { + Filter(criteria: LabelCriteria(labelId: $0.labelId), description: $0.labelName, isChild: true) + } + } + } + + func reloadMilestoneFilters() { + NetworkManager.shared.getRequest(url: .milestone, type: MilestoneArray.self) { result in + guard let milestoneArray = result else { return } + self.milestonFilters = milestoneArray.milestoneArray.map { + Filter(criteria: MilestoneCriteria(milestoneId: $0.milestoneId), description: $0.title, isChild: true) + } + } + } + func reloadAssigneeFilters() { + NetworkManager.shared.getRequest(url: .user, type: UserArray.self) { result in + guard let userArray = result else { return } + self.assigneeFilters = userArray.userArray.map { + Filter(criteria: AssignedCriteria(assignee: $0), description: $0.userName, isChild: true) + } + } + } + @IBAction func cancelButtonDidTouch(_ sender: Any) { dismiss(animated: true, completion: nil) } @IBAction func doneButtonDidTouch(_ sender: Any) { - // 처리 + var mainFilters = [Filterable]() + var detailFilters = [Filterable]() + + guard let indexPaths = filterTableView.indexPathsForSelectedRows else { + NotificationCenter.default.post(name: .issueFilterDidChange, object: nil, userInfo: ["filters": []]) + dismiss(animated: true, completion: nil) + return + } + indexPaths.forEach { indexPath in + guard let filter = dataSource.itemIdentifier(for: indexPath)?.criteria else { return } + if indexPath.section == 0 { + mainFilters.append(filter) + } else { + detailFilters.append(filter) + } + } + NotificationCenter.default.post(name: .issueFilterDidChange, object: nil, userInfo: ["mainFilters": mainFilters, "detailFilters": detailFilters]) dismiss(animated: true, completion: nil) } - } extension IssueFilterViewController: UITableViewDelegate { @@ -83,16 +145,35 @@ extension IssueFilterViewController: UITableViewDelegate { ]) return headerView } - + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 50 } - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let selectedItem = dataSource.itemIdentifier(for: indexPath), let cell = tableView.cellForRow(at: indexPath) as? FilterTableViewCell else { return } - if selectedItem.childItem.count > 0 { - detailFilterContents.insert(contentsOf: selectedItem.childItem, at: indexPath.row + 1) + if !selectedItem.isChild && indexPath.section == 1 { + switch selectedItem.criteria { + case is WriterCriteria: + reloadWriterFilters() + selectedItem.setChildren(childItems: writerFilters) + case is LabelCriteria: + reloadLabelFilters() + selectedItem.setChildren(childItems: labelFilters) + case is MilestoneCriteria: + reloadMilestoneFilters() + selectedItem.setChildren(childItems: milestonFilters) + case is AssignedCriteria: + reloadAssigneeFilters() + selectedItem.setChildren(childItems: assigneeFilters) + default: + break + } + } + + if selectedItem.childItems.count > 0 { + detailFilterContents.insert(contentsOf: selectedItem.childItems, at: indexPath.row + 1) applySnapshot() } cell.initCell(filter: selectedItem, section: indexPath.section) @@ -101,8 +182,8 @@ extension IssueFilterViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { guard let selectedItem = dataSource.itemIdentifier(for: indexPath), let cell = tableView.cellForRow(at: indexPath) as? FilterTableViewCell else { return } - if selectedItem.childItem.count > 0 { - let range = indexPath.row + 1...indexPath.row + selectedItem.childItem.count + if selectedItem.childItems.count > 0 { + let range = indexPath.row + 1...indexPath.row + selectedItem.childItems.count detailFilterContents.removeSubrange(range) applySnapshot() } diff --git a/iOS/IssueTracker/IssueTracker/Model/Filter.swift b/iOS/IssueTracker/IssueTracker/Model/Filter.swift index b13cc877..763d8f29 100644 --- a/iOS/IssueTracker/IssueTracker/Model/Filter.swift +++ b/iOS/IssueTracker/IssueTracker/Model/Filter.swift @@ -9,23 +9,29 @@ import Foundation class Filter: Hashable { static func == (lhs: Filter, rhs: Filter) -> Bool { - return lhs.description == rhs.description + return lhs.uuid == rhs.uuid && lhs.description == rhs.description } func hash(into hasher: inout Hasher) { - return hasher.combine(description) + hasher.combine(uuid) + hasher.combine(description) } - let criteria: filterable + let uuid = UUID() + let criteria: Filterable let description: String - let hasChild: Bool - let childItem: [Filter] + let isChild: Bool + var childItems: [Filter] - init(criteria: filterable, description: String, hasChild: Bool = false, childItem: [Filter] = []) { + init(criteria: Filterable, description: String, isChild: Bool = false, childItem: [Filter] = []) { self.criteria = criteria self.description = description - self.hasChild = hasChild - self.childItem = childItem + self.isChild = isChild + self.childItems = childItem + } + + func setChildren(childItems: [Filter]) { + self.childItems = childItems } } @@ -34,8 +40,7 @@ struct MainFilters { let contents: [Filter] = [ Filter(criteria: OpenCriteria(), description: "열린 이슈들"), Filter(criteria: CloseCriteria(), description: "닫힌 이슈들"), - - Filter(criteria: WriterCriteria(writer: User(userName: "me", social: "test")), description: "내가 작성한 이슈들"), + Filter(criteria: WriterCriteria(writer: "githubtest"), description: "내가 작성한 이슈들"), Filter(criteria: AssignedCriteria(assignee: User(userName: "me", social: "test")), description: "나한테 할당된 이슈들"), Filter(criteria: CommentCriteria(), description: "내가 댓글을 남긴 이슈들") ] @@ -44,25 +49,9 @@ struct MainFilters { struct DetailFilters { // 필터들 구현 필요 let contents: [Filter] = [ - Filter(criteria: WriterCriteria( - writer: User(userName: "me", social: "test")), - description: "작성자", - hasChild: true, - childItem: [Filter(criteria: CommentCriteria(), description: "테스트1"),Filter(criteria: CommentCriteria(), description: "테스트2."), Filter(criteria: CommentCriteria(), description: "테스트3")]), - Filter(criteria: WriterCriteria( - writer: User(userName: "me1", social: "test")), - description: "레이블", - hasChild: true, - childItem: [Filter(criteria: CommentCriteria(), description: "테스트1"), Filter(criteria: CommentCriteria(), description: "테스트2."), Filter(criteria: CommentCriteria(), description: "테스트3")]), - Filter(criteria: WriterCriteria( - writer: User(userName: "me2", social: "test")), - description: "마일스톤", - hasChild: true, - childItem: [Filter(criteria: CommentCriteria(), description: "테스트1"), Filter(criteria: CommentCriteria(), description: "테스트2."), Filter(criteria: CommentCriteria(), description: "테스트3")]), - Filter(criteria: WriterCriteria( - writer: User(userName: "me3", social: "test")), - description: "담당자", - hasChild: true, - childItem: [Filter(criteria: CommentCriteria(), description: "테스트1"), Filter(criteria: CommentCriteria(), description: "테스트2."), Filter(criteria: CommentCriteria(), description: "테스트3")]) + Filter(criteria: WriterCriteria(writer: "githubtest"), description: "작성자"), + Filter(criteria: LabelCriteria(labelId: -1), description: "레이블"), + Filter(criteria: MilestoneCriteria(milestoneId: -1), description: "마일스톤"), + Filter(criteria: WriterCriteria(writer: "githubtest"), description: "담당자") ] } diff --git a/iOS/IssueTracker/IssueTracker/Model/FilterCriteria.swift b/iOS/IssueTracker/IssueTracker/Model/FilterCriteria.swift index 794c8b9c..d231f0d3 100644 --- a/iOS/IssueTracker/IssueTracker/Model/FilterCriteria.swift +++ b/iOS/IssueTracker/IssueTracker/Model/FilterCriteria.swift @@ -7,39 +7,36 @@ import Foundation -protocol filterable { +protocol Filterable { func apply(issues: [Issue]) -> [Issue] } -class CloseCriteria: filterable { - +class CloseCriteria: Filterable { func apply(issues: [Issue]) -> [Issue] { return issues.filter { $0.isOpen == 0 } } } -class OpenCriteria: filterable { +class OpenCriteria: Filterable { func apply(issues: [Issue]) -> [Issue] { return issues.filter { $0.isOpen == 1 } } } -class WriterCriteria: filterable { - - let writer: User +class WriterCriteria: Filterable { + let writer: String - init(writer: User) { + init(writer: String) { self.writer = writer } func apply(issues: [Issue]) -> [Issue] { - return issues.filter { $0.writer == writer.userName } + return issues.filter { $0.writer == writer } } } -class AssignedCriteria: filterable { - +class AssignedCriteria: Filterable { let assignee: User init(assignee: User) { @@ -51,15 +48,14 @@ class AssignedCriteria: filterable { } } -class CommentCriteria: filterable { - +class CommentCriteria: Filterable { func apply(issues: [Issue]) -> [Issue] { //로직 추가하기 return issues } } -class TitleCriteria: filterable { +class TitleCriteria: Filterable { let inputText: String @@ -74,11 +70,10 @@ class TitleCriteria: filterable { } } -class AndCriteria: filterable { +class AndCriteria: Filterable { + let criterias: [Filterable] - let criterias: [filterable] - - init(criterias: [filterable]) { + init(criterias: [Filterable]) { self.criterias = criterias } @@ -87,38 +82,48 @@ class AndCriteria: filterable { } } -class OrCriteria: filterable { +class OrCriteria: Filterable { - let left: filterable - let right: filterable + let criterias: [Filterable] - init(left: filterable, right: filterable) { - self.left = left - self.right = right + init(criterias: [Filterable]) { + self.criterias = criterias } func apply(issues: [Issue]) -> [Issue] { - let leftResult = left.apply(issues: issues) - let rightResult = right.apply(issues: issues) - return Array(Set(leftResult + rightResult)).sorted(by: >) + let filterResults = criterias.map { $0.apply(issues: issues) } + let result = filterResults.reduce([]) { Array(Set($0 + $1))} + return result.sorted(by: <) } } -class LabelNameCriteria: filterable { +class LabelCriteria: Filterable { + + let labelId: Int - let name: String + init(labelId: Int) { + self.labelId = labelId + } + + func apply(issues: [Issue]) -> [Issue] { + return issues.filter { $0.labels.filter { $0.labelId == labelId }.count > 0 } + } +} + +class MilestoneCriteria: Filterable { + let milestoneId: Int - init(name: String) { - self.name = name + init(milestoneId: Int) { + self.milestoneId = milestoneId } func apply(issues: [Issue]) -> [Issue] { - return issues.filter { $0.labels.filter{ $0.labelName == name }.count > 0 } + return issues.filter { $0.milestoneId == milestoneId } } } -class EmptyCriteria: filterable { +class EmptyCriteria: Filterable { func apply(issues: [Issue]) -> [Issue] { return issues diff --git a/iOS/IssueTracker/IssueTracker/Model/Issue.swift b/iOS/IssueTracker/IssueTracker/Model/Issue.swift index 68c39f24..59376ad3 100644 --- a/iOS/IssueTracker/IssueTracker/Model/Issue.swift +++ b/iOS/IssueTracker/IssueTracker/Model/Issue.swift @@ -47,8 +47,8 @@ struct Issue: Codable, Hashable { return lhs.issueId == rhs.issueId } - static func > (lhs: Issue, rhs: Issue) -> Bool { - return lhs.issueId > rhs.issueId + static func < (lhs: Issue, rhs: Issue) -> Bool { + return lhs.issueId < rhs.issueId } func hash(into hasher: inout Hasher) { diff --git a/iOS/IssueTracker/IssueTracker/Model/User.swift b/iOS/IssueTracker/IssueTracker/Model/User.swift index 7f3ad771..a7f051f4 100644 --- a/iOS/IssueTracker/IssueTracker/Model/User.swift +++ b/iOS/IssueTracker/IssueTracker/Model/User.swift @@ -30,6 +30,6 @@ struct UserArray: Codable, Hashable { let userArray: [User] enum CodingKeys: String, CodingKey { - case userArray = "userArr" + case userArray } } diff --git a/iOS/IssueTracker/IssueTracker/Network/URLs.swift b/iOS/IssueTracker/IssueTracker/Network/URLs.swift index 50f144a2..d62742db 100644 --- a/iOS/IssueTracker/IssueTracker/Network/URLs.swift +++ b/iOS/IssueTracker/IssueTracker/Network/URLs.swift @@ -12,4 +12,5 @@ enum URLs: String { case issue = "http://101.101.217.148:8080/api/issue" case label = "http://101.101.217.148:8080/api/label" case milestone = "http://101.101.217.148:8080/api/milestone" + case user = "http://101.101.217.148:8080/api/user" }