diff --git a/App/Entities/JiraUser.swift b/App/Entities/JiraUser.swift new file mode 100644 index 0000000..9ce0bf6 --- /dev/null +++ b/App/Entities/JiraUser.swift @@ -0,0 +1,17 @@ +// +// JiraUser.swift +// Jirassic +// +// Created by Cristian Baluta on 30/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +struct JiraUser { + var url: String + var user: String + var password: String + var project: String + var issue: String +} diff --git a/App/Entities/Project.swift b/App/Entities/Project.swift new file mode 100644 index 0000000..2cd5efc --- /dev/null +++ b/App/Entities/Project.swift @@ -0,0 +1,25 @@ +// +// Project.swift +// Jirassic +// +// Created by Cristian Baluta on 20/12/2018. +// Copyright © 2018 Imagin soft. All rights reserved. +// + +import Foundation + +struct Project: Equatable { + + var objectId: String? + var lastModifiedDate: Date? + var title: String + var jiraBaseUrl: String? + var jiraUser: String? + var jiraProject: String? + var jiraIssue: String? + + var gitBaseUrls: [String] + var gitUsers: [String] + /// Git commits with this prefix are considered part of the project automatically + var taskNumberPrefix: String? +} diff --git a/App/Entities/Task.swift b/App/Entities/Task.swift index 63316c9..3211e78 100644 --- a/App/Entities/Task.swift +++ b/App/Entities/Task.swift @@ -8,9 +8,9 @@ import Foundation -// Never change the indexes because they are already stored in the database enum TaskType: Int { - + + /// Never change the indexes because they are already stored in the database case issue = 0 case startDay = 1 case scrum = 2 @@ -22,35 +22,57 @@ enum TaskType: Int { case coderev = 8 case endDay = 9 case calendar = 10 + case support = 11 var defaultNotes: String { switch self { - case .startDay: return "Working day started" - case .endDay: return "Working day ended" - case .scrum: return "Scrum meeting" - case .lunch: return "Lunch break" - case .meeting: return "Meeting" - case .waste: return "Social & Media" - case .learning: return "Learning session" - case .coderev: return "Reviewing and fixing code" - case .calendar: return "Calendar event" - default: return "" + case .startDay: return "Working day started" + case .endDay: return "Working day ended" + case .scrum: return "Scrum meeting" + case .lunch: return "Lunch break 🥑🥑🥑" + case .meeting: return "Meeting" + case .waste: return "Social & Media 👤👤👤" + case .learning: return "Learning session" + case .coderev: return "Reviewing and fixing code" + case .calendar: return "Calendar event" + case .support: return "Support" + default: return "" } } - // Used to group reports + /// Used to group reports together var defaultTaskNumber: String? { switch self { - case .scrum: return "scrum" - case .lunch: return "lunch" - case .meeting: return "meeting" - case .waste: return "waste" - case .learning: return "learning" - case .coderev: return "coderev" - case .calendar: return "meeting" - default: return nil + case .scrum: return "scrum" + case .lunch: return "lunch" + case .meeting: return "meeting" + case .waste: return "waste" + case .learning: return "learning" + case .coderev: return "coderev" + case .calendar: return "meeting" + case .support: return "support" + default: return nil } } + + var title: String { + switch self { + case .startDay: return "Start of day" + case .endDay: return "End of day" + case .issue: return "Task" + case .scrum: return "Scrum" + case .lunch: return "Food" + case .meeting: return "Meeting" + case .waste: return "Social & Media" + case .learning: return "Learning" + case .coderev: return "Code review" + case .support: return "Support" + case .gitCommit: return "Git commit" + default: return "" + } + } + + static var allValues: [TaskType] = [] } // Object representing a task @@ -72,6 +94,7 @@ struct Task { /// Created locally and used for matching with the remote object /// If objectId is missing means the task is not saved to db nor to server (eg. unsaved git and calendar items) var objectId: String? + var projectId: String? } extension Task { @@ -94,6 +117,10 @@ extension Task { self.taskType = type self.objectId = String.generateId() } + + var isSaved: Bool { + return objectId != nil + } } /// Object used to pass task data to and from the cell, for displaying and editing @@ -102,5 +129,6 @@ typealias TaskCreationData = ( dateEnd: Date, taskNumber: String?, notes: String?, - taskType: TaskType + taskType: TaskType, + projectId: String? ) diff --git a/App/Extensions/DateExtension.swift b/App/Extensions/DateExtension.swift index 8ed86b3..73282bf 100644 --- a/App/Extensions/DateExtension.swift +++ b/App/Extensions/DateExtension.swift @@ -12,6 +12,7 @@ let ymdUnitFlags: Set = [.year, .month, .day] let ymdhmsUnitFlags: Set = [.year, .month, .weekday, .day, .hour, .minute, .second] let gregorian = Calendar(identifier: Calendar.Identifier.gregorian) +/// Instantiate a date extension Date { init (hour: Int, minute: Int, second: Int = 0) { @@ -51,6 +52,7 @@ extension Date { } } +/// Format the date extension Date { func HHmmddMM() -> String { @@ -90,6 +92,12 @@ extension Date { return f.string(from: self) } + func E() -> String { + let f = DateFormatter() + f.dateFormat = "E" + return f.string(from: self) + } + // eg. Thursday, February 01 func EEEEMMMMdd() -> String { let f = DateFormatter() @@ -139,6 +147,12 @@ extension Date { f.dateFormat = "YYYY" return f.string(from: self) } + + func MMMyyyy() -> String { + let f = DateFormatter() + f.dateFormat = "MMM yyyy" + return f.string(from: self) + } } extension Date { @@ -182,10 +196,8 @@ extension Date { } func daysInMonth() -> Int { - - let daysRange = gregorian.range(of: Calendar.Component.day, in: Calendar.Component.month, for: self) - - return daysRange!.count as Int + let daysRange = gregorian.range(of: .day, in: .month, for: self) + return daysRange?.count ?? 0 } func components() -> (hour: Int, minute: Int) { @@ -234,10 +246,27 @@ extension Date { return gregorian.date(from: comps)! } + func dateByUpdating (day: Int) -> Date { + + var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) + comps.day = day + + return gregorian.date(from: comps)! + } + func dateByKeepingTime() -> Date { let comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) return Date().dateByUpdating(hour: comps.hour!, minute: comps.minute!) } + + func dateByKeepingTime(from date: Date) -> Date { + let comps = gregorian.dateComponents(ymdhmsUnitFlags, from: date) + return self.dateByUpdating(hour: comps.hour!, minute: comps.minute!) + } + + func dateByAddingMonths(_ numberOfMonths: Int) -> Date { + return Calendar.current.date(byAdding: .month, value: numberOfMonths, to: self) ?? self + } static func parseHHmm (_ hhmm: String) -> (hour: Int, min: Int) { let hm = hhmm.components(separatedBy: ":") diff --git a/App/Extensions/TableViewCell.swift b/App/Extensions/TableViewCell.swift index f537b0d..eb6437e 100644 --- a/App/Extensions/TableViewCell.swift +++ b/App/Extensions/TableViewCell.swift @@ -45,7 +45,7 @@ extension TableViewCell { // return UIStoryboard(name: name, bundle: nil).instantiateViewControllerWithIdentifier(self.className) as! T #else guard let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: className), owner: self) as? T else { - fatalError("Cell \(className) might not be registered in thsi tableView") + fatalError("Cell \(className) might not be registered in this tableView") } return cell #endif diff --git a/App/Metadata/ReadMetadataInteractor.swift b/App/Metadata/ReadMetadataInteractor.swift new file mode 100644 index 0000000..147f3eb --- /dev/null +++ b/App/Metadata/ReadMetadataInteractor.swift @@ -0,0 +1,44 @@ +// +// ReadMetadataInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 17/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit + +class ReadMetadataInteractor: RepositoryInteractor { + + init() { + super.init(repository: localRepository, remoteRepository: nil) + } + + func tasksLastSyncDate() -> Date? { + return repository.tasksLastSyncDate() + } + func projectsLastSyncDate() -> Date? { + return repository.projectsLastSyncDate() + } + + func tasksLastSyncToken() -> CKServerChangeToken? { + let stringToken = repository.tasksLastSyncToken() + return token(from: stringToken) + } + func projectsLastSyncToken() -> CKServerChangeToken? { + let stringToken = repository.projectsLastSyncToken() + return token(from: stringToken) + } + + private func token (from stringToken: String?) -> CKServerChangeToken? { + + guard let string = stringToken, + let data = Data(base64Encoded: string), + let token = NSKeyedUnarchiver.unarchiveObject(with: data) as? CKServerChangeToken else { + return nil + } + + return token + } +} diff --git a/App/Metadata/WriteMetadataInteractor.swift b/App/Metadata/WriteMetadataInteractor.swift new file mode 100644 index 0000000..aed1d6a --- /dev/null +++ b/App/Metadata/WriteMetadataInteractor.swift @@ -0,0 +1,41 @@ +// +// WriteMetadataInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 17/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit + +class WriteMetadataInteractor: RepositoryInteractor { + + init() { + super.init(repository: localRepository, remoteRepository: nil) + } + + func set (tasksLastSyncDate: Date?) { + repository.set(tasksLastSyncDate: tasksLastSyncDate) + } + func set (projectsLastSyncDate: Date?) { + repository.set(tasksLastSyncDate: projectsLastSyncDate) + } + + func set (tasksLastSyncToken: CKServerChangeToken?) { + repository.set(tasksLastSyncToken: string(from: tasksLastSyncToken)) + } + func set (projectsLastSyncToken: CKServerChangeToken?) { + repository.set(tasksLastSyncToken: string(from: projectsLastSyncToken)) + } + + private func string (from token: CKServerChangeToken?) -> String? { + + guard let token = token else { + return nil + } + let data = NSKeyedArchiver.archivedData(withRootObject: token) + let string = data.base64EncodedString() + return string + } +} diff --git a/App/Projects/ProjectInteractor.swift b/App/Projects/ProjectInteractor.swift new file mode 100644 index 0000000..246ceef --- /dev/null +++ b/App/Projects/ProjectInteractor.swift @@ -0,0 +1,66 @@ +// +// ProjectInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +class ProjectInteractor: RepositoryInteractor { + +// func queryTask (withId objectId: String) -> Task? { +// return repository.queryTask(withId: objectId) +// } + + func saveProject (_ project: Project, allowSyncing: Bool, completion: @escaping (_ savedProject: Project?) -> Void) { + + guard project.objectId != nil else { + fatalError("Cannot save a project without objectId") + } + var project = project + project.lastModifiedDate = nil + + self.repository.saveProject(project, completion: { [weak self] savedProject in + guard let localProject = savedProject else { + completion(nil) + return + } + if allowSyncing { + // We don't care if the task doesn't get saved to server + self?.syncProject(localProject, completion: { project in }) + } + completion(localProject) + }) + } + + func deleteProject (_ project: Project) { + + guard project.objectId != nil else { + fatalError("Cannot delete a task without objectId") + } + self.repository.deleteProject(project, permanently: false, completion: { success in + #if !CMD + if let remoteRepository = self.remoteRepository { + let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) +// sync.deleteProject(project, completion: { success in }) + } + #endif + }) + } + + private func syncProject (_ project: Project, completion: @escaping (_ uploadedTask: Task) -> Void) { + + #if !CMD + if let remoteRepository = self.remoteRepository { + let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) +// sync.uploadProject(project, completion: { (success) in +// DispatchQueue.main.async { +// completion(project) +// } +// }) + } + #endif + } +} diff --git a/App/Projects/ReadProjectsInteractor.swift b/App/Projects/ReadProjectsInteractor.swift new file mode 100644 index 0000000..a437820 --- /dev/null +++ b/App/Projects/ReadProjectsInteractor.swift @@ -0,0 +1,26 @@ +// +// ReadReportsInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import RCLog + +class ReadProjectsInteractor: RepositoryInteractor { + + // Return a list of all projects + func allProjects() -> [Project] { + return self.repository.projects() + } + + func allGitPaths() -> [String] { + return allProjects().flatMap({$0.gitBaseUrls}) + } + + func allGitUsers() -> [String] { + return allProjects().flatMap({$0.gitUsers}) + } +} diff --git a/App/Tasks/CloseDay.swift b/App/Tasks/CloseDayInteractor.swift similarity index 76% rename from App/Tasks/CloseDay.swift rename to App/Tasks/CloseDayInteractor.swift index 1df9c78..ef04960 100644 --- a/App/Tasks/CloseDay.swift +++ b/App/Tasks/CloseDayInteractor.swift @@ -9,30 +9,31 @@ import Foundation import RCLog -class CloseDay { +class CloseDayInteractor { func close (with tasks: [Task]) { + RCLog("Close day with \(tasks.count) tasks") guard tasks.count > 1 else { return } let interactor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) - // Find if the day ended already + /// Find if the day ended already let endDayTask: Task? = tasks.filter({$0.taskType == .endDay}).first - // If not, end it now + /// If not, end it now if endDayTask == nil { let endDayDate = tasks.last?.endDate ?? Date() let endDayTask = Task(endDate: endDayDate, type: .endDay) - interactor.saveTask(endDayTask, allowSyncing: true) { savedTask in } + interactor.saveTask(endDayTask, allowSyncing: true) { _ in } } - // Save to db only the tasks that are not already saved, like git commits and calendar events + /// Save to db only the tasks that are not already saved, like git commits and calendar events for task in tasks { - if task.objectId == nil { + if !task.isSaved { var task = task RCLog("Unsaved task found \(task)") task.objectId = String.generateId() - interactor.saveTask(task, allowSyncing: true) { savedTask in } + interactor.saveTask(task, allowSyncing: true) { _ in } } } } diff --git a/App/Tasks/ReadDaysInteractor.swift b/App/Tasks/ReadDaysInteractor.swift index 61a28be..90ead67 100644 --- a/App/Tasks/ReadDaysInteractor.swift +++ b/App/Tasks/ReadDaysInteractor.swift @@ -19,7 +19,7 @@ class ReadDaysInteractor: RepositoryInteractor { /// @parameters /// completion block will be called once with local tasks and once with updated tasks if remote had any changes to download func queryAll (_ completion: @escaping (_ weeks: [Week]) -> Void) { - query(startingDate: Date(timeIntervalSince1970: 0), completion: completion) + query(startingDate: Date(timeIntervalSince1970: 0), endingDate: Date(), completion: completion) } /// Query all startDay and endDay objects from the local repository @@ -27,31 +27,34 @@ class ReadDaysInteractor: RepositoryInteractor { /// @parameters /// startingDate - Query between this date and current date /// completion block - will be called once with local tasks and once with updated tasks if remote had any changes to download - func query (startingDate: Date, completion: @escaping (_ weeks: [Week]) -> Void) { + func query (startingDate: Date, endingDate: Date, completion: @escaping (_ weeks: [Week]) -> Void) { - queryLocalTasks(startDate: startingDate, endDate: Date()) { [weak self] (tasks: [Task]) in + queryLocalTasks(startDate: startingDate, endDate: endingDate) { [weak self] (tasks: [Task]) in - guard let _self = self else { + guard let self = self else { return } - _self.tasks = tasks - completion(_self.weeks()) + self.tasks = tasks + completion(self.createWeeks()) - if let remoteRepository = _self.remoteRepository { + guard let remoteRepository = self.remoteRepository else { + return + } - let sync = RCSync(localRepository: _self.repository, remoteRepository: remoteRepository) - sync.start { [weak self] hasIncomingChanges in - - guard let _self = self, hasIncomingChanges else { - return - } - // Delete dusplicate start day - RemoveDuplicate(repository: _self.repository, remoteRepository: _self.remoteRepository, date: Date()).execute() - // Fetch again the local tasks if they were updated - _self.queryLocalTasks(startDate: startingDate, endDate: Date()) { (tasks: [Task]) in - _self.tasks = tasks - completion(_self.weeks()) - } + let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) + sync.start { [weak self] hasIncomingChanges in + + WriteMetadataInteractor().set(tasksLastSyncDate: Date()) + + guard let self = self, hasIncomingChanges else { + return + } + // Delete dusplicate start day + RemoveDuplicate(repository: self.repository, remoteRepository: self.remoteRepository, date: Date()).execute() + // Fetch again the local tasks if they were updated + self.queryLocalTasks(startDate: startingDate, endDate: Date()) { (tasks: [Task]) in + self.tasks = tasks + completion(self.createWeeks()) } } } @@ -71,50 +74,75 @@ class ReadDaysInteractor: RepositoryInteractor { }) } - private func weeks() -> [Week] { + private func createWeeks() -> [Week] { - var objects = [Week]() + var weeks = [Week]() var referenceDate = Date.distantFuture for task in tasks { if !task.endDate.isSameWeekAs(referenceDate) { referenceDate = task.endDate - let obj = Week(date: task.endDate) - obj.days = days(ofWeek: obj) - objects.append(obj) + let week = Week(date: task.endDate) + week.days = createDaysFromAscendingTasks(ofWeek: week) + weeks.append(week) } } - return objects + return weeks } - private func days() -> [Day] { - - var objects = [Day]() - var obj: Day? - var referenceDate = Date.distantFuture - - for task in tasks { - if task.endDate.isSameDayAs(referenceDate) { - if task.taskType == .endDay { - let tempObj = objects.removeLast() - obj = Day(dateStart: tempObj.dateStart, dateEnd: task.endDate) - objects.append(obj!) - } - } else { - referenceDate = task.endDate - obj = Day(dateStart: task.endDate, dateEnd: nil) - objects.append(obj!) - } - } - - return objects - } +// private func createDays() -> [Day] { +// +// var objects = [Day]() +// var obj: Day? +// var referenceDate = Date.distantFuture +// +// for task in tasks { +// if task.endDate.isSameDayAs(referenceDate) { +// if task.taskType == .endDay { +// let tempObj = objects.removeLast() +// obj = Day(dateStart: tempObj.dateStart, dateEnd: task.endDate) +// objects.append(obj!) +// } +// } else { +// referenceDate = task.endDate +// obj = Day(dateStart: task.endDate, dateEnd: nil) +// objects.append(obj!) +// } +// } +// +// return objects +// } - private func sorted (tasks: [Task]) -> [Task] { - return tasks.sorted { (task1: Task, task2: Task) -> Bool in - return task1.endDate.compare(task2.endDate) == .orderedDescending + private func createDaysFromAscendingTasks (ofWeek week: Week) -> [Day] { + + var days = [Day]() + var activeDay: Day? + var referenceDate = Date.distantFuture + + for task in tasks { + guard task.endDate.isSameWeekAs(week.date) else { + continue + } + switch task.taskType { + case .startDay: + // New day found + referenceDate = task.endDate + activeDay = Day(dateStart: referenceDate, dateEnd: nil) + days.append(activeDay!) + case .endDay: + // End of day found. + guard task.endDate.isSameDayAs(referenceDate) else { + continue + } + let tempDay = days.removeLast() + activeDay = Day(dateStart: tempDay.dateStart, dateEnd: task.endDate) + days.append(activeDay!) + default: break + } } + + return days } private func days (ofWeek week: Week) -> [Day] { @@ -151,4 +179,10 @@ class ReadDaysInteractor: RepositoryInteractor { return objects } + + private func sorted (tasks: [Task]) -> [Task] { + return tasks.sorted { (task1: Task, task2: Task) -> Bool in + return task1.endDate.compare(task2.endDate) == .orderedAscending + } + } } diff --git a/App/Tasks/TaskInteractor.swift b/App/Tasks/TaskInteractor.swift index e792a0e..cace9b9 100644 --- a/App/Tasks/TaskInteractor.swift +++ b/App/Tasks/TaskInteractor.swift @@ -57,8 +57,11 @@ class TaskInteractor: RepositoryInteractor { #if !CMD if let remoteRepository = self.remoteRepository { let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) - sync.uploadTask(task, completion: { (success) in + sync.uploadTask(task, completion: { success, lastSyncDate in DispatchQueue.main.async { + if let date = lastSyncDate { + WriteMetadataInteractor().set(tasksLastSyncDate: date) + } completion(task) } }) diff --git a/App/Tasks/TaskTypeSelection.swift b/App/Tasks/TaskTypeSelection.swift index 64df7f1..671fd0b 100644 --- a/App/Tasks/TaskTypeSelection.swift +++ b/App/Tasks/TaskTypeSelection.swift @@ -22,6 +22,6 @@ class TaskTypeSelection { if let type = ListType(rawValue: UserDefaults.standard.integer(forKey: kLastSelectedTabKey)) { return type } - return ListType.allTasks + return ListType.tasks } } diff --git a/Delivery/macOS-cmd/main.swift b/Delivery/macOS-cmd/main.swift index 3eaeb11..c9e9a1e 100644 --- a/Delivery/macOS-cmd/main.swift +++ b/Delivery/macOS-cmd/main.swift @@ -9,7 +9,7 @@ import Foundation var shouldKeepRunning = true let theRL = RunLoop.current -let appVersion = "18.12.12" +let appVersion = "20.01.08" //while shouldKeepRunning && theRL.run(mode: .defaultRunLoopMode, before: .distantFuture) {} enum ArgType { @@ -20,6 +20,8 @@ enum ArgType { enum Command: String { case list = "list" case reports = "reports" + case start = "start" + case end = "end" case insert = "insert" case scrum = "scrum" case lunch = "lunch" @@ -32,11 +34,12 @@ enum Command: String { func printHelp() { print("") - print("jirassic \(appVersion) - (c)2018 Imagin soft") + print("jirassic \(appVersion) - (c)2020 Imagin soft") print("") print("Usage:") print(" list [yyyy.mm.dd] If date is missing list tasks from today") print(" reports [yyyy.mm.dd|yyyy.mm] [hours per day] If date is missing, list reports from today") + print(" start|end Current date is used") print(" insert -nr -notes -duration ") print(" scrum|lunch|meeting|waste|learning|coderev Duration in minutes") print("") @@ -74,7 +77,7 @@ guard arguments.count > 0 else { exit(0) } -func dayStarted() -> Bool { +func isDayStarted() -> Bool { let currentTasks = reader.tasksInDay(Date()) guard currentTasks.count > 0 else { @@ -100,7 +103,7 @@ func list (dayOnDate date: Date) { print("") } -func reports (forDay date: Date, targetHoursInDay: Double?) { +func listReports (forDay date: Date, targetHoursInDay: Double?) { print("") let tasks = reader.tasksInDay(date) @@ -119,7 +122,7 @@ func reports (forDay date: Date, targetHoursInDay: Double?) { print("") } -func reports (forMonth date: Date, targetHoursInDay: Double?) { +func listReports (forMonth date: Date, targetHoursInDay: Double?) { print("") print("Reports for the month of \(date.startOfMonth().MMMMdd()) - \(date.endOfMonth().MMMMdd()) \(date.YYYY())") @@ -128,7 +131,7 @@ func reports (forMonth date: Date, targetHoursInDay: Double?) { let tasks = reader.tasksInMonth(date) let monthReportsInteractor = CreateMonthReport() let duration: Double? = targetHoursInDay != nil ? targetHoursInDay!.hoursToSec : nil - let result = monthReportsInteractor.reports(fromTasks: tasks, targetHoursInDay: duration) + let result = monthReportsInteractor.reports(fromTasks: tasks, targetHoursInDay: duration, roundHours: true) if result.byTasks.count > 0 { let joined = monthReportsInteractor.joinReports(result.byTasks) print(joined.notes) @@ -142,7 +145,7 @@ func reports (forMonth date: Date, targetHoursInDay: Double?) { func insertIssue (arguments: [String]) { - guard dayStarted() else { + guard isDayStarted() else { return } @@ -198,26 +201,19 @@ func insertIssue (arguments: [String]) { func insert (taskType: Command, arguments: [String]) { - guard dayStarted() else { + guard isDayStarted() else { return } var task: Task? switch taskType { case .scrum: task = Task(endDate: Date(), type: .scrum) - break case .lunch: task = Task(endDate: Date(), type: .lunch) - break case .meeting: task = Task(endDate: Date(), type: .meeting) - break case .waste: task = Task(endDate: Date(), type: .waste) - break case .learning: task = Task(endDate: Date(), type: .learning) - break case .coderev: task = Task(endDate: Date(), type: .coderev) - break - default: - return + default: return } if let duration = arguments.first { @@ -236,6 +232,24 @@ func insert (taskType: Command, arguments: [String]) { print(taskType.rawValue.capitalized + " saved") } +func startDay() { + guard !isDayStarted() else { + let tasks = reader.tasksInDay(Date()) + let startDate = tasks.filter({$0.taskType == .startDay}).first?.endDate + print("Day was already started at \(startDate?.HHmm() ?? "...")") + return + } + let task = Task(endDate: Date(), type: .startDay) + let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: nil) + saveInteractor.saveTask(task, allowSyncing: false, completion: { _ in }) + + print("Day was started") +} + +func closeDay() { + print("Day cannot be closed because git commits and calendars will be lost, use the app to close the day!") +} + let commandStr = arguments.remove(at: 0) if let command = Command(rawValue: commandStr) { switch command { @@ -246,7 +260,7 @@ if let command = Command(rawValue: commandStr) { date = Date(YYYYMMddString: arg) } list (dayOnDate: date) - break + case .reports: var date = Date() var duration: Double? = nil @@ -254,25 +268,27 @@ if let command = Command(rawValue: commandStr) { duration = Double(arguments[1]) } if arguments.count > 0 { - let arg = arguments.remove(at: 0) - if arg.components(separatedBy: ".").count == 2 { - // We have only year and month. Add a day and call the month reports - date = Date(YYYYMMddString: "\(arg).01") - reports (forMonth: date, targetHoursInDay: duration) + let dateString = arguments.remove(at: 0) + if dateString.components(separatedBy: ".").count == 2 { + /// We have only year.month. Add a day and list the month reports + date = Date(YYYYMMddString: "\(dateString).01") + listReports (forMonth: date, targetHoursInDay: duration) break - } else if arg.components(separatedBy: ".").count == 3 { - // We have year, month and day - date = Date(YYYYMMddString: arg) + } else if dateString.components(separatedBy: ".").count == 3 { + /// We have year.month.day + date = Date(YYYYMMddString: dateString) } } - reports (forDay: date, targetHoursInDay: duration) - break + listReports (forDay: date, targetHoursInDay: duration) + + case .start: + startDay() + case .end: + closeDay() case .insert: insertIssue (arguments: arguments) - break case .scrum, .lunch, .meeting, .waste, .learning, .coderev: insert (taskType: command, arguments: arguments) - break case .version: print(appVersion) } diff --git a/Delivery/macOS/Animations/NoAnimation.swift b/Delivery/macOS/Animations/NoAnimation.swift new file mode 100644 index 0000000..c729ce0 --- /dev/null +++ b/Delivery/macOS/Animations/NoAnimation.swift @@ -0,0 +1,21 @@ +// +// NoAnimation.swift +// Jirassic +// +// Created by Cristian Baluta on 17/11/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class NoAnimation: NSObject { + + var animationReachedMiddle: (() -> ())? + var animationFinished: (() -> ())? + weak var layer: CALayer? + + func startWithLayer (_ layer: CALayer) { + self.animationReachedMiddle!() + self.animationFinished!() + } +} diff --git a/Delivery/macOS/App/AppDelegate.swift b/Delivery/macOS/App/AppDelegate.swift index a13178e..ec277de 100644 --- a/Delivery/macOS/App/AppDelegate.swift +++ b/Delivery/macOS/App/AppDelegate.swift @@ -9,6 +9,7 @@ import Cocoa import RCPreferences import RCLog +import RCHttp var localRepository: Repository! var remoteRepository: Repository? @@ -42,21 +43,25 @@ class AppDelegate: NSObject, NSApplicationDelegate { // pref.set("", forKey: .appVersion) // UserDefaults.standard.set(5, forKey: "wizardStep") // localPreferences.set(false, forKey: .enableGit) + RCHttp.loggingEnabled = true #else disableTraces() + RCHttp.loggingEnabled = false #endif self.window?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.floatingWindow))) localRepository = SqliteRepository() + Migrator.migrate() + #if APPSTORE if SettingsInteractor().getAppSettings().enableBackup { remoteRepository = CloudKitRepository() - remoteRepository?.getUser({ (user) in + remoteRepository?.getUser { user in if user == nil { remoteRepository = nil } - }) + } } // _ = Store.shared #else @@ -156,7 +161,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { taskNumber: nil, taskTitle: nil, taskType: .coderev, - objectId: String.generateId() + objectId: String.generateId(), + projectId: nil ) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in @@ -229,7 +235,7 @@ extension AppDelegate { popover.contentViewController = appWireframe.appViewController popover.animates = true appWireframe.removeCurrentController() - _ = appWireframe.presentTasksController() + _ = appWireframe.presentMainController() appWireframe.showPopover(popover, fromIcon: menu.iconView) } @@ -259,4 +265,3 @@ extension AppDelegate: NSUserNotificationCenterDelegate { return true } } - diff --git a/Delivery/macOS/App/AppWireframe.swift b/Delivery/macOS/App/AppWireframe.swift index 6f92e19..66fd8cc 100644 --- a/Delivery/macOS/App/AppWireframe.swift +++ b/Delivery/macOS/App/AppWireframe.swift @@ -8,11 +8,6 @@ import Cocoa -enum SplitViewColumn: Int { - case calendar = 0 - case tasks = 1 -} - class AppWireframe { private var _appViewController: AppViewController? @@ -30,7 +25,7 @@ class AppWireframe { return _appViewController! } - private var welcomeViewController: WelcomeViewController { + private func createWelcomeViewController() -> WelcomeViewController { let controller = WelcomeViewController.instantiateFromStoryboard("Welcome") controller.appWireframe = self @@ -38,7 +33,7 @@ class AppWireframe { return controller } - private var wizardViewController: WizardViewController { + private func createWizardViewController() -> WizardViewController { let controller = WizardViewController.instantiateFromStoryboard("Welcome") controller.appWireframe = self @@ -46,7 +41,7 @@ class AppWireframe { return controller } - private var loginViewController: LoginViewController { + private func createLoginViewController() -> LoginViewController { let controller = LoginViewController.instantiateFromStoryboard("Login") let presenter = LoginPresenter() @@ -57,23 +52,36 @@ class AppWireframe { return controller } - private var tasksViewController: TasksViewController { + private func createMainViewController() -> MainViewController { - let controller = TasksViewController.instantiateFromStoryboard("Tasks") - let presenter = TasksPresenter() - let interactor = TasksInteractor() + let controller = MainViewController.instantiateFromStoryboard("Main") + let presenter = MainPresenter() presenter.ui = controller - presenter.interactor = interactor presenter.appWireframe = self - interactor.presenter = presenter controller.presenter = presenter controller.appWireframe = self return controller } - private var taskSuggestionViewController: TaskSuggestionViewController { +// private var tasksViewController: TasksViewController { +// +// let controller = TasksViewController.instantiateFromStoryboard("Tasks") +// let presenter = TasksPresenter() +// let interactor = TasksInteractor() +// +// presenter.ui = controller +// presenter.interactor = interactor +// presenter.appWireframe = self +// interactor.presenter = presenter +// controller.presenter = presenter +// controller.appWireframe = self +// +// return controller +// } + + private func createTaskSuggestionViewController() -> TaskSuggestionViewController { let controller = TaskSuggestionViewController.instantiateFromStoryboard("Tasks") let presenter = TaskSuggestionPresenter() @@ -84,7 +92,7 @@ class AppWireframe { return controller } - private var settingsViewController: SettingsViewController { + private func createSettingsViewController() -> SettingsViewController { let controller = SettingsViewController.instantiateFromStoryboard("Settings") let presenter = SettingsPresenter() @@ -100,11 +108,11 @@ class AppWireframe { return controller } - private var placeholderViewController: PlaceholderViewController { + private func createPlaceholderViewController() -> PlaceholderViewController { return PlaceholderViewController.instantiateFromStoryboard("Placeholder") } - - private var worklogsViewController: WorklogsViewController { + + func createWorklogsViewController() -> WorklogsViewController { let controller = WorklogsViewController.instantiateFromStoryboard("Worklogs") let presenter = WorklogsPresenter() @@ -115,7 +123,6 @@ class AppWireframe { return controller } - } extension AppWireframe { @@ -158,7 +165,7 @@ extension AppWireframe { func presentWelcomeController() -> WelcomeViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) - let controller = self.welcomeViewController + let controller = createWelcomeViewController() addController(controller) currentController = controller @@ -168,7 +175,7 @@ extension AppWireframe { func presentWizardController() -> WizardViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) - let controller = self.wizardViewController + let controller = createWizardViewController() addController(controller) currentController = controller @@ -177,17 +184,17 @@ extension AppWireframe { func presentLoginController() -> LoginViewController { - let controller = self.loginViewController + let controller = createLoginViewController() addController(controller) currentController = controller return controller } - func presentTasksController() -> TasksViewController { + func presentMainController() -> MainViewController { - appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) - let controller = self.tasksViewController + appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 600)) + let controller = createMainViewController() addController(controller) currentController = controller @@ -197,7 +204,7 @@ extension AppWireframe { func presentTaskSuggestionController (startSleepDate: Date?, endSleepDate: Date) -> TaskSuggestionViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 450, height: 150)) - let controller = self.taskSuggestionViewController + let controller = createTaskSuggestionViewController() controller.startSleepDate = startSleepDate controller.endSleepDate = endSleepDate addController(controller) @@ -207,16 +214,16 @@ extension AppWireframe { } // Placeholder - func presentPlaceholder (_ message: MessageViewModel, intoSplitView splitView: NSSplitView) -> PlaceholderViewController { + func presentPlaceholder (_ message: MessageViewModel, in view: NSView) -> PlaceholderViewController { var controller = _placeholderViewController if controller == nil { - controller = self.placeholderViewController + controller = createPlaceholderViewController() appViewController.addChild(controller!) _placeholderViewController = controller } - splitView.subviews[SplitViewColumn.tasks.rawValue].addSubview(controller!.view) + view.addSubview(controller!.view) controller!.view.constrainToSuperview() controller!.viewModel = message @@ -233,7 +240,7 @@ extension AppWireframe { // EndDay func presentEndDayController (date: Date, tasks: [Task]) -> WorklogsViewController { - let controller = self.worklogsViewController + let controller = createWorklogsViewController() controller.date = date controller.tasks = tasks addController(controller) @@ -253,14 +260,14 @@ extension AppWireframe { extension AppWireframe { - func flipToTasksController() { + func flipToMainController() { - let tasksController = self.tasksViewController - let flip = FlipAnimation() + let mainController = createMainViewController() + let flip = NoAnimation() flip.animationReachedMiddle = { self.removeCurrentController() - self.addController(tasksController) - self.currentController = tasksController + self.addController(mainController) + self.currentController = mainController } flip.animationFinished = {} flip.startWithLayer(layerToAnimate()) @@ -268,23 +275,27 @@ extension AppWireframe { func flipToSettingsController() { - let settingsController = self.settingsViewController - let flip = FlipAnimation() - flip.animationReachedMiddle = { - self.removeCurrentController() - self.removePlaceholder() - self.removeEndDayController() - self.addController(settingsController) - self.currentController = settingsController - } - flip.animationFinished = {} - flip.startWithLayer(layerToAnimate()) + let settingsController = createSettingsViewController() + let window = NSWindow(contentViewController: settingsController) + window.title = "Jirassic settings" + window.level = .popUpMenu + window.makeKeyAndOrderFront(nil) +// let flip = NoAnimation() +// flip.animationReachedMiddle = { +// self.removeCurrentController() +// self.removePlaceholder() +// self.removeEndDayController() +// self.addController(settingsController) +// self.currentController = settingsController +// } +// flip.animationFinished = {} +// flip.startWithLayer(layerToAnimate()) } func flipToLoginController() { - let loginController = self.loginViewController - let flip = FlipAnimation() + let loginController = createLoginViewController() + let flip = NoAnimation() flip.animationReachedMiddle = { self.removeController(self.currentController!) self.addController(loginController) @@ -296,8 +307,8 @@ extension AppWireframe { func flipToWizardController() { - let wizardController = self.wizardViewController - let flip = FlipAnimation() + let wizardController = createWizardViewController() + let flip = NoAnimation() flip.animationReachedMiddle = { self.removeCurrentController() self.removePlaceholder() diff --git a/Delivery/macOS/App/LocalPreferences.swift b/Delivery/macOS/App/LocalPreferences.swift index 41ea938..eca0e91 100644 --- a/Delivery/macOS/App/LocalPreferences.swift +++ b/Delivery/macOS/App/LocalPreferences.swift @@ -25,8 +25,6 @@ enum LocalPreferences: String, RCPreferencesProtocol { case settingsJiraProjectIssueKey = "settingsJiraProjectIssueKey" case settingsHookupCmdName = "settingsHookupCmdName" case settingsHookupAppName = "settingsHookupAppName" - case settingsGitPaths = "settingsGitPaths" - case settingsGitAuthors = "settingsGitAuthors" case settingsSelectedCalendars = "settingsSelectedCalendars" case enableGit = "enableGit" case enableJit = "enableJit" @@ -52,8 +50,6 @@ enum LocalPreferences: String, RCPreferencesProtocol { case .settingsJiraProjectIssueKey:return "" case .settingsHookupCmdName: return "" case .settingsHookupAppName: return "" - case .settingsGitPaths: return "" - case .settingsGitAuthors: return "" case .settingsSelectedCalendars:return "Work,Calendar" case .enableGit: return false case .enableJit: return true diff --git a/Delivery/macOS/App/Migrator.swift b/Delivery/macOS/App/Migrator.swift new file mode 100644 index 0000000..074b407 --- /dev/null +++ b/Delivery/macOS/App/Migrator.swift @@ -0,0 +1,35 @@ +// +// Migrator.swift +// Jirassic +// +// Created by Cristian Baluta on 19/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit + +class Migrator { + + static func migrate() { + + if let lastSyncDateWithRemote = UserDefaults.standard.object(forKey: "localChangeDate") as? Date { + WriteMetadataInteractor().set(tasksLastSyncDate: lastSyncDateWithRemote) + UserDefaults.standard.removeObject(forKey: "localChangeDate") + } + + if let data = UserDefaults.standard.value(forKey: "ChangeToken") as? Data, + let token = NSKeyedUnarchiver.unarchiveObject(with: data) as? CKServerChangeToken { + + WriteMetadataInteractor().set(tasksLastSyncToken: token) + UserDefaults.standard.removeObject(forKey: "ChangeToken") + } + + if let _ = UserDefaults.standard.object(forKey: "RCPreferences-settingsGitPaths") as? String { + UserDefaults.standard.removeObject(forKey: "RCPreferences-settingsGitPaths") + } + if let _ = UserDefaults.standard.object(forKey: "RCPreferences-settingsGitAuthors") as? String { + UserDefaults.standard.removeObject(forKey: "RCPreferences-settingsGitAuthors") + } + } +} diff --git a/Delivery/macOS/Components/Components.storyboard b/Delivery/macOS/Components/Components.storyboard index 5fb6d6b..b70979a 100644 --- a/Delivery/macOS/Components/Components.storyboard +++ b/Delivery/macOS/Components/Components.storyboard @@ -1,291 +1,11 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Usually Jira task ids which are of form LETTER-NUMBER, but can be anything really, important is that same task should have same id - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -302,14 +22,14 @@ - + - + @@ -321,7 +41,7 @@ - + @@ -358,7 +78,7 @@ - + diff --git a/Delivery/macOS/Components/GitUsersViewController.swift b/Delivery/macOS/Components/GitUsersViewController.swift index 4933a80..7fc3ec8 100644 --- a/Delivery/macOS/Components/GitUsersViewController.swift +++ b/Delivery/macOS/Components/GitUsersViewController.swift @@ -7,7 +7,6 @@ // import Cocoa -import RCPreferences class GitUsersViewController: NSViewController { @@ -15,16 +14,19 @@ class GitUsersViewController: NSViewController { @IBOutlet private weak var tableView: NSTableView! @IBOutlet private weak var doneButton: NSButton! - var onDone: (() -> Void)? - - private let pref = RCPreferences() private let gitModule = ModuleGitLogs() - private var users: [GitUser] = [] + private var gitUsers: [GitUser] = [] + var selectedUsers: [String] = [] { + didSet { + tableView.reloadData() + } + } + var onDone: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() gitModule.fetchUsers { [weak self] users in - self?.users = users + self?.gitUsers = users self?.tableView.reloadData() } tableView.headerView = nil @@ -38,7 +40,7 @@ class GitUsersViewController: NSViewController { extension GitUsersViewController: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { - return users.count + return gitUsers.count } } @@ -46,11 +48,10 @@ extension GitUsersViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - let user = users[row] + let user = gitUsers[row] if (tableColumn?.identifier)?.rawValue == "isSelected" { - let allowedAuthors: [String] = pref.string(.settingsGitAuthors).split(separator: ",").map { String($0) } - let isSelected = allowedAuthors.contains(user.email) + let isSelected = selectedUsers.contains(user.email) return NSNumber(booleanLiteral: isSelected) } if (tableColumn?.identifier)?.rawValue == "email" { @@ -61,19 +62,17 @@ extension GitUsersViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { - let user = users[row] + let user = gitUsers[row] if (tableColumn?.identifier)?.rawValue == "isSelected" { - var allowedAuthors: [String] = pref.string(.settingsGitAuthors).split(separator: ",").map { String($0) } guard let isSelected = (object as? NSNumber)?.boolValue else { return } if isSelected { - allowedAuthors.append(user.email) + selectedUsers.append(user.email) } else { - allowedAuthors = allowedAuthors.filter({$0 != user.email}) + selectedUsers = selectedUsers.filter({$0 != user.email}) } - pref.set(allowedAuthors.joined(separator: ","), forKey: .settingsGitAuthors) } } } diff --git a/Delivery/macOS/External/AppleScript.swift b/Delivery/macOS/External/AppleScript.swift index e9df4e2..2bfe83a 100644 --- a/Delivery/macOS/External/AppleScript.swift +++ b/Delivery/macOS/External/AppleScript.swift @@ -96,7 +96,7 @@ class AppleScript: AppleScriptProtocol { let validJson = rawJson.replacingOccurrences(of: "'", with: "\"") var dict: [String: String] = [:] if let data = validJson.data(using: String.Encoding.utf8) { - if let d = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String] { + if let d = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]) as [String : String]??) { if let _d = d { dict = _d } diff --git a/Delivery/macOS/Images.xcassets/EditIcon.imageset/Contents.json b/Delivery/macOS/Images.xcassets/EditIcon.imageset/Contents.json new file mode 100644 index 0000000..88611c1 --- /dev/null +++ b/Delivery/macOS/Images.xcassets/EditIcon.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "icons8-edit-33.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "icons8-edit-32.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-32.png b/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-32.png new file mode 100644 index 0000000..25beea1 Binary files /dev/null and b/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-32.png differ diff --git a/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-33.png b/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-33.png new file mode 100644 index 0000000..25beea1 Binary files /dev/null and b/Delivery/macOS/Images.xcassets/EditIcon.imageset/icons8-edit-33.png differ diff --git a/Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json b/Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json index 345adc5..537070c 100644 --- a/Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json +++ b/Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json @@ -1,17 +1,14 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "GitIcon.png", + "idiom" : "mac", + "filename" : "icons8-commit-git-81.png", "scale" : "1x" }, { - "idiom" : "universal", + "idiom" : "mac", + "filename" : "icons8-commit-git-80.png", "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" } ], "info" : { diff --git a/Delivery/macOS/Images.xcassets/GitIcon.imageset/GitIcon.png b/Delivery/macOS/Images.xcassets/GitIcon.imageset/GitIcon.png deleted file mode 100644 index dfcfd8a..0000000 Binary files a/Delivery/macOS/Images.xcassets/GitIcon.imageset/GitIcon.png and /dev/null differ diff --git a/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-80.png b/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-80.png new file mode 100644 index 0000000..a6298be Binary files /dev/null and b/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-80.png differ diff --git a/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-81.png b/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-81.png new file mode 100644 index 0000000..a6298be Binary files /dev/null and b/Delivery/macOS/Images.xcassets/GitIcon.imageset/icons8-commit-git-81.png differ diff --git a/Delivery/macOS/Modules/GitLogs/GitUserParser.swift b/Delivery/macOS/Modules/GitLogs/GitUserParser.swift index 2da910c..908f7ce 100644 --- a/Delivery/macOS/Modules/GitLogs/GitUserParser.swift +++ b/Delivery/macOS/Modules/GitLogs/GitUserParser.swift @@ -23,19 +23,22 @@ class GitUserParser { let r = raw.replacingOccurrences(of: "\r", with: "\n") let results = r.split(separator: "\n").map { String($0) } for result in results { - if result != "" { - users.append( self.parseUser(result) ) + if let user = parseUser(result) { + users.append(user) } } return users } - private func parseUser (_ user: String) -> GitUser { + private func parseUser (_ user: String) -> GitUser? { var comps = user.split(separator: ";").map { String($0) } - let name = comps.count > 0 ? comps.removeFirst() : "" - let email = comps.count > 0 ? comps.removeFirst() : "" + guard comps.count >= 2 else { + return nil + } + let name = comps.removeFirst() + let email = comps.removeFirst() return GitUser(name: name, email: email) } diff --git a/Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift b/Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift index 1cad883..bf15862 100644 --- a/Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift +++ b/Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift @@ -7,13 +7,12 @@ // import Foundation -import RCPreferences import RCLog class ModuleGitLogs { private let extensions = ExtensionsInteractor() - private let pref = RCPreferences() + private let projectsInteractor = ReadProjectsInteractor(repository: localRepository, remoteRepository: nil) // func isReachable (completion: @escaping (Bool) -> Void) { // checkIfGitInstalled(completion: completion) @@ -39,9 +38,9 @@ class ModuleGitLogs { } /// Returns a list of commiters emails - func fetchUsers(completion: @escaping (([GitUser]) -> Void)) { + func fetchUsers (completion: @escaping (([GitUser]) -> Void)) { - let paths = pref.string(.settingsGitPaths).split(separator: ",").map { String($0) } + let paths = projectsInteractor.allGitPaths() users(paths: paths, previousUsers: []) { gitUsers in completion(gitUsers) } @@ -75,7 +74,7 @@ class ModuleGitLogs { } let interval = dates.removeFirst() - let paths = pref.string(.settingsGitPaths).split(separator: ",").map { String($0) } + let paths = projectsInteractor.allGitPaths() logs(dateStart: interval.dateStart, dateEnd: interval.dateEnd, paths: paths, previousCommits: []) { commits in for commit in commits { // Sometimes git returns commits that are not in the provided interval, filter them out @@ -125,7 +124,7 @@ class ModuleGitLogs { } paths.removeFirst() - let allowedAuthors: [String] = pref.string(.settingsGitAuthors).split(separator: ",").map { String($0) } + let allowedAuthors = projectsInteractor.allGitUsers() getGitLogs(at: path, dateStart: dateStart, dateEnd: dateEnd, completion: { rawResults in diff --git a/Delivery/macOS/Modules/Hookup/ModuleHookup.swift b/Delivery/macOS/Modules/Hookup/ModuleHookup.swift index 5611583..f35a2a6 100644 --- a/Delivery/macOS/Modules/Hookup/ModuleHookup.swift +++ b/Delivery/macOS/Modules/Hookup/ModuleHookup.swift @@ -48,7 +48,7 @@ class ModuleHookup { completion?(false) return } - guard let jdict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String], + guard let jdict = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]) as [String : String]??), let dict = jdict else { completion?(false) return diff --git a/Delivery/macOS/Screens/Account/LoginPresenter.swift b/Delivery/macOS/Screens/Account/LoginPresenter.swift index f0f97a4..24a5244 100644 --- a/Delivery/macOS/Screens/Account/LoginPresenter.swift +++ b/Delivery/macOS/Screens/Account/LoginPresenter.swift @@ -35,7 +35,7 @@ extension LoginPresenter: LoginPresenterInput { let login = UserInteractor(repository: repository, remoteRepository: remoteRepository) login.onLoginSuccess = { self.userInterface?.showLoadingIndicator(false) - _ = self.appWireframe?.presentTasksController() + _ = self.appWireframe?.presentMainController() } login.onLoginFailure = { self.userInterface?.showLoadingIndicator(false) @@ -45,6 +45,6 @@ extension LoginPresenter: LoginPresenterInput { } func cancelScreen() { - _ = appWireframe?.presentTasksController() + _ = appWireframe?.presentMainController() } } diff --git a/Delivery/macOS/Screens/Calendar/Calendar.storyboard b/Delivery/macOS/Screens/Calendar/Calendar.storyboard new file mode 100644 index 0000000..14469b2 --- /dev/null +++ b/Delivery/macOS/Screens/Calendar/Calendar.storyboard @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Calendar/CalendarDayCellView.swift b/Delivery/macOS/Screens/Calendar/CalendarDayCellView.swift new file mode 100644 index 0000000..200d460 --- /dev/null +++ b/Delivery/macOS/Screens/Calendar/CalendarDayCellView.swift @@ -0,0 +1,107 @@ +// +// CalendarDayView.swift +// Jirassic +// +// Created by Cristian Baluta on 25/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class CalendarDayCellView: NSView { + + private var dayText: NSTextField + private var weekdayText: NSTextField + private var bulletText: NSTextField + private var backgroundView: NSBox + + override init(frame frameRect: NSRect) { + + let w = frameRect.size.width + let h = frameRect.size.height + + backgroundView = NSBox(frame: NSRect(x: 0, y: 0, width: w, height: h)) + backgroundView.boxType = .custom + backgroundView.borderType = .noBorder + backgroundView.cornerRadius = 10 + backgroundView.fillColor = .darkGray + + dayText = NSTextField(frame: NSRect(x: -4, y: 0, width: w+8, height: w)) + dayText.font = NSFont.systemFont(ofSize: 9) + dayText.alignment = .center + dayText.backgroundColor = NSColor.clear + dayText.isBordered = false + dayText.isEditable = false + + weekdayText = NSTextField(frame: NSRect(x: 0, y: w, width: w, height: w)) + weekdayText.font = NSFont.systemFont(ofSize: 9) + weekdayText.alignment = .center + weekdayText.backgroundColor = NSColor.clear + weekdayText.isBordered = false + weekdayText.isEditable = false + + bulletText = NSTextField(frame: NSRect(x: 0, y: -10, width: w, height: w)) + bulletText.font = NSFont.systemFont(ofSize: 9) + bulletText.alignment = .center + bulletText.backgroundColor = NSColor.clear + bulletText.isBordered = false + bulletText.isEditable = false + + super.init(frame: frameRect) + self.addSubview(backgroundView) + self.addSubview(dayText) + self.addSubview(weekdayText) + self.addSubview(bulletText) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var day: Int = 0 { + didSet { + dayText.stringValue = "\(day)" + } + } + + var weekday: String = "" { + didSet { + weekdayText.stringValue = weekday + } + } + + var isSelected: Bool = false { + didSet { +// backgroundView.isHidden = !isSelected + backgroundView.fillColor = isSelected ? .darkGray : .clear + dayText.font = isSelected ? NSFont.boldSystemFont(ofSize: 10) : NSFont.systemFont(ofSize: 9) + dayText.textColor = isSelected ? .white : .labelColor + weekdayText.textColor = isSelected ? .white : .labelColor + } + } + + var isStarted: Bool = false { + didSet { + dayText.alphaValue = isStarted ? 1.0 : 0.4 + weekdayText.alphaValue = isStarted ? 1.0 : 0.4 + } + } + + var isToday: Bool = false { + didSet { + backgroundView.borderType = isToday ? .lineBorder : .noBorder + } + } + + var isEnded: Bool = false { + didSet { + bulletText.stringValue = isEnded ? "•" : "" + } + } + + var onClick: (() -> Void)? + + override func mouseDown(with event: NSEvent) { + onClick?() + } +} diff --git a/Delivery/macOS/Screens/Calendar/CalendarInteractor.swift b/Delivery/macOS/Screens/Calendar/CalendarInteractor.swift new file mode 100644 index 0000000..ffe0e36 --- /dev/null +++ b/Delivery/macOS/Screens/Calendar/CalendarInteractor.swift @@ -0,0 +1,41 @@ +// +// CalendarInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation +import RCLog + +protocol CalendarInteractorInput: class { + + func loadCalendar(date: Date) +} + +protocol CalendarInteractorOutput: class { + + func calendarDidLoad (_ weeks: [Week]) +} + +class CalendarInteractor { + + weak var presenter: CalendarInteractorOutput? + private let daysReader: ReadDaysInteractor! + + init() { + daysReader = ReadDaysInteractor(repository: localRepository, remoteRepository: remoteRepository) + } +} + +extension CalendarInteractor: CalendarInteractorInput { + + func loadCalendar(date: Date) { + + daysReader.query(startingDate: date.startOfMonth(), endingDate: date.endOfMonth()) { [weak self] weeks in + self?.presenter?.calendarDidLoad(weeks) + } + } + +} diff --git a/Delivery/macOS/Screens/Calendar/CalendarPresenter.swift b/Delivery/macOS/Screens/Calendar/CalendarPresenter.swift new file mode 100644 index 0000000..88f7591 --- /dev/null +++ b/Delivery/macOS/Screens/Calendar/CalendarPresenter.swift @@ -0,0 +1,89 @@ +// +// CalendarPresenter.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation + +protocol CalendarPresenterInput: class { + func reloadData() + func goPrevMonth() + func goNextMonth() + var selectedMonth: Date { get } +} + +protocol CalendarPresenterOutput: class { + func addCell (at index: Int, day: Day, isStarted: Bool) + func clearCells() + func showMonthName (_ name: String) +} + +class CalendarPresenter { + + weak var appWireframe: AppWireframe? + weak var ui: CalendarPresenterOutput? + var interactor: CalendarInteractorInput? + var selectedMonth: Date = Date() { + didSet { + reloadData() + } + } +} + +extension CalendarPresenter: CalendarPresenterInput { + + func reloadData() { + ui!.showMonthName(selectedMonth.MMMyyyy()) + ui!.clearCells() + interactor!.loadCalendar(date: selectedMonth) + } + + func goPrevMonth() { + selectedMonth = selectedMonth.dateByAddingMonths(-1) + } + + func goNextMonth() { + selectedMonth = selectedMonth.dateByAddingMonths(1) + } +} + +extension CalendarPresenter: CalendarInteractorOutput { + + func calendarDidLoad(_ weeks: [Week]) { + DispatchQueue.main.async { + let existingDays = weeks.flatMap({$0.days}) + let firstDate = existingDays.first?.dateStart ?? self.selectedMonth + var days = [(Day, Bool)]() + for i in 1...firstDate.daysInMonth() { + if let day = existingDays.filter({$0.dateStart.day() == i}).first { + days.append((day, true)) + } else { + let day = Day(dateStart: firstDate.dateByUpdating(day: i), dateEnd: nil) + days.append((day, false)) + } + } + var i = 0 + var isWeekend = false + for day in days { + if day.0.dateStart.day() == 1 && day.0.dateStart.isWeekend() { + isWeekend = false + continue + } + if isWeekend && day.0.dateStart.isWeekend() { + isWeekend = false + continue + } + if day.0.dateStart.isWeekend() && !day.0.dateStart.isToday() { + i += 1 + isWeekend = true + } else { + self.ui!.addCell(at: i, day: day.0, isStarted: day.1) + i += 1 + } + } + } + } +} diff --git a/Delivery/macOS/Screens/Calendar/CalendarScrollView.swift b/Delivery/macOS/Screens/Calendar/CalendarScrollView.swift deleted file mode 100644 index 3f57ed8..0000000 --- a/Delivery/macOS/Screens/Calendar/CalendarScrollView.swift +++ /dev/null @@ -1,155 +0,0 @@ -// -// DatesScrollView.swift -// Jirassic -// -// Created by Baluta Cristian on 30/12/15. -// Copyright © 2015 Cristian Baluta. All rights reserved. -// - -import Cocoa - -class CalendarScrollView: NSScrollView { - - @IBOutlet fileprivate var outlineView: NSOutlineView? - var _weeks = [Week]() - var weeks: [Week] { - get { - return _weeks - } - set { - _weeks = newValue - guard let firstWeek = _weeks.first else { - return - } - let now = Date() - if firstWeek.date.isSameWeekAs(now) { - if let firstDay = firstWeek.days.first { - if !firstDay.dateStart.isSameDayAs(now) { - _weeks[0].days.insert(Day(dateStart: now, dateEnd: nil), at: 0) - } - } - } else { - let week = Week(date: now) - week.days.append( Day(dateStart: now, dateEnd: nil) ) - _weeks.insert(week, at: 0) - } - } - } - var didSelectDay: ((_ day: Day) -> ())? - var selectedDay: Day? - - override func awakeFromNib() { - super.awakeFromNib() - outlineView?.dataSource = self - outlineView?.delegate = self - } - - func reloadData() { - self.outlineView?.reloadData() - self.outlineView?.expandItem(nil, expandChildren: true) - } - - func selectDay (_ dayToSelect: Day) { - - var i = -1 - for week in weeks { - i += 1 - for day in week.days { - i += 1 - if day.dateStart.isSameDayAs(dayToSelect.dateStart) { - let indexSet = IndexSet(integer: i) - outlineView?.selectRowIndexes(indexSet, byExtendingSelection: true) - break - } - } - } - } -} - -extension CalendarScrollView: NSOutlineViewDataSource { - - func outlineView (_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - - if let item: AnyObject = item as AnyObject? { - switch item { - case let week as Week: - return week.days[index] - default: - return self - } - } else { - return weeks[index] - } - } - - func outlineView (_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - - switch item { - case let week as Week: - return week.days.count > 0 - default: - return false - } - } - - func outlineView (_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - - if let item: AnyObject = item as AnyObject? { - switch item { - case let week as Week: - return week.days.count - default: - return 0 - } - } else { - return weeks.count - } - } -} - -extension CalendarScrollView: NSOutlineViewDelegate { - - func outlineView (_ outlineView: NSOutlineView, viewFor viewForTableColumn: NSTableColumn?, item: Any) -> NSView? { - - switch item { - case let week as Week: - - let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "HeaderCell"), owner: self) as! NSTableCellView - if let textField = view.textField { - textField.font = NSFont.boldSystemFont(ofSize: 14) - textField.stringValue = week.date.weekInterval() - } - return view - - case let day as Day: - - let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as! NSTableCellView - if let textField = view.textField { - textField.font = NSFont.boldSystemFont(ofSize: 12) - textField.textColor = day.dateEnd == nil ? NSColor.lightGray : NSColor.darkGray - textField.stringValue = day.dateStart.isToday() ? "Today" : day.dateStart.ddEEE() - } - - return view - - default: - return nil - } - } - - func outlineView (_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { - return item is Week - } - - func outlineViewSelectionDidChange (_ notification: Notification) { - - if let outlineView = notification.object as? NSOutlineView { - let selectedRow = outlineView.selectedRow - - if let selectedObject = outlineView.item(atRow: selectedRow) as? Day { - selectedDay = selectedObject - didSelectDay?(selectedObject) - } - } - } -} diff --git a/Delivery/macOS/Screens/Calendar/CalendarViewController.swift b/Delivery/macOS/Screens/Calendar/CalendarViewController.swift new file mode 100644 index 0000000..285e10d --- /dev/null +++ b/Delivery/macOS/Screens/Calendar/CalendarViewController.swift @@ -0,0 +1,94 @@ +// +// CalendarViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class CalendarViewController: NSViewController { + + @IBOutlet var monthTextField: NSTextField! + + weak var appWireframe: AppWireframe? + var presenter: CalendarPresenterInput? + + var cells: [CalendarDayCellView] = [] + + var didChangeDay: ((_ day: Day) -> Void)? + var didChangeMonth: ((_ date: Date) -> Void)? + + private var day: Day? = Day(dateStart: Date().startOfDay(), dateEnd: nil) + var selectedDay: Day? { + get { + return day + } + set { + day = newValue + if let d = day { + selectCell(day: d) + } + } + } + + override func viewDidAppear() { + super.viewDidAppear() + reloadData() + } + + func reloadData() { + presenter!.reloadData() + didChangeDay?(day!) + } + + private func selectCell (day: Day) { + for cell in cells { + cell.isSelected = cell.day == day.dateStart.day() + } + } + + @IBAction func handlePrevMonth (_ sender: NSButton) { + presenter!.goPrevMonth() + didChangeMonth?(presenter!.selectedMonth) + } + + @IBAction func handleNextMonth (_ sender: NSButton) { + presenter!.goNextMonth() + didChangeMonth?(presenter!.selectedMonth) + } +} + +extension CalendarViewController: CalendarPresenterOutput { + + func addCell (at index: Int, day: Day, isStarted: Bool) { + let w = 20 + let y = 4 + + let cell = CalendarDayCellView(frame: NSRect(x: index*w, y: y, width: w, height: w+w+6)) + cell.day = day.dateStart.day() + cell.weekday = "\(day.dateStart.E())" + cell.isSelected = day.dateStart.isSameDayAs(selectedDay?.dateStart ?? Date()) + cell.isStarted = isStarted + cell.isEnded = day.dateEnd != nil + cell.isToday = day.dateStart.isToday() + cell.onClick = { + self.selectCell(day: day) + self.didChangeDay?(day) + } + self.view.addSubview(cell) + cells.append(cell) + } + + func clearCells() { + for cell in cells { + cell.removeFromSuperview() + } + cells = [] + } + + func showMonthName (_ name: String) { + monthTextField.stringValue = name + } +} diff --git a/Delivery/macOS/Base.lproj/Main.storyboard b/Delivery/macOS/Screens/Main/Base.lproj/Main.storyboard similarity index 78% rename from Delivery/macOS/Base.lproj/Main.storyboard rename to Delivery/macOS/Screens/Main/Base.lproj/Main.storyboard index 9146e98..811d6b3 100644 --- a/Delivery/macOS/Base.lproj/Main.storyboard +++ b/Delivery/macOS/Screens/Main/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -660,5 +661,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Main/MainPresenter.swift b/Delivery/macOS/Screens/Main/MainPresenter.swift new file mode 100644 index 0000000..f2aee1e --- /dev/null +++ b/Delivery/macOS/Screens/Main/MainPresenter.swift @@ -0,0 +1,90 @@ +// +// MainPresenter.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation +import RCPreferences + +enum ListType: Int { + + case tasks = 0 + case reports = 1 + case projects = 2 +} + +protocol MainPresenterInput: class { + + func viewDidAppear() + func select(listType: ListType) +} + +protocol MainPresenterOutput: class { + + func showWarning (_ show: Bool) +// func showMessage (_ message: MessageViewModel) + func showCalendar() + func showTasks() + func showReports() + func showProjects() + func removeCalendar() + func removeTasks() + func removeReports() + func removeProjects() + func select(listType: ListType) +} + +class MainPresenter { + + weak var appWireframe: AppWireframe? + weak var ui: MainPresenterOutput? + + private var selectedListType = ListType.tasks + private let pref = RCPreferences() + private var extensions = ExtensionsInteractor() + private var lastSelectedDay: Day? +} + +extension MainPresenter: MainPresenterInput { + + func viewDidAppear() { + ui!.showWarning(false) + ui!.showCalendar() + let lastType = TaskTypeSelection().lastType() + select(listType: lastType) + ui!.select(listType: lastType) + // Check for compatibility of components + extensions.getVersions { [weak self] (versions) in + guard let userInterface = self?.ui else { + return + } + let compatibility = Versioning(versions: versions) + if compatibility.shellScript.available { + userInterface.showWarning(!compatibility.jirassic.compatible || !compatibility.jit.compatible) + } else { + userInterface.showWarning(false) + } + } + } + + func select(listType: ListType) { + TaskTypeSelection().setType(listType) + ui!.removeTasks() + ui!.removeReports() + ui!.removeProjects() + switch listType { + case .tasks: + ui!.showCalendar() + ui!.showTasks() + case .reports: + ui!.showCalendar() + ui!.showReports() + case .projects: + ui!.removeCalendar() + ui!.showProjects() + } + } +} diff --git a/Delivery/macOS/Screens/Main/MainViewController.swift b/Delivery/macOS/Screens/Main/MainViewController.swift new file mode 100644 index 0000000..ade87ff --- /dev/null +++ b/Delivery/macOS/Screens/Main/MainViewController.swift @@ -0,0 +1,287 @@ +// +// MainViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa +import RCPreferences +import RCLog + +class MainViewController: NSViewController { + + @IBOutlet private var splitView: NSSplitView! + @IBOutlet private var splitTopView: NSView! + @IBOutlet private var splitBottomView: NSView! + @IBOutlet private var splitTopViewHeightConstraint: NSLayoutConstraint! + @IBOutlet private var listSegmentedControl: NSSegmentedControl! + @IBOutlet private var syncIndicator: NSProgressIndicator! + @IBOutlet private var butRefresh: NSButton! + @IBOutlet private var butSettings: NSButton! + @IBOutlet private var butWarning: NSButton! + @IBOutlet private var butWarningRightConstraint: NSLayoutConstraint! + @IBOutlet private var butQuit: NSButton! + @IBOutlet private var butMinimize: NSButton! + + private var calendarViewController: CalendarViewController? + private var tasksViewController: TasksViewController? + private var reportsViewController: ReportsViewController? + private var projectsViewController: ProjectsViewController? + + weak var appWireframe: AppWireframe? + var presenter: MainPresenterInput? + + override func awakeFromNib() { + super.awakeFromNib() + createLayer() + } + + override func viewDidLoad() { + super.viewDidLoad() + registerForNotifications() + hideControls(false) + } + + override func viewDidAppear() { + super.viewDidAppear() + presenter!.viewDidAppear() + } + + deinit { + RCLog(self) + NotificationCenter.default.removeObserver(self) + } + + private func hideControls (_ hide: Bool) { + butSettings.isHidden = hide + butRefresh.isHidden = remoteRepository == nil ? true : hide + butWarning.isHidden = hide + butQuit.isHidden = hide + butMinimize.isHidden = hide + listSegmentedControl.isHidden = hide + } +} + +extension MainViewController: Animatable { + + func createLayer() { + view.layer = CALayer() + view.wantsLayer = true + } +} + +extension MainViewController { + + @IBAction func handleSegmentedControl (_ sender: NSSegmentedControl) { + let listType = ListType(rawValue: sender.selectedSegment)! + presenter!.select(listType: listType) + } + + @IBAction func handleRefreshButton (_ sender: NSButton) { +// presenter!.syncData() + } + + @IBAction func handleSettingsButton (_ sender: NSButton) { + appWireframe!.flipToSettingsController() + } + + @IBAction func handleWarningButton (_ sender: NSButton) { + RCPreferences().set(SettingsTab.input.rawValue, forKey: .settingsActiveTab) + appWireframe!.flipToSettingsController() + } + + @IBAction func handleQuitAppButton (_ sender: NSButton) { + NSApplication.shared.terminate(nil) + } + + @IBAction func handleMinimizeAppButton (_ sender: NSButton) { + AppDelegate.sharedApp().menu.triggerClose() + } +} + +extension MainViewController: MainPresenterOutput { + + func select(listType: ListType) { + listSegmentedControl!.selectedSegment = listType.rawValue + } + + func showLoadingIndicator (_ show: Bool) { + + butRefresh.isHidden = remoteRepository == nil ? true : show + butWarningRightConstraint.constant = butRefresh.isHidden ? 0 : 22 +// if show { +// loadingTasksIndicator.isHidden = false +// loadingTasksIndicator.startAnimation(nil) +// } else { +// loadingTasksIndicator.stopAnimation(nil) +// loadingTasksIndicator.isHidden = true +// } + } + + func showWarning (_ show: Bool) { + butWarning.isHidden = !show + } + + func showMessage (_ message: MessageViewModel) { + + let controller = appWireframe!.presentPlaceholder(message, in: self.view) + controller.didPressButton = { +// self.presenter?.messageButtonDidPress() + } + } + + func showCalendar() { + + guard calendarViewController == nil else { + return + } + splitTopViewHeightConstraint.constant = 90 + + let controller = CalendarViewController.instantiateFromStoryboard("Calendar") + let presenter = CalendarPresenter() + let interactor = CalendarInteractor() + + presenter.ui = controller + presenter.interactor = interactor + interactor.presenter = presenter + controller.presenter = presenter + controller.appWireframe = appWireframe + + splitTopView.addSubview(controller.view) + self.addChild(controller) + controller.view.constrainToSuperview() + + calendarViewController = controller + calendarViewController!.didChangeDay = { [weak self] day in + guard let controller = self?.tasksViewController else { + return + } + controller.presenter?.reloadTasksOnDay(day) + } + calendarViewController!.didChangeMonth = { [weak self] date in + guard let controller = self?.reportsViewController else { + return + } + controller.presenter?.reloadReportsInMonth(date) + } + } + + func showTasks() { + + let controller = TasksViewController.instantiateFromStoryboard("Tasks") + let presenter = TasksPresenter() + let interactor = TasksInteractor() + + presenter.ui = controller + presenter.interactor = interactor + presenter.appWireframe = appWireframe + interactor.presenter = presenter + controller.presenter = presenter + controller.appWireframe = appWireframe + + splitBottomView.addSubview(controller.view) + self.addChild(controller) + controller.view.constrainToSuperview() + + tasksViewController = controller + + guard let selectedDay = calendarViewController!.selectedDay else { + return + } + controller.presenter?.reloadTasksOnDay(selectedDay) + } + + func showReports () { + + let controller = ReportsViewController.instantiateFromStoryboard("Reports") + let presenter = ReportsPresenter() + let interactor = TasksInteractor() + + presenter.ui = controller + presenter.interactor = interactor + presenter.appWireframe = appWireframe + interactor.presenter = presenter + controller.presenter = presenter + controller.appWireframe = appWireframe + + splitBottomView.addSubview(controller.view) + self.addChild(controller) + controller.view.constrainToSuperview() + + reportsViewController = controller + } + + func showProjects() { + + let controller = ProjectsViewController.instantiateFromStoryboard("Projects") + let presenter = ProjectsPresenter() + let interactor = ProjectsInteractor() + + presenter.ui = controller + presenter.interactor = interactor + interactor.presenter = presenter + controller.presenter = presenter + controller.appWireframe = appWireframe + + splitBottomView.addSubview(controller.view) + self.addChild(controller) + controller.view.constrainToSuperview() + + projectsViewController = controller + } + + func removeCalendar() { + guard let controller = calendarViewController else { + return + } + controller.removeFromSuperview() + controller.removeFromParent() + calendarViewController = nil + splitTopViewHeightConstraint.constant = 10 + } + func removeTasks() { + guard let controller = tasksViewController else { + return + } + controller.removeFromSuperview() + controller.removeFromParent() + tasksViewController = nil + } + func removeReports() { + guard let controller = reportsViewController else { + return + } + controller.removeFromSuperview() + controller.removeFromParent() + reportsViewController = nil + } + func removeProjects() { + guard let controller = projectsViewController else { + return + } + controller.removeFromSuperview() + controller.removeFromParent() + projectsViewController = nil + } + + func tasksDidClear() { + calendarViewController?.reloadData() + } +} + +extension MainViewController { + + func registerForNotifications() { + + NotificationCenter.default.addObserver(self, + selector: #selector(MainViewController.handleNewTaskAdded(_:)), + name: NSNotification.Name(rawValue: kNewTaskWasAddedNotification), + object: nil) + } + + @objc func handleNewTaskAdded (_ notif: Notification) { +// presenter!.reloadData() + } +} diff --git a/Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift b/Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift index fc2c493..c1174bf 100644 --- a/Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift +++ b/Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift @@ -17,12 +17,12 @@ class WizardCalendarView: NSView { var onSkip: (() -> Void)? private let pref = RCPreferences() private var calendarsButtons = [NSButton]() - private var presenter: CalendarPresenterInput = CalendarPresenter() + private var presenter: CalendarAppPresenterInput = CalendarAppPresenter() override func awakeFromNib() { super.awakeFromNib() - (presenter as! CalendarPresenter).userInterface = self - (presenter as! CalendarPresenter).refresh() + (presenter as! CalendarAppPresenter).userInterface = self + (presenter as! CalendarAppPresenter).refresh() } func save() { @@ -56,7 +56,7 @@ class WizardCalendarView: NSView { } } -extension WizardCalendarView: CalendarPresenterOutput { +extension WizardCalendarView: CalendarAppPresenterOutput { func enable (_ enabled: Bool) { butAuthorize.isHidden = enabled diff --git a/Delivery/macOS/Screens/Onboarding/WizardGitView.swift b/Delivery/macOS/Screens/Onboarding/WizardGitView.swift index 2202ef0..fd5efd0 100644 --- a/Delivery/macOS/Screens/Onboarding/WizardGitView.swift +++ b/Delivery/macOS/Screens/Onboarding/WizardGitView.swift @@ -17,8 +17,6 @@ class WizardGitView: NSView { @IBOutlet var butSkip: NSButton! var onSkip: (() -> Void)? private let pref = RCPreferences() - private var emailClickGestureRecognizer: NSClickGestureRecognizer? - private var gitUsersPopover: NSPopover? var presenter: GitPresenterInput = GitPresenter() @@ -28,24 +26,10 @@ class WizardGitView: NSView { pref.set(true, forKey: .enableGit) (presenter as! GitPresenter).userInterface = self presenter.isShellScriptInstalled = true - - let emailClickGestureRecognizer = NSClickGestureRecognizer(target: self, action: #selector(GitCell.emailTextFieldClicked)) - emailsTextField.addGestureRecognizer(emailClickGestureRecognizer) - self.emailClickGestureRecognizer = emailClickGestureRecognizer - } - - deinit { - if let gesture = emailClickGestureRecognizer { - emailsTextField.removeGestureRecognizer(gesture) - } } func save() { - presenter.save(emails: emailsTextField.stringValue, paths: pathsTextField.stringValue) - } - - @IBAction func handlePickButton (_ sender: NSButton) { - presenter.pickPath() + } @IBAction func handleSkipButton (_ sender: NSButton) { @@ -56,23 +40,6 @@ class WizardGitView: NSView { save() onSkip?() } - - @objc func emailTextFieldClicked() { - guard gitUsersPopover == nil else { - return - } - let popover = NSPopover() - let view = GitUsersViewController.instantiateFromStoryboard("Components") - view.onDone = { - self.gitUsersPopover?.performClose(nil) - self.gitUsersPopover = nil - self.presenter.isShellScriptInstalled = true - } - popover.contentViewController = view - let rect = CGRect(origin: CGPoint(x: emailsTextField.frame.origin.x, y: emailsTextField.frame.origin.y), size: emailsTextField.frame.size) - popover.show(relativeTo: rect, of: self, preferredEdge: NSRectEdge.minY) - gitUsersPopover = popover - } } extension WizardGitView: GitPresenterOutput { @@ -83,21 +50,4 @@ extension WizardGitView: GitPresenterOutput { func setButInstall (enabled: Bool) {} func setButPurchase(enabled: Bool) {} func setButEnable (on: Bool?, enabled: Bool?) {} - func setPaths (_ paths: String?, enabled: Bool?) { - if let paths = paths { - pathsTextField.stringValue = paths - } - if let enabled = enabled { - pathsTextField.isEnabled = enabled - butPick.isEnabled = enabled - } - } - func setEmails (_ emails: String?, enabled: Bool?) { - if let emails = emails { - emailsTextField.stringValue = emails - } - if let enabled = enabled { - emailsTextField.isEnabled = enabled - } - } } diff --git a/Delivery/macOS/Screens/Onboarding/WizardJiraView.swift b/Delivery/macOS/Screens/Onboarding/WizardJiraView.swift index c5d14b1..c8c9524 100644 --- a/Delivery/macOS/Screens/Onboarding/WizardJiraView.swift +++ b/Delivery/macOS/Screens/Onboarding/WizardJiraView.swift @@ -45,9 +45,7 @@ class WizardJiraView: NSView { } func save() { - presenter.save(url: baseUrlTextField.stringValue, - user: userTextField.stringValue, - password: passwordTextField.stringValue) + } @IBAction func handleLoginButton (_ sender: NSButton) { @@ -57,7 +55,12 @@ class WizardJiraView: NSView { passwordTextField.stringValue != "" else { return } - presenter.checkCredentials() + let user = JiraUser(url: baseUrlTextField.stringValue, + user: userTextField.stringValue, + password: passwordTextField.stringValue, + project: "", + issue: "") + presenter.checkCredentials(user) } @IBAction func handleSkipButton (_ sender: NSButton) { diff --git a/Delivery/macOS/Screens/Onboarding/WizardViewController.swift b/Delivery/macOS/Screens/Onboarding/WizardViewController.swift index 5e5d037..1745ce8 100644 --- a/Delivery/macOS/Screens/Onboarding/WizardViewController.swift +++ b/Delivery/macOS/Screens/Onboarding/WizardViewController.swift @@ -174,7 +174,7 @@ class WizardViewController: NSViewController { @IBAction func handleSkipButton (_ sender: NSButton) { let stepsToSave: [Int] = WizardStep.allCases.map({ $0.rawValue }) pref.set(stepsToSave, forKey: .wizardSteps) - appWireframe!.flipToTasksController() + appWireframe!.flipToMainController() } @IBAction func handleQuitAppButton (_ sender: NSButton) { diff --git a/Delivery/macOS/Screens/Projects/ProjectDetailsPresenter.swift b/Delivery/macOS/Screens/Projects/ProjectDetailsPresenter.swift new file mode 100644 index 0000000..df3984f --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectDetailsPresenter.swift @@ -0,0 +1,133 @@ +// +// ProjectDetailsPresenter.swift +// Jirassic +// +// Created by Cristian Baluta on 28/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation +import RCPreferences + +protocol ProjectDetailsPresenterInput: class { + + var project: Project? {get set} + var editedProject: Project? {get set} + func enableDefaultCredentials(_ enabled: Bool) + func didPickUrl (_ url: URL) + func save (emails: String) + func save (paths: String) + func saveProject (_ project: Project) + func deleteProject (_ project: Project) +} + +protocol ProjectDetailsPresenterOutput: class { + + func pickPath() + func show (_ project: Project) + func setCredentialsCheckbox (enabled: Bool) + func setCredentials (url: String, user: String, password: String, editable: Bool) + func setPaths (_ paths: String?, enabled: Bool?) + func setEmails (_ emails: String?, enabled: Bool?) + func enableSaveButton (_ enable: Bool) + func handleProjectDidSave (_ project: Project) +} + +class ProjectDetailsPresenter { + + weak var ui: ProjectDetailsPresenterOutput? + private let prefs = RCPreferences() + + var project: Project? { + didSet { + editedProject = project + reloadData() + } + } + /// Do all changes on the copy + var editedProject: Project? { + didSet { + enableDisableSave() + } + } + + private func enableDisableSave() { + ui!.enableSaveButton(project != editedProject) + } +} + +extension ProjectDetailsPresenter: ProjectDetailsPresenterInput { + + func reloadData() { + + guard let project = project else { + return + } + ui!.show(project) + ui!.setPaths(project.gitBaseUrls.toString(), enabled: prefs.bool(.enableGit)) + ui!.setEmails(project.gitUsers.toString(), enabled: prefs.bool(.enableGit)) + enableDisableSave() + + let credentialsEnabled = (project.jiraBaseUrl ?? "") == "" && (project.jiraUser ?? "") == "" + enableDefaultCredentials(credentialsEnabled) + ui!.setCredentialsCheckbox(enabled: credentialsEnabled) + } + + func enableDefaultCredentials(_ enabled: Bool) { + + if enabled { + ui!.setCredentials(url: prefs.string(.settingsJiraUrl), + user: prefs.string(.settingsJiraUser), + password: "", + editable: false) + } else { + ui!.setCredentials(url: "", user: "", password: "", editable: true) + } + } + + func didPickUrl (_ url: URL) { + + guard var project = editedProject else { + return + } + var path = url.absoluteString + path = path.replacingOccurrences(of: "file://", with: "") + path.removeLast() + // TODO: Validate if the picked project is a git project + + var existingPaths = project.gitBaseUrls + existingPaths.append(path) + project.gitBaseUrls = existingPaths + editedProject = project + + ui!.setPaths(existingPaths.toString(), enabled: prefs.bool(.enableGit)) + } + + func save (emails: String) { + editedProject?.gitUsers = emails.toArray() + } + + func save (paths: String) { + editedProject?.gitBaseUrls = paths.toArray() + } + + func saveProject (_ project: Project) { +// if project.objectId == nil { +// +// } + let interactor = ProjectInteractor(repository: localRepository, remoteRepository: remoteRepository) + interactor.saveProject(project, allowSyncing: true) { savedProject in + guard let project = savedProject else { + return + } + self.project = project + self.enableDisableSave() + self.ui!.handleProjectDidSave(project) + self.enableDisableSave() + } + } + + func deleteProject (_ project: Project) { + + } +} diff --git a/Delivery/macOS/Screens/Projects/ProjectDetailsViewController.swift b/Delivery/macOS/Screens/Projects/ProjectDetailsViewController.swift new file mode 100644 index 0000000..775ca98 --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectDetailsViewController.swift @@ -0,0 +1,260 @@ +// +// ProjectDetailsViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 24/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class ProjectDetailsViewController: NSViewController { + + @IBOutlet private var butCredentials: NSButton! + @IBOutlet private var baseUrlTextField: NSTextField! + @IBOutlet private var userTextField: NSTextField! + @IBOutlet private var passwordTextField: NSTextField! + @IBOutlet private var errorTextField: NSTextField! + @IBOutlet private var projectNamePopup: NSPopUpButton! + @IBOutlet private var projectIssueNamePopup: NSPopUpButton! + @IBOutlet private var progressIndicator: NSProgressIndicator! + @IBOutlet private var emailsTextField: NSTextField! + @IBOutlet private var pathsTextField: NSTextField! + @IBOutlet private var taskNumberPrefixTextField: NSTextField! + @IBOutlet private var butPick: NSButton! + @IBOutlet private var butSave: NSButton! + @IBOutlet private var butDelete: NSButton! + @IBOutlet private var butPurchase: NSButton! + + var project: Project? { + didSet { + presenter?.project = project + } + } + var projectDidSave: ((Project) -> Void)? + + var presenter: ProjectDetailsPresenterInput? + private let jiraPresenter: JiraTempoPresenterInput = JiraTempoPresenter() + + private var emailClickGestureRecognizer: NSClickGestureRecognizer? + private var gitUsersPopover: NSPopover? + + override func viewDidLoad() { + super.viewDidLoad() + + baseUrlTextField.delegate = self + userTextField.delegate = self + passwordTextField.delegate = self + taskNumberPrefixTextField.delegate = self + emailsTextField.delegate = self + pathsTextField.delegate = self + + let gesture = NSClickGestureRecognizer(target: self, action: #selector(ProjectDetailsViewController.emailTextFieldClicked)) + emailsTextField.addGestureRecognizer(gesture) + self.emailClickGestureRecognizer = gesture + + (jiraPresenter as! JiraTempoPresenter).userInterface = self + jiraPresenter.setupUserInterface() + } + + deinit { + if let gesture = emailClickGestureRecognizer { + emailsTextField.removeGestureRecognizer(gesture) + } + } + + @IBAction func handlePickerButton (_ sender: NSButton) { + pickPath() + } + + @IBAction func handleSaveButton (_ sender: NSButton) { + + /// Make a copy of the project. Directly editing the project will call reloadData for each change + guard var project = self.project else { + return + } + project.jiraBaseUrl = baseUrlTextField.stringValue + project.jiraUser = userTextField.stringValue + project.jiraProject = projectNamePopup.selectedItem?.title + project.jiraIssue = projectIssueNamePopup.selectedItem?.title + project.gitBaseUrls = pathsTextField.stringValue.toArray() + project.gitUsers = emailsTextField.stringValue.toArray() + project.taskNumberPrefix = taskNumberPrefixTextField.stringValue + + self.project = project + + presenter!.saveProject(project) + } + + @IBAction func handleDeleteButton (_ sender: NSButton) { + presenter!.deleteProject(project!) + } + + @IBAction func handleCredentialsButton (_ sender: NSButton) { + presenter!.enableDefaultCredentials(sender.state == .on) + } + + @IBAction func projectNamePopupSelected (_ sender: NSPopUpButton) { + if let title = sender.selectedItem?.title { + presenter!.editedProject?.jiraProject = title + jiraPresenter.loadProjectIssues(for: title) + } + } + + @IBAction func projectIssueNamePopupSelected (_ sender: NSPopUpButton) { + presenter!.editedProject?.jiraIssue = sender.selectedItem?.title + } + + @objc func emailTextFieldClicked() { + guard gitUsersPopover == nil else { + return + } + let popover = NSPopover() + let view = GitUsersViewController.instantiateFromStoryboard("Components") + view.onDone = { + let emails = view.selectedUsers.toString() + self.presenter?.save(emails: emails) + self.gitUsersPopover?.performClose(nil) + self.gitUsersPopover = nil + self.emailsTextField.stringValue = emails + } + popover.contentViewController = view + let rect = CGRect(origin: CGPoint(x: emailsTextField.frame.width/2, y: 0), + size: emailsTextField.frame.size) + popover.show(relativeTo: rect, of: emailsTextField, preferredEdge: NSRectEdge.minY) + gitUsersPopover = popover + /// Set emails after popover is presented + view.selectedUsers = presenter?.project?.gitUsers ?? [] + } +} + +extension ProjectDetailsViewController: ProjectDetailsPresenterOutput { + + func show (_ project: Project) { + + butCredentials.state = project.jiraBaseUrl == nil ? .on : .off + baseUrlTextField.stringValue = project.jiraBaseUrl ?? "" + userTextField.stringValue = project.jiraUser ?? "" + errorTextField.stringValue = "" + projectNamePopup.removeAllItems() + projectNamePopup.addItem(withTitle: project.jiraProject ?? "") + projectIssueNamePopup.removeAllItems() + projectIssueNamePopup.addItem(withTitle: project.jiraIssue ?? "") + pathsTextField.stringValue = project.gitBaseUrls.toString() + emailsTextField.stringValue = project.gitUsers.toString() + taskNumberPrefixTextField.stringValue = project.taskNumberPrefix ?? "" + + let user = JiraUser(url: project.jiraBaseUrl ?? "", + user: project.jiraUser ?? "", + password: Keychain.getPassword(), + project: project.jiraProject ?? "", + issue: project.jiraIssue ?? "") + jiraPresenter.checkCredentials(user) + } + + func pickPath() { + + let panel = NSOpenPanel() + panel.canChooseFiles = false + panel.canChooseDirectories = true + panel.allowsMultipleSelection = false + panel.message = "Select the root of the git project you want to track" + panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) + panel.begin { [weak self] result in + + guard let url = panel.urls.first, result.rawValue == NSFileHandlingPanelOKButton else { + return + } + self?.presenter?.didPickUrl(url) + } + } + + func setCredentialsCheckbox (enabled: Bool) { + butCredentials.state = enabled ? .on : .off + } + + func setCredentials (url: String, user: String, password: String, editable: Bool) { + + baseUrlTextField.stringValue = url + userTextField.stringValue = user + passwordTextField.stringValue = password + baseUrlTextField.isEditable = editable + userTextField.isEditable = editable + passwordTextField.isEditable = editable + } + + func setPaths (_ paths: String?, enabled: Bool?) { + if let paths = paths { + pathsTextField.stringValue = paths + } + if let enabled = enabled { + pathsTextField.isEnabled = enabled + butPick.isEnabled = enabled + } + } + + func setEmails (_ emails: String?, enabled: Bool?) { + if let emails = emails { + emailsTextField.stringValue = emails + } + if let enabled = enabled { + emailsTextField.isEnabled = enabled + } + } + + func enableSaveButton (_ enable: Bool) { + butSave.isEnabled = enable + } + + func handleProjectDidSave (_ project: Project) { + projectDidSave?(project) + } +} + +extension ProjectDetailsViewController: NSTextFieldDelegate { + + func controlTextDidEndEditing(_ obj: Notification) { + + presenter!.editedProject?.jiraBaseUrl = baseUrlTextField.stringValue + presenter!.editedProject?.jiraUser = userTextField.stringValue + presenter!.editedProject?.gitBaseUrls = pathsTextField.stringValue.toArray() +// project.gitUsers = emailsTextField.stringValue.toArray() +// project.taskNumberPrefix = taskNumberPrefixTextField.stringValue + presenter!.save(emails: emailsTextField.stringValue) + presenter!.save(paths: pathsTextField.stringValue) + } +} + +extension ProjectDetailsViewController: JiraTempoPresenterOutput { + + func setPurchased (_ purchased: Bool) { + butPurchase.isHidden = purchased + baseUrlTextField.isEnabled = purchased + userTextField.isEnabled = purchased + passwordTextField.isEnabled = purchased + projectNamePopup.isEnabled = purchased + projectIssueNamePopup.isEnabled = purchased + } + + func enableProgressIndicator (_ enabled: Bool) { + enabled + ? progressIndicator.startAnimation(nil) + : progressIndicator.stopAnimation(nil) + } + + func showProjects (_ projects: [String], selectedProject: String) { + projectNamePopup.removeAllItems() + projectNamePopup.addItems(withTitles: projects) + projectNamePopup.selectItem(withTitle: selectedProject) + } + + func showProjectIssues (_ issues: [String], selectedIssue: String) { + projectIssueNamePopup.removeAllItems() + projectIssueNamePopup.addItems(withTitles: issues) + projectIssueNamePopup.selectItem(withTitle: selectedIssue) + } + + func showErrorMessage (_ message: String) { + errorTextField.stringValue = message + } +} diff --git a/Delivery/macOS/Screens/Projects/Projects.storyboard b/Delivery/macOS/Screens/Projects/Projects.storyboard new file mode 100644 index 0000000..118954a --- /dev/null +++ b/Delivery/macOS/Screens/Projects/Projects.storyboard @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Projects/ProjectsInteractor.swift b/Delivery/macOS/Screens/Projects/ProjectsInteractor.swift new file mode 100644 index 0000000..5b025ee --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectsInteractor.swift @@ -0,0 +1,34 @@ +// +// ProjectsInteractor.swift +// Jirassic +// +// Created by Cristian Baluta on 23/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation + +protocol ProjectsInteractorInput: class { + + func reloadProjects() +} + +protocol ProjectsInteractorOutput: class { + + func projectsDidLoad (_ projects: [Project]) +} + +class ProjectsInteractor { + + weak var presenter: ProjectsPresenter? + +} + +extension ProjectsInteractor: ProjectsInteractorInput { + + func reloadProjects() { + let interactor = ReadProjectsInteractor(repository: localRepository, remoteRepository: remoteRepository) + let projects = interactor.allProjects() + presenter?.projectsDidLoad(projects) + } +} diff --git a/Delivery/macOS/Screens/Projects/ProjectsListViewController.swift b/Delivery/macOS/Screens/Projects/ProjectsListViewController.swift new file mode 100644 index 0000000..cfba82e --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectsListViewController.swift @@ -0,0 +1,81 @@ +// +// ProjectsListViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 28/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class ProjectsListViewController: NSViewController { + + @IBOutlet private weak var scrollView: NSScrollView! + @IBOutlet private weak var tableView: NSTableView! + @IBOutlet private weak var butAdd: NSButton! + +// private var selectedProject: Project? + + var projects = [Project]() { + didSet { + tableView.reloadData() + } + } + var didSelectProject: ((Project) -> Void)? + var didUpdateProject: ((Project) -> Void)? + var didSelectAddProject: (() -> Void)? + var didSelectRemoveProject: ((Project) -> Void)? + + override func viewDidLoad() { + super.viewDidLoad() + scrollView.focusRingType = .none + tableView.focusRingType = .none + } + + @IBAction func handleAddButton (_ sender: NSButton) { + didSelectAddProject?() + } + + func selectProject (_ project: Project) { + for i in 0...projects.count { + if projects[i].objectId == project.objectId { + let indexSet = IndexSet(integer: i) + tableView.selectRowIndexes(indexSet, byExtendingSelection: false) + break + } + } + } +} + +extension ProjectsListViewController: NSTableViewDataSource { + + func numberOfRows (in aTableView: NSTableView) -> Int { + return projects.count + } +} + +extension ProjectsListViewController: NSTableViewDelegate { + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + + let project = projects[row] + if (tableColumn?.identifier)?.rawValue == "name" { + return project.title + } + return nil + } + + func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { + + if (tableColumn?.identifier)?.rawValue == "name" { + projects[row].title = object as? String ?? "" + didUpdateProject?(projects[row]) + } + } + + func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { +// selectedProject = projects[row] + didSelectProject?(projects[row]) + return true + } +} diff --git a/Delivery/macOS/Screens/Projects/ProjectsPresenter.swift b/Delivery/macOS/Screens/Projects/ProjectsPresenter.swift new file mode 100644 index 0000000..1a3d05f --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectsPresenter.swift @@ -0,0 +1,85 @@ +// +// ProjectsPresenter.swift +// Jirassic +// +// Created by Cristian Baluta on 20/12/2018. +// Copyright © 2018 Imagin soft. All rights reserved. +// + +import Foundation + +protocol ProjectsPresenterInput: class { + func reloadProjects() + func addProject() + func removeProject (_ project: Project) + func updateProject (_ project: Project) +} + +protocol ProjectsPresenterOutput: class { + func showProjects(_ projects: [Project]) + func hideProjects() + func showMessage (_ message: MessageViewModel) + func hideMessage() +} + +class ProjectsPresenter { + + weak var appWireframe: AppWireframe? + weak var ui: ProjectsPresenterOutput? + var interactor: ProjectsInteractorInput? + + private var projects = [Project]() +} + +extension ProjectsPresenter: ProjectsPresenterInput { + + func reloadProjects() { + ui!.hideProjects() + interactor!.reloadProjects() + } + + func addProject() { + ui!.hideMessage() + let newProject = Project(objectId: String.generateId(), + lastModifiedDate: nil, + title: "Project \(projects.count + 1)", + jiraBaseUrl: nil, + jiraUser: nil, + jiraProject: nil, + jiraIssue: nil, + gitBaseUrls: [], + gitUsers: [], + taskNumberPrefix: nil) + projects.append(newProject) + projectsDidLoad(projects) + } + + func removeProject (_ project: Project) { + projects = projects.filter( { $0.title != project.title } ) + projectsDidLoad(projects) + } + + func updateProject (_ project: Project) { + for i in 0...projects.count { + if projects[i].objectId == project.objectId { + projects[i] = project + break + } + } + } +} + +extension ProjectsPresenter: ProjectsInteractorOutput { + + func projectsDidLoad(_ projects: [Project]) { + self.projects = projects + if projects.count == 0 { + ui!.showMessage(( + title: "No projects", + message: "Add your first project!", + buttonTitle: "Add")) + } else { + ui!.showProjects(projects) + } + } +} diff --git a/Delivery/macOS/Screens/Projects/ProjectsViewController.swift b/Delivery/macOS/Screens/Projects/ProjectsViewController.swift new file mode 100644 index 0000000..1a3b3bd --- /dev/null +++ b/Delivery/macOS/Screens/Projects/ProjectsViewController.swift @@ -0,0 +1,78 @@ +// +// ProjectsViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 20/12/2018. +// Copyright © 2018 Imagin soft. All rights reserved. +// + +import Cocoa + +class ProjectsViewController: NSSplitViewController { + + weak var appWireframe: AppWireframe? + var presenter: ProjectsPresenterInput? + + override func viewDidLoad() { + super.viewDidLoad() + self.view.focusRingType = .none + self.splitViewItems.first?.minimumThickness = 120 + + presenter!.reloadProjects() + } +} + +extension ProjectsViewController: ProjectsPresenterOutput { + + func showMessage (_ message: MessageViewModel) { + + let controller = appWireframe!.presentPlaceholder(message, in: self.view) + controller.didPressButton = { + self.presenter?.addProject() + } + } + + func hideMessage() { + appWireframe!.removePlaceholder() + } + + func hideProjects() { + self.splitViewItems = [] + } + + func showProjects(_ projects: [Project]) { + + let projectsList = ProjectsListViewController.instantiateFromStoryboard("Projects") + + let projectDetails = ProjectDetailsViewController.instantiateFromStoryboard("Projects") + let c2Presenter = ProjectDetailsPresenter() + projectDetails.presenter = c2Presenter + c2Presenter.ui = projectDetails + + let s1 = NSSplitViewItem(viewController: projectsList) + s1.minimumThickness = 120 + let s2 = NSSplitViewItem(viewController: projectDetails) + self.splitViewItems = [s1, s2] + + projectsList.projects = projects + projectsList.didSelectProject = { project in + projectDetails.project = project + } + projectsList.didUpdateProject = { project in + /// In the list of projects only the title can change + projectDetails.project?.title = project.title + } + projectsList.didSelectAddProject = { + self.presenter?.addProject() + } + + projectDetails.projectDidSave = { project in + self.presenter!.updateProject(project) + } + + if let firstProject = projects.first { + projectsList.selectProject(firstProject) + projectDetails.project = firstProject + } + } +} diff --git a/Delivery/macOS/Screens/Reports/Reports.storyboard b/Delivery/macOS/Screens/Reports/Reports.storyboard new file mode 100644 index 0000000..e5729d0 --- /dev/null +++ b/Delivery/macOS/Screens/Reports/Reports.storyboard @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Tasks/Reports/ReportsDataSource.swift b/Delivery/macOS/Screens/Reports/ReportsDataSource.swift similarity index 65% rename from Delivery/macOS/Screens/Tasks/Reports/ReportsDataSource.swift rename to Delivery/macOS/Screens/Reports/ReportsDataSource.swift index e5f97e2..be61bae 100644 --- a/Delivery/macOS/Screens/Tasks/Reports/ReportsDataSource.swift +++ b/Delivery/macOS/Screens/Reports/ReportsDataSource.swift @@ -16,10 +16,18 @@ class ReportsDataSource: NSObject, TasksAndReportsDataSource { tableView.usesAutomaticRowHeights = true } ReportCell.register(in: tableView) + CopyReportCell.register(in: tableView) + TaskCell.register(in: tableView) } } var didClickAddRow: ((_ row: Int) -> Void)? var didClickRemoveRow: ((_ row: Int) -> Void)? + var didClickCloseDay: ((_ tasks: [Task]) -> Void)? + var didClickSaveWorklogs: (() -> Void)? + var didClickSetupJira: (() -> Void)? + var didClickCopyMonthlyReport: ((_ asHtml: Bool) -> Void)? + var didChangeSettings: (() -> Void)? + private var tempCell: ReportCell? let numberOfDays: Int var reports: [Report] @@ -41,13 +49,17 @@ class ReportsDataSource: NSObject, TasksAndReportsDataSource { extension ReportsDataSource: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { - return reports.count + return reports.count > 0 ? reports.count + 1 : 0 } func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - + + guard row < reports.count else { + // Doesn't seem to have effect, the cell will be resized based on constrains + return CGFloat(164) + } if #available(OSX 10.13, *) { - // This version of osx supports cell autoresizing + // This version of osx supports cell autoresizing so it doesn't matter the height return CGFloat(50) } let theData = reports[row] @@ -65,7 +77,19 @@ extension ReportsDataSource: NSTableViewDataSource { extension ReportsDataSource: NSTableViewDelegate { func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - + + guard row < reports.count else { + let cell = CopyReportCell.instantiate(in: tableView) + cell.numberOfDays = numberOfDays + cell.didClickCopyAll = { isHtml in + self.didClickCopyMonthlyReport?(isHtml) + } + cell.didChangeSettings = { + self.didChangeSettings?() + } + return cell + } + let theData = reports[row] let cell: CellProtocol = ReportCell.instantiate(in: self.tableView) ReportCellPresenter(cell: cell).present(theReport: theData) diff --git a/Delivery/macOS/Screens/Reports/ReportsPresenter.swift b/Delivery/macOS/Screens/Reports/ReportsPresenter.swift new file mode 100644 index 0000000..a89a3dd --- /dev/null +++ b/Delivery/macOS/Screens/Reports/ReportsPresenter.swift @@ -0,0 +1,143 @@ +// +// ReportsPresenter.swift +// Jirassic +// +// Created by Cristian Baluta on 27/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Foundation +import Cocoa +import RCPreferences + +protocol ReportsPresenterInput: class { + + func viewDidLoad() + func reloadLastSelectedMonth() + func reloadReportsOnDay (_ day: Day) + func reloadReportsInMonth (_ date: Date) + func copyMonthlyReportsToClipboard(asHtml: Bool) + func messageButtonDidPress() +} + +protocol ReportsPresenterOutput: class { + + func showLoadingIndicator (_ show: Bool) + func showMessage (_ message: MessageViewModel) + func showReports (_ reports: [Report], numberOfDays: Int) + func removeReports() +} + +class ReportsPresenter { + + weak var appWireframe: AppWireframe? + weak var ui: ReportsPresenterOutput? + var interactor: TasksInteractorInput? + + private var currentTasks = [Task]() + private var currentReports = [Report]() + private let pref = RCPreferences() + private var extensions = ExtensionsInteractor() + private var lastSelectedDay: Day? + private var lastSelectedMonth: Date = Date() + + + private func updateNoTasksState() { + + if currentTasks.count == 1 { + ui!.showMessage(( + title: "No task yet", + message: "Go to 'Tasks' tab and log some work first!", + buttonTitle: nil)) + } else { + appWireframe!.removePlaceholder() + } + } + + private func startDay() { + + let task = Task(endDate: Date(), type: .startDay) + let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) + saveInteractor.saveTask(task, allowSyncing: true, completion: { [weak self] savedTask in + self?.reloadLastSelectedMonth() + }) + ModuleHookup().insert(task: task) + } +} + +extension ReportsPresenter: ReportsPresenterInput { + + func viewDidLoad() { + ui!.showLoadingIndicator(true) + reloadLastSelectedMonth() + } + + func reloadLastSelectedMonth() { + reloadReportsInMonth(lastSelectedMonth) + } + + func reloadReportsOnDay (_ day: Day) { + // TODO + } + + func reloadReportsInMonth (_ date: Date) { + ui!.removeReports() + ui!.showLoadingIndicator(true) + interactor!.reloadTasks(inMonth: date) + } + + func messageButtonDidPress() { + + if currentTasks.count == 0 { + startDay() + } + } + + func copyMonthlyReportsToClipboard(asHtml: Bool) { + var string = "" + let interactor = CreateMonthReport() + if asHtml { + string = interactor.htmlReports(currentReports) + } else { + let joined = interactor.joinReports(currentReports) + string = joined.notes + "\n\n" + joined.totalDuration.secToHoursAndMin + } + NSPasteboard.general.clearContents() + NSPasteboard.general.writeObjects([string as NSPasteboardWriting]) + } +} + +extension ReportsPresenter: TasksInteractorOutput { + + func tasksDidLoad (_ tasks: [Task]) { + + guard let ui = self.ui else { + return + } + ui.showLoadingIndicator(false) + currentTasks = tasks + + let settings = SettingsInteractor().getAppSettings() + let targetHoursInDay = pref.bool(.enableRoundingDay) + ? TimeInteractor(settings: settings).workingDayLength() + : nil + let reportInteractor = CreateMonthReport() + let reports = reportInteractor.reports(fromTasks: currentTasks, + targetHoursInDay: targetHoursInDay, + roundHours: true) + currentReports = reports.byTasks + ui.showReports(currentReports, numberOfDays: reports.byDays.count) + + +// let settings = SettingsInteractor().getAppSettings() +// let targetHoursInDay = pref.bool(.enableRoundingDay) +// ? TimeInteractor(settings: settings).workingDayLength() +// : nil +// let reportInteractor = CreateReport() +// let reports = reportInteractor.reports(fromTasks: currentTasks, targetHoursInDay: targetHoursInDay) +// currentReports = reports.reversed() +// ui.showReports(currentReports, numberOfDays: 1, type: selectedListType) + + updateNoTasksState() + } +} diff --git a/Delivery/macOS/Screens/Reports/ReportsViewController.swift b/Delivery/macOS/Screens/Reports/ReportsViewController.swift new file mode 100644 index 0000000..b8737e7 --- /dev/null +++ b/Delivery/macOS/Screens/Reports/ReportsViewController.swift @@ -0,0 +1,76 @@ +// +// ReportsViewController.swift +// Jirassic +// +// Created by Cristian Baluta on 27/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa +import RCLog + +class ReportsViewController: NSViewController { + + @IBOutlet private var loadingTasksIndicator: NSProgressIndicator! + private var tasksScrollView: TasksScrollView? + + weak var appWireframe: AppWireframe? + var presenter: ReportsPresenterInput? + + override func viewDidLoad() { + super.viewDidLoad() + presenter!.viewDidLoad() + } + + deinit { + RCLog(self) + } +} + +extension ReportsViewController: ReportsPresenterOutput { + + func showLoadingIndicator (_ show: Bool) { + + if show { + loadingTasksIndicator.isHidden = false + loadingTasksIndicator.startAnimation(nil) + } else { + loadingTasksIndicator.stopAnimation(nil) + loadingTasksIndicator.isHidden = true + } + } + + func showMessage (_ message: MessageViewModel) { + + let controller = appWireframe!.presentPlaceholder(message, in: self.view) + controller.didPressButton = { + self.presenter?.messageButtonDidPress() + } + } + + func showReports (_ reports: [Report], numberOfDays: Int) { + + let dataSource = ReportsDataSource(reports: reports, numberOfDays: numberOfDays) + dataSource.didChangeSettings = { [weak self] in + self?.presenter?.reloadLastSelectedMonth() + } + dataSource.didClickCopyMonthlyReport = { [weak self] asHtml in + self?.presenter?.copyMonthlyReportsToClipboard(asHtml: asHtml) + } + + let scrollView = TasksScrollView(dataSource: dataSource) + self.view.addSubview(scrollView) + scrollView.constrainToSuperview() + scrollView.reloadData() + + tasksScrollView = scrollView + } + + func removeReports() { + + if tasksScrollView != nil { + tasksScrollView?.removeFromSuperview() + tasksScrollView = nil + } + } +} diff --git a/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.swift b/Delivery/macOS/Screens/Reports/cells/CopyReportCell/CopyReportCell.swift similarity index 54% rename from Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.swift rename to Delivery/macOS/Screens/Reports/cells/CopyReportCell/CopyReportCell.swift index e334a3f..af0f053 100644 --- a/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.swift +++ b/Delivery/macOS/Screens/Reports/cells/CopyReportCell/CopyReportCell.swift @@ -1,22 +1,26 @@ // -// ReportsHeaderView.swift +// MonthReportsHeaderView.swift // Jirassic // -// Created by Cristian Baluta on 19/02/2017. -// Copyright © 2017 Imagin soft. All rights reserved. +// Created by Cristian Baluta on 25/10/2018. +// Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences -class ReportsHeaderView: NSTableHeaderView { +class CopyReportCell: NSTableRowView { - @IBOutlet private var backgroundView: NSVisualEffectView! + @IBOutlet private var butCopyAll: NSButton! + @IBOutlet private var butCopyAsHtml: NSButton! + @IBOutlet private var totalDaysTextField: NSTextField! @IBOutlet private var butPercents: NSButton! @IBOutlet private var butRound: NSButton! @IBOutlet private var totalTimeTextField: NSTextField! + internal let pref = RCPreferences() - + + var didClickCopyAll: ((Bool) -> Void)? var didChangeSettings: (() -> Void)? var didClickCopyAll: ((Bool) -> Void)? @@ -37,39 +41,45 @@ class ReportsHeaderView: NSTableHeaderView { butRound.title = "Round to \(newValue) hours" } } + var numberOfDays: Int { + get { + return 0 + } + set { + totalDaysTextField.stringValue = "Number of days: \(newValue)" + } + } override func awakeFromNib() { super.awakeFromNib() - butPercents.title = "Show time in percents" + butCopyAsHtml.state = pref.bool(.copyWorklogsAsHtml) ? .on : .off + butCopyAsHtml.toolTip = "This can be set in 'Settings/Tracking/Working between'" + butPercents.state = pref.bool(.usePercents) ? .on : .off - + butPercents.title = "Show time in units" + butPercents.toolTip = "1 hour means 1 unit, 30 minutes meand 0.5 units" + butRound.state = pref.bool(.enableRoundingDay) ? .on : .off - butRound.toolTip = "This can be set in 'Settings/Tracking/Working between'" - - if #available(OSX 10.14, *) { - // In OS14 there is already a default blurry background - backgroundView.isHidden = true - } + butRound.toolTip = "Time can be set in 'Settings/Tracking/Working between'" } +} - // Overriding this in OS14 removes default blurry background and the custom one adds ugly edges to buttons -// override func draw (_ dirtyRect: NSRect) { -// } +extension CopyReportCell { - override func headerRect(ofColumn column: Int) -> NSRect { - // This will prevent for a label to appear in the middle of the header - return NSRect.zero + @IBAction func handleCopyAllButton (_ sender: NSButton) { + didClickCopyAll?(pref.bool(.copyWorklogsAsHtml)) } -} - -extension ReportsHeaderView { + @IBAction func handleHtmlButton (_ sender: NSButton) { + pref.set(sender.state == .on, forKey: .copyWorklogsAsHtml) + } + @IBAction func handleRoundButton (_ sender: NSButton) { pref.set(sender.state == .on, forKey: .enableRoundingDay) didChangeSettings?() } - + @IBAction func handlePercentsButton (_ sender: NSButton) { pref.set(sender.state == .on, forKey: .usePercents) didChangeSettings?() diff --git a/Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.xib b/Delivery/macOS/Screens/Reports/cells/CopyReportCell/CopyReportCell.xib similarity index 100% rename from Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.xib rename to Delivery/macOS/Screens/Reports/cells/CopyReportCell/CopyReportCell.xib diff --git a/Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.swift b/Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCell.swift similarity index 97% rename from Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.swift rename to Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCell.swift index 94a5b68..975cc05 100644 --- a/Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.swift +++ b/Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCell.swift @@ -19,7 +19,7 @@ class ReportCell: NSTableRowView, CellProtocol { fileprivate var trackingArea: NSTrackingArea? fileprivate var bgColor: NSColor = NSColor.clear - var didEndEditingCell: ((_ cell: CellProtocol) -> ())? + var didClickEditCell: ((_ cell: CellProtocol) -> ())? var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? var didClickAddCell: ((_ cell: CellProtocol) -> ())? var didCopyContentCell: ((_ cell: CellProtocol) -> ())? @@ -31,7 +31,8 @@ class ReportCell: NSTableRowView, CellProtocol { dateEnd: Date(), taskNumber: self.taskNrTextField!.stringValue, notes: self.notesTextField!.stringValue, - taskType: .issue + taskType: .issue, + projectId: nil ) } set { diff --git a/Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.xib b/Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCell.xib similarity index 100% rename from Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.xib rename to Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCell.xib diff --git a/Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCellPresenter.swift b/Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCellPresenter.swift similarity index 96% rename from Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCellPresenter.swift rename to Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCellPresenter.swift index 322b6b7..6106cf1 100644 --- a/Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCellPresenter.swift +++ b/Delivery/macOS/Screens/Reports/cells/ReportCell/ReportCellPresenter.swift @@ -42,7 +42,8 @@ class ReportCellPresenter: NSObject { dateEnd: Date(), taskNumber: taskNumber + " " + title, notes: notesJoined, - taskType: .issue + taskType: .issue, + projectId: nil ) cell.duration = pref.bool(.usePercents) ? "\(theReport.duration.secToPercent)" diff --git a/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarPresenter.swift b/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarAppPresenter.swift similarity index 92% rename from Delivery/macOS/Screens/Settings/Input/Calendar/CalendarPresenter.swift rename to Delivery/macOS/Screens/Settings/Input/Calendar/CalendarAppPresenter.swift index c7ddd11..a4fbbb7 100644 --- a/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarPresenter.swift +++ b/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarAppPresenter.swift @@ -10,7 +10,7 @@ import Foundation import Cocoa import RCPreferences -protocol CalendarPresenterInput: class { +protocol CalendarAppPresenterInput: class { func enable (_ enabled: Bool) func enableCalendar (_ calendarTitle: String) @@ -19,7 +19,7 @@ protocol CalendarPresenterInput: class { func authorize() } -protocol CalendarPresenterOutput: class { +protocol CalendarAppPresenterOutput: class { func enable (_ enabled: Bool) func setStatusImage (_ imageName: NSImage.Name) @@ -29,14 +29,14 @@ protocol CalendarPresenterOutput: class { func setCalendars (_ calendars: [String], selected: [String]) } -class CalendarPresenter { +class CalendarAppPresenter { - weak var userInterface: CalendarPresenterOutput? + weak var userInterface: CalendarAppPresenterOutput? private let calendarModule = ModuleCalendar() private let pref = RCPreferences() } -extension CalendarPresenter: CalendarPresenterInput { +extension CalendarAppPresenter: CalendarAppPresenterInput { func enable (_ enabled: Bool) { pref.set(enabled, forKey: .enableCalendar) diff --git a/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift b/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift index 76ea46f..fd76908 100644 --- a/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift +++ b/Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift @@ -20,11 +20,11 @@ class CalendarCell: NSTableRowView { @IBOutlet private var scrollView: NSScrollView! private var calendarsButtons = [NSButton]() - private var presenter: CalendarPresenterInput = CalendarPresenter() + private var presenter: CalendarAppPresenterInput = CalendarAppPresenter() override func awakeFromNib() { super.awakeFromNib() - (presenter as! CalendarPresenter).userInterface = self + (presenter as! CalendarAppPresenter).userInterface = self presenter.refresh() } @@ -57,7 +57,7 @@ class CalendarCell: NSTableRowView { } } -extension CalendarCell: CalendarPresenterOutput { +extension CalendarCell: CalendarAppPresenterOutput { func enable (_ enabled: Bool) { for but in calendarsButtons { @@ -81,7 +81,7 @@ extension CalendarCell: CalendarPresenterOutput { statusImageView.isHidden = authorized butAuthorize.isHidden = authorized butEnable.isHidden = !authorized - butEnable.state = enabled ? NSControl.StateValue.on : NSControl.StateValue.off + butEnable.state = enabled ? .on : .off } func setCalendars (_ calendars: [String], selected: [String]) { diff --git a/Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift b/Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift index 1415d19..085398b 100644 --- a/Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift +++ b/Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift @@ -10,44 +10,27 @@ import Cocoa class GitCell: NSTableRowView, Saveable { - static let height = CGFloat(195) + static let height = CGFloat(120) @IBOutlet private var statusImageView: NSImageView! @IBOutlet private var butEnable: NSButton! @IBOutlet private var statusTextField: NSTextField! @IBOutlet private var descriptionTextField: NSTextField! - @IBOutlet private var emailsTextField: NSTextField! - @IBOutlet private var pathsTextField: NSTextField! @IBOutlet private var butInstall: NSButton! @IBOutlet private var butPurchase: NSButton! - @IBOutlet private var butPick: NSButton! var presenter: GitPresenterInput = GitPresenter() var onPurchasePressed: (() -> Void)? - private var emailClickGestureRecognizer: NSClickGestureRecognizer? - private var gitUsersPopover: NSPopover? override func awakeFromNib() { super.awakeFromNib() (presenter as! GitPresenter).userInterface = self butEnable.isHidden = true butPurchase.isHidden = true - emailsTextField.delegate = self - pathsTextField.delegate = self - - let emailClickGestureRecognizer = NSClickGestureRecognizer(target: self, action: #selector(GitCell.emailTextFieldClicked)) - emailsTextField.addGestureRecognizer(emailClickGestureRecognizer) - self.emailClickGestureRecognizer = emailClickGestureRecognizer - } - - deinit { - if let gesture = emailClickGestureRecognizer { - emailsTextField.removeGestureRecognizer(gesture) - } } func save() { - presenter.save(emails: emailsTextField.stringValue, paths: pathsTextField.stringValue) + } @IBAction func handleInstallButton (_ sender: NSButton) { @@ -65,34 +48,6 @@ class GitCell: NSTableRowView, Saveable { @IBAction func handleEnableButton (_ sender: NSButton) { presenter.enableGit(sender.state == .on) } - - @IBAction func handlePickButton (_ sender: NSButton) { - presenter.pickPath() - } - - @objc func emailTextFieldClicked() { - guard gitUsersPopover == nil else { - return - } - let popover = NSPopover() - let view = GitUsersViewController.instantiateFromStoryboard("Components") - view.onDone = { - self.gitUsersPopover?.performClose(nil) - self.gitUsersPopover = nil - self.presenter.isShellScriptInstalled = true - } - popover.contentViewController = view - let rect = CGRect(origin: CGPoint(x: emailsTextField.frame.origin.x, y: GitCell.height-45), size: emailsTextField.frame.size) - popover.show(relativeTo: rect, of: self, preferredEdge: NSRectEdge.minY) - gitUsersPopover = popover - } -} - -extension GitCell: NSTextFieldDelegate { - - func controlTextDidEndEditing(_ obj: Notification) { - save() - } } extension GitCell: GitPresenterOutput { @@ -118,21 +73,4 @@ extension GitCell: GitPresenterOutput { } butEnable.isHidden = enabled == false } - func setPaths (_ paths: String?, enabled: Bool?) { - if let paths = paths { - pathsTextField.stringValue = paths - } - if let enabled = enabled { - pathsTextField.isEnabled = enabled - butPick.isEnabled = enabled - } - } - func setEmails (_ emails: String?, enabled: Bool?) { - if let emails = emails { - emailsTextField.stringValue = emails - } - if let enabled = enabled { - emailsTextField.isEnabled = enabled - } - } } diff --git a/Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib b/Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib index 2b07f9a..9db52ae 100644 --- a/Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib +++ b/Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib @@ -1,8 +1,8 @@ - + - + @@ -10,17 +10,17 @@ - + - + - + - + @@ -28,7 +28,7 @@ - + @@ -36,68 +36,26 @@ - - - - - - - - - - - - - - - - - - + - + Commits made with Git are loaded in Jirassic on demand, meaning that they don't get saved to local database or synced over iCloud. If you wish to save them use Jit - - - - - - - - - - - - - - - - - + - + @@ -46,7 +47,7 @@ - + @@ -84,7 +85,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.xib b/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.xib deleted file mode 100644 index be5889d..0000000 --- a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.xib +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellTests.swift b/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellTests.swift deleted file mode 100644 index c73484b..0000000 --- a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellTests.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// TaskCellTests.swift -// Jirassic -// -// Created by Baluta Cristian on 17/09/15. -// Copyright © 2015 Cristian Baluta. All rights reserved. -// - -import XCTest -@testable import Jirassic_no_cloud - -class TaskCellTests: XCTestCase { - -// func testSettingData() { -// -// let _cell = cell() -// RCLog(_cell) -// _cell.data = (dateStart: "dateStart", dateEnd: "dateEnd", issue: "IOS-01", notes: "notes") -// -// XCTAssert(_cell.data.dateStart == "dateStart", "") -// XCTAssert(_cell.data.dateEnd == "dateEnd", "") -// XCTAssert(_cell.data.issue == "IOS-01", "") -// XCTAssert(_cell.data.notes == "notes", "") -// } -// -// func cell() -> TaskCell { -// var views: NSArray? -// NSBundle.mainBundle().loadNibNamed("TaskCell", owner: self, topLevelObjects: &views) -// RCLog(views) -// RCLog(views?.objectAtIndex(0)) -// RCLog(views?.objectAtIndex(1)) -// if let c = views?.objectAtIndex(0) as? TaskCell { -// return c -// } else if let c = views?.objectAtIndex(1) as? TaskCell { -// return c -// } -// return TaskCell() -// } -} diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/TasksDataSource.swift b/Delivery/macOS/Screens/Tasks/AllTasks/TasksDataSource.swift deleted file mode 100644 index b33a5ba..0000000 --- a/Delivery/macOS/Screens/Tasks/AllTasks/TasksDataSource.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// TasksDataSource.swift -// Jirassic -// -// Created by Cristian Baluta on 17/02/2017. -// Copyright © 2017 Imagin soft. All rights reserved. -// - -import Cocoa - -let kNonTaskCellHeight = CGFloat(40.0) -let kTaskCellHeight = CGFloat(90.0) -let kGapBetweenCells = CGFloat(16.0) -let kCellLeftPadding = CGFloat(10.0) - -class TasksDataSource: NSObject, TasksAndReportsDataSource { - - var tableView: NSTableView! { - didSet { - TaskCell.register(in: tableView) - NonTaskCell.register(in: tableView) - } - } - var tasks: [Task] - var didClickAddRow: ((_ row: Int) -> Void)? - var didClickRemoveRow: ((_ row: Int) -> Void)? - var isDayEnded: Bool { - return self.tasks.contains(where: { $0.taskType == .endDay }) - } - - init (tasks: [Task]) { - self.tasks = tasks - } - - private func cellForTaskType (_ taskType: TaskType) -> CellProtocol { - - switch taskType { - case TaskType.issue, TaskType.gitCommit: - return TaskCell.instantiate(in: self.tableView) - default: - return NonTaskCell.instantiate(in: self.tableView) - } - } - - func addTask (_ task: Task, at row: Int) { - tasks.insert(task, at: row) - } - - func removeTask (at row: Int) { - tasks.remove(at: row) - } -} - -extension TasksDataSource: NSTableViewDataSource { - - func numberOfRows (in aTableView: NSTableView) -> Int { - return tasks.count - } - - func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - - let theData = tasks[row] - switch theData.taskType { - case TaskType.issue, TaskType.gitCommit: - return kTaskCellHeight - default: - return kNonTaskCellHeight - } - } -} - -extension TasksDataSource: NSTableViewDelegate { - - func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - - var theData = tasks[row] - let thePreviousData: Task? = row == 0 ? nil : tasks[row-1] - - var cell: CellProtocol = self.cellForTaskType(theData.taskType) - TaskCellPresenter(cell: cell).present(previousTask: thePreviousData, currentTask: theData) - - cell.didEndEditingCell = { [weak self] (cell: CellProtocol) in - let updatedData = cell.data - theData.taskNumber = updatedData.taskNumber - theData.notes = updatedData.notes - theData.startDate = updatedData.dateStart - theData.endDate = updatedData.dateEnd - // Save to local variable - self?.tasks[row] = theData - // Save to db and server - let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) - saveInteractor.saveTask(theData, allowSyncing: true, completion: { savedTask in - tableView.reloadData(forRowIndexes: [row], columnIndexes: [0]) - }) - } - cell.didClickRemoveCell = { [weak self] (cell: CellProtocol) in - // Ugly hack to find the row number from which the action came - tableView.enumerateAvailableRowViews({ (rowView, rowIndex) -> Void in - if rowView.subviews.first! == cell as! NSTableRowView { - self?.didClickRemoveRow!(rowIndex) - return - } - }) - } - cell.didClickAddCell = { [weak self] (cell: CellProtocol) in - // Ugly hack to find the row number from which the action came - tableView.enumerateAvailableRowViews( { rowView, rowIndex in - if rowView.subviews.first! == cell as! NSTableRowView { - self?.didClickAddRow!(rowIndex) - return - } - }) - } - - return cell as? NSView - } -} diff --git a/Delivery/macOS/Screens/Tasks/DataSource.swift b/Delivery/macOS/Screens/Tasks/DataSource.swift index 615b0e8..e25af83 100644 --- a/Delivery/macOS/Screens/Tasks/DataSource.swift +++ b/Delivery/macOS/Screens/Tasks/DataSource.swift @@ -9,11 +9,20 @@ import Cocoa protocol TasksAndReportsDataSource { + var tableView: NSTableView! {get set} - var didClickAddRow: ((_ row: Int) -> Void)? {get set} - var didClickRemoveRow: ((_ row: Int) -> Void)? {get set} + func addTask (_ task: Task, at row: Int) func removeTask (at row: Int) + // Used by tasks + var didClickAddRow: ((_ row: Int) -> Void)? {get set} + var didClickRemoveRow: ((_ row: Int) -> Void)? {get set} + var didClickCloseDay: ((_ tasks: [Task]) -> Void)? {get set} + var didClickSaveWorklogs: (() -> Void)? {get set} + var didClickSetupJira: (() -> Void)? {get set} + // Used by reports + var didClickCopyMonthlyReport: ((_ asHtml: Bool) -> Void)? {get set} + var didChangeSettings: (() -> Void)? {get set} } typealias DataSource = NSTableViewDataSource & NSTableViewDelegate & TasksAndReportsDataSource diff --git a/Delivery/macOS/Components/EditableTimeBox.swift b/Delivery/macOS/Screens/Tasks/EditableTimeBox.swift similarity index 90% rename from Delivery/macOS/Components/EditableTimeBox.swift rename to Delivery/macOS/Screens/Tasks/EditableTimeBox.swift index 2824c94..a904e62 100644 --- a/Delivery/macOS/Components/EditableTimeBox.swift +++ b/Delivery/macOS/Screens/Tasks/EditableTimeBox.swift @@ -21,6 +21,7 @@ class EditableTimeBox: TimeBox { override func awakeFromNib() { super.awakeFromNib() timeTextField?.delegate = self +// backgroundBox?.fillColor = .darkGray } } @@ -29,8 +30,8 @@ extension EditableTimeBox: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true partialValue = stringValue - self.borderColor = NSColor.darkGray - timeTextField?.textColor = NSColor.black +// backgroundBox?.borderColor = .darkGray + timeTextField?.textColor = .black return true } @@ -40,8 +41,8 @@ extension EditableTimeBox: NSTextFieldDelegate { wasEdited = false didEndEditing?() } - self.borderColor = NSColor.white - timeTextField?.textColor = NSColor.darkGray +// backgroundBox?.borderColor = .white + timeTextField?.textColor = .darkGray return true } diff --git a/Delivery/macOS/Components/NewTaskViewController.swift b/Delivery/macOS/Screens/Tasks/NewTaskViewController.swift similarity index 50% rename from Delivery/macOS/Components/NewTaskViewController.swift rename to Delivery/macOS/Screens/Tasks/NewTaskViewController.swift index de36f46..6b1fc76 100644 --- a/Delivery/macOS/Components/NewTaskViewController.swift +++ b/Delivery/macOS/Screens/Tasks/NewTaskViewController.swift @@ -8,18 +8,20 @@ import Cocoa import RCPreferences +import RCLog class NewTaskViewController: NSViewController { + @IBOutlet private weak var projectSelector: NSPopUpButton! @IBOutlet private weak var taskTypeSelector: NSPopUpButton! - @IBOutlet private weak var issueIdTextField: NSTextField! - @IBOutlet private weak var notesTextField: NSTextField! - @IBOutlet private weak var endDateTextField: NSTextField! + @IBOutlet private weak var issueIdTextField: NSTextField! + @IBOutlet private weak var notesTextField: NSTextField! + @IBOutlet private weak var endDateTextField: NSTextField! @IBOutlet private weak var startDateTextField: NSTextField! @IBOutlet private weak var startDateButton: NSButton! - var onSave: ((_ taskData: TaskCreationData) -> Void)? - var onCancel: (() -> Void)? + var onSave: ((_ taskData: TaskCreationData) -> Void)? + var onCancel: (() -> Void)? private var activeEditingTextFieldContent = "" private var issueTypes = [String]() private let predictor = PredictiveTimeTyping() @@ -64,30 +66,83 @@ class NewTaskViewController: NSViewController { return Double(hm.min).minToSec + Double(hm.hour).hoursToSec } } - var notes: String { + // If no notes inserted return nil + var notes: String? { get { - return notesTextField.stringValue + return notesTextField.stringValue != "" ? notesTextField.stringValue : nil } set { - self.notesTextField.stringValue = newValue + self.notesTextField.stringValue = newValue ?? "" } } - var taskNumber: String { + var taskNumber: String? { get { - return issueIdTextField.stringValue + return issueIdTextField.stringValue != "" ? issueIdTextField.stringValue : nil } set { - self.issueIdTextField.stringValue = newValue + self.issueIdTextField.stringValue = newValue ?? "" } } - + var taskType: TaskType = .issue { + didSet { + taskTypeSelector.isEnabled = true + issueIdTextField.isEnabled = true + notesTextField.isEnabled = true + + for i in 0.. Project? { + guard projects.count > 0 else { + return nil + } + return projects[projectSelector.indexOfSelectedItem] + } + private func estimateTaskType() { let typeEstimator = TaskTypeEstimator() let settings = SettingsInteractor().getAppSettings() let estimatedType: TaskType = typeEstimator.taskTypeAroundDate(initialDate, withSettings: settings) - if estimatedType == .scrum { - taskTypeSelector.selectItem(at: 1) - handleTaskTypeSelector(taskTypeSelector) - - let settingsScrumTime = gregorian.dateComponents(ymdhmsUnitFlags, from: settings.settingsTracking.scrumTime) - self.dateStart = self.initialDate.dateByUpdating(hour: settingsScrumTime.hour!, minute: settingsScrumTime.minute!) + guard estimatedType == .scrum else { + return } + taskTypeSelector.selectItem(at: 1) + handleTaskTypeSelector(taskTypeSelector) + + let settingsScrumTime = gregorian.dateComponents(ymdhmsUnitFlags, + from: settings.settingsTracking.scrumTime) + self.dateStart = self.initialDate.dateByUpdating(hour: settingsScrumTime.hour!, + minute: settingsScrumTime.minute!) } private func setupStartDateButtonTitle() { @@ -128,32 +194,30 @@ extension NewTaskViewController: NSTextFieldDelegate { func controlTextDidBeginEditing (_ obj: Notification) { - if let textField = obj.object as? NSTextField { - guard textField == endDateTextField || textField == startDateTextField else { - return - } - activeEditingTextFieldContent = textField.stringValue + guard let textField = obj.object as? NSTextField, + textField == endDateTextField || textField == startDateTextField else { + return } + activeEditingTextFieldContent = textField.stringValue } func controlTextDidChange (_ obj: Notification) { - if let textField = obj.object as? NSTextField { - guard textField == endDateTextField || textField == startDateTextField else { - return - } - let comps = textField.stringValue.map { String($0) } - let newDigit = activeEditingTextFieldContent.count > comps.count ? "" : comps.last - activeEditingTextFieldContent = predictor.timeByAdding(newDigit!, to: activeEditingTextFieldContent) - textField.stringValue = activeEditingTextFieldContent + guard let textField = obj.object as? NSTextField, + textField == endDateTextField || textField == startDateTextField else { + return } + let comps = textField.stringValue.map { String($0) } + let newDigit = activeEditingTextFieldContent.count > comps.count ? "" : comps.last + activeEditingTextFieldContent = predictor.timeByAdding(newDigit!, to: activeEditingTextFieldContent) + textField.stringValue = activeEditingTextFieldContent } } extension NewTaskViewController { @IBAction func handleTaskTypeSelector (_ sender: NSPopUpButton) { - issueIdTextField.isEnabled = selectedTaskType() == .issue + taskType = selectedTaskType() } @IBAction func handleSaveButton (_ sender: NSButton) { @@ -163,7 +227,8 @@ extension NewTaskViewController { dateEnd: self.dateEnd, taskNumber: self.taskNumber != "" ? self.taskNumber : nil, notes: self.notes != "" ? self.notes : nil, - taskType: selectedTaskType() + taskType: self.taskType, + projectId: selectedProject()?.objectId ) self.onSave?(taskData) } diff --git a/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib b/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib index c924654..97ed67c 100644 --- a/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib +++ b/Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib @@ -19,7 +19,7 @@ - + diff --git a/Delivery/macOS/Screens/Tasks/Tasks.storyboard b/Delivery/macOS/Screens/Tasks/Tasks.storyboard index c6ad531..f76d69e 100644 --- a/Delivery/macOS/Screens/Tasks/Tasks.storyboard +++ b/Delivery/macOS/Screens/Tasks/Tasks.storyboard @@ -1,284 +1,20 @@ - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -308,20 +44,20 @@ - + - - + + - + @@ -335,14 +71,28 @@ - + - + + + + + + + + + + + + + + + @@ -356,31 +106,368 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Usually Jira task ids which are of form LETTER-NUMBER, but can be anything really, important is that same task should have same id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - diff --git a/Delivery/macOS/Screens/Tasks/TasksDataSource.swift b/Delivery/macOS/Screens/Tasks/TasksDataSource.swift new file mode 100644 index 0000000..e210296 --- /dev/null +++ b/Delivery/macOS/Screens/Tasks/TasksDataSource.swift @@ -0,0 +1,144 @@ +// +// TasksDataSource.swift +// Jirassic +// +// Created by Cristian Baluta on 17/02/2017. +// Copyright © 2017 Imagin soft. All rights reserved. +// + +import Cocoa + +let kTaskCellHeight = CGFloat(40.0) +let kCloseDayCellHeight = CGFloat(90.0) +let kGapBetweenCells = CGFloat(16.0) +let kCellLeftPadding = CGFloat(10.0) + +class TasksDataSource: NSObject, TasksAndReportsDataSource { + + var didClickAddRow: ((_ row: Int) -> Void)? + var didClickRemoveRow: ((_ row: Int) -> Void)? + var didClickEditRow: ((_ row: Int) -> Void)? + var didClickCloseDay: ((_ tasks: [Task]) -> Void)? + var didClickSaveWorklogs: (() -> Void)? + var didClickSetupJira: (() -> Void)? + var didClickCopyMonthlyReport: ((_ asHtml: Bool) -> Void)? + var didChangeSettings: (() -> Void)? + + internal var tableView: NSTableView! { + didSet { + TaskCell.register(in: tableView) + CloseDayCell.register(in: tableView) + ClosedDayCell.register(in: tableView) + } + } + private var tasks: [Task] + private var isDayEnded: Bool { + return self.tasks.contains(where: { $0.taskType == .endDay }) + } + + init (tasks: [Task]) { + self.tasks = tasks + } + + func addTask (_ task: Task, at row: Int) { + tasks.insert(task, at: row) + } + + func removeTask (at row: Int) { + tasks.remove(at: row) + } +} + +extension TasksDataSource: NSTableViewDataSource { + + func numberOfRows (in aTableView: NSTableView) -> Int { + // Add an extra row at the end for 'Add task' and 'Close day' buttons + return tasks.count > 0 ? tasks.count + 1 : 0 + } + + func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + + guard row < tasks.count else { + return kCloseDayCellHeight + } + return kTaskCellHeight + } +} + +extension TasksDataSource: NSTableViewDelegate { + + func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + guard row < tasks.count else { + if self.isDayEnded { + let cell = ClosedDayCell.instantiate(in: tableView) + cell.didClickSaveWorklogs = { + self.didClickSaveWorklogs?() + } + cell.didClickSetupJira = { + self.didClickSetupJira?() + } + return cell + } else { + let cell = CloseDayCell.instantiate(in: tableView) + cell.didClickAddTask = { + self.didClickAddRow?(self.tasks.count - 1) + } + cell.didClickCloseDay = { + self.didClickCloseDay?(self.tasks) + } + return cell + } + } + + var theData = tasks[row] + let lastData = tasks.last + let thePreviousData: Task? = row == 0 ? nil : tasks[row-1] + var cell: CellProtocol = TaskCell.instantiate(in: tableView) + TaskCellPresenter(cell: cell).present(previousTask: thePreviousData, + currentTask: theData, + lastTask: lastData) + + cell.didClickEditCell = { [weak self] (cell: CellProtocol) in + // Ugly hack to find the row number from which the action came + tableView.enumerateAvailableRowViews( { (rowView, rowIndex) -> Void in + if rowView.subviews.first! == cell as! NSTableRowView { + self?.didClickEditRow!(rowIndex) + return + } + }) +// let updatedData = cell.data +// theData.taskNumber = updatedData.taskNumber +// theData.notes = updatedData.notes +// theData.startDate = updatedData.dateStart +// theData.endDate = updatedData.dateEnd +// // Save to local variable +// self?.tasks[row] = theData +// // Save to db and server +// let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) +// saveInteractor.saveTask(theData, allowSyncing: true, completion: { savedTask in +// tableView.reloadData(forRowIndexes: [row], columnIndexes: [0]) +// }) + } + cell.didClickRemoveCell = { [weak self] (cell: CellProtocol) in + // Ugly hack to find the row number from which the action came + tableView.enumerateAvailableRowViews( { (rowView, rowIndex) -> Void in + if rowView.subviews.first! == cell as! NSTableRowView { + self?.didClickRemoveRow!(rowIndex) + return + } + }) + } + cell.didClickAddCell = { [weak self] (cell: CellProtocol) in + // Ugly hack to find the row number from which the action came + tableView.enumerateAvailableRowViews( { rowView, rowIndex in + if rowView.subviews.first! == cell as! NSTableRowView { + self?.didClickAddRow!(rowIndex) + return + } + }) + } + + return cell as? NSView + } +} diff --git a/Delivery/macOS/Screens/Tasks/TasksInteractor.swift b/Delivery/macOS/Screens/Tasks/TasksInteractor.swift index 095e93d..a05f505 100644 --- a/Delivery/macOS/Screens/Tasks/TasksInteractor.swift +++ b/Delivery/macOS/Screens/Tasks/TasksInteractor.swift @@ -12,22 +12,19 @@ import RCLog protocol TasksInteractorInput: class { - func reloadCalendar() func reloadTasks (inDay day: Day) - func reloadTasks (inMonth day: Day) + func reloadTasks (inMonth date: Date) } protocol TasksInteractorOutput: class { - func calendarDidLoad (_ weeks: [Week]) func tasksDidLoad (_ tasks: [Task]) } class TasksInteractor { - weak var presenter: TasksPresenter? + weak var presenter: TasksInteractorOutput? - private let daysReader: ReadDaysInteractor! private let tasksReader: ReadTasksInteractor! private let moduleGit = ModuleGitLogs() private let moduleCalendar = ModuleCalendar() @@ -36,28 +33,12 @@ class TasksInteractor { private var currentDateStart: Date? init() { - daysReader = ReadDaysInteractor(repository: localRepository, remoteRepository: remoteRepository) tasksReader = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) } } extension TasksInteractor: TasksInteractorInput { - - func reloadCalendar() { - - let startRequestDate = Date() - daysReader.query(startingDate: Date(timeIntervalSinceNow: -12.monthsToSec).endOfDay()) { [weak self] weeks in - let endRequestDate = Date() - RCLog(endRequestDate.timeIntervalSince(startRequestDate)) - DispatchQueue.main.async { - guard let wself = self else { - return - } - wself.presenter?.calendarDidLoad(weeks) - } - } - } - + func reloadTasks (inDay day: Day) { let dateStart = day.dateStart @@ -65,15 +46,15 @@ extension TasksInteractor: TasksInteractorInput { currentDateStart = dateStart reloadTasks(dateStart: dateStart, dateEnd: dateEnd) } + + func reloadTasks (inMonth date: Date) { - func reloadTasks (inMonth day: Day) { - - let dateStart = day.dateStart.startOfMonth() - let dateEnd = dateStart.endOfMonth() + let dateStart = date.startOfMonth() + let dateEnd = date.endOfMonth() currentDateStart = dateStart reloadTasks(dateStart: dateStart, dateEnd: dateEnd) } - + private func reloadTasks (dateStart: Date, dateEnd: Date) { self.currentTasks = [] diff --git a/Delivery/macOS/Screens/Tasks/TasksPresenter.swift b/Delivery/macOS/Screens/Tasks/TasksPresenter.swift index 8bdaa1d..04bd0f6 100644 --- a/Delivery/macOS/Screens/Tasks/TasksPresenter.swift +++ b/Delivery/macOS/Screens/Tasks/TasksPresenter.swift @@ -7,42 +7,34 @@ // import Cocoa -import RCPreferences +import RCLog protocol TasksPresenterInput: class { - func initUI() - func syncData() - func reloadData() - func reloadTasksOnDay (_ day: Day, listType: ListType) + func reloadLastSelectedDay() + func reloadTasksOnDay (_ day: Day) func updateNoTasksState() - func messageButtonDidPress() - func startDay() - func closeDay (shouldSaveToJira: Bool) - func insertTaskWithData (_ taskData: TaskCreationData) + func closeDay (showWorklogs: Bool) + func saveNewTask (with taskData: TaskCreationData) + func updateTask (_ task: Task, with taskData: TaskCreationData) func insertTask (after row: Int) func removeTask (at row: Int) + func editTask (at row: Int) + func didClickStartDay() + func didClickSaveWorklogs() } protocol TasksPresenterOutput: class { func showLoadingIndicator (_ show: Bool) - func showWarning (_ show: Bool) func showMessage (_ message: MessageViewModel) - func showCalendar (_ weeks: [Week]) func showTasks (_ tasks: [Task]) - func showReports (_ reports: [Report], numberOfDays: Int, type: ListType) - func removeTasksController() - func selectDay (_ day: Day) func presentNewTaskController (date: Date) - func presentEndDayController (date: Date, tasks: [Task]) -} - -enum ListType: Int { - - case allTasks = 0 - case report = 1 - case monthlyReports = 2 + func presentTaskEditor(task: Task) + func closeTaskEditor() + func showWorklogs (date: Date, tasks: [Task]) + func removeTasks() + func removeWorklogs() } class TasksPresenter { @@ -52,118 +44,104 @@ class TasksPresenter { var interactor: TasksInteractorInput? private var currentTasks = [Task]() - private var currentReports = [Report]() - private var selectedListType = ListType.allTasks - private let pref = RCPreferences() - private var extensions = ExtensionsInteractor() - private var lastSelectedDay: Day? + private var lastSelectedDay: Day = Day(dateStart: Date(), dateEnd: nil) } extension TasksPresenter: TasksPresenterInput { - func initUI() { - ui!.showWarning(false) - ui!.showLoadingIndicator(false) - reloadData() - extensions.getVersions { [weak self] (versions) in - guard let userInterface = self?.ui else { - return - } - let compatibility = Versioning(versions: versions) - if compatibility.shellScript.available { - userInterface.showWarning(!compatibility.jirassic.compatible || !compatibility.jit.compatible) - } else { - userInterface.showWarning(false) - } - } -// updateNoTasksState() - } - - func syncData() { - reloadData() + func reloadLastSelectedDay() { + reloadTasksOnDay(lastSelectedDay) } - func reloadData() { - ui!.removeTasksController() - ui!.showLoadingIndicator(true) - interactor!.reloadCalendar() - } - - func reloadTasksOnDay (_ day: Day, listType: ListType) { - ui!.removeTasksController() - ui!.showLoadingIndicator(true) + func reloadTasksOnDay (_ day: Day) { lastSelectedDay = day - selectedListType = listType - switch selectedListType { - case .allTasks, .report: - interactor!.reloadTasks(inDay: day) - case .monthlyReports: - interactor!.reloadTasks(inMonth: day) - } + ui!.removeTasks() + ui!.removeWorklogs() + ui!.showLoadingIndicator(true) + interactor!.reloadTasks(inDay: day) } func updateNoTasksState() { if currentTasks.count == 0 { - ui!.showMessage(( - title: "Good morning!", - message: "Ready to start working today?", + if lastSelectedDay.dateStart.isToday() { + ui!.showMessage(( + title: "Good morning!", + message: "Ready to start working today?", + buttonTitle: "Start day")) + } else { + ui!.showMessage(( + title: "Day was not started!", + message: "Do you want to start it now?", buttonTitle: "Start day")) - } - else if currentTasks.count == 1, selectedListType == .report { - ui!.showMessage(( - title: "No task yet", - message: "Go to 'All tasks' tab and log some work first!", - buttonTitle: nil)) + } } else { appWireframe!.removePlaceholder() } } - func messageButtonDidPress() { + func didClickStartDay() { if currentTasks.count == 0 { startDay() } else { + ui!.closeTaskEditor() ui!.presentNewTaskController(date: Date()) } } func startDay() { - let task = Task(endDate: Date(), type: .startDay) + /// The day will start + /// 1. current timestamp if day is today + /// 2. start timestamp from settings if day is not today + let settings: Settings = SettingsInteractor().getAppSettings() + let startDate = lastSelectedDay.dateStart.isToday() + ? Date() + : lastSelectedDay.dateStart.dateByKeepingTime(from: settings.settingsTracking.startOfDayTime) + let task = Task(endDate: startDate, type: .startDay) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { [weak self] savedTask in - self?.reloadData() + self?.reloadLastSelectedDay() }) ModuleHookup().insert(task: task) } - - func closeDay (shouldSaveToJira: Bool) { + + func closeDay (showWorklogs: Bool) { - let closeDay = CloseDay() + let closeDay = CloseDayInteractor() closeDay.close(with: currentTasks) - if shouldSaveToJira { - // Reload data will be called after save with success - ui!.presentEndDayController(date: lastSelectedDay?.dateStart ?? Date(), tasks: currentTasks) + + if showWorklogs { + didClickSaveWorklogs() } else { - reloadData() + reloadLastSelectedDay() } } + + func didClickSaveWorklogs() { + // Reload data will be called after save with success + ui!.removeTasks() + ui!.showWorklogs(date: lastSelectedDay.dateStart, tasks: currentTasks) + } - func insertTaskWithData (_ taskData: TaskCreationData) { - - var task = Task() + func saveNewTask (with taskData: TaskCreationData) { + updateTask(Task(), with: taskData) + } + + func updateTask (_ task: Task, with taskData: TaskCreationData) { + + var task = task task.notes = taskData.notes task.taskNumber = taskData.taskNumber task.startDate = taskData.dateStart task.endDate = taskData.dateEnd task.taskType = taskData.taskType - + task.projectId = taskData.projectId + RCLog("Edited task: \(task)") + let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) - saveInteractor.saveTask(task, allowSyncing: false, completion: { savedTask in - - }) + saveInteractor.saveTask(task, allowSyncing: false, completion: { _ in }) } func insertTask (after row: Int) { @@ -172,6 +150,7 @@ extension TasksPresenter: TasksPresenterInput { // Insert task at the end let taskBefore = currentTasks[row] let nextDate = taskBefore.endDate.isSameDayAs(Date()) ? Date() : taskBefore.endDate.addingTimeInterval(3600) + ui!.closeTaskEditor() ui!.presentNewTaskController(date: nextDate) return } @@ -180,6 +159,7 @@ extension TasksPresenter: TasksPresenterInput { let taskAfter = currentTasks[row+1] let middleTimestamp = taskAfter.endDate.timeIntervalSince(taskBefore.endDate) / 2 let middleDate = taskBefore.endDate.addingTimeInterval(middleTimestamp) + ui!.closeTaskEditor() ui!.presentNewTaskController(date: middleDate) } @@ -190,35 +170,28 @@ extension TasksPresenter: TasksPresenterInput { let deleteInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) deleteInteractor.deleteTask(task) updateNoTasksState() + if currentTasks.count == 0 { - let reader = ReadDaysInteractor(repository: localRepository, remoteRepository: nil) - reader.queryAll { [weak self] (weeks) in - self?.ui?.showCalendar(weeks) - } + } } + + func editTask (at row: Int) { + let task = currentTasks[row] + RCLog("Star tediting task: \(task)") + ui!.closeTaskEditor()// Close current task editor if exists + ui!.presentTaskEditor(task: task) + } } extension TasksPresenter: TasksInteractorOutput { - func calendarDidLoad (_ weeks: [Week]) { - - guard let ui = self.ui else { - return - } - ui.showLoadingIndicator(false) - let day = lastSelectedDay ?? Day(dateStart: Date(), dateEnd: nil) - ui.showCalendar(weeks) - ui.selectDay(day) - } - func tasksDidLoad (_ tasks: [Task]) { - + RCLog(tasks) guard let ui = self.ui else { return } ui.showLoadingIndicator(false) - ui.removeTasksController() currentTasks = tasks switch selectedListType { diff --git a/Delivery/macOS/Components/TimeBox.swift b/Delivery/macOS/Screens/Tasks/TimeBox.swift similarity index 74% rename from Delivery/macOS/Components/TimeBox.swift rename to Delivery/macOS/Screens/Tasks/TimeBox.swift index 69445cc..cf81c92 100644 --- a/Delivery/macOS/Components/TimeBox.swift +++ b/Delivery/macOS/Screens/Tasks/TimeBox.swift @@ -10,6 +10,7 @@ import Cocoa class TimeBox: NSBox { + internal var backgroundBox: NSBox? internal var timeTextField: NSTextField? var stringValue: String { @@ -24,10 +25,10 @@ class TimeBox: NSBox { didSet { let isDark = self.isDark if #available(OSX 10.14, *) { - self.fillColor = isDark ? NSColor.white : NSColor.darkGray - self.timeTextField?.textColor = isDark ? NSColor.darkGray : NSColor.white +// self.fillColor = .clear //isDark ? .white : .darkGray + self.timeTextField?.textColor = .labelColor //isDark ? .darkGray : .white } else { - self.fillColor = isDark ? NSColor.darkGray : NSColor.white +// self.fillColor = isDark ? NSColor.darkGray : NSColor.white // self.borderColor = isDark ? NSColor.clear : NSColor.white self.timeTextField?.textColor = isDark ? NSColor.white : NSColor.darkGray // self.timeTextField?.backgroundColor = isDark ? NSColor.darkGray : NSColor.darkGray @@ -38,6 +39,8 @@ class TimeBox: NSBox { init() { super.init(frame: NSRect.zero) + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.clear.cgColor stringValue = "" } @@ -48,13 +51,19 @@ class TimeBox: NSBox { override func awakeFromNib() { super.awakeFromNib() - self.borderType = .noBorder + backgroundBox = NSBox() + backgroundBox?.borderType = .noBorder + backgroundBox?.cornerRadius = 7 + backgroundBox?.boxType = .custom + backgroundBox?.fillColor = .clear +// self.addSubview(backgroundBox!) +// backgroundBox?.constrainToSuperview() timeTextField = NSTextField() - timeTextField?.font = NSFont.boldSystemFont(ofSize: 10) - timeTextField?.textColor = NSColor.darkGray - timeTextField?.backgroundColor = NSColor.clear - timeTextField?.drawsBackground = false + timeTextField?.font = NSFont.systemFont(ofSize: 10) + timeTextField?.textColor = .darkGray + timeTextField?.backgroundColor = .clear +// timeTextField?.drawsBackground = false timeTextField?.alignment = .center timeTextField?.focusRingType = .none timeTextField?.placeholderString = "00:00" @@ -68,7 +77,7 @@ class TimeBox: NSBox { self.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|-(-5)-[view]-(-5)-|", options: [], metrics: nil, views: viewsDictionary)) self.addConstraints(NSLayoutConstraint.constraints( - withVisualFormat: "V:|-(-3)-[view]-(-5)-|", options: [], metrics: nil, views: viewsDictionary)) + withVisualFormat: "V:|-(-3)-[view]-(-6)-|", options: [], metrics: nil, views: viewsDictionary)) } override func mouseDown(with event: NSEvent) { diff --git a/Delivery/macOS/Components/TimeBoxViewController.swift b/Delivery/macOS/Screens/Tasks/TimeBoxViewController.swift similarity index 100% rename from Delivery/macOS/Components/TimeBoxViewController.swift rename to Delivery/macOS/Screens/Tasks/TimeBoxViewController.swift diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/CellProtocol.swift b/Delivery/macOS/Screens/Tasks/cells/CellProtocol.swift similarity index 91% rename from Delivery/macOS/Screens/Tasks/AllTasks/CellProtocol.swift rename to Delivery/macOS/Screens/Tasks/cells/CellProtocol.swift index dcc7f29..05168e7 100644 --- a/Delivery/macOS/Screens/Tasks/AllTasks/CellProtocol.swift +++ b/Delivery/macOS/Screens/Tasks/cells/CellProtocol.swift @@ -20,7 +20,7 @@ protocol CellProtocol { var color: NSColor {get set} var timeToolTip: String? {get set} - var didEndEditingCell: ((_ cell: CellProtocol) -> ())? {get set} + var didClickEditCell: ((_ cell: CellProtocol) -> ())? {get set} var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? {get set} var didClickAddCell: ((_ cell: CellProtocol) -> ())? {get set} var didCopyContentCell: ((_ cell: CellProtocol) -> ())? {get set} diff --git a/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.swift b/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.swift new file mode 100644 index 0000000..c51836e --- /dev/null +++ b/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.swift @@ -0,0 +1,27 @@ +// +// CloseDayCell.swift +// Jirassic +// +// Created by Cristian Baluta on 25/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa + +class CloseDayCell: NSTableRowView { + + @IBOutlet private var butAdd: NSButton! + @IBOutlet private var butCloseDay: NSButton! + + var didClickAddTask: (() -> Void)? + var didClickCloseDay: (() -> Void)? + + @IBAction func handleAddButton (_ sender: NSButton) { + didClickAddTask?() + } + + @IBAction func handleCloseDayButton (_ sender: NSButton) { + didClickCloseDay?() + } + +} diff --git a/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.xib b/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.xib new file mode 100644 index 0000000..4480ce8 --- /dev/null +++ b/Delivery/macOS/Screens/Tasks/cells/CloseDayCell/CloseDayCell.xib @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.swift b/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.swift new file mode 100644 index 0000000..d09ee64 --- /dev/null +++ b/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.swift @@ -0,0 +1,42 @@ +// +// ClosedDayCell.swift +// Jirassic +// +// Created by Cristian Baluta on 25/12/2019. +// Copyright © 2019 Imagin soft. All rights reserved. +// + +import Cocoa +import RCPreferences + +class ClosedDayCell: NSTableRowView { + + @IBOutlet private var butSaveWorklogs: NSButton! + @IBOutlet private var butSetupJira: NSButton! + + private unowned let appWireframe = AppDelegate.sharedApp().appWireframe + private var store = Store.shared + private var moduleJira = ModuleJiraTempo() + private let pref = RCPreferences() + + var didClickSaveWorklogs: (() -> Void)? + var didClickSetupJira: (() -> Void)? + + override func awakeFromNib() { + super.awakeFromNib() + let isJiraAvailable = store.isJiraTempoPurchased && + moduleJira.isConfigured && + moduleJira.isProjectConfigured + butSaveWorklogs.isEnabled = isJiraAvailable + butSetupJira.isHidden = isJiraAvailable + } + + @IBAction func handleSaveWorklogsButton (_ sender: NSButton) { + didClickSaveWorklogs?() + } + + @IBAction func handleSetupJiraButton (_ sender: NSButton) { + didClickSetupJira?() + } + +} diff --git a/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.xib b/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.xib new file mode 100644 index 0000000..e2fe486 --- /dev/null +++ b/Delivery/macOS/Screens/Tasks/cells/ClosedDayCell/ClosedDayCell.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.swift b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.swift similarity index 75% rename from Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.swift rename to Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.swift index 7fffb7d..91f5d6c 100644 --- a/Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.swift +++ b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.swift @@ -8,24 +8,26 @@ import Cocoa -class NonTaskCell: NSTableRowView, CellProtocol { +class TaskCell: NSTableRowView, CellProtocol { @IBOutlet var statusImage: NSImageView? - @IBOutlet private var statusImageWidthContraint: NSLayoutConstraint! @IBOutlet private var dateStartTextField: TimeBox! @IBOutlet private var dateStartTextFieldLeadingContraint: NSLayoutConstraint! - @IBOutlet private var dateEndTextField: TimeBox! - @IBOutlet private var notesTextField: NSTextField! - @IBOutlet private var notesTextFieldTrailingContraint: NSLayoutConstraint! - @IBOutlet private var butRemove: NSButton! - @IBOutlet private var butAdd: NSButton! + @IBOutlet private var bullet: NSTextField! + @IBOutlet private var dateEndTextField: TimeBox! + @IBOutlet private var notesTextField: NSTextField! + @IBOutlet private var notesTextFieldWidthContraint: NSLayoutConstraint! + @IBOutlet private var butRemove: NSButton! + @IBOutlet private var butAdd: NSButton! + @IBOutlet private var butEdit: NSButton! private var isEditing = false private var wasEdited = false + private var isMouseOver = false private var trackingArea: NSTrackingArea? private var activeTimeboxPopover: NSPopover? - var didEndEditingCell: ((_ cell: CellProtocol) -> ())? + var didClickEditCell: ((_ cell: CellProtocol) -> ())? var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? var didClickAddCell: ((_ cell: CellProtocol) -> ())? var didCopyContentCell: ((_ cell: CellProtocol) -> ())? @@ -53,13 +55,15 @@ class NonTaskCell: NSTableRowView, CellProtocol { if let dateStart = newValue.dateStart { self.dateStartTextField.stringValue = dateStart.HHmm() self.dateStartTextField.isHidden = false - self.dateStartTextFieldLeadingContraint.constant = 14 + self.bullet.isHidden = false + self.dateStartTextFieldLeadingContraint.constant = 20 } else { self.dateStartTextField.isHidden = true - self.dateStartTextFieldLeadingContraint.constant = 14 - 36 - 4 + self.bullet.isHidden = true + self.dateStartTextFieldLeadingContraint.constant = 20 - 36 - 8 } self.dateEndTextField.stringValue = newValue.dateEnd.HHmm() - self.notesTextField.stringValue = newValue.notes ?? "" + self.notesTextField.stringValue = (newValue.taskNumber ?? "") + " - " + (newValue.notes ?? "") } } var duration: String { @@ -104,6 +108,7 @@ class NonTaskCell: NSTableRowView, CellProtocol { butRemove.isHidden = true butAdd.isHidden = true + butEdit.isHidden = true butRemove.wantsLayer = true dateStartTextField.onClick = { self.createTimeboxPopover(timebox: self.dateStartTextField) @@ -114,27 +119,32 @@ class NonTaskCell: NSTableRowView, CellProtocol { if AppDelegate.sharedApp().theme.isDark { notesTextField!.textColor = NSColor.white } - } - - override func drawBackground (in dirtyRect: NSRect) { - - NSColor(calibratedWhite: 1.0, alpha: 0.0).setFill() - let selectionPath = NSBezierPath(roundedRect: dirtyRect, xRadius: 0, yRadius: 0) - selectionPath.fill() - } + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + self.notesTextFieldWidthContraint.constant = dirtyRect.width - notesTextField.frame.origin.x - (isMouseOver ? 70 : 10) + } + + override func drawBackground (in dirtyRect: NSRect) { + + NSColor(calibratedWhite: 1.0, alpha: 0.0).setFill() + let selectionPath = NSBezierPath(roundedRect: dirtyRect, xRadius: 0, yRadius: 0) + selectionPath.fill() + } private func createTimeboxPopover (timebox: TimeBox) { guard activeTimeboxPopover == nil, isEditable else { return } let popover = NSPopover() - let view = TimeBoxViewController.instantiateFromStoryboard("Components") + let view = TimeBoxViewController.instantiateFromStoryboard("Tasks") view.didSave = { let hasChanges = timebox.stringValue != view.stringValue timebox.stringValue = view.stringValue popover.performClose(nil) if hasChanges { - self.didEndEditingCell?(self) + self.didClickEditCell?(self) } } view.didCancel = { @@ -149,7 +159,7 @@ class NonTaskCell: NSTableRowView, CellProtocol { } } -extension NonTaskCell { +extension TaskCell { @IBAction func handleRemoveButton (_ sender: NSButton) { didClickRemoveCell?(self) @@ -158,42 +168,41 @@ extension NonTaskCell { @IBAction func handleAddButton (_ sender: NSButton) { didClickAddCell?(self) } + + @IBAction func handleEditButton (_ sender: NSButton) { + didClickEditCell?(self) + } } -extension NonTaskCell { +/// Mouse tracking +extension TaskCell { override func mouseEntered (with theEvent: NSEvent) { super.mouseEntered(with: theEvent) - self.butRemove.isHidden = false - self.butAdd.isHidden = false - self.notesTextFieldTrailingContraint.constant = 80 - self.setNeedsDisplay(self.frame) + butRemove.isHidden = false + butAdd.isHidden = false + butEdit.isHidden = false + statusImage?.isHidden = true + isMouseOver = true + setNeedsDisplay(frame) } -// override func mouseMoved(with event: NSEvent) { -// -// let locationInWindow = event.locationInWindow -// let locationInView = self.convert(locationInWindow, from: nil) -// RCLog(locationInView) -// if dateStartTextField.frame.contains(locationInView) { -// dateStartTextField.font = NSFont.systemFont(ofSize: 14) -// } -// } - override func mouseExited (with theEvent: NSEvent) { super.mouseExited(with: theEvent) - self.butRemove.isHidden = true - self.butAdd.isHidden = true - self.notesTextFieldTrailingContraint.constant = 10 - self.setNeedsDisplay(self.frame) + butRemove.isHidden = true + butAdd.isHidden = true + butEdit.isHidden = true + statusImage?.isHidden = false + isMouseOver = false + setNeedsDisplay(frame) } func ensureTrackingArea() { if trackingArea == nil { - trackingArea = NSTrackingArea(rect: NSZeroRect, + trackingArea = NSTrackingArea(rect: self.frame, options: [ NSTrackingArea.Options.inVisibleRect, NSTrackingArea.Options.activeAlways, @@ -214,7 +223,7 @@ extension NonTaskCell { } } -extension NonTaskCell: NSTextFieldDelegate { +extension TaskCell: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true @@ -224,7 +233,7 @@ extension NonTaskCell: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if wasEdited { wasEdited = false - didEndEditingCell?(self) + didClickEditCell?(self) } return true } @@ -232,7 +241,7 @@ extension NonTaskCell: NSTextFieldDelegate { public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { // Detect Enter key if wasEdited && commandSelector == #selector(NSResponder.insertNewline(_:)) { - didEndEditingCell?(self) + didClickEditCell?(self) wasEdited = false } return false diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.xib b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.xib similarity index 60% rename from Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.xib rename to Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.xib index dc7be91..dbf3d1f 100644 --- a/Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.xib +++ b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell.xib @@ -1,49 +1,60 @@ - + - + - + - - + + + + + - + - - - + + + + + + + + + + + - - + + - - - + + + - - + + + @@ -85,33 +110,40 @@ - + + + + - + - - + + + - - + + + + - - - + + + - + + diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellPresenter.swift b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCellPresenter.swift similarity index 54% rename from Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellPresenter.swift rename to Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCellPresenter.swift index 7d59588..2916eb5 100644 --- a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellPresenter.swift +++ b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCellPresenter.swift @@ -17,11 +17,11 @@ class TaskCellPresenter: NSObject { self.cell = cell } - func present (previousTask: Task?, currentTask theTask: Task) { + func present (previousTask: Task?, currentTask: Task, lastTask: Task?) { cell.statusImage?.image = nil - switch theTask.taskType { + switch currentTask.taskType { case .issue: cell.statusImage?.image = NSImage(named: NSImage.statusAvailableName) @@ -42,25 +42,29 @@ class TaskCellPresenter: NSObject { cell.color = NSColor.systemGray } - var notes = theTask.notes ?? "" + var notes = currentTask.notes ?? "" if notes == "" { - notes = theTask.taskType.defaultNotes + notes = currentTask.taskType.defaultNotes } // The codereview notes is a list of tasks that were reviewed - if theTask.taskType == .coderev && theTask.notes != nil && theTask.notes != "" { - notes = "\(theTask.taskType.defaultNotes): \(notes)" + if currentTask.taskType == .coderev && currentTask.notes != nil && currentTask.notes != "" { + notes = "\(currentTask.taskType.defaultNotes): \(notes)" + } + if currentTask.taskType == .lunch { + notes = currentTask.taskType.defaultNotes } cell.data = ( - dateStart: theTask.startDate, - dateEnd: theTask.endDate, - taskNumber: theTask.taskNumber, + dateStart: currentTask.startDate, + dateEnd: currentTask.endDate, + taskNumber: currentTask.taskNumber, notes: notes, - taskType: theTask.taskType + taskType: currentTask.taskType, + projectId: currentTask.projectId ) cell.isDark = AppDelegate.sharedApp().theme.isDark - cell.isEditable = theTask.objectId != nil - cell.isRemovable = theTask.objectId != nil - cell.isIgnored = theTask.taskType == .lunch || theTask.taskType == .waste - cell.timeToolTip = theTask.objectId != nil ? "Click to edit" : "Item can be edited after the day is closed" + cell.isEditable = currentTask.isSaved && false + cell.isRemovable = currentTask.isSaved + cell.isIgnored = currentTask.taskType == .lunch || currentTask.taskType == .waste + cell.timeToolTip = currentTask.isSaved ? "Click to edit" : "Item can be edited after the day is closed" } } diff --git a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.swift b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell_.swift similarity index 94% rename from Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.swift rename to Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell_.swift index e92a860..535fd3e 100644 --- a/Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.swift +++ b/Delivery/macOS/Screens/Tasks/cells/TaskCell/TaskCell_.swift @@ -7,10 +7,8 @@ // import Cocoa - -class TaskCell: NSTableRowView, CellProtocol { - - @IBOutlet var contentView: NSView! +/* +class TaskCell2: NSTableRowView { @IBOutlet var statusImage: NSImageView? @IBOutlet private var dateEndTextField: TimeBox! @@ -22,9 +20,6 @@ class TaskCell: NSTableRowView, CellProtocol { @IBOutlet private var butRemove: NSButton! @IBOutlet private var butRemoveWidthConstraint: NSLayoutConstraint! - @IBOutlet private var line1: NSBox! - @IBOutlet private var line2: NSBox! - private var isEditing = false private var wasEdited = false private var mouseInside = false @@ -112,7 +107,7 @@ class TaskCell: NSTableRowView, CellProtocol { return } let popover = NSPopover() - let view = TimeBoxViewController.instantiateFromStoryboard("Components") + let view = TimeBoxViewController.instantiateFromStoryboard("Tasks") view.didSave = { let hasChanges = timebox.stringValue != view.stringValue timebox.stringValue = view.stringValue @@ -133,7 +128,7 @@ class TaskCell: NSTableRowView, CellProtocol { } } -extension TaskCell { +extension TaskCell2 { @IBAction func handleRemoveButton (_ sender: NSButton) { didClickRemoveCell?(self) @@ -144,8 +139,8 @@ extension TaskCell { } } -extension TaskCell { - +extension TaskCell2 { +/* override func drawBackground (in dirtyRect: NSRect) { let width = dirtyRect.size.width - kCellLeftPadding * 2 @@ -158,8 +153,8 @@ extension TaskCell { selectionPath.stroke() } else if self.mouseInside { - notesTextFieldRightConstrain!.constant = isRemovable ? 90 : 40 - butRemoveWidthConstraint.constant = isRemovable ? 40 : 0 + notesTextFieldRightConstrain!.constant = isRemovable ? 36 : 0 + butRemoveWidthConstraint.constant = isRemovable ? 16 : 0 let selectionRect = NSRect(x: kCellLeftPadding, y: 2, width: width, height: height) //NSColor(calibratedWhite: 1.0, alpha: 0.0).setFill() AppDelegate.sharedApp().theme.highlightLineColor.setStroke() @@ -178,7 +173,7 @@ extension TaskCell { selectionPath.stroke() } } - + */ override func mouseEntered (with theEvent: NSEvent) { super.mouseEntered(with: theEvent) self.mouseInside = true @@ -196,8 +191,8 @@ extension TaskCell { func showMouseOverControls (_ show: Bool) { butRemove.isHidden = !show butAdd.isHidden = !show - line1.isHidden = !show - line2.isHidden = !show + //line1.isHidden = !show + //line2.isHidden = !show } func ensureTrackingArea() { @@ -222,7 +217,7 @@ extension TaskCell { } } -extension TaskCell: NSTextFieldDelegate { +extension TaskCell2: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true @@ -255,3 +250,4 @@ extension TaskCell: NSTextFieldDelegate { wasEdited = true } } +*/ diff --git a/Delivery/macOS/Screens/Worklogs/Worklogs.storyboard b/Delivery/macOS/Screens/Worklogs/Worklogs.storyboard index 7c5e649..f8df507 100644 --- a/Delivery/macOS/Screens/Worklogs/Worklogs.storyboard +++ b/Delivery/macOS/Screens/Worklogs/Worklogs.storyboard @@ -1,8 +1,8 @@ - + - + @@ -15,13 +15,13 @@ - + - + - - - - - - - - - - - - + @@ -84,7 +73,10 @@ Gw - + + + + @@ -92,24 +84,24 @@ Gw - + - + - + - + - + - - + + @@ -120,7 +112,7 @@ Gw - + @@ -134,7 +126,7 @@ Gw - + @@ -161,7 +153,7 @@ Gw - + @@ -170,14 +162,13 @@ Gw + - - - + + - - + @@ -188,22 +179,22 @@ Gw - + - - + + + - + - diff --git a/Delivery/macOS/Screens/Worklogs/WorklogsPresenter.swift b/Delivery/macOS/Screens/Worklogs/WorklogsPresenter.swift index 60a7f27..34daf45 100644 --- a/Delivery/macOS/Screens/Worklogs/WorklogsPresenter.swift +++ b/Delivery/macOS/Screens/Worklogs/WorklogsPresenter.swift @@ -11,7 +11,7 @@ import RCPreferences protocol WorklogsPresenterInput: class { func setup (date: Date, tasks: [Task]) - func save (worklog: String) + func save (worklog: String, duration: String) func enableRounding (_ enabled: Bool) } @@ -53,7 +53,8 @@ extension WorklogsPresenter: WorklogsPresenterInput { let settings = SettingsInteractor().getAppSettings() workdayLength = TimeInteractor(settings: settings).workingDayLength() workedLength = StatisticsInteractor().duration(of: reports) - let duration = (pref.bool(.enableRoundingDay) ? workdayLength : workedLength).secToPercent + let isRoundingEnabled = pref.bool(.enableRoundingDay) + let duration = (isRoundingEnabled ? workdayLength : workedLength).secToPercent userInterface!.showDuration(duration) userInterface!.showWorklog(message) @@ -66,31 +67,31 @@ extension WorklogsPresenter: WorklogsPresenterInput { title: "Round worklogs duration to \(String(describing: workdayLength)) hours") } - func save (worklog: String) { + func save (worklog: String, duration: String) { userInterface!.showJiraMessage("", isError: false) - // Save to jira tempo - let isRoundingEnabled = pref.bool(.enableRoundingDay) - + guard let d = Double(duration) else { + return + } userInterface!.showProgressIndicator(true) - let duration = isRoundingEnabled ? workdayLength : workedLength - moduleJira.postWorklog(worklog: worklog, duration: duration, date: date!, success: { [weak self] in + /// Save to jira tempo + moduleJira.postWorklog(worklog: worklog, duration: d.hoursToSec, date: date!, success: { [weak self] in - DispatchQueue.main.async { - if let userInterface = self?.userInterface { - userInterface.showProgressIndicator(false) - userInterface.showJiraMessage("Worklogs saved to Jira", isError: false) - userInterface.saveSuccess() - } + DispatchQueue.main.async { + if let userInterface = self?.userInterface { + userInterface.showProgressIndicator(false) + userInterface.showJiraMessage("Worklogs saved to Jira", isError: false) + userInterface.saveSuccess() } - }, failure: { [weak self] error in - - DispatchQueue.main.async { - if let userInterface = self?.userInterface { - userInterface.showProgressIndicator(false) - userInterface.showJiraMessage(error.localizedDescription, isError: true) - } + } + }, failure: { [weak self] error in + + DispatchQueue.main.async { + if let userInterface = self?.userInterface { + userInterface.showProgressIndicator(false) + userInterface.showJiraMessage(error.localizedDescription, isError: true) } + } }) } diff --git a/Delivery/macOS/Screens/Worklogs/WorklogsViewController.swift b/Delivery/macOS/Screens/Worklogs/WorklogsViewController.swift index c568c27..295836d 100644 --- a/Delivery/macOS/Screens/Worklogs/WorklogsViewController.swift +++ b/Delivery/macOS/Screens/Worklogs/WorklogsViewController.swift @@ -10,7 +10,6 @@ import Cocoa class WorklogsViewController: NSViewController { - @IBOutlet private var dateTextField: NSTextField! @IBOutlet private var durationTextField: NSTextField! @IBOutlet private var worklogTextView: NSTextView! @IBOutlet private var progressIndicator: NSProgressIndicator! @@ -21,14 +20,13 @@ class WorklogsViewController: NSViewController { var onSave: (() -> Void)? var onCancel: (() -> Void)? var presenter: WorklogsPresenterInput? + weak var appWireframe: AppWireframe? var date: Date? var tasks: [Task]? - weak var appWireframe: AppWireframe? override func viewDidLoad() { super.viewDidLoad() - dateTextField.stringValue = date!.EEEEMMMdd() jiraErrorTextField.stringValue = "" worklogTextView.drawsBackground = false @@ -42,7 +40,8 @@ class WorklogsViewController: NSViewController { } @IBAction func handleSaveButton (_ sender: NSButton) { - presenter!.save(worklog: worklogTextView.string) + let duration = durationTextField.stringValue + presenter!.save(worklog: worklogTextView.string, duration: duration) } @IBAction func handleRoundButton (_ sender: NSButton) { diff --git a/External/AppleScriptCommands/NewTaskCommand.swift b/External/AppleScriptCommands/NewTaskCommand.swift index e0fd6cf..b688c57 100644 --- a/External/AppleScriptCommands/NewTaskCommand.swift +++ b/External/AppleScriptCommands/NewTaskCommand.swift @@ -30,7 +30,7 @@ class NewTaskCommand: NSScriptCommand { guard let data = validJson.data(using: String.Encoding.utf8) else { return nil } - guard let jdict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String], let dict = jdict else { + guard let jdict = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]) as [String : String]??), let dict = jdict else { return nil } RCLog(dict) diff --git a/External/CloudKit/CKRecord+Project.swift b/External/CloudKit/CKRecord+Project.swift new file mode 100644 index 0000000..7b2a1ac --- /dev/null +++ b/External/CloudKit/CKRecord+Project.swift @@ -0,0 +1,43 @@ +// +// CKRecord+Project.swift +// Jirassic +// +// Created by Cristian Baluta on 21/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit + +extension CKRecord { + + func toProject() -> Project { + + return Project( + objectId: self["objectId"] as? String, + lastModifiedDate: self["lastModifiedDate"] as? Date, + title: self["title"] as! String, + jiraBaseUrl: self["jiraBaseUrl"] as? String, + jiraUser: self["jiraUser"] as? String, + jiraProject: self["jiraProject"] as? String, + jiraIssue: self["jiraIssue"] as? String, + gitBaseUrls: (self["gitBaseUrls"] as? String ?? "").toArray(), + gitUsers: (self["gitUsers"] as? String ?? "").toArray(), + taskNumberPrefix: self["taskNumberPrefix"] as? String + ) + } + + func update (with project: Project) { + + self["objectId"] = project.objectId as CKRecordValue? + self["lastModifiedDate"] = project.lastModifiedDate as CKRecordValue? + self["title"] = project.title as CKRecordValue + self["jiraBaseUrl"] = project.jiraBaseUrl as CKRecordValue? + self["jiraUser"] = project.jiraUser as CKRecordValue? + self["jiraProject"] = project.jiraProject as CKRecordValue? + self["jiraIssue"] = project.jiraIssue as CKRecordValue? + self["gitBaseUrls"] = project.gitBaseUrls.toString() as CKRecordValue? + self["gitUsers"] = project.gitUsers.toString() as CKRecordValue? + self["taskNumberPrefix"] = project.taskNumberPrefix as CKRecordValue? + } +} diff --git a/External/CloudKit/CKRecord+Task.swift b/External/CloudKit/CKRecord+Task.swift new file mode 100644 index 0000000..e599b6d --- /dev/null +++ b/External/CloudKit/CKRecord+Task.swift @@ -0,0 +1,39 @@ +// +// CKRecord+Task.swift +// Jirassic +// +// Created by Cristian Baluta on 21/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit + +extension CKRecord { + + func toTask() -> Task { + + return Task(lastModifiedDate: self.modificationDate, + startDate: self["startDate"] as? Date, + endDate: self["endDate"] as! Date, + notes: self["notes"] as? String, + taskNumber: self["taskNumber"] as? String, + taskTitle: self["taskTitle"] as? String, + taskType: TaskType(rawValue: (self["taskType"] as! NSNumber).intValue)!, + objectId: self["objectId"] as? String, + projectId: self["projectId"] as? String + ) + } + + func update (with task: Task) { + + self["startDate"] = task.startDate as CKRecordValue? + self["endDate"] = task.endDate as CKRecordValue + self["notes"] = task.notes as CKRecordValue? + self["taskNumber"] = task.taskNumber as CKRecordValue? + self["taskTitle"] = task.taskTitle as CKRecordValue? + self["taskType"] = task.taskType.rawValue as CKRecordValue + self["objectId"] = task.objectId as CKRecordValue? + self["projectId"] = task.projectId as CKRecordValue? + } +} diff --git a/External/CloudKit/CloudKitRepository+Metadata.swift b/External/CloudKit/CloudKitRepository+Metadata.swift new file mode 100644 index 0000000..76c7f75 --- /dev/null +++ b/External/CloudKit/CloudKitRepository+Metadata.swift @@ -0,0 +1,22 @@ +// +// CloudKitRepository+Metadata.swift +// Jirassic +// +// Created by Cristian Baluta on 13/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +extension CloudKitRepository: RepositoryMetadata { + + func tasksLastSyncDate() -> Date? { fatalError("This method is not applicable to CloudKitRepository") } + func projectsLastSyncDate() -> Date? { fatalError("This method is not applicable to CloudKitRepository") } + func tasksLastSyncToken() -> String? { fatalError("This method is not applicable to CloudKitRepository") } + func projectsLastSyncToken() -> String? { fatalError("This method is not applicable to CloudKitRepository") } + func set(tasksLastSyncDate: Date?) { fatalError("This method is not applicable to CloudKitRepository") } + func set(projectsLastSyncDate: Date?) { fatalError("This method is not applicable to CloudKitRepository") } + func set(tasksLastSyncToken: String?) { fatalError("This method is not applicable to CloudKitRepository") } + func set(projectsLastSyncToken: String?) { fatalError("This method is not applicable to CloudKitRepository") } + +} diff --git a/External/CloudKit/CloudKitRepository+Projects.swift b/External/CloudKit/CloudKitRepository+Projects.swift new file mode 100644 index 0000000..d9efaf7 --- /dev/null +++ b/External/CloudKit/CloudKitRepository+Projects.swift @@ -0,0 +1,131 @@ +// +// CloudKitRepository+Projects.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit +import RCLog + +extension CloudKitRepository: RepositoryProjects { + + func projects() -> [Project] { + fatalError("This method is not applicable to CloudKitRepository") + } + + func queryProjects(_ completion: @escaping (_ projects: [Project]) -> Void) { + let predicate = NSPredicate(value: true) + projectsZone?.fetchRecords(ofType: "Project", predicate: predicate) { records in + completion( self.projectsFromRecords(records ?? []) ) + } + } + + func queryUpdates (_ completion: @escaping ([Project], [String], NSError?) -> Void) { + + let changeToken = ReadMetadataInteractor().projectsLastSyncToken() + + projectsZone?.fetchChangedRecords(token: changeToken, + previousRecords: [], + previousDeletedRecordsIds: [], + completion: { changedRecords, deletedRecordsIds in + + completion(self.projectsFromRecords(changedRecords), self.stringIdsFromCKRecordIds(deletedRecordsIds), nil) + }) + } + + func saveProject (_ project: Project, completion: @escaping ((_ task: Project?) -> Void)) { + RCLogO("1. Save to cloudkit \(project)") + + guard let zoneId = self.tasksZone?.zoneId, let privateDB = self.privateDB else { + RCLog("Can't save, not logged in to iCloud") + return + } + + // Query for the task from server if exists + fetchCKRecordOfProject(project) { record in + var record: CKRecord? = record + // No record found on server, creating one now + if record == nil { + let recordId = CKRecord.ID(recordName: project.objectId!, zoneID: zoneId) + record = CKRecord(recordType: "Project", recordID: recordId) + } + record!.update(with: project) + + privateDB.save(record!, completionHandler: { savedRecord, error in + + RCLog("2. Record after saving to CloudKit") + RCLogO(savedRecord) + RCLogErrorO(error) + + if let record = savedRecord { + let project = record.toProject() + completion(project) + } + if let ckerror = error as? CKError { + switch ckerror { + case CKError.quotaExceeded: + // The user has run out of iCloud storage space. + // Prompt the user to go to iCloud Settings to manage storage. + #warning("Present quotaExceeded message to the user") + break + default: + break + } + completion(nil) + } + }) + } + } + + func deleteProject (_ project: Project, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { + + guard let privateDB = self.privateDB else { + RCLog("Not logged in") + return + } + + fetchCKRecordOfProject(project) { record in + if let cproject = record { + + privateDB.delete(withRecordID: cproject.recordID, completionHandler: { recordID, error in + RCLogO(recordID) + RCLogErrorO(error) + completion(error != nil) + }) + } else { + completion(false) + } + } + } +} + +extension CloudKitRepository { + + func fetchCKRecordOfProject (_ project: Project, completion: @escaping ((_ cproject: CKRecord?) -> Void)) { + + guard let zoneId = self.tasksZone?.zoneId, let privateDB = self.privateDB else { + RCLog("Not logged in") + return + } + + let predicate = NSPredicate(format: "objectId == %@", project.objectId! as CVarArg) + let query = CKQuery(recordType: "Project", predicate: predicate) + privateDB.perform(query, inZoneWith: zoneId) { (results: [CKRecord]?, error) in + + RCLogErrorO(error) + + if let result = results?.first { + completion(result) + } else { + completion(nil) + } + } + } + + private func projectsFromRecords (_ records: [CKRecord]) -> [Project] { + return records.map({ $0.toProject() }) + } +} diff --git a/External/CloudKit/CloudKitRepository+Tasks.swift b/External/CloudKit/CloudKitRepository+Tasks.swift index eab11bb..abcffba 100644 --- a/External/CloudKit/CloudKitRepository+Tasks.swift +++ b/External/CloudKit/CloudKitRepository+Tasks.swift @@ -22,12 +22,12 @@ extension CloudKitRepository: RepositoryTasks { func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil, completion: @escaping ([Task], NSError?) -> Void) { let predicate = NSPredicate(format: "endDate >= %@ AND endDate <= %@", startDate as CVarArg, endDate as CVarArg) - fetchRecords(ofType: "Task", predicate: predicate) { (records) in + tasksZone?.fetchRecords(ofType: "Task", predicate: predicate) { (records) in completion(self.tasksFromRecords(records ?? []), nil) } } - func queryUnsyncedTasks() -> [Task] { + func queryUnsyncedTasks(since lastSyncDate: Date?) -> [Task] { fatalError("This method is not applicable to CloudKitRepository") } @@ -35,15 +35,15 @@ extension CloudKitRepository: RepositoryTasks { fatalError("This method is not applicable to CloudKitRepository") } - func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { + func queryUpdatedTasks (_ completion: @escaping ([Task], [String], NSError?) -> Void) { - let changeToken = UserDefaults.standard.serverChangeToken + let changeToken = ReadMetadataInteractor().tasksLastSyncToken() - fetchChangedRecords(token: changeToken, - previousRecords: [], - previousDeletedRecordsIds: [], - completion: { (changedRecords, deletedRecordsIds) in - + tasksZone?.fetchChangedRecords(token: changeToken, + previousRecords: [], + previousDeletedRecordsIds: [], + completion: { (changedRecords, deletedRecordsIds) in + completion(self.tasksFromRecords(changedRecords), self.stringIdsFromCKRecordIds(deletedRecordsIds), nil) }) } @@ -76,7 +76,7 @@ extension CloudKitRepository: RepositoryTasks { func saveTask (_ task: Task, completion: @escaping ((_ task: Task?) -> Void)) { RCLogO("1. Save to cloudkit \(task)") - guard let customZone = self.customZone, let privateDB = self.privateDB else { + guard let zoneId = self.tasksZone?.zoneId, let privateDB = self.privateDB else { RCLog("Can't save, not logged in to iCloud") return } @@ -86,20 +86,19 @@ extension CloudKitRepository: RepositoryTasks { var record: CKRecord? = record // No record found on server, creating one now if record == nil { - let recordId = CKRecord.ID(recordName: task.objectId!, zoneID: customZone.zoneID) + let recordId = CKRecord.ID(recordName: task.objectId!, zoneID: zoneId) record = CKRecord(recordType: "Task", recordID: recordId) } - record = self.updatedRecord(record!, withTask: task) + record!.update(with: task) privateDB.save(record!, completionHandler: { savedRecord, error in - RCLog("2. Record after saving to CloudKit") - RCLogO(savedRecord) + RCLog("2. Saved to CloudKit \(String(describing: savedRecord))") RCLogErrorO(error) if let record = savedRecord { - let uploadedTask = self.taskFromRecord(record) - completion(uploadedTask) + let task = record.toTask() + completion(task) } if let ckerror = error as? CKError { switch ckerror { @@ -122,14 +121,14 @@ extension CloudKitRepository { func fetchCKRecordOfTask (_ task: Task, completion: @escaping ((_ ctask: CKRecord?) -> Void)) { - guard let customZone = self.customZone, let privateDB = self.privateDB else { + guard let zoneId = self.tasksZone?.zoneId, let privateDB = self.privateDB else { RCLog("Not logged in") return } let predicate = NSPredicate(format: "objectId == %@", task.objectId! as CVarArg) let query = CKQuery(recordType: "Task", predicate: predicate) - privateDB.perform(query, inZoneWith: customZone.zoneID) { (results: [CKRecord]?, error) in + privateDB.perform(query, inZoneWith: zoneId) { (results: [CKRecord]?, error) in RCLogErrorO(error) @@ -142,48 +141,6 @@ extension CloudKitRepository { } private func tasksFromRecords (_ records: [CKRecord]) -> [Task] { - - var tasks = [Task]() - for record in records { - tasks.append( taskFromRecord(record) ) - } - - return tasks - } - - private func taskFromRecord (_ record: CKRecord) -> Task { - - return Task(lastModifiedDate: record.modificationDate, - startDate: record["startDate"] as? Date, - endDate: record["endDate"] as! Date, - notes: record["notes"] as? String, - taskNumber: record["taskNumber"] as? String, - taskTitle: record["taskTitle"] as? String, - taskType: TaskType(rawValue: (record["taskType"] as! NSNumber).intValue)!, - objectId: record["objectId"] as? String - ) - } - - private func updatedRecord (_ record: CKRecord, withTask task: Task) -> CKRecord { - - record["startDate"] = task.startDate as CKRecordValue? - record["endDate"] = task.endDate as CKRecordValue - record["notes"] = task.notes as CKRecordValue? - record["taskNumber"] = task.taskNumber as CKRecordValue? - record["taskTitle"] = task.taskTitle as CKRecordValue? - record["taskType"] = task.taskType.rawValue as CKRecordValue - record["objectId"] = task.objectId as CKRecordValue? - - return record - } - - private func stringIdsFromCKRecordIds (_ ckrecords: [CKRecord.ID]) -> [String] { - - var ids = [String]() - for ckrecord in ckrecords { - ids.append( ckrecord.recordName ) - } - - return ids + return records.map({ $0.toTask() }) } } diff --git a/External/CloudKit/CloudKitRepository+User.swift b/External/CloudKit/CloudKitRepository+User.swift index cc14d9f..1d87e15 100644 --- a/External/CloudKit/CloudKitRepository+User.swift +++ b/External/CloudKit/CloudKitRepository+User.swift @@ -35,6 +35,7 @@ extension CloudKitRepository: RepositoryUser { func logout() { user = nil privateDB = nil - customZone = nil + tasksZone = nil + projectsZone = nil } } diff --git a/External/CloudKit/CloudKitRepository.swift b/External/CloudKit/CloudKitRepository.swift index 8ec9624..91832b8 100644 --- a/External/CloudKit/CloudKitRepository.swift +++ b/External/CloudKit/CloudKitRepository.swift @@ -15,7 +15,8 @@ class CloudKitRepository { internal var user: User? internal let container = CKContainer(identifier: "iCloud.com.jirassic.macos") internal var privateDB: CKDatabase? - internal var customZone: CKRecordZone? + internal var tasksZone: CloudKitZone? + internal var projectsZone: CloudKitZone? init() { getUser { [weak self] (user) in @@ -29,111 +30,14 @@ class CloudKitRepository { func initDB() { privateDB = container.privateCloudDatabase - customZone = CKRecordZone(zoneName: "TasksZone") - - privateDB!.save(customZone!) { (recordZone, err) in - RCLogO(recordZone) - RCLogErrorO(err) - } - } -} - -extension CloudKitRepository { - - func fetchChangedRecords (token: CKServerChangeToken?, - previousRecords: [CKRecord], - previousDeletedRecordsIds: [CKRecord.ID], - completion: @escaping ((_ changedRecords: [CKRecord], _ deletedRecordsIds: [CKRecord.ID]) -> Void)) { - - guard let customZone = self.customZone, let privateDB = self.privateDB else { - RCLog("Not logged in") + guard let db = privateDB else { return } - - var changedRecords = previousRecords - var deletedRecordsIds = previousDeletedRecordsIds - - let options = CKFetchRecordZoneChangesOperation.ZoneOptions() - options.previousServerChangeToken = token - - // let op = CKFetchRecordZoneChangesOperation(recordZoneIDs: [customZone.zoneID], previousServerChangeToken: token) - let op = CKFetchRecordZoneChangesOperation(recordZoneIDs: [customZone.zoneID], optionsByRecordZoneID: [customZone.zoneID: options]) - op.fetchAllChanges = true -// let op = CKFetchDatabaseChangesOperation(previousServerChangeToken: token) - - op.recordChangedBlock = { record in - RCLog("Changed record: \(record)") - changedRecords.append(record) - } - op.recordZoneChangeTokensUpdatedBlock = { zoneId, serverChangeToken, data in - - } - op.recordZoneFetchCompletionBlock = { zoneId, serverChangeToken, data, moreComing, error in - RCLogO(serverChangeToken) - RCLogO(data) - RCLogErrorO(error) - - guard error == nil else { - if let ckerror = error as? CKError { - switch ckerror { - case CKError.changeTokenExpired: - // Reset the token and try to do the request again - UserDefaults.standard.serverChangeToken = nil - self.fetchChangedRecords(token: nil, - previousRecords: changedRecords, - previousDeletedRecordsIds: deletedRecordsIds, - completion: completion) - return - default: - break - } - } - completion(changedRecords, deletedRecordsIds) - return - } - UserDefaults.standard.serverChangeToken = serverChangeToken - - if moreComing { - self.fetchChangedRecords(token: serverChangeToken, - previousRecords: changedRecords, - previousDeletedRecordsIds: deletedRecordsIds, - completion: completion) - } else { - completion(changedRecords, deletedRecordsIds) - } - } - op.fetchRecordZoneChangesCompletionBlock = { error in - - } - op.recordWithIDWasDeletedBlock = { recordID, recordType in - RCLog("Deleted recordID: \(recordID)") - deletedRecordsIds.append(recordID) - } -// op.fetchRecordChangesCompletionBlock = { serverChangeToken, data, error in -// -// -// } - - privateDB.add(op) + tasksZone = CloudKitZone(db: db, zoneName: "TasksZone") + projectsZone = CloudKitZone(db: db, zoneName: "ProjectsZone") } - - func fetchRecords (ofType type: String, predicate: NSPredicate, completion: @escaping ((_ ctask: [CKRecord]?) -> Void)) { - - guard let customZone = self.customZone, let privateDB = self.privateDB else { - RCLog("Not logged in") - return - } - - let query = CKQuery(recordType: type, predicate: predicate) - privateDB.perform(query, inZoneWith: customZone.zoneID) { (results: [CKRecord]?, error) in - - RCLogErrorO(error) - - if let results = results { - completion(results) - } else { - completion(nil) - } - } + + internal func stringIdsFromCKRecordIds (_ ckrecords: [CKRecord.ID]) -> [String] { + return ckrecords.map({ $0.recordName }) } } diff --git a/External/CloudKit/CloudKitZone.swift b/External/CloudKit/CloudKitZone.swift new file mode 100644 index 0000000..32cbf1b --- /dev/null +++ b/External/CloudKit/CloudKitZone.swift @@ -0,0 +1,131 @@ +// +// CloudKitZone.swift +// Jirassic +// +// Created by Cristian Baluta on 20/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CloudKit +import RCLog + +class CloudKitZone { + + private var db: CKDatabase + private var customZone: CKRecordZone? + + var zoneId: CKRecordZone.ID? { + return customZone?.zoneID + } + + init (db: CKDatabase, zoneName: String) { + + self.db = db + self.customZone = CKRecordZone(zoneName: zoneName) + + db.save(customZone!) { (recordZone, err) in + RCLogO(recordZone) + RCLogErrorO(err) + } + } + + func fetchChangedRecords (token: CKServerChangeToken?, + previousRecords: [CKRecord], + previousDeletedRecordsIds: [CKRecord.ID], + completion: @escaping ((_ changedRecords: [CKRecord], _ deletedRecordsIds: [CKRecord.ID]) -> Void)) { + + guard let zoneId = self.zoneId else { + RCLog("Not logged in") + return + } + + var changedRecords = previousRecords + var deletedRecordsIds = previousDeletedRecordsIds + + let options = CKFetchRecordZoneChangesOperation.ZoneOptions() + options.previousServerChangeToken = token + + // let op = CKFetchRecordZoneChangesOperation(recordZoneIDs: [customZone.zoneID], previousServerChangeToken: token) + let op = CKFetchRecordZoneChangesOperation(recordZoneIDs: [zoneId], + optionsByRecordZoneID: [zoneId: options]) + op.fetchAllChanges = true +// let op = CKFetchDatabaseChangesOperation(previousServerChangeToken: token) + + op.recordChangedBlock = { record in + RCLog("Changed record: \(record)") + changedRecords.append(record) + } + op.recordZoneChangeTokensUpdatedBlock = { zoneId, serverChangeToken, data in + /// Do not save the toke here because the results are not yet saved to local db +// WriteMetadataInteractor().set(tasksLastSyncToken: serverChangeToken) + } + op.recordZoneFetchCompletionBlock = { zoneId, serverChangeToken, data, moreComing, error in + RCLogO(serverChangeToken) + RCLogO(data) + RCLogErrorO(error) + + guard error == nil else { + if let ckerror = error as? CKError { + switch ckerror { + case CKError.changeTokenExpired: + // Reset the token and try to do the request again + WriteMetadataInteractor().set(tasksLastSyncToken: nil) + self.fetchChangedRecords(token: nil, + previousRecords: changedRecords, + previousDeletedRecordsIds: deletedRecordsIds, + completion: completion) + return + default: + break + } + } + completion(changedRecords, deletedRecordsIds) + return + } + WriteMetadataInteractor().set(tasksLastSyncToken: serverChangeToken) + + if moreComing { + self.fetchChangedRecords(token: serverChangeToken, + previousRecords: changedRecords, + previousDeletedRecordsIds: deletedRecordsIds, + completion: completion) + } else { + completion(changedRecords, deletedRecordsIds) + } + } + op.fetchRecordZoneChangesCompletionBlock = { error in + + } + op.recordWithIDWasDeletedBlock = { recordID, recordType in + RCLog("Deleted recordID: \(recordID)") + deletedRecordsIds.append(recordID) + } +// op.fetchRecordChangesCompletionBlock = { serverChangeToken, data, error in +// +// +// } + + db.add(op) + } + + func fetchRecords (ofType type: String, predicate: NSPredicate, completion: @escaping ((_ record: [CKRecord]?) -> Void)) { + + guard let zoneId = self.zoneId else { + RCLog("Not logged in") + return + } + + let query = CKQuery(recordType: type, predicate: predicate) + db.perform(query, inZoneWith: zoneId) { (results: [CKRecord]?, error) in + + RCLogErrorO(error) + + if let results = results { + completion(results) + } else { + completion(nil) + } + } + } +} diff --git a/External/CloudKit/UserDefaults+token.swift b/External/CloudKit/UserDefaults+token.swift deleted file mode 100644 index 44c608d..0000000 --- a/External/CloudKit/UserDefaults+token.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// UserDefaults+token.swift -// Jirassic -// -// Created by Cristian Baluta on 09/04/2017. -// Copyright © 2017 Imagin soft. All rights reserved. -// - -import Foundation -import CloudKit - -public extension UserDefaults { - - var serverChangeToken: CKServerChangeToken? { - get { - guard let data = self.value(forKey: "ChangeToken") as? Data else { - return nil - } - guard let token = NSKeyedUnarchiver.unarchiveObject(with: data) as? CKServerChangeToken else { - return nil - } - - return token - } - set { - if let token = newValue { - let data = NSKeyedArchiver.archivedData(withRootObject: token) - self.set(data, forKey: "ChangeToken") - self.synchronize() - } else { - self.removeObject(forKey: "ChangeToken") - } - } - } -} diff --git a/External/CoreData/CProject.swift b/External/CoreData/CProject.swift new file mode 100644 index 0000000..4bd54c9 --- /dev/null +++ b/External/CoreData/CProject.swift @@ -0,0 +1,18 @@ +// +// CProject.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import CoreData + + +class CProject: NSManagedObject { + +// @NSManaged var userId: String? +// @NSManaged var email: String? + +} diff --git a/External/CoreData/CoreDataRepository+Projects.swift b/External/CoreData/CoreDataRepository+Projects.swift new file mode 100644 index 0000000..6475cbb --- /dev/null +++ b/External/CoreData/CoreDataRepository+Projects.swift @@ -0,0 +1,28 @@ +// +// CoreDataRepository+Project.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +extension CoreDataRepository: RepositoryProjects { + + func projects() -> [Project] { + return [] + } + + func queryProjects(_ completion: @escaping ((_ task: [Project]) -> Void)) { + + } + + func saveProject (_ project: Project, completion: @escaping ((_ task: Project?) -> Void)) { + + } + + func deleteProject (_ project: Project, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { + + } +} diff --git a/External/CoreData/CoreDataRepository+Tasks.swift b/External/CoreData/CoreDataRepository+Tasks.swift index 43a2398..a03e886 100644 --- a/External/CoreData/CoreDataRepository+Tasks.swift +++ b/External/CoreData/CoreDataRepository+Tasks.swift @@ -28,13 +28,13 @@ extension CoreDataRepository: RepositoryTasks { completion(tasks, nil) } - func queryUnsyncedTasks() -> [Task] { + func queryUnsyncedTasks(since lastSyncDate: Date?) -> [Task] { var subpredicates = [ NSPredicate(format: "markedForDeletion == NO || markedForDeletion == nil") ] - if let lastSyncDateWithRemote = UserDefaults.standard.lastSyncDateWithRemote { - subpredicates.append(NSPredicate(format: "lastModifiedDate == nil || lastModifiedDate > %@", lastSyncDateWithRemote as CVarArg)) + if let date = lastSyncDate { + subpredicates.append(NSPredicate(format: "lastModifiedDate == nil || lastModifiedDate > %@", date as CVarArg)) } else { subpredicates.append(NSPredicate(format: "lastModifiedDate == nil")) } @@ -54,9 +54,11 @@ extension CoreDataRepository: RepositoryTasks { completion(tasks) } - func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { + func queryUpdatedTasks (_ completion: @escaping ([Task], [String], NSError?) -> Void) { - completion(queryUnsyncedTasks(), [], nil) + let lastSyncDate = ReadMetadataInteractor().tasksLastSyncDate() + let unsyncedTasks = queryUnsyncedTasks(since: lastSyncDate) + completion(unsyncedTasks, [], nil) } func deleteTask (_ task: Task, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { diff --git a/External/Jira/JiraRepository+Projects.swift b/External/Jira/JiraRepository+Projects.swift index 5d3d687..a993cca 100644 --- a/External/Jira/JiraRepository+Projects.swift +++ b/External/Jira/JiraRepository+Projects.swift @@ -11,13 +11,31 @@ import RCHttp extension JiraRepository { - // GET https://.../rest/api/2/project - // Returns an array of projects + /// GET https://.../rest/api/2/project + /// Returns an array of projects + /* Most common error is captcha + HTTP/1.1 403 Forbidden + Date: Mon, 05 Apr 2021 13:03:25 GMT + Server: Apache-Coyote/1.1 + X-AREQUESTID: 963x1873889x1 + X-ASEN: SEN-2826527 + X-Seraph-LoginReason: AUTHENTICATION_DENIED + WWW-Authenticate: OAuth realm="https" + X-ASESSIONID: 6pk78p + X-Content-Type-Options: nosniff + X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira...../login.jsp + Content-Type: text/html;charset=UTF-8 + Set-Cookie: JSESSIONID=CF54E3A2E1CAA26E3C36CBA7118A541B; Path=/; HttpOnly + Transfer-Encoding: chunked*/ func fetchProjects (success: @escaping ([JProject]) -> Void, failure: @escaping (Error) -> Void) { let path = "rest/api/2/project" - request?.get(at: path, success: { responseData in + request?.get(at: path, success: { httpResponse, responseData in + guard httpResponse.statusCode != 403 else { + failure(RCHttpError(errorDescription: "Authentication failed. Please verify via browser, possible cause is expired password which is causing Jira to ask for captcha.")) + return + } guard let responseJson = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments), let projects = responseJson as? [[String: Any]] else { failure(RCHttpError(errorDescription: "Invalid json response")) @@ -44,7 +62,7 @@ extension JiraRepository { func fetchProjectIssues (projectKey: String, success: @escaping ([JProjectIssue]) -> Void, failure: @escaping (Error) -> Void) { let path = "rest/api/2/search?jql=project=\(projectKey)&fields=*none&maxResults=-1" - request?.get(at: path, success: { responseData in + request?.get(at: path, success: { httpResponse, responseData in guard let responseJson = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments), let response = responseJson as? [String: Any], diff --git a/External/Jira/JiraRepository+Reports.swift b/External/Jira/JiraRepository+Reports.swift index 914f367..1f4ba36 100644 --- a/External/Jira/JiraRepository+Reports.swift +++ b/External/Jira/JiraRepository+Reports.swift @@ -44,7 +44,7 @@ extension JiraRepository { "dateStarted": dateStarted, "timeSpentSeconds": duration ] - request?.post(at: path, parameters: parameters, success: { responseData in + request?.post(at: path, parameters: parameters, success: { httpResponse, responseData in guard let _ = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) else { failure(RCHttpError(errorDescription: "Invalid json response")) @@ -52,7 +52,7 @@ extension JiraRepository { } success() - }, failure: { (err) in + }, failure: { err in failure(err) }) } diff --git a/External/RCSync.swift b/External/RCSync.swift index 6196829..d4c8d24 100644 --- a/External/RCSync.swift +++ b/External/RCSync.swift @@ -28,57 +28,57 @@ class RCSync { RCLog("1. Sync already in progress, not starting it again") return } - objectsToUpload = localRepository.queryUnsyncedTasks() + let lastSyncDate = ReadMetadataInteractor().tasksLastSyncDate() + objectsToUpload = localRepository.queryUnsyncedTasks(since: lastSyncDate) RCLog("1. Nr of unsynced tasks: \(objectsToUpload.count)") localRepository.queryDeletedTasks { deletedTasks in RCLog("1. Nr of deleted tasks: \(deletedTasks.count)") self.objectsToDelete = deletedTasks - self.syncNext { (success) in + self.syncNext { success in self.getLatestServerChanges(completion) } } } - // Send to CloudKit the changes recursivelly then call the completion block + /// Send to CloudKit the changes recursivelly then call the completion block private func syncNext (_ completion: @escaping ((_ success: Bool) -> Void)) { var task = objectsToUpload.first if task != nil { objectsToUpload.remove(at: 0) - uploadTask(task!, completion: { (success) in + uploadTask(task!, completion: { success, lastSyncDate in self.syncNext(completion) }) } else { task = objectsToDelete.first if task != nil { objectsToDelete.remove(at: 0) - deleteTask(task!, completion: { (success) in + deleteTask(task!, completion: { success in self.syncNext(completion) }) } else { - UserDefaults.standard.lastSyncDateWithRemote = Date() completion(true) } } } - func uploadTask (_ task: Task, completion: @escaping ((_ success: Bool) -> Void)) { + func uploadTask (_ task: Task, completion: @escaping ((_ success: Bool, _ lastSyncDate: Date?) -> Void)) { RCLog("1.1 >>> Save \(task)") _ = remoteRepository.saveTask(task) { uploadedTask in guard let uploadedTask = uploadedTask else { - completion(false) + completion(false, nil) return } RCLog("1.1 Save <<< uploadedTask \(String(describing: uploadedTask.objectId))") - // After task was saved to server update it to local datastore + /// After task was saved to server update it to local datastore _ = self.localRepository.saveTask(uploadedTask, completion: { savedTask in - // If task was saved locally successful update the last sync date - // Otherwise last sync date will be an older date than the tasks last modified date - if let savedTask = savedTask { - UserDefaults.standard.lastSyncDateWithRemote = savedTask.lastModifiedDate - } - completion(savedTask != nil) +// /// If task was saved locally successful update the last sync date +// /// Otherwise last sync date will be an older date than the tasks last modified date +// if let savedTask = savedTask { +// UserDefaults.standard.lastSyncDateWithRemote = savedTask.lastModifiedDate +// } + completion(savedTask != nil, savedTask?.lastModifiedDate) }) } } @@ -86,9 +86,9 @@ class RCSync { func deleteTask (_ task: Task, completion: @escaping ((_ success: Bool) -> Void)) { RCLog("1.1 Delete \(task)") - _ = remoteRepository.deleteTask(task, permanently: true) { (uploadedTask) in - // After task was marked as deleted to server, delete it permanently from local db - _ = self.localRepository.deleteTask(task, permanently: true, completion: { (task) in + _ = remoteRepository.deleteTask(task, permanently: true) { uploadedTask in + /// After task was marked as deleted to server, delete it permanently from local db + _ = self.localRepository.deleteTask(task, permanently: true, completion: { task in completion(true) }) } @@ -97,19 +97,20 @@ class RCSync { private func getLatestServerChanges (_ completion: @escaping ((_ hasIncomingChanges: Bool) -> Void)) { RCLog("2. Request latest server changes") - remoteRepository.queryUpdates { changedTasks, deletedTasksIds, error in - RCLog("2. Number of changes: \(changedTasks.count)") + remoteRepository.queryUpdatedTasks { changedTasks, deletedTasksIds, error in + RCLog("2. Number of changes on server: \(changedTasks.count)") for task in changedTasks { - self.localRepository.saveTask(task, completion: { (task) in - RCLog("2. Saved to local db \(String(describing: task?.objectId))") + self.localRepository.saveTask(task, completion: { task in + RCLog("2. Change saved to local db \(String(describing: task?.objectId))") }) } - RCLog("2. Number of deletes: \(deletedTasksIds.count)") + RCLog("2. Number of deletes on server: \(deletedTasksIds.count)") for remoteId in deletedTasksIds { - self.localRepository.deleteTask(objectId: remoteId, completion: { (success) in + self.localRepository.deleteTask(objectId: remoteId, completion: { success in RCLog("2. Deleted from local db: \(remoteId) \(success)") }) } + RCLog("3. Sync finished") completion(changedTasks.count > 0 || deletedTasksIds.count > 0) } } diff --git a/External/Repository.swift b/External/Repository.swift index bcc4013..d5b1617 100644 --- a/External/Repository.swift +++ b/External/Repository.swift @@ -22,9 +22,9 @@ protocol RepositoryTasks { func queryTask (withId objectId: String) -> Task? func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate?) -> [Task] func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate?, completion: @escaping ([Task], NSError?) -> Void) - func queryUnsyncedTasks() -> [Task] + func queryUnsyncedTasks (since lastSyncDate: Date?) -> [Task] func queryDeletedTasks (_ completion: @escaping ([Task]) -> Void) - func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) + func queryUpdatedTasks (_ completion: @escaping ([Task], [String], NSError?) -> Void) // Marks the Task as deleted. If permanently is true it will be removed from db func deleteTask (_ task: Task, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) func deleteTask (objectId: String, completion: @escaping ((_ success: Bool) -> Void)) @@ -40,4 +40,39 @@ protocol RepositorySettings { } -typealias Repository = RepositoryUser & RepositoryTasks & RepositorySettings +protocol RepositoryProjects { + + func projects() -> [Project] + func queryProjects(_ completion: @escaping ((_ task: [Project]) -> Void)) + func saveProject (_ project: Project, completion: @escaping ((_ task: Project?) -> Void)) + func deleteProject (_ project: Project, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) + +} + +protocol RepositoryMetadata { + + func tasksLastSyncDate() -> Date? + func projectsLastSyncDate() -> Date? + func tasksLastSyncToken() -> String? + func projectsLastSyncToken() -> String? + func set(tasksLastSyncDate: Date?) + func set(projectsLastSyncDate: Date?) + func set(tasksLastSyncToken: String?) + func set(projectsLastSyncToken: String?) + +} + +extension RepositoryMetadata { + + func tasksLastSyncDate() -> Date? { return nil } + func projectsLastSyncDate() -> Date? { return nil } + func tasksLastSyncToken() -> String? { return nil } + func projectsLastSyncToken() -> String? { return nil } + func set(tasksLastSyncDate: Date?) {} + func set(projectsLastSyncDate: Date?) {} + func set(tasksLastSyncToken: String?) {} + func set(projectsLastSyncToken: String?) {} + +} + +typealias Repository = RepositoryUser & RepositoryTasks & RepositorySettings & RepositoryProjects & RepositoryMetadata diff --git a/External/sqlite/SMetadata.swift b/External/sqlite/SMetadata.swift new file mode 100644 index 0000000..2f74dfb --- /dev/null +++ b/External/sqlite/SMetadata.swift @@ -0,0 +1,22 @@ +// +// SMetadata.swift +// Jirassic +// +// Created by Cristian Baluta on 13/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +class SMetadata: SQLTable { + + var i: Int = 0 + var tasksLastSyncDate: Date? = nil + var projectsLastSyncDate: Date? = nil + var tasksLastSyncToken: String? = nil + var projectsLastSyncToken: String? = nil + + override func primaryKey() -> String { + return "i" + } +} diff --git a/External/sqlite/SProject.swift b/External/sqlite/SProject.swift new file mode 100644 index 0000000..865397d --- /dev/null +++ b/External/sqlite/SProject.swift @@ -0,0 +1,69 @@ +// +// SProject.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +class SProject: SQLTable { + + var objectId: String? = nil + var lastModifiedDate: Date? = nil + var markedForDeletion = false + var title: String? = nil + var jiraBaseUrl: String? = nil + var jiraUser: String? = nil + var jiraProject: String? = nil + var jiraIssue: String? = nil + + var gitBaseUrls: String? = nil + var gitUsers: String? = nil + var taskNumberPrefix: String? = nil + + override func primaryKey() -> String { + return "objectId" + } +} + +extension SProject { + + func toProject() -> Project { + + return Project( + objectId: self.objectId, + lastModifiedDate: self.lastModifiedDate, + title: self.title ?? "", + jiraBaseUrl: self.jiraBaseUrl, + jiraUser: self.jiraUser, + jiraProject: self.jiraProject, + jiraIssue: self.jiraIssue, + + gitBaseUrls: (self.gitBaseUrls ?? "").toArray(), + gitUsers: (self.gitUsers ?? "").toArray(), + taskNumberPrefix: self.taskNumberPrefix + ) + } +} + +extension Project { + + func toSProject() -> SProject { + + let sproject = SProject() + sproject.objectId = self.objectId + sproject.lastModifiedDate = self.lastModifiedDate + sproject.title = self.title + sproject.jiraBaseUrl = self.jiraBaseUrl + sproject.jiraUser = self.jiraUser + sproject.jiraProject = self.jiraProject + sproject.jiraIssue = self.jiraIssue + sproject.gitBaseUrls = self.gitBaseUrls.toString() + sproject.gitUsers = self.gitUsers.toString() + sproject.taskNumberPrefix = self.taskNumberPrefix + + return sproject + } +} diff --git a/External/sqlite/SQLiteSchema.swift b/External/sqlite/SQLiteSchema.swift deleted file mode 100644 index ab6b548..0000000 --- a/External/sqlite/SQLiteSchema.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SQLiteMigrator.swift -// Jirassic -// -// Created by Cristian Baluta on 01/04/2017. -// Copyright © 2017 Imagin soft. All rights reserved. -// - -import Foundation - -enum SQLiteSchemaVersion: Int { - case v1 = 1 -} - -class SQLiteSchema { - - fileprivate let expectedVersion: SQLiteSchemaVersion = .v1 - - init (db: SQLiteDB) { - - if db.version != expectedVersion.rawValue { - migrate(db: db, toVersion: expectedVersion) - #warning("This should not be here") -// UserDefaults.standard.serverChangeToken = nil - } - } -} - -extension SQLiteSchema { - - func migrate (db: SQLiteDB, toVersion version: SQLiteSchemaVersion) { - - switch version { - case .v1: - let _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS stasks (lastModifiedDate DATETIME, markedForDeletion BOOL DEFAULT 0, startDate DATETIME, endDate DATETIME, notes TEXT, taskNumber TEXT, taskTitle TEXT, taskType INTEGER NOT NULL, objectId varchar(30) PRIMARY KEY);") - - let _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS ssettingss (autotrack BOOL, autotrackingMode INTEGER, trackLunch BOOL, trackScrum BOOL, trackMeetings BOOL, trackCodeReviews BOOL, trackWastedTime BOOL, trackStartOfDay BOOL, enableBackup BOOL, startOfDayTime DATETIME, endOfDayTime DATETIME, lunchTime DATETIME, scrumTime DATETIME, minSleepDuration INTEGER, minCodeRevDuration INTEGER, codeRevLink TEXT, minWasteDuration INTEGER, wasteLinks TEXT, i INTEGER NOT NULL PRIMARY KEY);") - - let _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS susers (userId TEXT, email TEXT, lastSyncDate DATETIME, isLoggedIn BOOL, i INTEGER NOT NULL PRIMARY KEY);") - break - } - db.version = version.rawValue - } -} diff --git a/External/sqlite/STask.swift b/External/sqlite/STask.swift index e5ec72a..151f8b9 100644 --- a/External/sqlite/STask.swift +++ b/External/sqlite/STask.swift @@ -19,12 +19,49 @@ class STask: SQLTable { var taskTitle: String? var taskType: Int = 0 var objectId: String? + var projectId: String? override func primaryKey() -> String { return "objectId" } override var description: String { - return "" + return "" } } + +extension STask { + + func toTask() -> Task { + + return Task(lastModifiedDate: self.lastModifiedDate, + startDate: self.startDate, + endDate: self.endDate!, + notes: self.notes, + taskNumber: self.taskNumber, + taskTitle: self.taskTitle, + taskType: TaskType(rawValue: self.taskType)!, + objectId: self.objectId!, + projectId: self.projectId + ) + } + + func update (with task: Task) { + // Update only updatable properties. objectId can't be updated + self.taskNumber = task.taskNumber + self.taskType = task.taskType.rawValue + self.taskTitle = task.taskTitle + self.notes = task.notes + self.startDate = task.startDate + self.endDate = task.endDate + self.lastModifiedDate = task.lastModifiedDate + self.projectId = task.projectId + } +} + +//extension Task { +// +// func toSTask() -> STask { +// +// } +//} diff --git a/External/sqlite/SqliteRepository+Metadata.swift b/External/sqlite/SqliteRepository+Metadata.swift new file mode 100644 index 0000000..f263f30 --- /dev/null +++ b/External/sqlite/SqliteRepository+Metadata.swift @@ -0,0 +1,60 @@ +// +// SqliteRepository+Metadata.swift +// Jirassic +// +// Created by Cristian Baluta on 13/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation + +extension SqliteRepository: RepositoryMetadata { + + func tasksLastSyncDate() -> Date? { return metadata().tasksLastSyncDate } + func projectsLastSyncDate() -> Date? { return metadata().projectsLastSyncDate } + func tasksLastSyncToken() -> String? { return metadata().tasksLastSyncToken } + func projectsLastSyncToken() -> String? { return metadata().projectsLastSyncToken } + + func set(tasksLastSyncDate: Date?) { + let meta = metadata() + meta.tasksLastSyncDate = tasksLastSyncDate + saveMetadata(meta) + } + func set(projectsLastSyncDate: Date?) { + let meta = metadata() + meta.projectsLastSyncDate = projectsLastSyncDate + saveMetadata(meta) + } + func set(tasksLastSyncToken: String?) { + let meta = metadata() + meta.tasksLastSyncToken = tasksLastSyncToken + saveMetadata(meta) + } + func set(projectsLastSyncToken: String?) { + let meta = metadata() + meta.projectsLastSyncToken = projectsLastSyncToken + saveMetadata(meta) + } + + + private func metadata() -> SMetadata { + + let results: [SMetadata] = queryWithPredicate(nil, sortingKeyPath: nil) + guard let smetadata = results.first else { + let smetadata = SMetadata() + smetadata.tasksLastSyncDate = nil + smetadata.projectsLastSyncDate = nil + smetadata.tasksLastSyncToken = nil + smetadata.projectsLastSyncToken = nil + return smetadata + } + + return smetadata + } + + + func saveMetadata (_ metadata: SMetadata) { + _ = metadata.save() + } + +} diff --git a/External/sqlite/SqliteRepository+Projects.swift b/External/sqlite/SqliteRepository+Projects.swift new file mode 100644 index 0000000..9cb3963 --- /dev/null +++ b/External/sqlite/SqliteRepository+Projects.swift @@ -0,0 +1,59 @@ +// +// SqliteRepository+Projects.swift +// Jirassic +// +// Created by Cristian Baluta on 12/01/2020. +// Copyright © 2020 Imagin soft. All rights reserved. +// + +import Foundation +import RCLog + +extension SqliteRepository: RepositoryProjects { + + func projects() -> [Project] { + + let projects: [SProject] = queryWithPredicate(nil, sortingKeyPath: nil) + return projects.map({ $0.toProject() }) + } + + func queryProjects(_ completion: @escaping ((_ projects: [Project]) -> Void)) { + completion(projects()) + } + + func saveProject (_ project: Project, completion: @escaping ((_ project: Project?) -> Void)) { + + let sproject = project.toSProject() + let saved = sproject.save() + #if !CMD + RCLog("Project saved to sqlite \(saved) \(project)") + #endif + if saved == 1 { + completion( sproject.toProject() ) + } else { + completion(nil) + } + } + + func deleteProject (_ project: Project, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { + + let sproject = project.toSProject() + if permanently { + completion( sproject.delete() ) + } else { + sproject.markedForDeletion = true + completion( sproject.save() == 1 ) + } + } + + func deleteProject (objectId: String, completion: @escaping ((_ success: Bool) -> Void)) { + + let projectPredicate = "objectId == '\(objectId)'" + let projects: [SProject] = queryWithPredicate(projectPredicate, sortingKeyPath: nil) + if let sproject = projects.first { + completion( sproject.delete() ) + } else { + completion( false ) + } + } +} diff --git a/External/sqlite/SqliteRepository+Settings.swift b/External/sqlite/SqliteRepository+Settings.swift index f6df166..35c73fb 100644 --- a/External/sqlite/SqliteRepository+Settings.swift +++ b/External/sqlite/SqliteRepository+Settings.swift @@ -46,7 +46,7 @@ extension SqliteRepository: RepositorySettings { _ = ssettings.save() } - fileprivate func settingsFromSSettings (_ ssettings: SSettings) -> Settings { + private func settingsFromSSettings (_ ssettings: SSettings) -> Settings { return Settings(enableBackup: ssettings.enableBackup, settingsTracking: SettingsTracking( @@ -73,7 +73,7 @@ extension SqliteRepository: RepositorySettings { ) } - fileprivate func ssettingsFromSettings (_ settings: Settings) -> SSettings { + private func ssettingsFromSettings (_ settings: Settings) -> SSettings { let results: [SSettings] = queryWithPredicate(nil, sortingKeyPath: nil) var ssettings: SSettings? = results.first diff --git a/External/sqlite/SqliteRepository+Tasks.swift b/External/sqlite/SqliteRepository+Tasks.swift index 5bc3d02..be71680 100644 --- a/External/sqlite/SqliteRepository+Tasks.swift +++ b/External/sqlite/SqliteRepository+Tasks.swift @@ -16,7 +16,7 @@ extension SqliteRepository: RepositoryTasks { let taskPredicate = "objectId == '\(objectId)'" let tasks: [STask] = queryWithPredicate(taskPredicate, sortingKeyPath: nil) if let stask = tasks.first { - return taskFromSTask(stask) + return stask.toTask() } return nil } @@ -35,14 +35,14 @@ extension SqliteRepository: RepositoryTasks { } } - func queryUnsyncedTasks() -> [Task] { + func queryUnsyncedTasks (since lastSyncDate: Date?) -> [Task] { #if !CMD - RCLog("Query tasks since last sync date: \(String(describing: UserDefaults.standard.lastSyncDateWithRemote))") + RCLog("Query tasks since last sync date: \(String(describing: lastSyncDate))") #endif var sinceDatePredicate = "" - if let lastSyncDateWithRemote = UserDefaults.standard.lastSyncDateWithRemote { - sinceDatePredicate = " OR datetime(lastModifiedDate) > datetime('\(lastSyncDateWithRemote.YYYYMMddHHmmssGMT())')" + if let date = lastSyncDate { + sinceDatePredicate = " OR datetime(lastModifiedDate) > datetime('\(date.YYYYMMddHHmmssGMT())')" } let predicate = "(lastModifiedDate is NULL\(sinceDatePredicate)) AND markedForDeletion == 0" let results: [STask] = queryWithPredicate(predicate, sortingKeyPath: nil) @@ -60,10 +60,11 @@ extension SqliteRepository: RepositoryTasks { completion(tasks) } - func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { + func queryUpdatedTasks (_ completion: @escaping ([Task], [String], NSError?) -> Void) { - queryDeletedTasks { (deletedTasks) in - let unsyncedTasks = self.queryUnsyncedTasks() + queryDeletedTasks { deletedTasks in + let lastSyncDate = ReadMetadataInteractor().tasksLastSyncDate() + let unsyncedTasks = self.queryUnsyncedTasks(since: lastSyncDate) let deletedTasksIds = deletedTasks.map{ $0.objectId! } completion(unsyncedTasks, deletedTasksIds, nil) } @@ -99,7 +100,7 @@ extension SqliteRepository: RepositoryTasks { RCLog("Saved to sqlite \(saved) \(task)") #endif if saved == 1 { - completion( taskFromSTask(stask)) + completion( stask.toTask() ) } else { completion(nil) } @@ -125,27 +126,8 @@ extension SqliteRepository { return tasks } - private func taskFromSTask (_ stask: STask) -> Task { - - return Task(lastModifiedDate: stask.lastModifiedDate, - startDate: stask.startDate, - endDate: stask.endDate!, - notes: stask.notes, - taskNumber: stask.taskNumber, - taskTitle: stask.taskTitle, - taskType: TaskType(rawValue: stask.taskType)!, - objectId: stask.objectId! - ) - } - - private func tasksFromSTasks (_ rtasks: [STask]) -> [Task] { - - var tasks = [Task]() - for rtask in rtasks { - tasks.append( taskFromSTask(rtask) ) - } - - return tasks + private func tasksFromSTasks (_ stasks: [STask]) -> [Task] { + return stasks.map({ $0.toTask() }) } private func staskFromTask (_ task: Task) -> STask { @@ -157,21 +139,8 @@ extension SqliteRepository { stask = STask() stask!.objectId = task.objectId } + stask!.update(with: task) - return updatedSTask(stask!, withTask: task) - } - - // Update only updatable properties. objectId can't be updated - private func updatedSTask (_ stask: STask, withTask task: Task) -> STask { - - stask.taskNumber = task.taskNumber - stask.taskType = task.taskType.rawValue - stask.taskTitle = task.taskTitle - stask.notes = task.notes - stask.startDate = task.startDate - stask.endDate = task.endDate - stask.lastModifiedDate = task.lastModifiedDate - - return stask + return stask! } } diff --git a/External/sqlite/SqliteRepository.swift b/External/sqlite/SqliteRepository.swift index 755a53e..68ad92c 100644 --- a/External/sqlite/SqliteRepository.swift +++ b/External/sqlite/SqliteRepository.swift @@ -38,7 +38,7 @@ class SqliteRepository { #endif db = SQLiteDB(url: dbUrl) - _ = SQLiteSchema(db: db) + UpdateSchemaInteractor().execute(with: db) } internal func queryWithPredicate (_ predicate: String?, sortingKeyPath: String?) -> [T] { diff --git a/External/sqlite/UpdateSchemaInteractor.swift b/External/sqlite/UpdateSchemaInteractor.swift new file mode 100644 index 0000000..1e0902d --- /dev/null +++ b/External/sqlite/UpdateSchemaInteractor.swift @@ -0,0 +1,57 @@ +// +// SQLiteMigrator.swift +// Jirassic +// +// Created by Cristian Baluta on 01/04/2017. +// Copyright © 2017 Imagin soft. All rights reserved. +// + +import Foundation + +enum SQLiteSchemaVersion: Int { + case v1 = 1 + case v2 = 2 + + static func allVersions() -> [SQLiteSchemaVersion] { + return [.v1, .v2] + } +} + +class UpdateSchemaInteractor { + + func execute (with db: SQLiteDB) { + guard let expectedVersion = SQLiteSchemaVersion.allVersions().last, db.version != expectedVersion.rawValue else { + return + } + for version in SQLiteSchemaVersion.allVersions() { + guard version.rawValue > db.version else { + /// Skip versions already executed + continue + } + migrate(db: db, toVersion: version) + } + } + + private func migrate (db: SQLiteDB, toVersion version: SQLiteSchemaVersion) { + + /// Note: TABLES MUST BE AT PLURAL + switch version { + case .v1: + /// Tasks table + _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS stasks (lastModifiedDate DATETIME, markedForDeletion BOOL DEFAULT 0, startDate DATETIME, endDate DATETIME, notes TEXT, taskNumber TEXT, taskTitle TEXT, taskType INTEGER NOT NULL, objectId varchar(30) PRIMARY KEY);") + /// Settings table + _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS ssettingss (autotrack BOOL, autotrackingMode INTEGER, trackLunch BOOL, trackScrum BOOL, trackMeetings BOOL, trackCodeReviews BOOL, trackWastedTime BOOL, trackStartOfDay BOOL, enableBackup BOOL, startOfDayTime DATETIME, endOfDayTime DATETIME, lunchTime DATETIME, scrumTime DATETIME, minSleepDuration INTEGER, minCodeRevDuration INTEGER, codeRevLink TEXT, minWasteDuration INTEGER, wasteLinks TEXT, i INTEGER NOT NULL PRIMARY KEY);") + /// Users table + _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS susers (userId TEXT, email TEXT, lastSyncDate DATETIME, isLoggedIn BOOL, i INTEGER NOT NULL PRIMARY KEY);") + + case .v2: + /// Edit Tasks table + _ = db.execute(sql: "ALTER TABLE stasks ADD COLUMN projectId varchar(30);") + /// Projects table + _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS sprojects (objectId varchar(30) PRIMARY KEY, lastModifiedDate DATETIME, markedForDeletion BOOL DEFAULT 0, title TEXT, jiraBaseUrl TEXT, jiraUser TEXT, jiraProject TEXT, jiraIssue TEXT, gitBaseUrls TEXT, gitUsers TEXT, taskNumberPrefix TEXT);") + /// Metadata table + _ = db.execute(sql: "CREATE TABLE IF NOT EXISTS smetadatas (i INTEGER NOT NULL PRIMARY KEY, tasksLastSyncDate DATETIME, projectsLastSyncDate DATETIME, tasksLastSyncToken TEXT, projectsLastSyncToken TEXT);") + } + db.version = version.rawValue + } +} diff --git a/External/sqlite/UserDefaults+uploadToken.swift b/External/sqlite/UserDefaults+uploadToken.swift deleted file mode 100644 index ba11435..0000000 --- a/External/sqlite/UserDefaults+uploadToken.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// UserDefaults+uploadToken.swift -// Jirassic -// -// Created by Cristian Baluta on 23/04/2017. -// Copyright © 2017 Imagin soft. All rights reserved. -// - -import Foundation - -public extension UserDefaults { - - var lastSyncDateWithRemote: Date? { - get { - return self.object(forKey: "localChangeDate") as? Date - } - set { - self.set(newValue, forKey: "localChangeDate") - } - } -} diff --git a/Jirassic.xcodeproj/project.pbxproj b/Jirassic.xcodeproj/project.pbxproj index efda615..1fc406b 100644 --- a/Jirassic.xcodeproj/project.pbxproj +++ b/Jirassic.xcodeproj/project.pbxproj @@ -93,30 +93,11 @@ 280D71611ED6043D005D2689 /* MenuBarIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3661DD3B44200B73201 /* MenuBarIconView.swift */; }; 280D71621ED6043D005D2689 /* FlipAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3521DD3B44200B73201 /* FlipAnimation.swift */; }; 280D71631ED6043D005D2689 /* Animatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE42F1DFD7C8D00D4FD45 /* Animatable.swift */; }; - 280D71641ED6043D005D2689 /* PlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28279E9B1E8300E200EAF9FC /* PlaceholderViewController.swift */; }; - 280D71661ED6043D005D2689 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE42B1DFC3F8700D4FD45 /* WelcomeViewController.swift */; }; - 280D71681ED6043D005D2689 /* TaskSuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15601DEF3D080009871C /* TaskSuggestionViewController.swift */; }; - 280D71691ED6043D005D2689 /* TaskSuggestionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15621DEF3F2A0009871C /* TaskSuggestionPresenter.swift */; }; - 280D716B1ED6043D005D2689 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C76B1DE8724400CA545E /* AccountViewController.swift */; }; - 280D716C1ED6043D005D2689 /* CloudKitLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7631DE8721100CA545E /* CloudKitLoginViewController.swift */; }; - 280D716E1ED6043D005D2689 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7651DE8721100CA545E /* LoginPresenter.swift */; }; - 280D716F1ED6043D005D2689 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7661DE8721100CA545E /* LoginViewController.swift */; }; 280D71701ED6043D005D2689 /* ExtensionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4231DF7646F00D4FD45 /* ExtensionsInteractor.swift */; }; 280D71711ED6043D005D2689 /* ExtensionsInstallerInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280F507A1EC8541D007416AB /* ExtensionsInstallerInteractor.swift */; }; 280D71721ED6043D005D2689 /* AppleScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4221DF7646F00D4FD45 /* AppleScript.swift */; }; 280D71731ED6043D005D2689 /* SandboxedAppleScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4241DF7646F00D4FD45 /* SandboxedAppleScript.swift */; }; - 280D71741ED6043D005D2689 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3751DD3B44200B73201 /* SettingsViewController.swift */; }; - 280D71751ED6043D005D2689 /* SettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3741DD3B44200B73201 /* SettingsPresenter.swift */; }; - 280D71761ED6043D005D2689 /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3731DD3B44200B73201 /* SettingsInteractor.swift */; }; 280D71781ED6043D005D2689 /* NewTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3711DD3B44200B73201 /* NewTaskViewController.swift */; }; - 280D717A1ED6043D005D2689 /* TasksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3851DD3B44200B73201 /* TasksViewController.swift */; }; - 280D717B1ED6043D005D2689 /* TasksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3831DD3B44200B73201 /* TasksPresenter.swift */; }; - 280D717C1ED6043D005D2689 /* CalendarScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3771DD3B44200B73201 /* CalendarScrollView.swift */; }; - 280D717D1ED6043D005D2689 /* TasksScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3841DD3B44200B73201 /* TasksScrollView.swift */; }; - 280D717E1ED6043D005D2689 /* TasksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A5F27D1E5789FC002BE564 /* TasksDataSource.swift */; }; - 280D71811ED6043D005D2689 /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A5F2811E586426002BE564 /* DataSource.swift */; }; - 280D71821ED6043D005D2689 /* CellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3781DD3B44200B73201 /* CellProtocol.swift */; }; - 280D71831ED6043D005D2689 /* TasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EDE9381E59EC1500B360A4 /* TasksView.swift */; }; 280D718D1ED6043D005D2689 /* InternalNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3681DD3B44200B73201 /* InternalNotifications.swift */; }; 280D718E1ED6043D005D2689 /* UserNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3691DD3B44200B73201 /* UserNotifications.swift */; }; 280D718F1ED6043D005D2689 /* SleepNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D36A1DD3B44200B73201 /* SleepNotifications.swift */; }; @@ -137,11 +118,6 @@ 280D71BC1ED60683005D2689 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 28577EB91E8AF379002B07FD /* libsqlite3.tbd */; }; 280D71BE1ED6069A005D2689 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4055B1381E0D82A900279430 /* ServiceManagement.framework */; }; 280D71BF1ED608D6005D2689 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3591DD3B44200B73201 /* Main.storyboard */; }; - 280D71C01ED608D6005D2689 /* Placeholder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28E896621E830D6700722032 /* Placeholder.storyboard */; }; - 280D71C11ED608D6005D2689 /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40FCE42D1DFC576C00D4FD45 /* Welcome.storyboard */; }; - 280D71C21ED608D6005D2689 /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5685C7641DE8721100CA545E /* Login.storyboard */; }; - 280D71C31ED608D6005D2689 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40E092401DE385E4001EF5DA /* Settings.storyboard */; }; - 280D71C41ED608D6005D2689 /* Tasks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 406384881DE388C5004795A4 /* Tasks.storyboard */; }; 280D71C81ED608D6005D2689 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4065D35B1DD3B44200B73201 /* Images.xcassets */; }; 280D71C91ED60908005D2689 /* jirassic.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3611DD3B44200B73201 /* jirassic.sdef */; }; 280F507D1EC868B0007416AB /* StringArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280F507C1EC868B0007416AB /* StringArray.swift */; }; @@ -157,7 +133,6 @@ 28279AD321BBF09900376304 /* Jirassic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2818847F21A4A2F800B33B9C /* Jirassic.xcdatamodeld */; }; 28279AD521C6245700376304 /* GitUsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28279AD421C6245700376304 /* GitUsersViewController.swift */; }; 28279AD621C6245700376304 /* GitUsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28279AD421C6245700376304 /* GitUsersViewController.swift */; }; - 28279E9C1E8300E200EAF9FC /* PlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28279E9B1E8300E200EAF9FC /* PlaceholderViewController.swift */; }; 284192D72018841700E64A9A /* JProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192D62018841700E64A9A /* JProject.swift */; }; 284192D82018841700E64A9A /* JProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192D62018841700E64A9A /* JProject.swift */; }; 284192DA2018855B00E64A9A /* JiraRepository+Projects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192D92018855B00E64A9A /* JiraRepository+Projects.swift */; }; @@ -170,38 +145,9 @@ 2845B13A206703C5006EFB3B /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B138206703C5006EFB3B /* Keychain.swift */; }; 2845B13F2068351F006EFB3B /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B13E2068351F006EFB3B /* TableViewCell.swift */; }; 2845B1402068351F006EFB3B /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B13E2068351F006EFB3B /* TableViewCell.swift */; }; - 2845B149206AE3A8006EFB3B /* ReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B144206AE3A8006EFB3B /* ReportCell.swift */; }; - 2845B14A206AE3A8006EFB3B /* ReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B144206AE3A8006EFB3B /* ReportCell.swift */; }; - 2845B14B206AE3A8006EFB3B /* ReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B145206AE3A8006EFB3B /* ReportCell.xib */; }; - 2845B14C206AE3A8006EFB3B /* ReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B145206AE3A8006EFB3B /* ReportCell.xib */; }; - 2845B14D206AE3A8006EFB3B /* ReportCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B146206AE3A8006EFB3B /* ReportCellPresenter.swift */; }; - 2845B14E206AE3A8006EFB3B /* ReportCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B146206AE3A8006EFB3B /* ReportCellPresenter.swift */; }; - 2845B14F206AE3A8006EFB3B /* ReportsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B147206AE3A8006EFB3B /* ReportsDataSource.swift */; }; - 2845B150206AE3A8006EFB3B /* ReportsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B147206AE3A8006EFB3B /* ReportsDataSource.swift */; }; - 2845B151206AE3A8006EFB3B /* ReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B148206AE3A8006EFB3B /* ReportsHeaderView.swift */; }; - 2845B152206AE3A8006EFB3B /* ReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B148206AE3A8006EFB3B /* ReportsHeaderView.swift */; }; - 2845B160206AE468006EFB3B /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B155206AE467006EFB3B /* TaskCell.swift */; }; - 2845B161206AE468006EFB3B /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B155206AE467006EFB3B /* TaskCell.swift */; }; - 2845B162206AE468006EFB3B /* TaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B156206AE467006EFB3B /* TaskCell.xib */; }; - 2845B163206AE468006EFB3B /* TaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B156206AE467006EFB3B /* TaskCell.xib */; }; - 2845B164206AE468006EFB3B /* TaskCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B157206AE467006EFB3B /* TaskCellPresenter.swift */; }; - 2845B165206AE468006EFB3B /* TaskCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B157206AE467006EFB3B /* TaskCellPresenter.swift */; }; - 2845B166206AE468006EFB3B /* TasksHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B159206AE467006EFB3B /* TasksHeaderView.swift */; }; - 2845B167206AE468006EFB3B /* TasksHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B159206AE467006EFB3B /* TasksHeaderView.swift */; }; - 2845B168206AE468006EFB3B /* TasksHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B15A206AE467006EFB3B /* TasksHeaderView.xib */; }; - 2845B169206AE468006EFB3B /* TasksHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B15A206AE467006EFB3B /* TasksHeaderView.xib */; }; - 2845B16A206AE468006EFB3B /* NonTaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B15C206AE467006EFB3B /* NonTaskCell.swift */; }; - 2845B16B206AE468006EFB3B /* NonTaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B15C206AE467006EFB3B /* NonTaskCell.swift */; }; - 2845B16C206AE468006EFB3B /* NonTaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B15D206AE467006EFB3B /* NonTaskCell.xib */; }; - 2845B16D206AE468006EFB3B /* NonTaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2845B15D206AE467006EFB3B /* NonTaskCell.xib */; }; - 2845B16E206AE46F006EFB3B /* TaskCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B154206AE467006EFB3B /* TaskCellTests.swift */; }; 285465A2216C84E10052CB6A /* CreateMonthReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285465A1216C84E10052CB6A /* CreateMonthReport.swift */; }; 285465A3216C84E10052CB6A /* CreateMonthReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285465A1216C84E10052CB6A /* CreateMonthReport.swift */; }; 285465A4216C84E10052CB6A /* CreateMonthReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285465A1216C84E10052CB6A /* CreateMonthReport.swift */; }; - 285465A7217858790052CB6A /* StoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285465A6217858790052CB6A /* StoreView.swift */; }; - 285465A8217858790052CB6A /* StoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285465A6217858790052CB6A /* StoreView.swift */; }; - 285465AD217858AF0052CB6A /* StoreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 285465AC217858AF0052CB6A /* StoreView.xib */; }; - 285465AE217858AF0052CB6A /* StoreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 285465AC217858AF0052CB6A /* StoreView.xib */; }; 28577EAB1E8ADC53002B07FD /* SqliteRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA71E8ADC53002B07FD /* SqliteRepository.swift */; }; 28577EAC1E8ADC53002B07FD /* SqliteRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA71E8ADC53002B07FD /* SqliteRepository.swift */; }; 28577EAD1E8ADC53002B07FD /* SSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA81E8ADC53002B07FD /* SSettings.swift */; }; @@ -219,20 +165,6 @@ 28667B5E1FCB7017007B98E3 /* ModuleHookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28667B5C1FCB7017007B98E3 /* ModuleHookup.swift */; }; 286BC00421909F85004D4CDD /* CloseDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286BC00321909F85004D4CDD /* CloseDay.swift */; }; 286BC00521909F85004D4CDD /* CloseDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286BC00321909F85004D4CDD /* CloseDay.swift */; }; - 287195152022E1E2001C237E /* HookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195142022E1E2001C237E /* HookupPresenter.swift */; }; - 287195162022E1E2001C237E /* HookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195142022E1E2001C237E /* HookupPresenter.swift */; }; - 287195182022E8BC001C237E /* OutputTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195172022E8BC001C237E /* OutputTableViewDataSource.swift */; }; - 287195192022E8BC001C237E /* OutputTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195172022E8BC001C237E /* OutputTableViewDataSource.swift */; }; - 2871951B2022ECF7001C237E /* OutputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871951A2022ECF7001C237E /* OutputsScrollView.swift */; }; - 2871951C2022ECF7001C237E /* OutputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871951A2022ECF7001C237E /* OutputsScrollView.swift */; }; - 2871951E2022FD14001C237E /* InputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871951D2022FD14001C237E /* InputsScrollView.swift */; }; - 2871951F2022FD14001C237E /* InputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871951D2022FD14001C237E /* InputsScrollView.swift */; }; - 287195212022FD87001C237E /* InputsTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195202022FD87001C237E /* InputsTableViewDataSource.swift */; }; - 287195222022FD87001C237E /* InputsTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195202022FD87001C237E /* InputsTableViewDataSource.swift */; }; - 2871952820241A6C001C237E /* TrackingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2871952720241A6C001C237E /* TrackingView.xib */; }; - 2871952920241A6C001C237E /* TrackingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2871952720241A6C001C237E /* TrackingView.xib */; }; - 2871952B20241B25001C237E /* TrackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871952A20241B25001C237E /* TrackingView.swift */; }; - 2871952C20241B25001C237E /* TrackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2871952A20241B25001C237E /* TrackingView.swift */; }; 2871952D2025C353001C237E /* CoreDataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38C1DD3B44200B73201 /* CoreDataRepository.swift */; }; 2871952E2025C356001C237E /* CoreDataRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928891E910F4D0022AB55 /* CoreDataRepository+Tasks.swift */; }; 2871952F2025C359001C237E /* CoreDataRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288C1E910F970022AB55 /* CoreDataRepository+Settings.swift */; }; @@ -243,16 +175,6 @@ 287195362027CAD9001C237E /* TimeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195352027CAD9001C237E /* TimeInteractor.swift */; }; 287195372027CAD9001C237E /* TimeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287195352027CAD9001C237E /* TimeInteractor.swift */; }; 287558451EFE5969009A2503 /* ReadDaysInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280D71171ED4CFCB005D2689 /* ReadDaysInteractorTests.swift */; }; - 287B358720FBAFC40022F43E /* WizardCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B358620FBAFC40022F43E /* WizardCalendarView.swift */; }; - 287B358820FBAFC40022F43E /* WizardCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B358620FBAFC40022F43E /* WizardCalendarView.swift */; }; - 287B358A20FBAFD60022F43E /* WizardCalendarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 287B358920FBAFD60022F43E /* WizardCalendarView.xib */; }; - 287B358B20FBAFD60022F43E /* WizardCalendarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 287B358920FBAFD60022F43E /* WizardCalendarView.xib */; }; - 288BB67E2085252900CF720A /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB67D2085252900CF720A /* WizardViewController.swift */; }; - 288BB67F2085252900CF720A /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB67D2085252900CF720A /* WizardViewController.swift */; }; - 288BB6812087152B00CF720A /* WizardAppleScriptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 288BB6802087152B00CF720A /* WizardAppleScriptView.xib */; }; - 288BB6822087152B00CF720A /* WizardAppleScriptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 288BB6802087152B00CF720A /* WizardAppleScriptView.xib */; }; - 288BB6842087157F00CF720A /* WizardAppleScriptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB6832087157F00CF720A /* WizardAppleScriptView.swift */; }; - 288BB6852087157F00CF720A /* WizardAppleScriptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB6832087157F00CF720A /* WizardAppleScriptView.swift */; }; 288BB6872087169F00CF720A /* ViewXib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB6862087169F00CF720A /* ViewXib.swift */; }; 288BB6882087169F00CF720A /* ViewXib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288BB6862087169F00CF720A /* ViewXib.swift */; }; 2892B2981F094A170085BAC2 /* JiraRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B2971F094A170085BAC2 /* JiraRepository.swift */; }; @@ -263,14 +185,8 @@ 2892B29F1F094B0D0085BAC2 /* JReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B29D1F094B0D0085BAC2 /* JReport.swift */; }; 2892B2A71F094C800085BAC2 /* JWorkAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B2A61F094C800085BAC2 /* JWorkAttribute.swift */; }; 2892B2A81F094C800085BAC2 /* JWorkAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B2A61F094C800085BAC2 /* JWorkAttribute.swift */; }; - 2892E80B208D9B42004E5298 /* InputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2892E80A208D9B42004E5298 /* InputsScrollView.xib */; }; - 2892E80C208D9B42004E5298 /* InputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2892E80A208D9B42004E5298 /* InputsScrollView.xib */; }; - 2892E80E208D9DD0004E5298 /* OutputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2892E80D208D9DD0004E5298 /* OutputsScrollView.xib */; }; - 2892E80F208D9DD0004E5298 /* OutputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2892E80D208D9DD0004E5298 /* OutputsScrollView.xib */; }; 2892E811208E6275004E5298 /* LocalPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892E810208E6275004E5298 /* LocalPreferences.swift */; }; 2892E812208E6275004E5298 /* LocalPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892E810208E6275004E5298 /* LocalPreferences.swift */; }; - 2898D3A82181907700CF5AD4 /* MonthReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3A72181907700CF5AD4 /* MonthReportsHeaderView.swift */; }; - 2898D3A92181907700CF5AD4 /* MonthReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3A72181907700CF5AD4 /* MonthReportsHeaderView.swift */; }; 2898D3AB2184D1DB00CF5AD4 /* TimeBoxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3AA2184D1DB00CF5AD4 /* TimeBoxViewController.swift */; }; 2898D3AC2184D1DB00CF5AD4 /* TimeBoxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3AA2184D1DB00CF5AD4 /* TimeBoxViewController.swift */; }; 2898D3AE2184ED3000CF5AD4 /* Components.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2898D3AD2184ED3000CF5AD4 /* Components.storyboard */; }; @@ -279,25 +195,17 @@ 2898D3B22185959300CF5AD4 /* EditableTimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3B02185959300CF5AD4 /* EditableTimeBox.swift */; }; 28A283942037874600DDCB63 /* ModuleGitLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283932037874600DDCB63 /* ModuleGitLogs.swift */; }; 28A283952037874600DDCB63 /* ModuleGitLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283932037874600DDCB63 /* ModuleGitLogs.swift */; }; - 28A283972037975600DDCB63 /* Saveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283962037975600DDCB63 /* Saveable.swift */; }; - 28A283982037975600DDCB63 /* Saveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283962037975600DDCB63 /* Saveable.swift */; }; 28A2839A20382BBB00DDCB63 /* GitCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A2839920382BBB00DDCB63 /* GitCommit.swift */; }; 28A2839B20382BBB00DDCB63 /* GitCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A2839920382BBB00DDCB63 /* GitCommit.swift */; }; 28A2839F20385BE000DDCB63 /* GitCommitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A2839E20385BE000DDCB63 /* GitCommitsParser.swift */; }; 28A283A020385BE000DDCB63 /* GitCommitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A2839E20385BE000DDCB63 /* GitCommitsParser.swift */; }; 28A283A220385F8C00DDCB63 /* GitCommitsParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283A120385F8C00DDCB63 /* GitCommitsParserTests.swift */; }; - 28A283A52038AFB100DDCB63 /* JirassicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283A42038AFB100DDCB63 /* JirassicCell.swift */; }; - 28A283A62038AFB100DDCB63 /* JirassicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283A42038AFB100DDCB63 /* JirassicCell.swift */; }; - 28A283A82038AFC500DDCB63 /* JirassicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28A283A72038AFC500DDCB63 /* JirassicCell.xib */; }; - 28A283A92038AFC500DDCB63 /* JirassicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28A283A72038AFC500DDCB63 /* JirassicCell.xib */; }; 28A283AB203A93AB00DDCB63 /* GitBranchParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283AA203A93AB00DDCB63 /* GitBranchParser.swift */; }; 28A283AC203A93AB00DDCB63 /* GitBranchParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283AA203A93AB00DDCB63 /* GitBranchParser.swift */; }; 28A283AE203C069F00DDCB63 /* GitBranchParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283AD203C069F00DDCB63 /* GitBranchParserTests.swift */; }; 28A283B0203CB1A100DDCB63 /* MergeTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283AF203CB1A100DDCB63 /* MergeTasksInteractor.swift */; }; 28A283B1203CB1A100DDCB63 /* MergeTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283AF203CB1A100DDCB63 /* MergeTasksInteractor.swift */; }; 28A283B3203CB1B900DDCB63 /* MergeTasksInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A283B2203CB1B900DDCB63 /* MergeTasksInteractorTests.swift */; }; - 28A5F27E1E5789FC002BE564 /* TasksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A5F27D1E5789FC002BE564 /* TasksDataSource.swift */; }; - 28A5F2821E586426002BE564 /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A5F2811E586426002BE564 /* DataSource.swift */; }; 28A928781E8F78580022AB55 /* SQLiteSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928771E8F78580022AB55 /* SQLiteSchema.swift */; }; 28A928791E8F78580022AB55 /* SQLiteSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928771E8F78580022AB55 /* SQLiteSchema.swift */; }; 28A9287A1E8FA8E90022AB55 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 28577EB91E8AF379002B07FD /* libsqlite3.tbd */; }; @@ -320,10 +228,6 @@ 28AA500D1EDCD51300AAF03D /* CTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38E1DD3B44200B73201 /* CTask.swift */; }; 28AA500E1EDCD51300AAF03D /* CUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38F1DD3B44200B73201 /* CUser.swift */; }; 28AFE7311E9A594500BAAD8C /* UserDefaults+token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AFE7301E9A594500BAAD8C /* UserDefaults+token.swift */; }; - 28B116B821AE5C45004ACE01 /* ReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28B116B721AE5C45004ACE01 /* ReportsHeaderView.xib */; }; - 28B116B921AE5C45004ACE01 /* ReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28B116B721AE5C45004ACE01 /* ReportsHeaderView.xib */; }; - 28B116BB21AE6179004ACE01 /* MonthReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28B116BA21AE6179004ACE01 /* MonthReportsHeaderView.xib */; }; - 28B116BC21AE6179004ACE01 /* MonthReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28B116BA21AE6179004ACE01 /* MonthReportsHeaderView.xib */; }; 28C6A38421D359E60036DB29 /* RemoveDuplicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C6A38321D359E60036DB29 /* RemoveDuplicate.swift */; }; 28C6A38521D359E60036DB29 /* RemoveDuplicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C6A38321D359E60036DB29 /* RemoveDuplicate.swift */; }; 28C9C6221EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */; }; @@ -334,42 +238,15 @@ 28CBB59A20474755006F9D3A /* ParseGitBranchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CBB59920474755006F9D3A /* ParseGitBranchTests.swift */; }; 28DCA4552018B6E700DFAE29 /* JProjectIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DCA4542018B6E700DFAE29 /* JProjectIssue.swift */; }; 28DCA4562018B6E700DFAE29 /* JProjectIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DCA4542018B6E700DFAE29 /* JProjectIssue.swift */; }; - 28E896631E830D6700722032 /* Placeholder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28E896621E830D6700722032 /* Placeholder.storyboard */; }; 28E896711E83732200722032 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E8966F1E83732200722032 /* AppDelegate.m */; }; 28E896721E83732200722032 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E896701E83732200722032 /* main.m */; }; 28E896751E83770D00722032 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28E896731E83770D00722032 /* MainMenu.xib */; }; - 28EDE9391E59EC1500B360A4 /* TasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EDE9381E59EC1500B360A4 /* TasksView.swift */; }; - 28EE1F5E20EE8D1000C5C1D6 /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EE1F5D20EE8D1000C5C1D6 /* CalendarCell.swift */; }; - 28EE1F5F20EE8D1000C5C1D6 /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EE1F5D20EE8D1000C5C1D6 /* CalendarCell.swift */; }; - 28EE1F6120EE8D6100C5C1D6 /* CalendarCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28EE1F6020EE8D6100C5C1D6 /* CalendarCell.xib */; }; - 28EE1F6220EE8D6100C5C1D6 /* CalendarCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28EE1F6020EE8D6100C5C1D6 /* CalendarCell.xib */; }; - 28EE1F6420EE92DA00C5C1D6 /* CalendarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EE1F6320EE92DA00C5C1D6 /* CalendarPresenter.swift */; }; - 28EE1F6520EE92DA00C5C1D6 /* CalendarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EE1F6320EE92DA00C5C1D6 /* CalendarPresenter.swift */; }; - 28FB265120224E3A00AEA38D /* JiraTempoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265020224E3A00AEA38D /* JiraTempoCell.swift */; }; - 28FB265220224E3A00AEA38D /* JiraTempoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265020224E3A00AEA38D /* JiraTempoCell.swift */; }; - 28FB265420224E5B00AEA38D /* JiraTempoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FB265320224E5B00AEA38D /* JiraTempoCell.xib */; }; - 28FB265520224E5B00AEA38D /* JiraTempoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FB265320224E5B00AEA38D /* JiraTempoCell.xib */; }; - 28FB265820224E9B00AEA38D /* HookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FB265720224E9B00AEA38D /* HookupCell.xib */; }; - 28FB265920224E9B00AEA38D /* HookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FB265720224E9B00AEA38D /* HookupCell.xib */; }; - 28FB265B20224EA700AEA38D /* HookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265A20224EA700AEA38D /* HookupCell.swift */; }; - 28FB265C20224EA700AEA38D /* HookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265A20224EA700AEA38D /* HookupCell.swift */; }; - 28FB265E2022DC2B00AEA38D /* JiraTempoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265D2022DC2B00AEA38D /* JiraTempoPresenter.swift */; }; - 28FB265F2022DC2B00AEA38D /* JiraTempoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FB265D2022DC2B00AEA38D /* JiraTempoPresenter.swift */; }; 28FE1887207A520B00DF796E /* NewTaskCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE1886207A520B00DF796E /* NewTaskCommand.swift */; }; 28FE1888207A520B00DF796E /* NewTaskCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE1886207A520B00DF796E /* NewTaskCommand.swift */; }; - 28FE18A5207B369800DF796E /* CocoaHookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE18A4207B369800DF796E /* CocoaHookupCell.swift */; }; - 28FE18A6207B369800DF796E /* CocoaHookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE18A4207B369800DF796E /* CocoaHookupCell.swift */; }; - 28FE18A8207B36CB00DF796E /* CocoaHookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FE18A7207B36CB00DF796E /* CocoaHookupCell.xib */; }; - 28FE18A9207B36CB00DF796E /* CocoaHookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28FE18A7207B36CB00DF796E /* CocoaHookupCell.xib */; }; - 28FE18AB207B375A00DF796E /* CocoaHookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE18AA207B375A00DF796E /* CocoaHookupPresenter.swift */; }; - 28FE18AC207B375A00DF796E /* CocoaHookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE18AA207B375A00DF796E /* CocoaHookupPresenter.swift */; }; 4051D5F91E0EA48A002042BB /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4051D5F81E0EA48A002042BB /* AppLauncher.swift */; }; 4055B1361E0D821500279430 /* JirassicLauncher.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4055B1231E0D802300279430 /* JirassicLauncher.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4055B1391E0D82A900279430 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4055B1381E0D82A900279430 /* ServiceManagement.framework */; }; - 405B15611DEF3D080009871C /* TaskSuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15601DEF3D080009871C /* TaskSuggestionViewController.swift */; }; - 405B15631DEF3F2A0009871C /* TaskSuggestionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15621DEF3F2A0009871C /* TaskSuggestionPresenter.swift */; }; 405B15661DEF75660009871C /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15651DEF75660009871C /* AppViewController.swift */; }; - 406384891DE388C5004795A4 /* Tasks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 406384881DE388C5004795A4 /* Tasks.storyboard */; }; 4065D39C1DD3B44200B73201 /* Day.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3181DD3B44200B73201 /* Day.swift */; }; 4065D39D1DD3B44200B73201 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3191DD3B44200B73201 /* Report.swift */; }; 4065D39E1DD3B44200B73201 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31A1DD3B44200B73201 /* Settings.swift */; }; @@ -405,14 +282,6 @@ 4065D3D91DD3B44200B73201 /* UserNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3691DD3B44200B73201 /* UserNotifications.swift */; }; 4065D3DA1DD3B44200B73201 /* SleepNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D36A1DD3B44200B73201 /* SleepNotifications.swift */; }; 4065D3DE1DD3B44200B73201 /* NewTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3711DD3B44200B73201 /* NewTaskViewController.swift */; }; - 4065D3DF1DD3B44200B73201 /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3731DD3B44200B73201 /* SettingsInteractor.swift */; }; - 4065D3E01DD3B44200B73201 /* SettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3741DD3B44200B73201 /* SettingsPresenter.swift */; }; - 4065D3E11DD3B44200B73201 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3751DD3B44200B73201 /* SettingsViewController.swift */; }; - 4065D3E21DD3B44200B73201 /* CalendarScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3771DD3B44200B73201 /* CalendarScrollView.swift */; }; - 4065D3E31DD3B44200B73201 /* CellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3781DD3B44200B73201 /* CellProtocol.swift */; }; - 4065D3EE1DD3B44200B73201 /* TasksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3831DD3B44200B73201 /* TasksPresenter.swift */; }; - 4065D3EF1DD3B44200B73201 /* TasksScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3841DD3B44200B73201 /* TasksScrollView.swift */; }; - 4065D3F01DD3B44200B73201 /* TasksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3851DD3B44200B73201 /* TasksViewController.swift */; }; 4065D3F21DD3B44200B73201 /* CloudKitRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38A1DD3B44200B73201 /* CloudKitRepository.swift */; }; 4065D3F91DD3B44200B73201 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3941DD3B44200B73201 /* Repository.swift */; }; 4065D3FA1DD3B44200B73201 /* RepositoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */; }; @@ -438,66 +307,21 @@ 4065D4311DD457B000B73201 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3201DD3B44200B73201 /* DateExtension.swift */; }; 4065D4321DD457B500B73201 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3261DD3B44200B73201 /* Conversions.swift */; }; 4073184F1DE9B1B40046F409 /* ComputerWakeUpInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4073184E1DE9B1B40046F409 /* ComputerWakeUpInteractorTests.swift */; }; - 40E092411DE385E4001EF5DA /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40E092401DE385E4001EF5DA /* Settings.storyboard */; }; 40FCE4251DF7646F00D4FD45 /* AppleScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4221DF7646F00D4FD45 /* AppleScript.swift */; }; 40FCE4261DF7646F00D4FD45 /* ExtensionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4231DF7646F00D4FD45 /* ExtensionsInteractor.swift */; }; 40FCE4271DF7646F00D4FD45 /* SandboxedAppleScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4241DF7646F00D4FD45 /* SandboxedAppleScript.swift */; }; - 40FCE4291DFC1CB400D4FD45 /* TaskSuggestionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE4281DFC1CB400D4FD45 /* TaskSuggestionTests.swift */; }; - 40FCE42C1DFC3F8700D4FD45 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE42B1DFC3F8700D4FD45 /* WelcomeViewController.swift */; }; - 40FCE42E1DFC576C00D4FD45 /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40FCE42D1DFC576C00D4FD45 /* Welcome.storyboard */; }; 40FCE4301DFD7C8D00D4FD45 /* Animatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FCE42F1DFD7C8D00D4FD45 /* Animatable.swift */; }; - 564E55F3202883DB00CE4C76 /* WorklogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564E55F2202883DB00CE4C76 /* WorklogsViewController.swift */; }; - 564E55F4202883DB00CE4C76 /* WorklogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564E55F2202883DB00CE4C76 /* WorklogsViewController.swift */; }; - 564E55F6202884DE00CE4C76 /* WorklogsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564E55F5202884DE00CE4C76 /* WorklogsPresenter.swift */; }; - 564E55F7202884DE00CE4C76 /* WorklogsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564E55F5202884DE00CE4C76 /* WorklogsPresenter.swift */; }; - 564E55F92028857300CE4C76 /* Worklogs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 564E55F82028857300CE4C76 /* Worklogs.storyboard */; }; - 564E55FA2028857300CE4C76 /* Worklogs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 564E55F82028857300CE4C76 /* Worklogs.storyboard */; }; 565E19F020A5E336003A5E2A /* RCSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E19EF20A5E336003A5E2A /* RCSync.swift */; }; 565E19F120A5E336003A5E2A /* RCSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E19EF20A5E336003A5E2A /* RCSync.swift */; }; 565E19F220A5E336003A5E2A /* RCSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E19EF20A5E336003A5E2A /* RCSync.swift */; }; - 566B9FA7217DE67800EAF324 /* TasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B9FA6217DE67700EAF324 /* TasksInteractor.swift */; }; - 566B9FA8217DE67800EAF324 /* TasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B9FA6217DE67700EAF324 /* TasksInteractor.swift */; }; 5683DC3E20ECDED30000A138 /* ModuleCalendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */; }; 5683DC3F20ECDED30000A138 /* ModuleCalendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */; }; - 5685C7671DE8721100CA545E /* CloudKitLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7631DE8721100CA545E /* CloudKitLoginViewController.swift */; }; - 5685C7681DE8721100CA545E /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5685C7641DE8721100CA545E /* Login.storyboard */; }; - 5685C7691DE8721100CA545E /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7651DE8721100CA545E /* LoginPresenter.swift */; }; - 5685C76A1DE8721100CA545E /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C7661DE8721100CA545E /* LoginViewController.swift */; }; - 5685C76C1DE8724400CA545E /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5685C76B1DE8724400CA545E /* AccountViewController.swift */; }; - 569C4C582023193B0049FBF1 /* ShellCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C572023193B0049FBF1 /* ShellCell.swift */; }; - 569C4C592023193B0049FBF1 /* ShellCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C572023193B0049FBF1 /* ShellCell.swift */; }; - 569C4C5E202319630049FBF1 /* JitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C5D202319630049FBF1 /* JitCell.swift */; }; - 569C4C5F202319630049FBF1 /* JitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C5D202319630049FBF1 /* JitCell.swift */; }; - 569C4C64202319870049FBF1 /* GitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C63202319870049FBF1 /* GitCell.swift */; }; - 569C4C65202319870049FBF1 /* GitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C63202319870049FBF1 /* GitCell.swift */; }; - 569C4C67202319930049FBF1 /* GitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C66202319930049FBF1 /* GitPresenter.swift */; }; - 569C4C68202319930049FBF1 /* GitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C66202319930049FBF1 /* GitPresenter.swift */; }; - 569C4C6A202319A20049FBF1 /* BrowserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C69202319A20049FBF1 /* BrowserCell.swift */; }; - 569C4C6B202319A20049FBF1 /* BrowserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C69202319A20049FBF1 /* BrowserCell.swift */; }; - 569C4C6D202319CA0049FBF1 /* BrowserPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C6C202319CA0049FBF1 /* BrowserPresenter.swift */; }; - 569C4C6E202319CA0049FBF1 /* BrowserPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C4C6C202319CA0049FBF1 /* BrowserPresenter.swift */; }; - 569C4C70202319DE0049FBF1 /* ShellCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C6F202319DE0049FBF1 /* ShellCell.xib */; }; - 569C4C71202319DE0049FBF1 /* ShellCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C6F202319DE0049FBF1 /* ShellCell.xib */; }; - 569C4C73202319EE0049FBF1 /* JitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C72202319EE0049FBF1 /* JitCell.xib */; }; - 569C4C74202319EE0049FBF1 /* JitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C72202319EE0049FBF1 /* JitCell.xib */; }; - 569C4C76202319FC0049FBF1 /* GitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C75202319FC0049FBF1 /* GitCell.xib */; }; - 569C4C77202319FC0049FBF1 /* GitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C75202319FC0049FBF1 /* GitCell.xib */; }; - 569C4C7920231A0B0049FBF1 /* BrowserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C7820231A0B0049FBF1 /* BrowserCell.xib */; }; - 569C4C7A20231A0B0049FBF1 /* BrowserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 569C4C7820231A0B0049FBF1 /* BrowserCell.xib */; }; 56ADBF6A21C3F625008350A6 /* GitUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56ADBF6921C3F625008350A6 /* GitUser.swift */; }; 56ADBF6B21C3F625008350A6 /* GitUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56ADBF6921C3F625008350A6 /* GitUser.swift */; }; 56ADBF6D21C3F94D008350A6 /* GitUserParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56ADBF6C21C3F94D008350A6 /* GitUserParser.swift */; }; 56ADBF6E21C3F94D008350A6 /* GitUserParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56ADBF6C21C3F94D008350A6 /* GitUserParser.swift */; }; 56CD22BF1E72F89700F9CDB8 /* BuildScript.sh in Resources */ = {isa = PBXBuildFile; fileRef = 56CD22BE1E72F89700F9CDB8 /* BuildScript.sh */; }; 56D069E1216CABCB000D051D /* CreateMonthReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D069E0216CABCB000D051D /* CreateMonthReportTests.swift */; }; - 56D90CF820876F1100F24442 /* WizardJiraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D90CF720876F1100F24442 /* WizardJiraView.swift */; }; - 56D90CF920876F1100F24442 /* WizardJiraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D90CF720876F1100F24442 /* WizardJiraView.swift */; }; - 56D90CFB20876F2B00F24442 /* WizardGitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D90CFA20876F2B00F24442 /* WizardGitView.swift */; }; - 56D90CFC20876F2B00F24442 /* WizardGitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D90CFA20876F2B00F24442 /* WizardGitView.swift */; }; - 56D90CFE20876F3C00F24442 /* WizardGitView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56D90CFD20876F3C00F24442 /* WizardGitView.xib */; }; - 56D90CFF20876F3C00F24442 /* WizardGitView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56D90CFD20876F3C00F24442 /* WizardGitView.xib */; }; - 56D90D0120876F4900F24442 /* WizardJiraView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56D90D0020876F4900F24442 /* WizardJiraView.xib */; }; - 56D90D0220876F4900F24442 /* WizardJiraView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56D90D0020876F4900F24442 /* WizardJiraView.xib */; }; 6D3702602BA83E9A002260D0 /* SwiftKeychainWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 6D37025F2BA83E9A002260D0 /* SwiftKeychainWrapper */; }; 6D3702622BA83EB9002260D0 /* RCLog in Frameworks */ = {isa = PBXBuildFile; productRef = 6D3702612BA83EB9002260D0 /* RCLog */; }; 6D3702642BA83EBF002260D0 /* RCPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = 6D3702632BA83EBF002260D0 /* RCPreferences */; }; @@ -505,6 +329,226 @@ 6D5BC4C82C1B5BE6002DA29B /* CreateDayReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC4C62C1B5B70002DA29B /* CreateDayReport.swift */; }; 6D5BC4C92C1B5BE6002DA29B /* CreateDayReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC4C62C1B5B70002DA29B /* CreateDayReport.swift */; }; 6D5BC4CB2C1B5BF7002DA29B /* CreateDayReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC4C62C1B5B70002DA29B /* CreateDayReport.swift */; }; + 6D5BC5C92C1C1970002DA29B /* WizardGitView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5502C1C1970002DA29B /* WizardGitView.xib */; }; + 6D5BC5CA2C1C1970002DA29B /* JitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC57D2C1C1970002DA29B /* JitCell.xib */; }; + 6D5BC5CB2C1C1970002DA29B /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC53A2C1C1970002DA29B /* Login.storyboard */; }; + 6D5BC5CC2C1C1970002DA29B /* Placeholder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5552C1C1970002DA29B /* Placeholder.storyboard */; }; + 6D5BC5CD2C1C1970002DA29B /* JiraTempoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC58F2C1C1970002DA29B /* JiraTempoCell.xib */; }; + 6D5BC5CE2C1C1970002DA29B /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5492C1C1970002DA29B /* Welcome.storyboard */; }; + 6D5BC5CF2C1C1970002DA29B /* Reports.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5682C1C1970002DA29B /* Reports.storyboard */; }; + 6D5BC5D02C1C1970002DA29B /* Projects.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC55A2C1C1970002DA29B /* Projects.storyboard */; }; + 6D5BC5D12C1C1970002DA29B /* TaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A92C1C1970002DA29B /* TaskCell.xib */; }; + 6D5BC5D22C1C1970002DA29B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5452C1C1970002DA29B /* Main.storyboard */; }; + 6D5BC5D32C1C1970002DA29B /* TrackingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC59A2C1C1970002DA29B /* TrackingView.xib */; }; + 6D5BC5D42C1C1970002DA29B /* CopyReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5612C1C1970002DA29B /* CopyReportCell.xib */; }; + 6D5BC5D52C1C1970002DA29B /* ReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5642C1C1970002DA29B /* ReportCell.xib */; }; + 6D5BC5D62C1C1970002DA29B /* CocoaHookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5872C1C1970002DA29B /* CocoaHookupCell.xib */; }; + 6D5BC5D72C1C1970002DA29B /* Tasks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B72C1C1970002DA29B /* Tasks.storyboard */; }; + 6D5BC5D82C1C1970002DA29B /* CalendarCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5732C1C1970002DA29B /* CalendarCell.xib */; }; + 6D5BC5D92C1C1970002DA29B /* InputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5832C1C1970002DA29B /* InputsScrollView.xib */; }; + 6D5BC5DA2C1C1970002DA29B /* ClosedDayCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A62C1C1970002DA29B /* ClosedDayCell.xib */; }; + 6D5BC5DB2C1C1970002DA29B /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC59D2C1C1970002DA29B /* Settings.storyboard */; }; + 6D5BC5DC2C1C1970002DA29B /* BrowserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC56E2C1C1970002DA29B /* BrowserCell.xib */; }; + 6D5BC5DD2C1C1970002DA29B /* ShellCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5802C1C1970002DA29B /* ShellCell.xib */; }; + 6D5BC5DE2C1C1970002DA29B /* Worklogs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C52C1C1970002DA29B /* Worklogs.storyboard */; }; + 6D5BC5DF2C1C1970002DA29B /* WizardJiraView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5522C1C1970002DA29B /* WizardJiraView.xib */; }; + 6D5BC5E02C1C1970002DA29B /* CloseDayCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A32C1C1970002DA29B /* CloseDayCell.xib */; }; + 6D5BC5E12C1C1970002DA29B /* StoreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5972C1C1970002DA29B /* StoreView.xib */; }; + 6D5BC5E22C1C1970002DA29B /* HookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC58B2C1C1970002DA29B /* HookupCell.xib */; }; + 6D5BC5E32C1C1970002DA29B /* WizardCalendarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC54E2C1C1970002DA29B /* WizardCalendarView.xib */; }; + 6D5BC5E42C1C1970002DA29B /* OutputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5932C1C1970002DA29B /* OutputsScrollView.xib */; }; + 6D5BC5E52C1C1970002DA29B /* Calendar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC53E2C1C1970002DA29B /* Calendar.storyboard */; }; + 6D5BC5E62C1C1970002DA29B /* ReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B12C1C1970002DA29B /* ReportsHeaderView.xib */; }; + 6D5BC5E72C1C1970002DA29B /* WizardAppleScriptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC54C2C1C1970002DA29B /* WizardAppleScriptView.xib */; }; + 6D5BC5E82C1C1970002DA29B /* GitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5762C1C1970002DA29B /* GitCell.xib */; }; + 6D5BC5E92C1C1970002DA29B /* JirassicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC57A2C1C1970002DA29B /* JirassicCell.xib */; }; + 6D5BC5EA2C1C1970002DA29B /* JitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC57C2C1C1970002DA29B /* JitCell.swift */; }; + 6D5BC5EB2C1C1970002DA29B /* TasksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BA2C1C1970002DA29B /* TasksPresenter.swift */; }; + 6D5BC5EC2C1C1970002DA29B /* ClosedDayCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A52C1C1970002DA29B /* ClosedDayCell.swift */; }; + 6D5BC5ED2C1C1970002DA29B /* PlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5562C1C1970002DA29B /* PlaceholderViewController.swift */; }; + 6D5BC5EE2C1C1970002DA29B /* ReportsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56A2C1C1970002DA29B /* ReportsPresenter.swift */; }; + 6D5BC5EF2C1C1970002DA29B /* WizardCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54D2C1C1970002DA29B /* WizardCalendarView.swift */; }; + 6D5BC5F02C1C1970002DA29B /* CocoaHookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5882C1C1970002DA29B /* CocoaHookupPresenter.swift */; }; + 6D5BC5F12C1C1970002DA29B /* OutputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5922C1C1970002DA29B /* OutputsScrollView.swift */; }; + 6D5BC5F22C1C1970002DA29B /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5382C1C1970002DA29B /* AccountViewController.swift */; }; + 6D5BC5F32C1C1970002DA29B /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A82C1C1970002DA29B /* TaskCell.swift */; }; + 6D5BC5F42C1C1970002DA29B /* SettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59F2C1C1970002DA29B /* SettingsPresenter.swift */; }; + 6D5BC5F52C1C1970002DA29B /* ReportCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5652C1C1970002DA29B /* ReportCellPresenter.swift */; }; + 6D5BC5F62C1C1970002DA29B /* TasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B92C1C1970002DA29B /* TasksInteractor.swift */; }; + 6D5BC5F72C1C1970002DA29B /* ProjectsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55B2C1C1970002DA29B /* ProjectsInteractor.swift */; }; + 6D5BC5F82C1C1970002DA29B /* TaskSuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C32C1C1970002DA29B /* TaskSuggestionViewController.swift */; }; + 6D5BC5F92C1C1970002DA29B /* ReportsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56B2C1C1970002DA29B /* ReportsViewController.swift */; }; + 6D5BC5FA2C1C1970002DA29B /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B42C1C1970002DA29B /* DataSource.swift */; }; + 6D5BC5FB2C1C1970002DA29B /* TimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BE2C1C1970002DA29B /* TimeBox.swift */; }; + 6D5BC5FC2C1C1970002DA29B /* HookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58A2C1C1970002DA29B /* HookupCell.swift */; }; + 6D5BC5FD2C1C1970002DA29B /* BrowserPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56F2C1C1970002DA29B /* BrowserPresenter.swift */; }; + 6D5BC5FE2C1C1970002DA29B /* JiraTempoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5902C1C1970002DA29B /* JiraTempoPresenter.swift */; }; + 6D5BC5FF2C1C1970002DA29B /* EditableTimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B52C1C1970002DA29B /* EditableTimeBox.swift */; }; + 6D5BC6002C1C1970002DA29B /* CalendarDayCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53F2C1C1970002DA29B /* CalendarDayCellView.swift */; }; + 6D5BC6012C1C1970002DA29B /* CopyReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5602C1C1970002DA29B /* CopyReportCell.swift */; }; + 6D5BC6022C1C1970002DA29B /* TrackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5992C1C1970002DA29B /* TrackingView.swift */; }; + 6D5BC6032C1C1970002DA29B /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59E2C1C1970002DA29B /* SettingsInteractor.swift */; }; + 6D5BC6042C1C1970002DA29B /* BrowserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56D2C1C1970002DA29B /* BrowserCell.swift */; }; + 6D5BC6052C1C1970002DA29B /* InputsTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5842C1C1970002DA29B /* InputsTableViewDataSource.swift */; }; + 6D5BC6062C1C1970002DA29B /* StoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5962C1C1970002DA29B /* StoreView.swift */; }; + 6D5BC6072C1C1970002DA29B /* WizardGitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54F2C1C1970002DA29B /* WizardGitView.swift */; }; + 6D5BC6082C1C1970002DA29B /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5532C1C1970002DA29B /* WizardViewController.swift */; }; + 6D5BC6092C1C1970002DA29B /* MainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5462C1C1970002DA29B /* MainPresenter.swift */; }; + 6D5BC60A2C1C1970002DA29B /* InputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5822C1C1970002DA29B /* InputsScrollView.swift */; }; + 6D5BC60B2C1C1970002DA29B /* CalendarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5422C1C1970002DA29B /* CalendarViewController.swift */; }; + 6D5BC60C2C1C1970002DA29B /* CellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AD2C1C1970002DA29B /* CellProtocol.swift */; }; + 6D5BC60D2C1C1970002DA29B /* ReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5632C1C1970002DA29B /* ReportCell.swift */; }; + 6D5BC60E2C1C1970002DA29B /* HookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58C2C1C1970002DA29B /* HookupPresenter.swift */; }; + 6D5BC60F2C1C1970002DA29B /* TaskCell_.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AA2C1C1970002DA29B /* TaskCell_.swift */; }; + 6D5BC6102C1C1970002DA29B /* ReportsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5692C1C1970002DA29B /* ReportsDataSource.swift */; }; + 6D5BC6112C1C1970002DA29B /* CalendarInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5402C1C1970002DA29B /* CalendarInteractor.swift */; }; + 6D5BC6122C1C1970002DA29B /* ProjectsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55D2C1C1970002DA29B /* ProjectsPresenter.swift */; }; + 6D5BC6132C1C1970002DA29B /* CloudKitLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5392C1C1970002DA29B /* CloudKitLoginViewController.swift */; }; + 6D5BC6142C1C1970002DA29B /* ProjectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55E2C1C1970002DA29B /* ProjectsViewController.swift */; }; + 6D5BC6152C1C1970002DA29B /* NewTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B62C1C1970002DA29B /* NewTaskViewController.swift */; }; + 6D5BC6162C1C1970002DA29B /* TaskSuggestionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C12C1C1970002DA29B /* TaskSuggestionPresenter.swift */; }; + 6D5BC6172C1C1970002DA29B /* CalendarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5412C1C1970002DA29B /* CalendarPresenter.swift */; }; + 6D5BC6182C1C1970002DA29B /* GitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5772C1C1970002DA29B /* GitPresenter.swift */; }; + 6D5BC6192C1C1970002DA29B /* OutputTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5942C1C1970002DA29B /* OutputTableViewDataSource.swift */; }; + 6D5BC61A2C1C1970002DA29B /* TaskSuggestionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C22C1C1970002DA29B /* TaskSuggestionTests.swift */; }; + 6D5BC61B2C1C1970002DA29B /* ProjectDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5592C1C1970002DA29B /* ProjectDetailsViewController.swift */; }; + 6D5BC61C2C1C1970002DA29B /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A02C1C1970002DA29B /* SettingsViewController.swift */; }; + 6D5BC61D2C1C1970002DA29B /* JiraTempoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58E2C1C1970002DA29B /* JiraTempoCell.swift */; }; + 6D5BC61E2C1C1970002DA29B /* ProjectDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5582C1C1970002DA29B /* ProjectDetailsPresenter.swift */; }; + 6D5BC61F2C1C1970002DA29B /* TaskCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AB2C1C1970002DA29B /* TaskCellPresenter.swift */; }; + 6D5BC6202C1C1970002DA29B /* CloseDayCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A22C1C1970002DA29B /* CloseDayCell.swift */; }; + 6D5BC6212C1C1970002DA29B /* Saveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59C2C1C1970002DA29B /* Saveable.swift */; }; + 6D5BC6222C1C1970002DA29B /* ShellCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC57F2C1C1970002DA29B /* ShellCell.swift */; }; + 6D5BC6232C1C1970002DA29B /* CalendarAppPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5712C1C1970002DA29B /* CalendarAppPresenter.swift */; }; + 6D5BC6242C1C1970002DA29B /* JirassicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5792C1C1970002DA29B /* JirassicCell.swift */; }; + 6D5BC6252C1C1970002DA29B /* TasksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B82C1C1970002DA29B /* TasksDataSource.swift */; }; + 6D5BC6262C1C1970002DA29B /* WizardJiraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5512C1C1970002DA29B /* WizardJiraView.swift */; }; + 6D5BC6272C1C1970002DA29B /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53C2C1C1970002DA29B /* LoginViewController.swift */; }; + 6D5BC6282C1C1970002DA29B /* MonthReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AF2C1C1970002DA29B /* MonthReportsHeaderView.swift */; }; + 6D5BC6292C1C1970002DA29B /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5722C1C1970002DA29B /* CalendarCell.swift */; }; + 6D5BC62A2C1C1970002DA29B /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53B2C1C1970002DA29B /* LoginPresenter.swift */; }; + 6D5BC62B2C1C1970002DA29B /* TimeBoxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BF2C1C1970002DA29B /* TimeBoxViewController.swift */; }; + 6D5BC62C2C1C1970002DA29B /* TasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BC2C1C1970002DA29B /* TasksView.swift */; }; + 6D5BC62D2C1C1970002DA29B /* GitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5752C1C1970002DA29B /* GitCell.swift */; }; + 6D5BC62E2C1C1970002DA29B /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5472C1C1970002DA29B /* MainViewController.swift */; }; + 6D5BC62F2C1C1970002DA29B /* TasksScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BB2C1C1970002DA29B /* TasksScrollView.swift */; }; + 6D5BC6302C1C1970002DA29B /* CocoaHookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5862C1C1970002DA29B /* CocoaHookupCell.swift */; }; + 6D5BC6312C1C1970002DA29B /* WizardAppleScriptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54B2C1C1970002DA29B /* WizardAppleScriptView.swift */; }; + 6D5BC6322C1C1970002DA29B /* ProjectsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55C2C1C1970002DA29B /* ProjectsListViewController.swift */; }; + 6D5BC6332C1C1970002DA29B /* TasksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BD2C1C1970002DA29B /* TasksViewController.swift */; }; + 6D5BC6342C1C1970002DA29B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54A2C1C1970002DA29B /* WelcomeViewController.swift */; }; + 6D5BC6352C1C1970002DA29B /* WorklogsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C62C1C1970002DA29B /* WorklogsPresenter.swift */; }; + 6D5BC6362C1C1970002DA29B /* WorklogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C72C1C1970002DA29B /* WorklogsViewController.swift */; }; + 6D5BC6372C1C1970002DA29B /* WizardGitView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5502C1C1970002DA29B /* WizardGitView.xib */; }; + 6D5BC6382C1C1970002DA29B /* JitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC57D2C1C1970002DA29B /* JitCell.xib */; }; + 6D5BC6392C1C1970002DA29B /* Login.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC53A2C1C1970002DA29B /* Login.storyboard */; }; + 6D5BC63A2C1C1970002DA29B /* Placeholder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5552C1C1970002DA29B /* Placeholder.storyboard */; }; + 6D5BC63B2C1C1970002DA29B /* JiraTempoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC58F2C1C1970002DA29B /* JiraTempoCell.xib */; }; + 6D5BC63C2C1C1970002DA29B /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5492C1C1970002DA29B /* Welcome.storyboard */; }; + 6D5BC63D2C1C1970002DA29B /* Reports.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5682C1C1970002DA29B /* Reports.storyboard */; }; + 6D5BC63E2C1C1970002DA29B /* Projects.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC55A2C1C1970002DA29B /* Projects.storyboard */; }; + 6D5BC63F2C1C1970002DA29B /* TaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A92C1C1970002DA29B /* TaskCell.xib */; }; + 6D5BC6402C1C1970002DA29B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5452C1C1970002DA29B /* Main.storyboard */; }; + 6D5BC6412C1C1970002DA29B /* TrackingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC59A2C1C1970002DA29B /* TrackingView.xib */; }; + 6D5BC6422C1C1970002DA29B /* CopyReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5612C1C1970002DA29B /* CopyReportCell.xib */; }; + 6D5BC6432C1C1970002DA29B /* ReportCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5642C1C1970002DA29B /* ReportCell.xib */; }; + 6D5BC6442C1C1970002DA29B /* CocoaHookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5872C1C1970002DA29B /* CocoaHookupCell.xib */; }; + 6D5BC6452C1C1970002DA29B /* Tasks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B72C1C1970002DA29B /* Tasks.storyboard */; }; + 6D5BC6462C1C1970002DA29B /* CalendarCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5732C1C1970002DA29B /* CalendarCell.xib */; }; + 6D5BC6472C1C1970002DA29B /* InputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5832C1C1970002DA29B /* InputsScrollView.xib */; }; + 6D5BC6482C1C1970002DA29B /* ClosedDayCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A62C1C1970002DA29B /* ClosedDayCell.xib */; }; + 6D5BC6492C1C1970002DA29B /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC59D2C1C1970002DA29B /* Settings.storyboard */; }; + 6D5BC64A2C1C1970002DA29B /* BrowserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC56E2C1C1970002DA29B /* BrowserCell.xib */; }; + 6D5BC64B2C1C1970002DA29B /* ShellCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5802C1C1970002DA29B /* ShellCell.xib */; }; + 6D5BC64C2C1C1970002DA29B /* Worklogs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C52C1C1970002DA29B /* Worklogs.storyboard */; }; + 6D5BC64D2C1C1970002DA29B /* WizardJiraView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5522C1C1970002DA29B /* WizardJiraView.xib */; }; + 6D5BC64E2C1C1970002DA29B /* CloseDayCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A32C1C1970002DA29B /* CloseDayCell.xib */; }; + 6D5BC64F2C1C1970002DA29B /* StoreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5972C1C1970002DA29B /* StoreView.xib */; }; + 6D5BC6502C1C1970002DA29B /* HookupCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC58B2C1C1970002DA29B /* HookupCell.xib */; }; + 6D5BC6512C1C1970002DA29B /* WizardCalendarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC54E2C1C1970002DA29B /* WizardCalendarView.xib */; }; + 6D5BC6522C1C1970002DA29B /* OutputsScrollView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5932C1C1970002DA29B /* OutputsScrollView.xib */; }; + 6D5BC6532C1C1970002DA29B /* Calendar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC53E2C1C1970002DA29B /* Calendar.storyboard */; }; + 6D5BC6542C1C1970002DA29B /* ReportsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B12C1C1970002DA29B /* ReportsHeaderView.xib */; }; + 6D5BC6552C1C1970002DA29B /* WizardAppleScriptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC54C2C1C1970002DA29B /* WizardAppleScriptView.xib */; }; + 6D5BC6562C1C1970002DA29B /* GitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC5762C1C1970002DA29B /* GitCell.xib */; }; + 6D5BC6572C1C1970002DA29B /* JirassicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D5BC57A2C1C1970002DA29B /* JirassicCell.xib */; }; + 6D5BC6582C1C1970002DA29B /* JitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC57C2C1C1970002DA29B /* JitCell.swift */; }; + 6D5BC6592C1C1970002DA29B /* TasksPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BA2C1C1970002DA29B /* TasksPresenter.swift */; }; + 6D5BC65A2C1C1970002DA29B /* ClosedDayCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A52C1C1970002DA29B /* ClosedDayCell.swift */; }; + 6D5BC65B2C1C1970002DA29B /* PlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5562C1C1970002DA29B /* PlaceholderViewController.swift */; }; + 6D5BC65C2C1C1970002DA29B /* ReportsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56A2C1C1970002DA29B /* ReportsPresenter.swift */; }; + 6D5BC65D2C1C1970002DA29B /* WizardCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54D2C1C1970002DA29B /* WizardCalendarView.swift */; }; + 6D5BC65E2C1C1970002DA29B /* CocoaHookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5882C1C1970002DA29B /* CocoaHookupPresenter.swift */; }; + 6D5BC65F2C1C1970002DA29B /* OutputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5922C1C1970002DA29B /* OutputsScrollView.swift */; }; + 6D5BC6602C1C1970002DA29B /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5382C1C1970002DA29B /* AccountViewController.swift */; }; + 6D5BC6612C1C1970002DA29B /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A82C1C1970002DA29B /* TaskCell.swift */; }; + 6D5BC6622C1C1970002DA29B /* SettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59F2C1C1970002DA29B /* SettingsPresenter.swift */; }; + 6D5BC6632C1C1970002DA29B /* ReportCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5652C1C1970002DA29B /* ReportCellPresenter.swift */; }; + 6D5BC6642C1C1970002DA29B /* TasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B92C1C1970002DA29B /* TasksInteractor.swift */; }; + 6D5BC6652C1C1970002DA29B /* ProjectsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55B2C1C1970002DA29B /* ProjectsInteractor.swift */; }; + 6D5BC6662C1C1970002DA29B /* TaskSuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C32C1C1970002DA29B /* TaskSuggestionViewController.swift */; }; + 6D5BC6672C1C1970002DA29B /* ReportsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56B2C1C1970002DA29B /* ReportsViewController.swift */; }; + 6D5BC6682C1C1970002DA29B /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B42C1C1970002DA29B /* DataSource.swift */; }; + 6D5BC6692C1C1970002DA29B /* TimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BE2C1C1970002DA29B /* TimeBox.swift */; }; + 6D5BC66A2C1C1970002DA29B /* HookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58A2C1C1970002DA29B /* HookupCell.swift */; }; + 6D5BC66B2C1C1970002DA29B /* BrowserPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56F2C1C1970002DA29B /* BrowserPresenter.swift */; }; + 6D5BC66C2C1C1970002DA29B /* JiraTempoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5902C1C1970002DA29B /* JiraTempoPresenter.swift */; }; + 6D5BC66D2C1C1970002DA29B /* EditableTimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B52C1C1970002DA29B /* EditableTimeBox.swift */; }; + 6D5BC66E2C1C1970002DA29B /* CalendarDayCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53F2C1C1970002DA29B /* CalendarDayCellView.swift */; }; + 6D5BC66F2C1C1970002DA29B /* CopyReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5602C1C1970002DA29B /* CopyReportCell.swift */; }; + 6D5BC6702C1C1970002DA29B /* TrackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5992C1C1970002DA29B /* TrackingView.swift */; }; + 6D5BC6712C1C1970002DA29B /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59E2C1C1970002DA29B /* SettingsInteractor.swift */; }; + 6D5BC6722C1C1970002DA29B /* BrowserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC56D2C1C1970002DA29B /* BrowserCell.swift */; }; + 6D5BC6732C1C1970002DA29B /* InputsTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5842C1C1970002DA29B /* InputsTableViewDataSource.swift */; }; + 6D5BC6742C1C1970002DA29B /* StoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5962C1C1970002DA29B /* StoreView.swift */; }; + 6D5BC6752C1C1970002DA29B /* WizardGitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54F2C1C1970002DA29B /* WizardGitView.swift */; }; + 6D5BC6762C1C1970002DA29B /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5532C1C1970002DA29B /* WizardViewController.swift */; }; + 6D5BC6772C1C1970002DA29B /* MainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5462C1C1970002DA29B /* MainPresenter.swift */; }; + 6D5BC6782C1C1970002DA29B /* InputsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5822C1C1970002DA29B /* InputsScrollView.swift */; }; + 6D5BC6792C1C1970002DA29B /* CalendarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5422C1C1970002DA29B /* CalendarViewController.swift */; }; + 6D5BC67A2C1C1970002DA29B /* CellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AD2C1C1970002DA29B /* CellProtocol.swift */; }; + 6D5BC67B2C1C1970002DA29B /* ReportCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5632C1C1970002DA29B /* ReportCell.swift */; }; + 6D5BC67C2C1C1970002DA29B /* HookupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58C2C1C1970002DA29B /* HookupPresenter.swift */; }; + 6D5BC67D2C1C1970002DA29B /* TaskCell_.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AA2C1C1970002DA29B /* TaskCell_.swift */; }; + 6D5BC67E2C1C1970002DA29B /* ReportsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5692C1C1970002DA29B /* ReportsDataSource.swift */; }; + 6D5BC67F2C1C1970002DA29B /* CalendarInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5402C1C1970002DA29B /* CalendarInteractor.swift */; }; + 6D5BC6802C1C1970002DA29B /* ProjectsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55D2C1C1970002DA29B /* ProjectsPresenter.swift */; }; + 6D5BC6812C1C1970002DA29B /* CloudKitLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5392C1C1970002DA29B /* CloudKitLoginViewController.swift */; }; + 6D5BC6822C1C1970002DA29B /* ProjectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55E2C1C1970002DA29B /* ProjectsViewController.swift */; }; + 6D5BC6832C1C1970002DA29B /* NewTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B62C1C1970002DA29B /* NewTaskViewController.swift */; }; + 6D5BC6842C1C1970002DA29B /* TaskSuggestionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C12C1C1970002DA29B /* TaskSuggestionPresenter.swift */; }; + 6D5BC6852C1C1970002DA29B /* CalendarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5412C1C1970002DA29B /* CalendarPresenter.swift */; }; + 6D5BC6862C1C1970002DA29B /* GitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5772C1C1970002DA29B /* GitPresenter.swift */; }; + 6D5BC6872C1C1970002DA29B /* OutputTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5942C1C1970002DA29B /* OutputTableViewDataSource.swift */; }; + 6D5BC6882C1C1970002DA29B /* TaskSuggestionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C22C1C1970002DA29B /* TaskSuggestionTests.swift */; }; + 6D5BC6892C1C1970002DA29B /* ProjectDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5592C1C1970002DA29B /* ProjectDetailsViewController.swift */; }; + 6D5BC68A2C1C1970002DA29B /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A02C1C1970002DA29B /* SettingsViewController.swift */; }; + 6D5BC68B2C1C1970002DA29B /* JiraTempoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC58E2C1C1970002DA29B /* JiraTempoCell.swift */; }; + 6D5BC68C2C1C1970002DA29B /* ProjectDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5582C1C1970002DA29B /* ProjectDetailsPresenter.swift */; }; + 6D5BC68D2C1C1970002DA29B /* TaskCellPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AB2C1C1970002DA29B /* TaskCellPresenter.swift */; }; + 6D5BC68E2C1C1970002DA29B /* CloseDayCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5A22C1C1970002DA29B /* CloseDayCell.swift */; }; + 6D5BC68F2C1C1970002DA29B /* Saveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC59C2C1C1970002DA29B /* Saveable.swift */; }; + 6D5BC6902C1C1970002DA29B /* ShellCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC57F2C1C1970002DA29B /* ShellCell.swift */; }; + 6D5BC6912C1C1970002DA29B /* CalendarAppPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5712C1C1970002DA29B /* CalendarAppPresenter.swift */; }; + 6D5BC6922C1C1970002DA29B /* JirassicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5792C1C1970002DA29B /* JirassicCell.swift */; }; + 6D5BC6932C1C1970002DA29B /* TasksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5B82C1C1970002DA29B /* TasksDataSource.swift */; }; + 6D5BC6942C1C1970002DA29B /* WizardJiraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5512C1C1970002DA29B /* WizardJiraView.swift */; }; + 6D5BC6952C1C1970002DA29B /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53C2C1C1970002DA29B /* LoginViewController.swift */; }; + 6D5BC6962C1C1970002DA29B /* MonthReportsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5AF2C1C1970002DA29B /* MonthReportsHeaderView.swift */; }; + 6D5BC6972C1C1970002DA29B /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5722C1C1970002DA29B /* CalendarCell.swift */; }; + 6D5BC6982C1C1970002DA29B /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC53B2C1C1970002DA29B /* LoginPresenter.swift */; }; + 6D5BC6992C1C1970002DA29B /* TimeBoxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BF2C1C1970002DA29B /* TimeBoxViewController.swift */; }; + 6D5BC69A2C1C1970002DA29B /* TasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BC2C1C1970002DA29B /* TasksView.swift */; }; + 6D5BC69B2C1C1970002DA29B /* GitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5752C1C1970002DA29B /* GitCell.swift */; }; + 6D5BC69C2C1C1970002DA29B /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5472C1C1970002DA29B /* MainViewController.swift */; }; + 6D5BC69D2C1C1970002DA29B /* TasksScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BB2C1C1970002DA29B /* TasksScrollView.swift */; }; + 6D5BC69E2C1C1970002DA29B /* CocoaHookupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5862C1C1970002DA29B /* CocoaHookupCell.swift */; }; + 6D5BC69F2C1C1970002DA29B /* WizardAppleScriptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54B2C1C1970002DA29B /* WizardAppleScriptView.swift */; }; + 6D5BC6A02C1C1970002DA29B /* ProjectsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC55C2C1C1970002DA29B /* ProjectsListViewController.swift */; }; + 6D5BC6A12C1C1970002DA29B /* TasksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5BD2C1C1970002DA29B /* TasksViewController.swift */; }; + 6D5BC6A22C1C1970002DA29B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC54A2C1C1970002DA29B /* WelcomeViewController.swift */; }; + 6D5BC6A32C1C1970002DA29B /* WorklogsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C62C1C1970002DA29B /* WorklogsPresenter.swift */; }; + 6D5BC6A42C1C1970002DA29B /* WorklogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC5C72C1C1970002DA29B /* WorklogsViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -585,29 +629,13 @@ 2818848021A4A2F800B33B9C /* Jirassic.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Jirassic.xcdatamodel; sourceTree = ""; }; 2823C9341E4F69970055D036 /* Versioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Versioning.swift; sourceTree = ""; }; 28279AD421C6245700376304 /* GitUsersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitUsersViewController.swift; sourceTree = ""; }; - 28279E9B1E8300E200EAF9FC /* PlaceholderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderViewController.swift; sourceTree = ""; }; 284192D62018841700E64A9A /* JProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JProject.swift; sourceTree = ""; }; 284192D92018855B00E64A9A /* JiraRepository+Projects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JiraRepository+Projects.swift"; sourceTree = ""; }; 284192DC2018A8B200E64A9A /* ModuleJiraTempo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleJiraTempo.swift; sourceTree = ""; }; 2845B1342066C6E6006EFB3B /* AppleScriptProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptProtocol.swift; sourceTree = ""; }; 2845B138206703C5006EFB3B /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 2845B13E2068351F006EFB3B /* TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; - 2845B144206AE3A8006EFB3B /* ReportCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportCell.swift; sourceTree = ""; }; - 2845B145206AE3A8006EFB3B /* ReportCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReportCell.xib; sourceTree = ""; }; - 2845B146206AE3A8006EFB3B /* ReportCellPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportCellPresenter.swift; sourceTree = ""; }; - 2845B147206AE3A8006EFB3B /* ReportsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportsDataSource.swift; sourceTree = ""; }; - 2845B148206AE3A8006EFB3B /* ReportsHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportsHeaderView.swift; sourceTree = ""; }; - 2845B154206AE467006EFB3B /* TaskCellTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCellTests.swift; sourceTree = ""; }; - 2845B155206AE467006EFB3B /* TaskCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; - 2845B156206AE467006EFB3B /* TaskCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TaskCell.xib; sourceTree = ""; }; - 2845B157206AE467006EFB3B /* TaskCellPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCellPresenter.swift; sourceTree = ""; }; - 2845B159206AE467006EFB3B /* TasksHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksHeaderView.swift; sourceTree = ""; }; - 2845B15A206AE467006EFB3B /* TasksHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TasksHeaderView.xib; sourceTree = ""; }; - 2845B15C206AE467006EFB3B /* NonTaskCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonTaskCell.swift; sourceTree = ""; }; - 2845B15D206AE467006EFB3B /* NonTaskCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonTaskCell.xib; sourceTree = ""; }; 285465A1216C84E10052CB6A /* CreateMonthReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateMonthReport.swift; sourceTree = ""; }; - 285465A6217858790052CB6A /* StoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreView.swift; sourceTree = ""; }; - 285465AC217858AF0052CB6A /* StoreView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StoreView.xib; sourceTree = ""; }; 28577EA71E8ADC53002B07FD /* SqliteRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteRepository.swift; sourceTree = ""; }; 28577EA81E8ADC53002B07FD /* SSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSettings.swift; sourceTree = ""; }; 28577EA91E8ADC53002B07FD /* STask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STask.swift; sourceTree = ""; }; @@ -618,44 +646,24 @@ 28577EBB1E8AFB1B002B07FD /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; 28667B5C1FCB7017007B98E3 /* ModuleHookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleHookup.swift; sourceTree = ""; }; 286BC00321909F85004D4CDD /* CloseDay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDay.swift; sourceTree = ""; }; - 287195142022E1E2001C237E /* HookupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HookupPresenter.swift; sourceTree = ""; }; - 287195172022E8BC001C237E /* OutputTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputTableViewDataSource.swift; sourceTree = ""; }; - 2871951A2022ECF7001C237E /* OutputsScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputsScrollView.swift; sourceTree = ""; }; - 2871951D2022FD14001C237E /* InputsScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputsScrollView.swift; sourceTree = ""; }; - 287195202022FD87001C237E /* InputsTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputsTableViewDataSource.swift; sourceTree = ""; }; - 2871952720241A6C001C237E /* TrackingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrackingView.xib; sourceTree = ""; }; - 2871952A20241B25001C237E /* TrackingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingView.swift; sourceTree = ""; }; 287195352027CAD9001C237E /* TimeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInteractor.swift; sourceTree = ""; }; - 287B358620FBAFC40022F43E /* WizardCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardCalendarView.swift; sourceTree = ""; }; - 287B358920FBAFD60022F43E /* WizardCalendarView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardCalendarView.xib; sourceTree = ""; }; - 288BB67D2085252900CF720A /* WizardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardViewController.swift; sourceTree = ""; }; - 288BB6802087152B00CF720A /* WizardAppleScriptView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardAppleScriptView.xib; sourceTree = ""; }; - 288BB6832087157F00CF720A /* WizardAppleScriptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardAppleScriptView.swift; sourceTree = ""; }; 288BB6862087169F00CF720A /* ViewXib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewXib.swift; sourceTree = ""; }; 2892B2971F094A170085BAC2 /* JiraRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JiraRepository.swift; sourceTree = ""; }; 2892B29A1F094A760085BAC2 /* JiraRepository+Reports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JiraRepository+Reports.swift"; sourceTree = ""; }; 2892B29D1F094B0D0085BAC2 /* JReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JReport.swift; sourceTree = ""; }; 2892B2A61F094C800085BAC2 /* JWorkAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWorkAttribute.swift; sourceTree = ""; }; - 2892E80A208D9B42004E5298 /* InputsScrollView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputsScrollView.xib; sourceTree = ""; }; - 2892E80D208D9DD0004E5298 /* OutputsScrollView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OutputsScrollView.xib; sourceTree = ""; }; 2892E810208E6275004E5298 /* LocalPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPreferences.swift; sourceTree = ""; }; - 2898D3A72181907700CF5AD4 /* MonthReportsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthReportsHeaderView.swift; sourceTree = ""; }; 2898D3AA2184D1DB00CF5AD4 /* TimeBoxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBoxViewController.swift; sourceTree = ""; }; 2898D3AD2184ED3000CF5AD4 /* Components.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Components.storyboard; sourceTree = ""; }; 2898D3B02185959300CF5AD4 /* EditableTimeBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTimeBox.swift; sourceTree = ""; }; 28A283932037874600DDCB63 /* ModuleGitLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleGitLogs.swift; sourceTree = ""; }; - 28A283962037975600DDCB63 /* Saveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Saveable.swift; sourceTree = ""; }; 28A2839920382BBB00DDCB63 /* GitCommit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCommit.swift; sourceTree = ""; }; 28A2839E20385BE000DDCB63 /* GitCommitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCommitsParser.swift; sourceTree = ""; }; 28A283A120385F8C00DDCB63 /* GitCommitsParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCommitsParserTests.swift; sourceTree = ""; }; - 28A283A42038AFB100DDCB63 /* JirassicCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JirassicCell.swift; sourceTree = ""; }; - 28A283A72038AFC500DDCB63 /* JirassicCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JirassicCell.xib; sourceTree = ""; }; 28A283AA203A93AB00DDCB63 /* GitBranchParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitBranchParser.swift; sourceTree = ""; }; 28A283AD203C069F00DDCB63 /* GitBranchParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitBranchParserTests.swift; sourceTree = ""; }; 28A283AF203CB1A100DDCB63 /* MergeTasksInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeTasksInteractor.swift; sourceTree = ""; }; 28A283B2203CB1B900DDCB63 /* MergeTasksInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeTasksInteractorTests.swift; sourceTree = ""; }; - 28A5F27D1E5789FC002BE564 /* TasksDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksDataSource.swift; sourceTree = ""; }; - 28A5F2811E586426002BE564 /* DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = ""; }; 28A928771E8F78580022AB55 /* SQLiteSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteSchema.swift; sourceTree = ""; }; 28A9287C1E9030BA0022AB55 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 28A928801E910DA40022AB55 /* SqliteRepository+Tasks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SqliteRepository+Tasks.swift"; sourceTree = ""; }; @@ -668,41 +676,23 @@ 28A928941E9110980022AB55 /* CloudKitRepository+User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CloudKitRepository+User.swift"; sourceTree = ""; }; 28A928961E9110BF0022AB55 /* CloudKitRepository+Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CloudKitRepository+Settings.swift"; sourceTree = ""; }; 28AFE7301E9A594500BAAD8C /* UserDefaults+token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+token.swift"; sourceTree = ""; }; - 28B116B721AE5C45004ACE01 /* ReportsHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReportsHeaderView.xib; sourceTree = ""; }; - 28B116BA21AE6179004ACE01 /* MonthReportsHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MonthReportsHeaderView.xib; sourceTree = ""; }; 28C6A38321D359E60036DB29 /* RemoveDuplicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveDuplicate.swift; sourceTree = ""; }; 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+uploadToken.swift"; sourceTree = ""; }; 28CBB595204554A2006F9D3A /* ParseGitBranch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseGitBranch.swift; sourceTree = ""; }; 28CBB59920474755006F9D3A /* ParseGitBranchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseGitBranchTests.swift; sourceTree = ""; }; 28DCA4542018B6E700DFAE29 /* JProjectIssue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JProjectIssue.swift; sourceTree = ""; }; - 28E896621E830D6700722032 /* Placeholder.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Placeholder.storyboard; sourceTree = ""; }; 28E8966E1E83732200722032 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = "macOS-launcher/AppDelegate.h"; sourceTree = ""; }; 28E8966F1E83732200722032 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = "macOS-launcher/AppDelegate.m"; sourceTree = ""; }; 28E896701E83732200722032 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = "macOS-launcher/main.m"; sourceTree = ""; }; 28E896741E83770D00722032 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "macOS-launcher/Base.lproj/MainMenu.xib"; sourceTree = ""; }; - 28EDE9381E59EC1500B360A4 /* TasksView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksView.swift; sourceTree = ""; }; - 28EE1F5D20EE8D1000C5C1D6 /* CalendarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarCell.swift; sourceTree = ""; }; - 28EE1F6020EE8D6100C5C1D6 /* CalendarCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CalendarCell.xib; sourceTree = ""; }; - 28EE1F6320EE92DA00C5C1D6 /* CalendarPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPresenter.swift; sourceTree = ""; }; - 28FB265020224E3A00AEA38D /* JiraTempoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JiraTempoCell.swift; sourceTree = ""; }; - 28FB265320224E5B00AEA38D /* JiraTempoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JiraTempoCell.xib; sourceTree = ""; }; - 28FB265720224E9B00AEA38D /* HookupCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HookupCell.xib; sourceTree = ""; }; - 28FB265A20224EA700AEA38D /* HookupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HookupCell.swift; sourceTree = ""; }; - 28FB265D2022DC2B00AEA38D /* JiraTempoPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JiraTempoPresenter.swift; sourceTree = ""; }; 28FE1886207A520B00DF796E /* NewTaskCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTaskCommand.swift; sourceTree = ""; }; - 28FE18A4207B369800DF796E /* CocoaHookupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaHookupCell.swift; sourceTree = ""; }; - 28FE18A7207B36CB00DF796E /* CocoaHookupCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CocoaHookupCell.xib; sourceTree = ""; }; - 28FE18AA207B375A00DF796E /* CocoaHookupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaHookupPresenter.swift; sourceTree = ""; }; 4051D5EF1E0EA0EA002042BB /* JirassicLauncher.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = JirassicLauncher.entitlements; path = "macOS-launcher/JirassicLauncher.entitlements"; sourceTree = ""; }; 4051D5F01E0EA0EA002042BB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "macOS-launcher/Info.plist"; sourceTree = ""; }; 4051D5F71E0EA320002042BB /* Jirassic.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Jirassic.entitlements; sourceTree = ""; }; 4051D5F81E0EA48A002042BB /* AppLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLauncher.swift; sourceTree = ""; }; 4055B1231E0D802300279430 /* JirassicLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JirassicLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4055B1381E0D82A900279430 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; - 405B15601DEF3D080009871C /* TaskSuggestionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskSuggestionViewController.swift; path = TaskSuggestion/TaskSuggestionViewController.swift; sourceTree = ""; }; - 405B15621DEF3F2A0009871C /* TaskSuggestionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskSuggestionPresenter.swift; path = TaskSuggestion/TaskSuggestionPresenter.swift; sourceTree = ""; }; 405B15651DEF75660009871C /* AppViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; }; - 406384881DE388C5004795A4 /* Tasks.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Tasks.storyboard; sourceTree = ""; }; 4065D3041DD3A1AA00B73201 /* Jirassic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jirassic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4065D3181DD3B44200B73201 /* Day.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Day.swift; sourceTree = ""; }; 4065D3191DD3B44200B73201 /* Report.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Report.swift; sourceTree = ""; }; @@ -756,14 +746,6 @@ 4065D3691DD3B44200B73201 /* UserNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotifications.swift; sourceTree = ""; }; 4065D36A1DD3B44200B73201 /* SleepNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SleepNotifications.swift; sourceTree = ""; }; 4065D3711DD3B44200B73201 /* NewTaskViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewTaskViewController.swift; sourceTree = ""; }; - 4065D3731DD3B44200B73201 /* SettingsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsInteractor.swift; sourceTree = ""; }; - 4065D3741DD3B44200B73201 /* SettingsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPresenter.swift; sourceTree = ""; }; - 4065D3751DD3B44200B73201 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; - 4065D3771DD3B44200B73201 /* CalendarScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarScrollView.swift; sourceTree = ""; }; - 4065D3781DD3B44200B73201 /* CellProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellProtocol.swift; sourceTree = ""; }; - 4065D3831DD3B44200B73201 /* TasksPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksPresenter.swift; sourceTree = ""; }; - 4065D3841DD3B44200B73201 /* TasksScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksScrollView.swift; sourceTree = ""; }; - 4065D3851DD3B44200B73201 /* TasksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksViewController.swift; sourceTree = ""; }; 4065D3871DD3B44200B73201 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 4065D38A1DD3B44200B73201 /* CloudKitRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitRepository.swift; sourceTree = ""; }; 4065D38C1DD3B44200B73201 /* CoreDataRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataRepository.swift; sourceTree = ""; }; @@ -777,45 +759,128 @@ 4065D4061DD4532100B73201 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4065D4101DD4534C00B73201 /* jirassic */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jirassic; sourceTree = BUILT_PRODUCTS_DIR; }; 4073184E1DE9B1B40046F409 /* ComputerWakeUpInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComputerWakeUpInteractorTests.swift; sourceTree = ""; }; - 40E092401DE385E4001EF5DA /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 40FCE4221DF7646F00D4FD45 /* AppleScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleScript.swift; sourceTree = ""; }; 40FCE4231DF7646F00D4FD45 /* ExtensionsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionsInteractor.swift; sourceTree = ""; }; 40FCE4241DF7646F00D4FD45 /* SandboxedAppleScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxedAppleScript.swift; sourceTree = ""; }; - 40FCE4281DFC1CB400D4FD45 /* TaskSuggestionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskSuggestionTests.swift; path = TaskSuggestion/TaskSuggestionTests.swift; sourceTree = ""; }; - 40FCE42B1DFC3F8700D4FD45 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; - 40FCE42D1DFC576C00D4FD45 /* Welcome.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Welcome.storyboard; sourceTree = ""; }; 40FCE42F1DFD7C8D00D4FD45 /* Animatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animatable.swift; sourceTree = ""; }; - 564E55F2202883DB00CE4C76 /* WorklogsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorklogsViewController.swift; sourceTree = ""; }; - 564E55F5202884DE00CE4C76 /* WorklogsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorklogsPresenter.swift; sourceTree = ""; }; - 564E55F82028857300CE4C76 /* Worklogs.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Worklogs.storyboard; sourceTree = ""; }; 565E19EF20A5E336003A5E2A /* RCSync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RCSync.swift; sourceTree = ""; }; - 566B9FA6217DE67700EAF324 /* TasksInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksInteractor.swift; sourceTree = ""; }; 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleCalendar.swift; sourceTree = ""; }; - 5685C7631DE8721100CA545E /* CloudKitLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitLoginViewController.swift; sourceTree = ""; }; - 5685C7641DE8721100CA545E /* Login.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = ""; }; - 5685C7651DE8721100CA545E /* LoginPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; - 5685C7661DE8721100CA545E /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - 5685C76B1DE8724400CA545E /* AccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; - 569C4C572023193B0049FBF1 /* ShellCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellCell.swift; sourceTree = ""; }; - 569C4C5D202319630049FBF1 /* JitCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitCell.swift; sourceTree = ""; }; - 569C4C63202319870049FBF1 /* GitCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCell.swift; sourceTree = ""; }; - 569C4C66202319930049FBF1 /* GitPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitPresenter.swift; sourceTree = ""; }; - 569C4C69202319A20049FBF1 /* BrowserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserCell.swift; sourceTree = ""; }; - 569C4C6C202319CA0049FBF1 /* BrowserPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserPresenter.swift; sourceTree = ""; }; - 569C4C6F202319DE0049FBF1 /* ShellCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShellCell.xib; sourceTree = ""; }; - 569C4C72202319EE0049FBF1 /* JitCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JitCell.xib; sourceTree = ""; }; - 569C4C75202319FC0049FBF1 /* GitCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GitCell.xib; sourceTree = ""; }; - 569C4C7820231A0B0049FBF1 /* BrowserCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserCell.xib; sourceTree = ""; }; 56ADBF6921C3F625008350A6 /* GitUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitUser.swift; sourceTree = ""; }; 56ADBF6C21C3F94D008350A6 /* GitUserParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitUserParser.swift; sourceTree = ""; }; 56CD22BE1E72F89700F9CDB8 /* BuildScript.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = BuildScript.sh; sourceTree = ""; }; 56D069E0216CABCB000D051D /* CreateMonthReportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateMonthReportTests.swift; sourceTree = ""; }; - 56D90CF720876F1100F24442 /* WizardJiraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardJiraView.swift; sourceTree = ""; }; - 56D90CFA20876F2B00F24442 /* WizardGitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardGitView.swift; sourceTree = ""; }; - 56D90CFD20876F3C00F24442 /* WizardGitView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardGitView.xib; sourceTree = ""; }; - 56D90D0020876F4900F24442 /* WizardJiraView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardJiraView.xib; sourceTree = ""; }; 6D3702672BA84233002260D0 /* Jirassic macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Jirassic macOS.entitlements"; sourceTree = ""; }; 6D5BC4C62C1B5B70002DA29B /* CreateDayReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDayReport.swift; sourceTree = ""; }; + 6D5BC5382C1C1970002DA29B /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; + 6D5BC5392C1C1970002DA29B /* CloudKitLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitLoginViewController.swift; sourceTree = ""; }; + 6D5BC53A2C1C1970002DA29B /* Login.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Login.storyboard; sourceTree = ""; }; + 6D5BC53B2C1C1970002DA29B /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; + 6D5BC53C2C1C1970002DA29B /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 6D5BC53E2C1C1970002DA29B /* Calendar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Calendar.storyboard; sourceTree = ""; }; + 6D5BC53F2C1C1970002DA29B /* CalendarDayCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDayCellView.swift; sourceTree = ""; }; + 6D5BC5402C1C1970002DA29B /* CalendarInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarInteractor.swift; sourceTree = ""; }; + 6D5BC5412C1C1970002DA29B /* CalendarPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPresenter.swift; sourceTree = ""; }; + 6D5BC5422C1C1970002DA29B /* CalendarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewController.swift; sourceTree = ""; }; + 6D5BC5442C1C1970002DA29B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6D5BC5462C1C1970002DA29B /* MainPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainPresenter.swift; sourceTree = ""; }; + 6D5BC5472C1C1970002DA29B /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 6D5BC5492C1C1970002DA29B /* Welcome.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Welcome.storyboard; sourceTree = ""; }; + 6D5BC54A2C1C1970002DA29B /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; + 6D5BC54B2C1C1970002DA29B /* WizardAppleScriptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardAppleScriptView.swift; sourceTree = ""; }; + 6D5BC54C2C1C1970002DA29B /* WizardAppleScriptView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardAppleScriptView.xib; sourceTree = ""; }; + 6D5BC54D2C1C1970002DA29B /* WizardCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardCalendarView.swift; sourceTree = ""; }; + 6D5BC54E2C1C1970002DA29B /* WizardCalendarView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardCalendarView.xib; sourceTree = ""; }; + 6D5BC54F2C1C1970002DA29B /* WizardGitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardGitView.swift; sourceTree = ""; }; + 6D5BC5502C1C1970002DA29B /* WizardGitView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardGitView.xib; sourceTree = ""; }; + 6D5BC5512C1C1970002DA29B /* WizardJiraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardJiraView.swift; sourceTree = ""; }; + 6D5BC5522C1C1970002DA29B /* WizardJiraView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WizardJiraView.xib; sourceTree = ""; }; + 6D5BC5532C1C1970002DA29B /* WizardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardViewController.swift; sourceTree = ""; }; + 6D5BC5552C1C1970002DA29B /* Placeholder.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Placeholder.storyboard; sourceTree = ""; }; + 6D5BC5562C1C1970002DA29B /* PlaceholderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderViewController.swift; sourceTree = ""; }; + 6D5BC5582C1C1970002DA29B /* ProjectDetailsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectDetailsPresenter.swift; sourceTree = ""; }; + 6D5BC5592C1C1970002DA29B /* ProjectDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectDetailsViewController.swift; sourceTree = ""; }; + 6D5BC55A2C1C1970002DA29B /* Projects.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Projects.storyboard; sourceTree = ""; }; + 6D5BC55B2C1C1970002DA29B /* ProjectsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsInteractor.swift; sourceTree = ""; }; + 6D5BC55C2C1C1970002DA29B /* ProjectsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsListViewController.swift; sourceTree = ""; }; + 6D5BC55D2C1C1970002DA29B /* ProjectsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsPresenter.swift; sourceTree = ""; }; + 6D5BC55E2C1C1970002DA29B /* ProjectsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsViewController.swift; sourceTree = ""; }; + 6D5BC5602C1C1970002DA29B /* CopyReportCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyReportCell.swift; sourceTree = ""; }; + 6D5BC5612C1C1970002DA29B /* CopyReportCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CopyReportCell.xib; sourceTree = ""; }; + 6D5BC5632C1C1970002DA29B /* ReportCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCell.swift; sourceTree = ""; }; + 6D5BC5642C1C1970002DA29B /* ReportCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReportCell.xib; sourceTree = ""; }; + 6D5BC5652C1C1970002DA29B /* ReportCellPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCellPresenter.swift; sourceTree = ""; }; + 6D5BC5682C1C1970002DA29B /* Reports.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Reports.storyboard; sourceTree = ""; }; + 6D5BC5692C1C1970002DA29B /* ReportsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportsDataSource.swift; sourceTree = ""; }; + 6D5BC56A2C1C1970002DA29B /* ReportsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportsPresenter.swift; sourceTree = ""; }; + 6D5BC56B2C1C1970002DA29B /* ReportsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportsViewController.swift; sourceTree = ""; }; + 6D5BC56D2C1C1970002DA29B /* BrowserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserCell.swift; sourceTree = ""; }; + 6D5BC56E2C1C1970002DA29B /* BrowserCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserCell.xib; sourceTree = ""; }; + 6D5BC56F2C1C1970002DA29B /* BrowserPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserPresenter.swift; sourceTree = ""; }; + 6D5BC5712C1C1970002DA29B /* CalendarAppPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarAppPresenter.swift; sourceTree = ""; }; + 6D5BC5722C1C1970002DA29B /* CalendarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarCell.swift; sourceTree = ""; }; + 6D5BC5732C1C1970002DA29B /* CalendarCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CalendarCell.xib; sourceTree = ""; }; + 6D5BC5752C1C1970002DA29B /* GitCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCell.swift; sourceTree = ""; }; + 6D5BC5762C1C1970002DA29B /* GitCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GitCell.xib; sourceTree = ""; }; + 6D5BC5772C1C1970002DA29B /* GitPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitPresenter.swift; sourceTree = ""; }; + 6D5BC5792C1C1970002DA29B /* JirassicCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JirassicCell.swift; sourceTree = ""; }; + 6D5BC57A2C1C1970002DA29B /* JirassicCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JirassicCell.xib; sourceTree = ""; }; + 6D5BC57C2C1C1970002DA29B /* JitCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitCell.swift; sourceTree = ""; }; + 6D5BC57D2C1C1970002DA29B /* JitCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JitCell.xib; sourceTree = ""; }; + 6D5BC57F2C1C1970002DA29B /* ShellCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellCell.swift; sourceTree = ""; }; + 6D5BC5802C1C1970002DA29B /* ShellCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShellCell.xib; sourceTree = ""; }; + 6D5BC5822C1C1970002DA29B /* InputsScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputsScrollView.swift; sourceTree = ""; }; + 6D5BC5832C1C1970002DA29B /* InputsScrollView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputsScrollView.xib; sourceTree = ""; }; + 6D5BC5842C1C1970002DA29B /* InputsTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputsTableViewDataSource.swift; sourceTree = ""; }; + 6D5BC5862C1C1970002DA29B /* CocoaHookupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaHookupCell.swift; sourceTree = ""; }; + 6D5BC5872C1C1970002DA29B /* CocoaHookupCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CocoaHookupCell.xib; sourceTree = ""; }; + 6D5BC5882C1C1970002DA29B /* CocoaHookupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaHookupPresenter.swift; sourceTree = ""; }; + 6D5BC58A2C1C1970002DA29B /* HookupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HookupCell.swift; sourceTree = ""; }; + 6D5BC58B2C1C1970002DA29B /* HookupCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HookupCell.xib; sourceTree = ""; }; + 6D5BC58C2C1C1970002DA29B /* HookupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HookupPresenter.swift; sourceTree = ""; }; + 6D5BC58E2C1C1970002DA29B /* JiraTempoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JiraTempoCell.swift; sourceTree = ""; }; + 6D5BC58F2C1C1970002DA29B /* JiraTempoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JiraTempoCell.xib; sourceTree = ""; }; + 6D5BC5902C1C1970002DA29B /* JiraTempoPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JiraTempoPresenter.swift; sourceTree = ""; }; + 6D5BC5922C1C1970002DA29B /* OutputsScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputsScrollView.swift; sourceTree = ""; }; + 6D5BC5932C1C1970002DA29B /* OutputsScrollView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OutputsScrollView.xib; sourceTree = ""; }; + 6D5BC5942C1C1970002DA29B /* OutputTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputTableViewDataSource.swift; sourceTree = ""; }; + 6D5BC5962C1C1970002DA29B /* StoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreView.swift; sourceTree = ""; }; + 6D5BC5972C1C1970002DA29B /* StoreView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StoreView.xib; sourceTree = ""; }; + 6D5BC5992C1C1970002DA29B /* TrackingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingView.swift; sourceTree = ""; }; + 6D5BC59A2C1C1970002DA29B /* TrackingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrackingView.xib; sourceTree = ""; }; + 6D5BC59C2C1C1970002DA29B /* Saveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Saveable.swift; sourceTree = ""; }; + 6D5BC59D2C1C1970002DA29B /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; + 6D5BC59E2C1C1970002DA29B /* SettingsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInteractor.swift; sourceTree = ""; }; + 6D5BC59F2C1C1970002DA29B /* SettingsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPresenter.swift; sourceTree = ""; }; + 6D5BC5A02C1C1970002DA29B /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 6D5BC5A22C1C1970002DA29B /* CloseDayCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDayCell.swift; sourceTree = ""; }; + 6D5BC5A32C1C1970002DA29B /* CloseDayCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CloseDayCell.xib; sourceTree = ""; }; + 6D5BC5A52C1C1970002DA29B /* ClosedDayCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedDayCell.swift; sourceTree = ""; }; + 6D5BC5A62C1C1970002DA29B /* ClosedDayCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ClosedDayCell.xib; sourceTree = ""; }; + 6D5BC5A82C1C1970002DA29B /* TaskCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; + 6D5BC5A92C1C1970002DA29B /* TaskCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TaskCell.xib; sourceTree = ""; }; + 6D5BC5AA2C1C1970002DA29B /* TaskCell_.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCell_.swift; sourceTree = ""; }; + 6D5BC5AB2C1C1970002DA29B /* TaskCellPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCellPresenter.swift; sourceTree = ""; }; + 6D5BC5AD2C1C1970002DA29B /* CellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellProtocol.swift; sourceTree = ""; }; + 6D5BC5AF2C1C1970002DA29B /* MonthReportsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthReportsHeaderView.swift; sourceTree = ""; }; + 6D5BC5B12C1C1970002DA29B /* ReportsHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReportsHeaderView.xib; sourceTree = ""; }; + 6D5BC5B42C1C1970002DA29B /* DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = ""; }; + 6D5BC5B52C1C1970002DA29B /* EditableTimeBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTimeBox.swift; sourceTree = ""; }; + 6D5BC5B62C1C1970002DA29B /* NewTaskViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTaskViewController.swift; sourceTree = ""; }; + 6D5BC5B72C1C1970002DA29B /* Tasks.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Tasks.storyboard; sourceTree = ""; }; + 6D5BC5B82C1C1970002DA29B /* TasksDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksDataSource.swift; sourceTree = ""; }; + 6D5BC5B92C1C1970002DA29B /* TasksInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksInteractor.swift; sourceTree = ""; }; + 6D5BC5BA2C1C1970002DA29B /* TasksPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksPresenter.swift; sourceTree = ""; }; + 6D5BC5BB2C1C1970002DA29B /* TasksScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksScrollView.swift; sourceTree = ""; }; + 6D5BC5BC2C1C1970002DA29B /* TasksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksView.swift; sourceTree = ""; }; + 6D5BC5BD2C1C1970002DA29B /* TasksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksViewController.swift; sourceTree = ""; }; + 6D5BC5BE2C1C1970002DA29B /* TimeBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBox.swift; sourceTree = ""; }; + 6D5BC5BF2C1C1970002DA29B /* TimeBoxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBoxViewController.swift; sourceTree = ""; }; + 6D5BC5C12C1C1970002DA29B /* TaskSuggestionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSuggestionPresenter.swift; sourceTree = ""; }; + 6D5BC5C22C1C1970002DA29B /* TaskSuggestionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSuggestionTests.swift; sourceTree = ""; }; + 6D5BC5C32C1C1970002DA29B /* TaskSuggestionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSuggestionViewController.swift; sourceTree = ""; }; + 6D5BC5C52C1C1970002DA29B /* Worklogs.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Worklogs.storyboard; sourceTree = ""; }; + 6D5BC5C62C1C1970002DA29B /* WorklogsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorklogsPresenter.swift; sourceTree = ""; }; + 6D5BC5C72C1C1970002DA29B /* WorklogsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorklogsViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -905,15 +970,6 @@ path = IAP; sourceTree = ""; }; - 28279E9A1E8300E200EAF9FC /* Placeholder */ = { - isa = PBXGroup; - children = ( - 28279E9B1E8300E200EAF9FC /* PlaceholderViewController.swift */, - 28E896621E830D6700722032 /* Placeholder.storyboard */, - ); - path = Placeholder; - sourceTree = ""; - }; 2845B137206703A8006EFB3B /* Keychain */ = { isa = PBXGroup; children = ( @@ -922,84 +978,6 @@ path = Keychain; sourceTree = ""; }; - 2845B141206AE2A4006EFB3B /* Reports */ = { - isa = PBXGroup; - children = ( - 2845B147206AE3A8006EFB3B /* ReportsDataSource.swift */, - 287B943B21A690C100FFC4A5 /* HeaderView */, - 2845B143206AE3A8006EFB3B /* ReportCell */, - ); - path = Reports; - sourceTree = ""; - }; - 2845B142206AE342006EFB3B /* Calendar */ = { - isa = PBXGroup; - children = ( - 4065D3771DD3B44200B73201 /* CalendarScrollView.swift */, - ); - path = Calendar; - sourceTree = ""; - }; - 2845B143206AE3A8006EFB3B /* ReportCell */ = { - isa = PBXGroup; - children = ( - 2845B144206AE3A8006EFB3B /* ReportCell.swift */, - 2845B145206AE3A8006EFB3B /* ReportCell.xib */, - 2845B146206AE3A8006EFB3B /* ReportCellPresenter.swift */, - ); - path = ReportCell; - sourceTree = ""; - }; - 2845B153206AE467006EFB3B /* TaskCell */ = { - isa = PBXGroup; - children = ( - 2845B155206AE467006EFB3B /* TaskCell.swift */, - 2845B156206AE467006EFB3B /* TaskCell.xib */, - 2845B154206AE467006EFB3B /* TaskCellTests.swift */, - 2845B157206AE467006EFB3B /* TaskCellPresenter.swift */, - ); - path = TaskCell; - sourceTree = ""; - }; - 2845B158206AE467006EFB3B /* HeaderView */ = { - isa = PBXGroup; - children = ( - 2845B159206AE467006EFB3B /* TasksHeaderView.swift */, - 2845B15A206AE467006EFB3B /* TasksHeaderView.xib */, - ); - path = HeaderView; - sourceTree = ""; - }; - 2845B15B206AE467006EFB3B /* NonTaskCell */ = { - isa = PBXGroup; - children = ( - 2845B15C206AE467006EFB3B /* NonTaskCell.swift */, - 2845B15D206AE467006EFB3B /* NonTaskCell.xib */, - ); - path = NonTaskCell; - sourceTree = ""; - }; - 2845B16F206C2888006EFB3B /* AllTasks */ = { - isa = PBXGroup; - children = ( - 28A5F27D1E5789FC002BE564 /* TasksDataSource.swift */, - 4065D3781DD3B44200B73201 /* CellProtocol.swift */, - 2845B158206AE467006EFB3B /* HeaderView */, - 2845B15B206AE467006EFB3B /* NonTaskCell */, - 2845B153206AE467006EFB3B /* TaskCell */, - ); - path = AllTasks; - sourceTree = ""; - }; - 285465A5217858120052CB6A /* Store */ = { - isa = PBXGroup; - children = ( - 285465A6217858790052CB6A /* StoreView.swift */, - 285465AC217858AF0052CB6A /* StoreView.xib */, - ); - path = Store; - sourceTree = ""; - }; 28577EA01E8ADBBD002B07FD /* sqlite */ = { isa = PBXGroup; children = ( @@ -1045,42 +1023,6 @@ path = Hookup; sourceTree = ""; }; - 287195232022FEA4001C237E /* Tracking */ = { - isa = PBXGroup; - children = ( - 2871952A20241B25001C237E /* TrackingView.swift */, - 2871952720241A6C001C237E /* TrackingView.xib */, - ); - path = Tracking; - sourceTree = ""; - }; - 287B358520FBAD160022F43E /* Onboarding */ = { - isa = PBXGroup; - children = ( - 40FCE42D1DFC576C00D4FD45 /* Welcome.storyboard */, - 40FCE42B1DFC3F8700D4FD45 /* WelcomeViewController.swift */, - 288BB67D2085252900CF720A /* WizardViewController.swift */, - 288BB6832087157F00CF720A /* WizardAppleScriptView.swift */, - 288BB6802087152B00CF720A /* WizardAppleScriptView.xib */, - 56D90CFA20876F2B00F24442 /* WizardGitView.swift */, - 56D90CFD20876F3C00F24442 /* WizardGitView.xib */, - 287B358620FBAFC40022F43E /* WizardCalendarView.swift */, - 287B358920FBAFD60022F43E /* WizardCalendarView.xib */, - 56D90CF720876F1100F24442 /* WizardJiraView.swift */, - 56D90D0020876F4900F24442 /* WizardJiraView.xib */, - ); - path = Onboarding; - sourceTree = ""; - }; - 287B943B21A690C100FFC4A5 /* HeaderView */ = { - isa = PBXGroup; - children = ( - 2845B148206AE3A8006EFB3B /* ReportsHeaderView.swift */, - 28B116B721AE5C45004ACE01 /* ReportsHeaderView.xib */, - ); - path = HeaderView; - sourceTree = ""; - }; 2892B2961F0949D70085BAC2 /* Jira */ = { isa = PBXGroup; children = ( @@ -1095,15 +1037,6 @@ path = Jira; sourceTree = ""; }; - 2898D3A62181905600CF5AD4 /* MonthReports */ = { - isa = PBXGroup; - children = ( - 2898D3A72181907700CF5AD4 /* MonthReportsHeaderView.swift */, - 28B116BA21AE6179004ACE01 /* MonthReportsHeaderView.xib */, - ); - path = MonthReports; - sourceTree = ""; - }; 28A28392203786F600DDCB63 /* GitLogs */ = { isa = PBXGroup; children = ( @@ -1117,15 +1050,6 @@ path = GitLogs; sourceTree = ""; }; - 28A283A32038AF9600DDCB63 /* JirassicCmd */ = { - isa = PBXGroup; - children = ( - 28A283A42038AFB100DDCB63 /* JirassicCell.swift */, - 28A283A72038AFC500DDCB63 /* JirassicCell.xib */, - ); - path = JirassicCmd; - sourceTree = ""; - }; 28CBB59420455467006F9D3A /* Parsing */ = { isa = PBXGroup; children = ( @@ -1135,65 +1059,6 @@ path = Parsing; sourceTree = ""; }; - 28EE1F5C20EE8CE600C5C1D6 /* Calendar */ = { - isa = PBXGroup; - children = ( - 28EE1F5D20EE8D1000C5C1D6 /* CalendarCell.swift */, - 28EE1F6320EE92DA00C5C1D6 /* CalendarPresenter.swift */, - 28EE1F6020EE8D6100C5C1D6 /* CalendarCell.xib */, - ); - path = Calendar; - sourceTree = ""; - }; - 28FB264D20224DE200AEA38D /* Input */ = { - isa = PBXGroup; - children = ( - 2871951D2022FD14001C237E /* InputsScrollView.swift */, - 2892E80A208D9B42004E5298 /* InputsScrollView.xib */, - 287195202022FD87001C237E /* InputsTableViewDataSource.swift */, - 28EE1F5C20EE8CE600C5C1D6 /* Calendar */, - 28A283A32038AF9600DDCB63 /* JirassicCmd */, - 569C4C53202318F50049FBF1 /* Shell */, - 569C4C56202319120049FBF1 /* Jit */, - 569C4C552023190A0049FBF1 /* Git */, - 569C4C54202319010049FBF1 /* Browser */, - ); - path = Input; - sourceTree = ""; - }; - 28FB264E20224DEB00AEA38D /* Output */ = { - isa = PBXGroup; - children = ( - 2871951A2022ECF7001C237E /* OutputsScrollView.swift */, - 2892E80D208D9DD0004E5298 /* OutputsScrollView.xib */, - 287195172022E8BC001C237E /* OutputTableViewDataSource.swift */, - 28FB264F20224E0A00AEA38D /* JiraTempo */, - 28FB265620224E8600AEA38D /* Hookup */, - 28FE18A3207B366A00DF796E /* CocoaHookup */, - ); - path = Output; - sourceTree = ""; - }; - 28FB264F20224E0A00AEA38D /* JiraTempo */ = { - isa = PBXGroup; - children = ( - 28FB265020224E3A00AEA38D /* JiraTempoCell.swift */, - 28FB265D2022DC2B00AEA38D /* JiraTempoPresenter.swift */, - 28FB265320224E5B00AEA38D /* JiraTempoCell.xib */, - ); - path = JiraTempo; - sourceTree = ""; - }; - 28FB265620224E8600AEA38D /* Hookup */ = { - isa = PBXGroup; - children = ( - 28FB265A20224EA700AEA38D /* HookupCell.swift */, - 287195142022E1E2001C237E /* HookupPresenter.swift */, - 28FB265720224E9B00AEA38D /* HookupCell.xib */, - ); - path = Hookup; - sourceTree = ""; - }; 28FE1885207A516300DF796E /* AppleScriptCommands */ = { isa = PBXGroup; children = ( @@ -1202,16 +1067,6 @@ path = AppleScriptCommands; sourceTree = ""; }; - 28FE18A3207B366A00DF796E /* CocoaHookup */ = { - isa = PBXGroup; - children = ( - 28FE18A4207B369800DF796E /* CocoaHookupCell.swift */, - 28FE18AA207B375A00DF796E /* CocoaHookupPresenter.swift */, - 28FE18A7207B36CB00DF796E /* CocoaHookupCell.xib */, - ); - path = CocoaHookup; - sourceTree = ""; - }; 4055B1371E0D82A800279430 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1237,16 +1092,6 @@ name = "macOS-launcher"; sourceTree = ""; }; - 405B155F1DEF3CE20009871C /* TaskSuggestion */ = { - isa = PBXGroup; - children = ( - 405B15601DEF3D080009871C /* TaskSuggestionViewController.swift */, - 405B15621DEF3F2A0009871C /* TaskSuggestionPresenter.swift */, - 40FCE4281DFC1CB400D4FD45 /* TaskSuggestionTests.swift */, - ); - name = TaskSuggestion; - sourceTree = ""; - }; 405B15641DEF66EE0009871C /* Resources */ = { isa = PBXGroup; children = ( @@ -1492,51 +1337,21 @@ 4065D36B1DD3B44200B73201 /* Screens */ = { isa = PBXGroup; children = ( - 28279E9A1E8300E200EAF9FC /* Placeholder */, - 287B358520FBAD160022F43E /* Onboarding */, - 405B155F1DEF3CE20009871C /* TaskSuggestion */, - 5685C7621DE8721100CA545E /* Account */, - 4065D3721DD3B44200B73201 /* Settings */, - 564E55F12028839F00CE4C76 /* Worklogs */, - 2845B142206AE342006EFB3B /* Calendar */, - 4065D3761DD3B44200B73201 /* Tasks */, + 6D5BC53D2C1C1970002DA29B /* Account */, + 6D5BC5432C1C1970002DA29B /* Calendar */, + 6D5BC5482C1C1970002DA29B /* Main */, + 6D5BC5542C1C1970002DA29B /* Onboarding */, + 6D5BC5572C1C1970002DA29B /* Placeholder */, + 6D5BC55F2C1C1970002DA29B /* Projects */, + 6D5BC56C2C1C1970002DA29B /* Reports */, + 6D5BC5A12C1C1970002DA29B /* Settings */, + 6D5BC5C02C1C1970002DA29B /* Tasks */, + 6D5BC5C42C1C1970002DA29B /* TaskSuggestion */, + 6D5BC5C82C1C1970002DA29B /* Worklogs */, ); path = Screens; sourceTree = ""; }; - 4065D3721DD3B44200B73201 /* Settings */ = { - isa = PBXGroup; - children = ( - 4065D3751DD3B44200B73201 /* SettingsViewController.swift */, - 4065D3741DD3B44200B73201 /* SettingsPresenter.swift */, - 4065D3731DD3B44200B73201 /* SettingsInteractor.swift */, - 28A283962037975600DDCB63 /* Saveable.swift */, - 40E092401DE385E4001EF5DA /* Settings.storyboard */, - 285465A5217858120052CB6A /* Store */, - 287195232022FEA4001C237E /* Tracking */, - 28FB264E20224DEB00AEA38D /* Output */, - 28FB264D20224DE200AEA38D /* Input */, - ); - path = Settings; - sourceTree = ""; - }; - 4065D3761DD3B44200B73201 /* Tasks */ = { - isa = PBXGroup; - children = ( - 406384881DE388C5004795A4 /* Tasks.storyboard */, - 4065D3851DD3B44200B73201 /* TasksViewController.swift */, - 4065D3831DD3B44200B73201 /* TasksPresenter.swift */, - 566B9FA6217DE67700EAF324 /* TasksInteractor.swift */, - 4065D3841DD3B44200B73201 /* TasksScrollView.swift */, - 28EDE9381E59EC1500B360A4 /* TasksView.swift */, - 28A5F2811E586426002BE564 /* DataSource.swift */, - 2845B16F206C2888006EFB3B /* AllTasks */, - 2845B141206AE2A4006EFB3B /* Reports */, - 2898D3A62181905600CF5AD4 /* MonthReports */, - ); - path = Tasks; - sourceTree = ""; - }; 4065D3861DD3B44200B73201 /* macOS-cmd */ = { isa = PBXGroup; children = ( @@ -1624,74 +1439,385 @@ path = External; sourceTree = ""; }; - 564E55F12028839F00CE4C76 /* Worklogs */ = { + 5683DC3C20ECDEA30000A138 /* CalendarEvents */ = { isa = PBXGroup; children = ( - 564E55F2202883DB00CE4C76 /* WorklogsViewController.swift */, - 564E55F5202884DE00CE4C76 /* WorklogsPresenter.swift */, - 564E55F82028857300CE4C76 /* Worklogs.storyboard */, + 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */, ); - path = Worklogs; + path = CalendarEvents; sourceTree = ""; }; - 5683DC3C20ECDEA30000A138 /* CalendarEvents */ = { + 6D5BC53D2C1C1970002DA29B /* Account */ = { isa = PBXGroup; children = ( - 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */, + 6D5BC5382C1C1970002DA29B /* AccountViewController.swift */, + 6D5BC5392C1C1970002DA29B /* CloudKitLoginViewController.swift */, + 6D5BC53A2C1C1970002DA29B /* Login.storyboard */, + 6D5BC53B2C1C1970002DA29B /* LoginPresenter.swift */, + 6D5BC53C2C1C1970002DA29B /* LoginViewController.swift */, ); - path = CalendarEvents; + path = Account; sourceTree = ""; }; - 5685C7621DE8721100CA545E /* Account */ = { + 6D5BC5432C1C1970002DA29B /* Calendar */ = { isa = PBXGroup; children = ( - 5685C76B1DE8724400CA545E /* AccountViewController.swift */, - 5685C7631DE8721100CA545E /* CloudKitLoginViewController.swift */, - 5685C7641DE8721100CA545E /* Login.storyboard */, - 5685C7651DE8721100CA545E /* LoginPresenter.swift */, - 5685C7661DE8721100CA545E /* LoginViewController.swift */, + 6D5BC53E2C1C1970002DA29B /* Calendar.storyboard */, + 6D5BC53F2C1C1970002DA29B /* CalendarDayCellView.swift */, + 6D5BC5402C1C1970002DA29B /* CalendarInteractor.swift */, + 6D5BC5412C1C1970002DA29B /* CalendarPresenter.swift */, + 6D5BC5422C1C1970002DA29B /* CalendarViewController.swift */, ); - path = Account; + path = Calendar; sourceTree = ""; }; - 569C4C53202318F50049FBF1 /* Shell */ = { + 6D5BC5482C1C1970002DA29B /* Main */ = { isa = PBXGroup; children = ( - 569C4C572023193B0049FBF1 /* ShellCell.swift */, - 569C4C6F202319DE0049FBF1 /* ShellCell.xib */, + 6D5BC5452C1C1970002DA29B /* Main.storyboard */, + 6D5BC5462C1C1970002DA29B /* MainPresenter.swift */, + 6D5BC5472C1C1970002DA29B /* MainViewController.swift */, ); - path = Shell; + path = Main; sourceTree = ""; }; - 569C4C54202319010049FBF1 /* Browser */ = { + 6D5BC5542C1C1970002DA29B /* Onboarding */ = { isa = PBXGroup; children = ( - 569C4C69202319A20049FBF1 /* BrowserCell.swift */, - 569C4C6C202319CA0049FBF1 /* BrowserPresenter.swift */, - 569C4C7820231A0B0049FBF1 /* BrowserCell.xib */, + 6D5BC5492C1C1970002DA29B /* Welcome.storyboard */, + 6D5BC54A2C1C1970002DA29B /* WelcomeViewController.swift */, + 6D5BC54B2C1C1970002DA29B /* WizardAppleScriptView.swift */, + 6D5BC54C2C1C1970002DA29B /* WizardAppleScriptView.xib */, + 6D5BC54D2C1C1970002DA29B /* WizardCalendarView.swift */, + 6D5BC54E2C1C1970002DA29B /* WizardCalendarView.xib */, + 6D5BC54F2C1C1970002DA29B /* WizardGitView.swift */, + 6D5BC5502C1C1970002DA29B /* WizardGitView.xib */, + 6D5BC5512C1C1970002DA29B /* WizardJiraView.swift */, + 6D5BC5522C1C1970002DA29B /* WizardJiraView.xib */, + 6D5BC5532C1C1970002DA29B /* WizardViewController.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + 6D5BC5572C1C1970002DA29B /* Placeholder */ = { + isa = PBXGroup; + children = ( + 6D5BC5552C1C1970002DA29B /* Placeholder.storyboard */, + 6D5BC5562C1C1970002DA29B /* PlaceholderViewController.swift */, + ); + path = Placeholder; + sourceTree = ""; + }; + 6D5BC55F2C1C1970002DA29B /* Projects */ = { + isa = PBXGroup; + children = ( + 6D5BC5582C1C1970002DA29B /* ProjectDetailsPresenter.swift */, + 6D5BC5592C1C1970002DA29B /* ProjectDetailsViewController.swift */, + 6D5BC55A2C1C1970002DA29B /* Projects.storyboard */, + 6D5BC55B2C1C1970002DA29B /* ProjectsInteractor.swift */, + 6D5BC55C2C1C1970002DA29B /* ProjectsListViewController.swift */, + 6D5BC55D2C1C1970002DA29B /* ProjectsPresenter.swift */, + 6D5BC55E2C1C1970002DA29B /* ProjectsViewController.swift */, + ); + path = Projects; + sourceTree = ""; + }; + 6D5BC5622C1C1970002DA29B /* CopyReportCell */ = { + isa = PBXGroup; + children = ( + 6D5BC5602C1C1970002DA29B /* CopyReportCell.swift */, + 6D5BC5612C1C1970002DA29B /* CopyReportCell.xib */, + ); + path = CopyReportCell; + sourceTree = ""; + }; + 6D5BC5662C1C1970002DA29B /* ReportCell */ = { + isa = PBXGroup; + children = ( + 6D5BC5632C1C1970002DA29B /* ReportCell.swift */, + 6D5BC5642C1C1970002DA29B /* ReportCell.xib */, + 6D5BC5652C1C1970002DA29B /* ReportCellPresenter.swift */, + ); + path = ReportCell; + sourceTree = ""; + }; + 6D5BC5672C1C1970002DA29B /* cells */ = { + isa = PBXGroup; + children = ( + 6D5BC5622C1C1970002DA29B /* CopyReportCell */, + 6D5BC5662C1C1970002DA29B /* ReportCell */, + ); + path = cells; + sourceTree = ""; + }; + 6D5BC56C2C1C1970002DA29B /* Reports */ = { + isa = PBXGroup; + children = ( + 6D5BC5672C1C1970002DA29B /* cells */, + 6D5BC5682C1C1970002DA29B /* Reports.storyboard */, + 6D5BC5692C1C1970002DA29B /* ReportsDataSource.swift */, + 6D5BC56A2C1C1970002DA29B /* ReportsPresenter.swift */, + 6D5BC56B2C1C1970002DA29B /* ReportsViewController.swift */, + ); + path = Reports; + sourceTree = ""; + }; + 6D5BC5702C1C1970002DA29B /* Browser */ = { + isa = PBXGroup; + children = ( + 6D5BC56D2C1C1970002DA29B /* BrowserCell.swift */, + 6D5BC56E2C1C1970002DA29B /* BrowserCell.xib */, + 6D5BC56F2C1C1970002DA29B /* BrowserPresenter.swift */, ); path = Browser; sourceTree = ""; }; - 569C4C552023190A0049FBF1 /* Git */ = { + 6D5BC5742C1C1970002DA29B /* Calendar */ = { isa = PBXGroup; children = ( - 569C4C63202319870049FBF1 /* GitCell.swift */, - 569C4C66202319930049FBF1 /* GitPresenter.swift */, - 569C4C75202319FC0049FBF1 /* GitCell.xib */, + 6D5BC5712C1C1970002DA29B /* CalendarAppPresenter.swift */, + 6D5BC5722C1C1970002DA29B /* CalendarCell.swift */, + 6D5BC5732C1C1970002DA29B /* CalendarCell.xib */, + ); + path = Calendar; + sourceTree = ""; + }; + 6D5BC5782C1C1970002DA29B /* Git */ = { + isa = PBXGroup; + children = ( + 6D5BC5752C1C1970002DA29B /* GitCell.swift */, + 6D5BC5762C1C1970002DA29B /* GitCell.xib */, + 6D5BC5772C1C1970002DA29B /* GitPresenter.swift */, ); path = Git; sourceTree = ""; }; - 569C4C56202319120049FBF1 /* Jit */ = { + 6D5BC57B2C1C1970002DA29B /* JirassicCmd */ = { + isa = PBXGroup; + children = ( + 6D5BC5792C1C1970002DA29B /* JirassicCell.swift */, + 6D5BC57A2C1C1970002DA29B /* JirassicCell.xib */, + ); + path = JirassicCmd; + sourceTree = ""; + }; + 6D5BC57E2C1C1970002DA29B /* Jit */ = { isa = PBXGroup; children = ( - 569C4C5D202319630049FBF1 /* JitCell.swift */, - 569C4C72202319EE0049FBF1 /* JitCell.xib */, + 6D5BC57C2C1C1970002DA29B /* JitCell.swift */, + 6D5BC57D2C1C1970002DA29B /* JitCell.xib */, ); path = Jit; sourceTree = ""; }; + 6D5BC5812C1C1970002DA29B /* Shell */ = { + isa = PBXGroup; + children = ( + 6D5BC57F2C1C1970002DA29B /* ShellCell.swift */, + 6D5BC5802C1C1970002DA29B /* ShellCell.xib */, + ); + path = Shell; + sourceTree = ""; + }; + 6D5BC5852C1C1970002DA29B /* Input */ = { + isa = PBXGroup; + children = ( + 6D5BC5702C1C1970002DA29B /* Browser */, + 6D5BC5742C1C1970002DA29B /* Calendar */, + 6D5BC5782C1C1970002DA29B /* Git */, + 6D5BC57B2C1C1970002DA29B /* JirassicCmd */, + 6D5BC57E2C1C1970002DA29B /* Jit */, + 6D5BC5812C1C1970002DA29B /* Shell */, + 6D5BC5822C1C1970002DA29B /* InputsScrollView.swift */, + 6D5BC5832C1C1970002DA29B /* InputsScrollView.xib */, + 6D5BC5842C1C1970002DA29B /* InputsTableViewDataSource.swift */, + ); + path = Input; + sourceTree = ""; + }; + 6D5BC5892C1C1970002DA29B /* CocoaHookup */ = { + isa = PBXGroup; + children = ( + 6D5BC5862C1C1970002DA29B /* CocoaHookupCell.swift */, + 6D5BC5872C1C1970002DA29B /* CocoaHookupCell.xib */, + 6D5BC5882C1C1970002DA29B /* CocoaHookupPresenter.swift */, + ); + path = CocoaHookup; + sourceTree = ""; + }; + 6D5BC58D2C1C1970002DA29B /* Hookup */ = { + isa = PBXGroup; + children = ( + 6D5BC58A2C1C1970002DA29B /* HookupCell.swift */, + 6D5BC58B2C1C1970002DA29B /* HookupCell.xib */, + 6D5BC58C2C1C1970002DA29B /* HookupPresenter.swift */, + ); + path = Hookup; + sourceTree = ""; + }; + 6D5BC5912C1C1970002DA29B /* JiraTempo */ = { + isa = PBXGroup; + children = ( + 6D5BC58E2C1C1970002DA29B /* JiraTempoCell.swift */, + 6D5BC58F2C1C1970002DA29B /* JiraTempoCell.xib */, + 6D5BC5902C1C1970002DA29B /* JiraTempoPresenter.swift */, + ); + path = JiraTempo; + sourceTree = ""; + }; + 6D5BC5952C1C1970002DA29B /* Output */ = { + isa = PBXGroup; + children = ( + 6D5BC5892C1C1970002DA29B /* CocoaHookup */, + 6D5BC58D2C1C1970002DA29B /* Hookup */, + 6D5BC5912C1C1970002DA29B /* JiraTempo */, + 6D5BC5922C1C1970002DA29B /* OutputsScrollView.swift */, + 6D5BC5932C1C1970002DA29B /* OutputsScrollView.xib */, + 6D5BC5942C1C1970002DA29B /* OutputTableViewDataSource.swift */, + ); + path = Output; + sourceTree = ""; + }; + 6D5BC5982C1C1970002DA29B /* Store */ = { + isa = PBXGroup; + children = ( + 6D5BC5962C1C1970002DA29B /* StoreView.swift */, + 6D5BC5972C1C1970002DA29B /* StoreView.xib */, + ); + path = Store; + sourceTree = ""; + }; + 6D5BC59B2C1C1970002DA29B /* Tracking */ = { + isa = PBXGroup; + children = ( + 6D5BC5992C1C1970002DA29B /* TrackingView.swift */, + 6D5BC59A2C1C1970002DA29B /* TrackingView.xib */, + ); + path = Tracking; + sourceTree = ""; + }; + 6D5BC5A12C1C1970002DA29B /* Settings */ = { + isa = PBXGroup; + children = ( + 6D5BC5852C1C1970002DA29B /* Input */, + 6D5BC5952C1C1970002DA29B /* Output */, + 6D5BC5982C1C1970002DA29B /* Store */, + 6D5BC59B2C1C1970002DA29B /* Tracking */, + 6D5BC59C2C1C1970002DA29B /* Saveable.swift */, + 6D5BC59D2C1C1970002DA29B /* Settings.storyboard */, + 6D5BC59E2C1C1970002DA29B /* SettingsInteractor.swift */, + 6D5BC59F2C1C1970002DA29B /* SettingsPresenter.swift */, + 6D5BC5A02C1C1970002DA29B /* SettingsViewController.swift */, + ); + path = Settings; + sourceTree = ""; + }; + 6D5BC5A42C1C1970002DA29B /* CloseDayCell */ = { + isa = PBXGroup; + children = ( + 6D5BC5A22C1C1970002DA29B /* CloseDayCell.swift */, + 6D5BC5A32C1C1970002DA29B /* CloseDayCell.xib */, + ); + path = CloseDayCell; + sourceTree = ""; + }; + 6D5BC5A72C1C1970002DA29B /* ClosedDayCell */ = { + isa = PBXGroup; + children = ( + 6D5BC5A52C1C1970002DA29B /* ClosedDayCell.swift */, + 6D5BC5A62C1C1970002DA29B /* ClosedDayCell.xib */, + ); + path = ClosedDayCell; + sourceTree = ""; + }; + 6D5BC5AC2C1C1970002DA29B /* TaskCell */ = { + isa = PBXGroup; + children = ( + 6D5BC5A82C1C1970002DA29B /* TaskCell.swift */, + 6D5BC5A92C1C1970002DA29B /* TaskCell.xib */, + 6D5BC5AA2C1C1970002DA29B /* TaskCell_.swift */, + 6D5BC5AB2C1C1970002DA29B /* TaskCellPresenter.swift */, + ); + path = TaskCell; + sourceTree = ""; + }; + 6D5BC5AE2C1C1970002DA29B /* cells */ = { + isa = PBXGroup; + children = ( + 6D5BC5A42C1C1970002DA29B /* CloseDayCell */, + 6D5BC5A72C1C1970002DA29B /* ClosedDayCell */, + 6D5BC5AC2C1C1970002DA29B /* TaskCell */, + 6D5BC5AD2C1C1970002DA29B /* CellProtocol.swift */, + ); + path = cells; + sourceTree = ""; + }; + 6D5BC5B02C1C1970002DA29B /* MonthReports */ = { + isa = PBXGroup; + children = ( + 6D5BC5AF2C1C1970002DA29B /* MonthReportsHeaderView.swift */, + ); + path = MonthReports; + sourceTree = ""; + }; + 6D5BC5B22C1C1970002DA29B /* HeaderView */ = { + isa = PBXGroup; + children = ( + 6D5BC5B12C1C1970002DA29B /* ReportsHeaderView.xib */, + ); + path = HeaderView; + sourceTree = ""; + }; + 6D5BC5B32C1C1970002DA29B /* Reports */ = { + isa = PBXGroup; + children = ( + 6D5BC5B22C1C1970002DA29B /* HeaderView */, + ); + path = Reports; + sourceTree = ""; + }; + 6D5BC5C02C1C1970002DA29B /* Tasks */ = { + isa = PBXGroup; + children = ( + 6D5BC5AE2C1C1970002DA29B /* cells */, + 6D5BC5B02C1C1970002DA29B /* MonthReports */, + 6D5BC5B32C1C1970002DA29B /* Reports */, + 6D5BC5B42C1C1970002DA29B /* DataSource.swift */, + 6D5BC5B52C1C1970002DA29B /* EditableTimeBox.swift */, + 6D5BC5B62C1C1970002DA29B /* NewTaskViewController.swift */, + 6D5BC5B72C1C1970002DA29B /* Tasks.storyboard */, + 6D5BC5B82C1C1970002DA29B /* TasksDataSource.swift */, + 6D5BC5B92C1C1970002DA29B /* TasksInteractor.swift */, + 6D5BC5BA2C1C1970002DA29B /* TasksPresenter.swift */, + 6D5BC5BB2C1C1970002DA29B /* TasksScrollView.swift */, + 6D5BC5BC2C1C1970002DA29B /* TasksView.swift */, + 6D5BC5BD2C1C1970002DA29B /* TasksViewController.swift */, + 6D5BC5BE2C1C1970002DA29B /* TimeBox.swift */, + 6D5BC5BF2C1C1970002DA29B /* TimeBoxViewController.swift */, + ); + path = Tasks; + sourceTree = ""; + }; + 6D5BC5C42C1C1970002DA29B /* TaskSuggestion */ = { + isa = PBXGroup; + children = ( + 6D5BC5C12C1C1970002DA29B /* TaskSuggestionPresenter.swift */, + 6D5BC5C22C1C1970002DA29B /* TaskSuggestionTests.swift */, + 6D5BC5C32C1C1970002DA29B /* TaskSuggestionViewController.swift */, + ); + path = TaskSuggestion; + sourceTree = ""; + }; + 6D5BC5C82C1C1970002DA29B /* Worklogs */ = { + isa = PBXGroup; + children = ( + 6D5BC5C52C1C1970002DA29B /* Worklogs.storyboard */, + 6D5BC5C62C1C1970002DA29B /* WorklogsPresenter.swift */, + 6D5BC5C72C1C1970002DA29B /* WorklogsViewController.swift */, + ); + path = Worklogs; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1930,41 +2056,45 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2892E80E208D9DD0004E5298 /* OutputsScrollView.xib in Resources */, - 569C4C7920231A0B0049FBF1 /* BrowserCell.xib in Resources */, - 569C4C70202319DE0049FBF1 /* ShellCell.xib in Resources */, - 28A283A82038AFC500DDCB63 /* JirassicCell.xib in Resources */, - 288BB6812087152B00CF720A /* WizardAppleScriptView.xib in Resources */, 280D71C91ED60908005D2689 /* jirassic.sdef in Resources */, - 2892E80B208D9B42004E5298 /* InputsScrollView.xib in Resources */, - 28EE1F6120EE8D6100C5C1D6 /* CalendarCell.xib in Resources */, 280D71BF1ED608D6005D2689 /* Main.storyboard in Resources */, 2898D3AE2184ED3000CF5AD4 /* Components.storyboard in Resources */, - 280D71C01ED608D6005D2689 /* Placeholder.storyboard in Resources */, 280C1D441ED74EF900C126A1 /* ShellSupport.scpt in Resources */, - 28FB265820224E9B00AEA38D /* HookupCell.xib in Resources */, - 28B116BB21AE6179004ACE01 /* MonthReportsHeaderView.xib in Resources */, - 287B358A20FBAFD60022F43E /* WizardCalendarView.xib in Resources */, - 280D71C11ED608D6005D2689 /* Welcome.storyboard in Resources */, - 280D71C21ED608D6005D2689 /* Login.storyboard in Resources */, - 56D90CFE20876F3C00F24442 /* WizardGitView.xib in Resources */, - 280D71C31ED608D6005D2689 /* Settings.storyboard in Resources */, - 280D71C41ED608D6005D2689 /* Tasks.storyboard in Resources */, - 564E55F92028857300CE4C76 /* Worklogs.storyboard in Resources */, - 2845B162206AE468006EFB3B /* TaskCell.xib in Resources */, - 56D90D0120876F4900F24442 /* WizardJiraView.xib in Resources */, 280C1D431ED74EF900C126A1 /* BrowserSupport.scpt in Resources */, - 28B116B821AE5C45004ACE01 /* ReportsHeaderView.xib in Resources */, - 2871952820241A6C001C237E /* TrackingView.xib in Resources */, - 28FB265420224E5B00AEA38D /* JiraTempoCell.xib in Resources */, - 2845B14B206AE3A8006EFB3B /* ReportCell.xib in Resources */, - 28FE18A8207B36CB00DF796E /* CocoaHookupCell.xib in Resources */, - 569C4C76202319FC0049FBF1 /* GitCell.xib in Resources */, - 2845B168206AE468006EFB3B /* TasksHeaderView.xib in Resources */, 280D71C81ED608D6005D2689 /* Images.xcassets in Resources */, - 285465AD217858AF0052CB6A /* StoreView.xib in Resources */, - 2845B16C206AE468006EFB3B /* NonTaskCell.xib in Resources */, - 569C4C73202319EE0049FBF1 /* JitCell.xib in Resources */, + 6D5BC5C92C1C1970002DA29B /* WizardGitView.xib in Resources */, + 6D5BC5CA2C1C1970002DA29B /* JitCell.xib in Resources */, + 6D5BC5CB2C1C1970002DA29B /* Login.storyboard in Resources */, + 6D5BC5CC2C1C1970002DA29B /* Placeholder.storyboard in Resources */, + 6D5BC5CD2C1C1970002DA29B /* JiraTempoCell.xib in Resources */, + 6D5BC5CE2C1C1970002DA29B /* Welcome.storyboard in Resources */, + 6D5BC5CF2C1C1970002DA29B /* Reports.storyboard in Resources */, + 6D5BC5D02C1C1970002DA29B /* Projects.storyboard in Resources */, + 6D5BC5D12C1C1970002DA29B /* TaskCell.xib in Resources */, + 6D5BC5D22C1C1970002DA29B /* Main.storyboard in Resources */, + 6D5BC5D32C1C1970002DA29B /* TrackingView.xib in Resources */, + 6D5BC5D42C1C1970002DA29B /* CopyReportCell.xib in Resources */, + 6D5BC5D52C1C1970002DA29B /* ReportCell.xib in Resources */, + 6D5BC5D62C1C1970002DA29B /* CocoaHookupCell.xib in Resources */, + 6D5BC5D72C1C1970002DA29B /* Tasks.storyboard in Resources */, + 6D5BC5D82C1C1970002DA29B /* CalendarCell.xib in Resources */, + 6D5BC5D92C1C1970002DA29B /* InputsScrollView.xib in Resources */, + 6D5BC5DA2C1C1970002DA29B /* ClosedDayCell.xib in Resources */, + 6D5BC5DB2C1C1970002DA29B /* Settings.storyboard in Resources */, + 6D5BC5DC2C1C1970002DA29B /* BrowserCell.xib in Resources */, + 6D5BC5DD2C1C1970002DA29B /* ShellCell.xib in Resources */, + 6D5BC5DE2C1C1970002DA29B /* Worklogs.storyboard in Resources */, + 6D5BC5DF2C1C1970002DA29B /* WizardJiraView.xib in Resources */, + 6D5BC5E02C1C1970002DA29B /* CloseDayCell.xib in Resources */, + 6D5BC5E12C1C1970002DA29B /* StoreView.xib in Resources */, + 6D5BC5E22C1C1970002DA29B /* HookupCell.xib in Resources */, + 6D5BC5E32C1C1970002DA29B /* WizardCalendarView.xib in Resources */, + 6D5BC5E42C1C1970002DA29B /* OutputsScrollView.xib in Resources */, + 6D5BC5E52C1C1970002DA29B /* Calendar.storyboard in Resources */, + 6D5BC5E62C1C1970002DA29B /* ReportsHeaderView.xib in Resources */, + 6D5BC5E72C1C1970002DA29B /* WizardAppleScriptView.xib in Resources */, + 6D5BC5E82C1C1970002DA29B /* GitCell.xib in Resources */, + 6D5BC5E92C1C1970002DA29B /* JirassicCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1981,39 +2111,43 @@ buildActionMask = 2147483647; files = ( 2898D3AF2184ED3000CF5AD4 /* Components.storyboard in Resources */, - 569C4C7A20231A0B0049FBF1 /* BrowserCell.xib in Resources */, - 288BB6822087152B00CF720A /* WizardAppleScriptView.xib in Resources */, - 569C4C74202319EE0049FBF1 /* JitCell.xib in Resources */, - 2845B16D206AE468006EFB3B /* NonTaskCell.xib in Resources */, - 2871952920241A6C001C237E /* TrackingView.xib in Resources */, - 2892E80F208D9DD0004E5298 /* OutputsScrollView.xib in Resources */, - 285465AE217858AF0052CB6A /* StoreView.xib in Resources */, - 28FB265920224E9B00AEA38D /* HookupCell.xib in Resources */, - 40FCE42E1DFC576C00D4FD45 /* Welcome.storyboard in Resources */, - 5685C7681DE8721100CA545E /* Login.storyboard in Resources */, - 569C4C71202319DE0049FBF1 /* ShellCell.xib in Resources */, - 28B116B921AE5C45004ACE01 /* ReportsHeaderView.xib in Resources */, - 2892E80C208D9B42004E5298 /* InputsScrollView.xib in Resources */, - 287B358B20FBAFD60022F43E /* WizardCalendarView.xib in Resources */, + 6D5BC6372C1C1970002DA29B /* WizardGitView.xib in Resources */, + 6D5BC6382C1C1970002DA29B /* JitCell.xib in Resources */, + 6D5BC6392C1C1970002DA29B /* Login.storyboard in Resources */, + 6D5BC63A2C1C1970002DA29B /* Placeholder.storyboard in Resources */, + 6D5BC63B2C1C1970002DA29B /* JiraTempoCell.xib in Resources */, + 6D5BC63C2C1C1970002DA29B /* Welcome.storyboard in Resources */, + 6D5BC63D2C1C1970002DA29B /* Reports.storyboard in Resources */, + 6D5BC63E2C1C1970002DA29B /* Projects.storyboard in Resources */, + 6D5BC63F2C1C1970002DA29B /* TaskCell.xib in Resources */, + 6D5BC6402C1C1970002DA29B /* Main.storyboard in Resources */, + 6D5BC6412C1C1970002DA29B /* TrackingView.xib in Resources */, + 6D5BC6422C1C1970002DA29B /* CopyReportCell.xib in Resources */, + 6D5BC6432C1C1970002DA29B /* ReportCell.xib in Resources */, + 6D5BC6442C1C1970002DA29B /* CocoaHookupCell.xib in Resources */, + 6D5BC6452C1C1970002DA29B /* Tasks.storyboard in Resources */, + 6D5BC6462C1C1970002DA29B /* CalendarCell.xib in Resources */, + 6D5BC6472C1C1970002DA29B /* InputsScrollView.xib in Resources */, + 6D5BC6482C1C1970002DA29B /* ClosedDayCell.xib in Resources */, + 6D5BC6492C1C1970002DA29B /* Settings.storyboard in Resources */, + 6D5BC64A2C1C1970002DA29B /* BrowserCell.xib in Resources */, + 6D5BC64B2C1C1970002DA29B /* ShellCell.xib in Resources */, + 6D5BC64C2C1C1970002DA29B /* Worklogs.storyboard in Resources */, + 6D5BC64D2C1C1970002DA29B /* WizardJiraView.xib in Resources */, + 6D5BC64E2C1C1970002DA29B /* CloseDayCell.xib in Resources */, + 6D5BC64F2C1C1970002DA29B /* StoreView.xib in Resources */, + 6D5BC6502C1C1970002DA29B /* HookupCell.xib in Resources */, + 6D5BC6512C1C1970002DA29B /* WizardCalendarView.xib in Resources */, + 6D5BC6522C1C1970002DA29B /* OutputsScrollView.xib in Resources */, + 6D5BC6532C1C1970002DA29B /* Calendar.storyboard in Resources */, + 6D5BC6542C1C1970002DA29B /* ReportsHeaderView.xib in Resources */, + 6D5BC6552C1C1970002DA29B /* WizardAppleScriptView.xib in Resources */, + 6D5BC6562C1C1970002DA29B /* GitCell.xib in Resources */, + 6D5BC6572C1C1970002DA29B /* JirassicCell.xib in Resources */, 4065D3CF1DD3B44200B73201 /* Images.xcassets in Resources */, 56CD22BF1E72F89700F9CDB8 /* BuildScript.sh in Resources */, - 28FB265520224E5B00AEA38D /* JiraTempoCell.xib in Resources */, - 2845B14C206AE3A8006EFB3B /* ReportCell.xib in Resources */, - 406384891DE388C5004795A4 /* Tasks.storyboard in Resources */, - 28FE18A9207B36CB00DF796E /* CocoaHookupCell.xib in Resources */, - 569C4C77202319FC0049FBF1 /* GitCell.xib in Resources */, - 28E896631E830D6700722032 /* Placeholder.storyboard in Resources */, - 56D90D0220876F4900F24442 /* WizardJiraView.xib in Resources */, 4065D3CE1DD3B44200B73201 /* Main.storyboard in Resources */, - 2845B163206AE468006EFB3B /* TaskCell.xib in Resources */, 4065D3D31DD3B44200B73201 /* jirassic.sdef in Resources */, - 40E092411DE385E4001EF5DA /* Settings.storyboard in Resources */, - 28B116BC21AE6179004ACE01 /* MonthReportsHeaderView.xib in Resources */, - 56D90CFF20876F3C00F24442 /* WizardGitView.xib in Resources */, - 564E55FA2028857300CE4C76 /* Worklogs.storyboard in Resources */, - 2845B169206AE468006EFB3B /* TasksHeaderView.xib in Resources */, - 28A283A92038AFC500DDCB63 /* JirassicCell.xib in Resources */, - 28EE1F6220EE8D6100C5C1D6 /* CalendarCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2081,7 +2215,6 @@ 280D712C1ED6043D005D2689 /* Day.swift in Sources */, 2892B29B1F094A760085BAC2 /* JiraRepository+Reports.swift in Sources */, 280D712D1ED6043D005D2689 /* Report.swift in Sources */, - 28EE1F6420EE92DA00C5C1D6 /* CalendarPresenter.swift in Sources */, 280D712E1ED6043D005D2689 /* Settings.swift in Sources */, 2898D3AB2184D1DB00CF5AD4 /* TimeBoxViewController.swift in Sources */, 280D712F1ED6043D005D2689 /* Task.swift in Sources */, @@ -2090,24 +2223,94 @@ 287195302025C35C001C237E /* CoreDataRepository+User.swift in Sources */, 280D71311ED6043D005D2689 /* User.swift in Sources */, 280D71321ED6043D005D2689 /* Week.swift in Sources */, - 566B9FA7217DE67800EAF324 /* TasksInteractor.swift in Sources */, - 288BB67E2085252900CF720A /* WizardViewController.swift in Sources */, - 28FB265E2022DC2B00AEA38D /* JiraTempoPresenter.swift in Sources */, 280D71331ED6043D005D2689 /* DateExtension.swift in Sources */, 56ADBF6D21C3F94D008350A6 /* GitUserParser.swift in Sources */, 280D71351ED6043D005D2689 /* StringIdGenerator.swift in Sources */, 280D71361ED6043D005D2689 /* StringArray.swift in Sources */, 287195362027CAD9001C237E /* TimeInteractor.swift in Sources */, 280D71371ED6043D005D2689 /* ViewAutolayout.swift in Sources */, - 285465A7217858790052CB6A /* StoreView.swift in Sources */, 280D71381ED6043D005D2689 /* ViewController.swift in Sources */, - 2871951E2022FD14001C237E /* InputsScrollView.swift in Sources */, - 28FE18A5207B369800DF796E /* CocoaHookupCell.swift in Sources */, 280D71391ED6043D005D2689 /* ViewControllerStoryboard.swift in Sources */, - 2871952B20241B25001C237E /* TrackingView.swift in Sources */, 286BC00421909F85004D4CDD /* CloseDay.swift in Sources */, 284192DD2018A8B200E64A9A /* ModuleJiraTempo.swift in Sources */, 28667B5D1FCB7017007B98E3 /* ModuleHookup.swift in Sources */, + 6D5BC5EA2C1C1970002DA29B /* JitCell.swift in Sources */, + 6D5BC5EB2C1C1970002DA29B /* TasksPresenter.swift in Sources */, + 6D5BC5EC2C1C1970002DA29B /* ClosedDayCell.swift in Sources */, + 6D5BC5ED2C1C1970002DA29B /* PlaceholderViewController.swift in Sources */, + 6D5BC5EE2C1C1970002DA29B /* ReportsPresenter.swift in Sources */, + 6D5BC5EF2C1C1970002DA29B /* WizardCalendarView.swift in Sources */, + 6D5BC5F02C1C1970002DA29B /* CocoaHookupPresenter.swift in Sources */, + 6D5BC5F12C1C1970002DA29B /* OutputsScrollView.swift in Sources */, + 6D5BC5F22C1C1970002DA29B /* AccountViewController.swift in Sources */, + 6D5BC5F32C1C1970002DA29B /* TaskCell.swift in Sources */, + 6D5BC5F42C1C1970002DA29B /* SettingsPresenter.swift in Sources */, + 6D5BC5F52C1C1970002DA29B /* ReportCellPresenter.swift in Sources */, + 6D5BC5F62C1C1970002DA29B /* TasksInteractor.swift in Sources */, + 6D5BC5F72C1C1970002DA29B /* ProjectsInteractor.swift in Sources */, + 6D5BC5F82C1C1970002DA29B /* TaskSuggestionViewController.swift in Sources */, + 6D5BC5F92C1C1970002DA29B /* ReportsViewController.swift in Sources */, + 6D5BC5FA2C1C1970002DA29B /* DataSource.swift in Sources */, + 6D5BC5FB2C1C1970002DA29B /* TimeBox.swift in Sources */, + 6D5BC5FC2C1C1970002DA29B /* HookupCell.swift in Sources */, + 6D5BC5FD2C1C1970002DA29B /* BrowserPresenter.swift in Sources */, + 6D5BC5FE2C1C1970002DA29B /* JiraTempoPresenter.swift in Sources */, + 6D5BC5FF2C1C1970002DA29B /* EditableTimeBox.swift in Sources */, + 6D5BC6002C1C1970002DA29B /* CalendarDayCellView.swift in Sources */, + 6D5BC6012C1C1970002DA29B /* CopyReportCell.swift in Sources */, + 6D5BC6022C1C1970002DA29B /* TrackingView.swift in Sources */, + 6D5BC6032C1C1970002DA29B /* SettingsInteractor.swift in Sources */, + 6D5BC6042C1C1970002DA29B /* BrowserCell.swift in Sources */, + 6D5BC6052C1C1970002DA29B /* InputsTableViewDataSource.swift in Sources */, + 6D5BC6062C1C1970002DA29B /* StoreView.swift in Sources */, + 6D5BC6072C1C1970002DA29B /* WizardGitView.swift in Sources */, + 6D5BC6082C1C1970002DA29B /* WizardViewController.swift in Sources */, + 6D5BC6092C1C1970002DA29B /* MainPresenter.swift in Sources */, + 6D5BC60A2C1C1970002DA29B /* InputsScrollView.swift in Sources */, + 6D5BC60B2C1C1970002DA29B /* CalendarViewController.swift in Sources */, + 6D5BC60C2C1C1970002DA29B /* CellProtocol.swift in Sources */, + 6D5BC60D2C1C1970002DA29B /* ReportCell.swift in Sources */, + 6D5BC60E2C1C1970002DA29B /* HookupPresenter.swift in Sources */, + 6D5BC60F2C1C1970002DA29B /* TaskCell_.swift in Sources */, + 6D5BC6102C1C1970002DA29B /* ReportsDataSource.swift in Sources */, + 6D5BC6112C1C1970002DA29B /* CalendarInteractor.swift in Sources */, + 6D5BC6122C1C1970002DA29B /* ProjectsPresenter.swift in Sources */, + 6D5BC6132C1C1970002DA29B /* CloudKitLoginViewController.swift in Sources */, + 6D5BC6142C1C1970002DA29B /* ProjectsViewController.swift in Sources */, + 6D5BC6152C1C1970002DA29B /* NewTaskViewController.swift in Sources */, + 6D5BC6162C1C1970002DA29B /* TaskSuggestionPresenter.swift in Sources */, + 6D5BC6172C1C1970002DA29B /* CalendarPresenter.swift in Sources */, + 6D5BC6182C1C1970002DA29B /* GitPresenter.swift in Sources */, + 6D5BC6192C1C1970002DA29B /* OutputTableViewDataSource.swift in Sources */, + 6D5BC61A2C1C1970002DA29B /* TaskSuggestionTests.swift in Sources */, + 6D5BC61B2C1C1970002DA29B /* ProjectDetailsViewController.swift in Sources */, + 6D5BC61C2C1C1970002DA29B /* SettingsViewController.swift in Sources */, + 6D5BC61D2C1C1970002DA29B /* JiraTempoCell.swift in Sources */, + 6D5BC61E2C1C1970002DA29B /* ProjectDetailsPresenter.swift in Sources */, + 6D5BC61F2C1C1970002DA29B /* TaskCellPresenter.swift in Sources */, + 6D5BC6202C1C1970002DA29B /* CloseDayCell.swift in Sources */, + 6D5BC6212C1C1970002DA29B /* Saveable.swift in Sources */, + 6D5BC6222C1C1970002DA29B /* ShellCell.swift in Sources */, + 6D5BC6232C1C1970002DA29B /* CalendarAppPresenter.swift in Sources */, + 6D5BC6242C1C1970002DA29B /* JirassicCell.swift in Sources */, + 6D5BC6252C1C1970002DA29B /* TasksDataSource.swift in Sources */, + 6D5BC6262C1C1970002DA29B /* WizardJiraView.swift in Sources */, + 6D5BC6272C1C1970002DA29B /* LoginViewController.swift in Sources */, + 6D5BC6282C1C1970002DA29B /* MonthReportsHeaderView.swift in Sources */, + 6D5BC6292C1C1970002DA29B /* CalendarCell.swift in Sources */, + 6D5BC62A2C1C1970002DA29B /* LoginPresenter.swift in Sources */, + 6D5BC62B2C1C1970002DA29B /* TimeBoxViewController.swift in Sources */, + 6D5BC62C2C1C1970002DA29B /* TasksView.swift in Sources */, + 6D5BC62D2C1C1970002DA29B /* GitCell.swift in Sources */, + 6D5BC62E2C1C1970002DA29B /* MainViewController.swift in Sources */, + 6D5BC62F2C1C1970002DA29B /* TasksScrollView.swift in Sources */, + 6D5BC6302C1C1970002DA29B /* CocoaHookupCell.swift in Sources */, + 6D5BC6312C1C1970002DA29B /* WizardAppleScriptView.swift in Sources */, + 6D5BC6322C1C1970002DA29B /* ProjectsListViewController.swift in Sources */, + 6D5BC6332C1C1970002DA29B /* TasksViewController.swift in Sources */, + 6D5BC6342C1C1970002DA29B /* WelcomeViewController.swift in Sources */, + 6D5BC6352C1C1970002DA29B /* WorklogsPresenter.swift in Sources */, + 6D5BC6362C1C1970002DA29B /* WorklogsViewController.swift in Sources */, 2812F61E2145031B008EE81E /* IAPHelper.swift in Sources */, 280D713A1ED6043D005D2689 /* Conversions.swift in Sources */, 280D713B1ED6043D005D2689 /* ComputerWakeUpInteractor.swift in Sources */, @@ -2117,16 +2320,12 @@ 280D71431ED6043D005D2689 /* TaskFinder.swift in Sources */, 280D71451ED6043D005D2689 /* TaskInteractor.swift in Sources */, 285465A2216C84E10052CB6A /* CreateMonthReport.swift in Sources */, - 56D90CF820876F1100F24442 /* WizardJiraView.swift in Sources */, 2801388F205F86460051B532 /* TimeBox.swift in Sources */, 280D71471ED6043D005D2689 /* TaskTypeEstimator.swift in Sources */, 280D71491ED6043D005D2689 /* TaskTypeSelection.swift in Sources */, - 2845B160206AE468006EFB3B /* TaskCell.swift in Sources */, - 28FE18AB207B375A00DF796E /* CocoaHookupPresenter.swift in Sources */, 280D714A1ED6043D005D2689 /* PredictiveTimeTyping.swift in Sources */, 280D714C1ED6043D005D2689 /* RegisterUserInteractor.swift in Sources */, 280D714D1ED6043D005D2689 /* UserInteractor.swift in Sources */, - 28FB265120224E3A00AEA38D /* JiraTempoCell.swift in Sources */, 280D71591ED6043D005D2689 /* AppDelegate.swift in Sources */, 280D715A1ED6043D005D2689 /* AppWireframe.swift in Sources */, 280D715B1ED6043D005D2689 /* AppViewController.swift in Sources */, @@ -2136,71 +2335,33 @@ 2892E811208E6275004E5298 /* LocalPreferences.swift in Sources */, 280D715D1ED6043D005D2689 /* AppLauncher.swift in Sources */, 280D715E1ED6043D005D2689 /* Versioning.swift in Sources */, - 2845B14D206AE3A8006EFB3B /* ReportCellPresenter.swift in Sources */, - 2845B164206AE468006EFB3B /* TaskCellPresenter.swift in Sources */, 280D715F1ED6043D005D2689 /* AppTheme.swift in Sources */, 28CBB596204554A2006F9D3A /* ParseGitBranch.swift in Sources */, 280D71601ED6043D005D2689 /* MenuBarController.swift in Sources */, 280D71611ED6043D005D2689 /* MenuBarIconView.swift in Sources */, - 569C4C67202319930049FBF1 /* GitPresenter.swift in Sources */, - 2871951B2022ECF7001C237E /* OutputsScrollView.swift in Sources */, 280D71621ED6043D005D2689 /* FlipAnimation.swift in Sources */, 280D71631ED6043D005D2689 /* Animatable.swift in Sources */, - 564E55F3202883DB00CE4C76 /* WorklogsViewController.swift in Sources */, - 280D71641ED6043D005D2689 /* PlaceholderViewController.swift in Sources */, 28FE1887207A520B00DF796E /* NewTaskCommand.swift in Sources */, - 280D71661ED6043D005D2689 /* WelcomeViewController.swift in Sources */, - 280D71681ED6043D005D2689 /* TaskSuggestionViewController.swift in Sources */, - 280D71691ED6043D005D2689 /* TaskSuggestionPresenter.swift in Sources */, 2871952E2025C356001C237E /* CoreDataRepository+Tasks.swift in Sources */, 28A283B0203CB1A100DDCB63 /* MergeTasksInteractor.swift in Sources */, 2812F6202145031B008EE81E /* Store.swift in Sources */, - 280D716B1ED6043D005D2689 /* AccountViewController.swift in Sources */, - 56D90CFB20876F2B00F24442 /* WizardGitView.swift in Sources */, - 280D716C1ED6043D005D2689 /* CloudKitLoginViewController.swift in Sources */, 2871952D2025C353001C237E /* CoreDataRepository.swift in Sources */, - 280D716E1ED6043D005D2689 /* LoginPresenter.swift in Sources */, - 28FB265B20224EA700AEA38D /* HookupCell.swift in Sources */, 287195332025C367001C237E /* CUser.swift in Sources */, - 280D716F1ED6043D005D2689 /* LoginViewController.swift in Sources */, 2898D3B12185959300CF5AD4 /* EditableTimeBox.swift in Sources */, - 2845B149206AE3A8006EFB3B /* ReportCell.swift in Sources */, 2892B29E1F094B0D0085BAC2 /* JReport.swift in Sources */, 280D71701ED6043D005D2689 /* ExtensionsInteractor.swift in Sources */, 2845B13F2068351F006EFB3B /* TableViewCell.swift in Sources */, 288BB6872087169F00CF720A /* ViewXib.swift in Sources */, - 28A283972037975600DDCB63 /* Saveable.swift in Sources */, 280D71711ED6043D005D2689 /* ExtensionsInstallerInteractor.swift in Sources */, 280D71721ED6043D005D2689 /* AppleScript.swift in Sources */, - 28A283A52038AFB100DDCB63 /* JirassicCell.swift in Sources */, 280D71731ED6043D005D2689 /* SandboxedAppleScript.swift in Sources */, - 280D71741ED6043D005D2689 /* SettingsViewController.swift in Sources */, - 280D71751ED6043D005D2689 /* SettingsPresenter.swift in Sources */, - 280D71761ED6043D005D2689 /* SettingsInteractor.swift in Sources */, 280D71781ED6043D005D2689 /* NewTaskViewController.swift in Sources */, 28A283942037874600DDCB63 /* ModuleGitLogs.swift in Sources */, - 280D717A1ED6043D005D2689 /* TasksViewController.swift in Sources */, - 288BB6842087157F00CF720A /* WizardAppleScriptView.swift in Sources */, - 2845B151206AE3A8006EFB3B /* ReportsHeaderView.swift in Sources */, 28A283AB203A93AB00DDCB63 /* GitBranchParser.swift in Sources */, - 280D717B1ED6043D005D2689 /* TasksPresenter.swift in Sources */, 2818848121A4A2F800B33B9C /* Jirassic.xcdatamodeld in Sources */, - 2845B14F206AE3A8006EFB3B /* ReportsDataSource.swift in Sources */, - 280D717C1ED6043D005D2689 /* CalendarScrollView.swift in Sources */, - 280D717D1ED6043D005D2689 /* TasksScrollView.swift in Sources */, - 280D717E1ED6043D005D2689 /* TasksDataSource.swift in Sources */, 28A2839F20385BE000DDCB63 /* GitCommitsParser.swift in Sources */, - 287195152022E1E2001C237E /* HookupPresenter.swift in Sources */, - 280D71811ED6043D005D2689 /* DataSource.swift in Sources */, 28C6A38421D359E60036DB29 /* RemoveDuplicate.swift in Sources */, - 280D71821ED6043D005D2689 /* CellProtocol.swift in Sources */, - 280D71831ED6043D005D2689 /* TasksView.swift in Sources */, - 2898D3A82181907700CF5AD4 /* MonthReportsHeaderView.swift in Sources */, - 2845B16A206AE468006EFB3B /* NonTaskCell.swift in Sources */, 2845B1352066C6E6006EFB3B /* AppleScriptProtocol.swift in Sources */, - 569C4C582023193B0049FBF1 /* ShellCell.swift in Sources */, - 569C4C64202319870049FBF1 /* GitCell.swift in Sources */, - 569C4C6D202319CA0049FBF1 /* BrowserPresenter.swift in Sources */, 280D718D1ED6043D005D2689 /* InternalNotifications.swift in Sources */, 280D718E1ED6043D005D2689 /* UserNotifications.swift in Sources */, 280D718F1ED6043D005D2689 /* SleepNotifications.swift in Sources */, @@ -2210,14 +2371,11 @@ 284192D72018841700E64A9A /* JProject.swift in Sources */, 280D719E1ED6043D005D2689 /* SqliteRepository+Tasks.swift in Sources */, 2892B2A71F094C800085BAC2 /* JWorkAttribute.swift in Sources */, - 2845B166206AE468006EFB3B /* TasksHeaderView.swift in Sources */, 28DCA4552018B6E700DFAE29 /* JProjectIssue.swift in Sources */, 280D719F1ED6043D005D2689 /* SqliteRepository+User.swift in Sources */, - 28EE1F5E20EE8D1000C5C1D6 /* CalendarCell.swift in Sources */, 280D71A01ED6043D005D2689 /* SqliteRepository+Settings.swift in Sources */, 280D71A11ED6043D005D2689 /* SSettings.swift in Sources */, 280D71A21ED6043D005D2689 /* STask.swift in Sources */, - 569C4C6A202319A20049FBF1 /* BrowserCell.swift in Sources */, 280D71A31ED6043D005D2689 /* SUser.swift in Sources */, 280D71A41ED6043D005D2689 /* SQLiteDB.swift in Sources */, 2871952F2025C359001C237E /* CoreDataRepository+Settings.swift in Sources */, @@ -2226,15 +2384,10 @@ 280D71A61ED6043D005D2689 /* SQLiteSchema.swift in Sources */, 280D71A71ED6043D005D2689 /* UserDefaults+uploadToken.swift in Sources */, 28279AD121BBF08C00376304 /* InMemoryCoreDataRepository.swift in Sources */, - 287195182022E8BC001C237E /* OutputTableViewDataSource.swift in Sources */, - 569C4C5E202319630049FBF1 /* JitCell.swift in Sources */, 28A2839A20382BBB00DDCB63 /* GitCommit.swift in Sources */, 284192DA2018855B00E64A9A /* JiraRepository+Projects.swift in Sources */, 565E19F020A5E336003A5E2A /* RCSync.swift in Sources */, 280D71B61ED6043D005D2689 /* Repository.swift in Sources */, - 564E55F6202884DE00CE4C76 /* WorklogsPresenter.swift in Sources */, - 287195212022FD87001C237E /* InputsTableViewDataSource.swift in Sources */, - 287B358720FBAFC40022F43E /* WizardCalendarView.swift in Sources */, 280D71B71ED6043D005D2689 /* RepositoryInteractor.swift in Sources */, 6D5BC4C82C1B5BE6002DA29B /* CreateDayReport.swift in Sources */, 280300D61EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */, @@ -2256,7 +2409,6 @@ files = ( 2803A0CA1EC184FF005F9389 /* BrowserNotification.swift in Sources */, 28279AD621C6245700376304 /* GitUsersViewController.swift in Sources */, - 4065D3EE1DD3B44200B73201 /* TasksPresenter.swift in Sources */, 4065D3AB1DD3B44200B73201 /* ComputerWakeUpInteractor.swift in Sources */, 2892B29F1F094B0D0085BAC2 /* JReport.swift in Sources */, 28A928811E910DA40022AB55 /* SqliteRepository+Tasks.swift in Sources */, @@ -2265,26 +2417,19 @@ 2898D3B22185959300CF5AD4 /* EditableTimeBox.swift in Sources */, 4065D39F1DD3B44200B73201 /* Task.swift in Sources */, 4065D3CA1DD3B44200B73201 /* AppWireframe.swift in Sources */, - 569C4C5F202319630049FBF1 /* JitCell.swift in Sources */, 4065D3C91DD3B44200B73201 /* AppDelegate.swift in Sources */, 405B15661DEF75660009871C /* AppViewController.swift in Sources */, 6D5BC4C92C1B5BE6002DA29B /* CreateDayReport.swift in Sources */, - 569C4C65202319870049FBF1 /* GitCell.swift in Sources */, 287195372027CAD9001C237E /* TimeInteractor.swift in Sources */, - 287195192022E8BC001C237E /* OutputTableViewDataSource.swift in Sources */, 28577EAD1E8ADC53002B07FD /* SSettings.swift in Sources */, 28577EB51E8AF08F002B07FD /* SQLiteDB.swift in Sources */, 2898D3AC2184D1DB00CF5AD4 /* TimeBoxViewController.swift in Sources */, 286BC00521909F85004D4CDD /* CloseDay.swift in Sources */, 28A928951E9110980022AB55 /* CloudKitRepository+User.swift in Sources */, - 4065D3EF1DD3B44200B73201 /* TasksScrollView.swift in Sources */, - 4065D3E01DD3B44200B73201 /* SettingsPresenter.swift in Sources */, - 569C4C6B202319A20049FBF1 /* BrowserCell.swift in Sources */, 4065D3A31DD3B44200B73201 /* DateExtension.swift in Sources */, 28AA500B1EDCD51300AAF03D /* CoreDataRepository+User.swift in Sources */, 4065D3D81DD3B44200B73201 /* InternalNotifications.swift in Sources */, 4065D3FA1DD3B44200B73201 /* RepositoryInteractor.swift in Sources */, - 2898D3A92181907700CF5AD4 /* MonthReportsHeaderView.swift in Sources */, 28DCA4562018B6E700DFAE29 /* JProjectIssue.swift in Sources */, 28A928871E910EA70022AB55 /* SqliteRepository+Settings.swift in Sources */, 28FE1888207A520B00DF796E /* NewTaskCommand.swift in Sources */, @@ -2294,31 +2439,21 @@ 4065D3B21DD3B44200B73201 /* TaskInteractor.swift in Sources */, 4065D3A21DD3B44200B73201 /* Week.swift in Sources */, 28AFE7311E9A594500BAAD8C /* UserDefaults+token.swift in Sources */, - 4065D3F01DD3B44200B73201 /* TasksViewController.swift in Sources */, - 5685C76A1DE8721100CA545E /* LoginViewController.swift in Sources */, - 569C4C592023193B0049FBF1 /* ShellCell.swift in Sources */, - 2845B14E206AE3A8006EFB3B /* ReportCellPresenter.swift in Sources */, 2823C9351E4F69970055D036 /* Versioning.swift in Sources */, 4065D3A11DD3B44200B73201 /* User.swift in Sources */, 4065D3A71DD3B44200B73201 /* ViewController.swift in Sources */, 4065D3BB1DD3B44200B73201 /* UserInteractor.swift in Sources */, - 4065D3E31DD3B44200B73201 /* CellProtocol.swift in Sources */, 2812F61F2145031B008EE81E /* IAPHelper.swift in Sources */, 4065D3A61DD3B44200B73201 /* ViewAutolayout.swift in Sources */, 28CBB597204554A2006F9D3A /* ParseGitBranch.swift in Sources */, 28667B5E1FCB7017007B98E3 /* ModuleHookup.swift in Sources */, 40FCE4271DF7646F00D4FD45 /* SandboxedAppleScript.swift in Sources */, 4065D3B81DD3B44200B73201 /* PredictiveTimeTyping.swift in Sources */, - 2845B14A206AE3A8006EFB3B /* ReportCell.swift in Sources */, 288BB6882087169F00CF720A /* ViewXib.swift in Sources */, 5683DC3F20ECDED30000A138 /* ModuleCalendar.swift in Sources */, 28C6A38521D359E60036DB29 /* RemoveDuplicate.swift in Sources */, 284192DB2018855B00E64A9A /* JiraRepository+Projects.swift in Sources */, 28A928971E9110BF0022AB55 /* CloudKitRepository+Settings.swift in Sources */, - 564E55F4202883DB00CE4C76 /* WorklogsViewController.swift in Sources */, - 288BB6852087157F00CF720A /* WizardAppleScriptView.swift in Sources */, - 2871951F2022FD14001C237E /* InputsScrollView.swift in Sources */, - 4065D3E11DD3B44200B73201 /* SettingsViewController.swift in Sources */, 4065D3C81DD3B44200B73201 /* FlipAnimation.swift in Sources */, 4065D39E1DD3B44200B73201 /* Settings.swift in Sources */, 28A283B1203CB1A100DDCB63 /* MergeTasksInteractor.swift in Sources */, @@ -2327,29 +2462,18 @@ 28C9C6221EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift in Sources */, 284192DE2018A8B200E64A9A /* ModuleJiraTempo.swift in Sources */, 4065D3BA1DD3B44200B73201 /* RegisterUserInteractor.swift in Sources */, - 28FE18A6207B369800DF796E /* CocoaHookupCell.swift in Sources */, 4065D3B41DD3B44200B73201 /* TaskTypeEstimator.swift in Sources */, - 285465A8217858790052CB6A /* StoreView.swift in Sources */, 4065D3D91DD3B44200B73201 /* UserNotifications.swift in Sources */, - 569C4C6E202319CA0049FBF1 /* BrowserPresenter.swift in Sources */, 4065D3A51DD3B44200B73201 /* StringIdGenerator.swift in Sources */, - 2845B150206AE3A8006EFB3B /* ReportsDataSource.swift in Sources */, 28577EAB1E8ADC53002B07FD /* SqliteRepository.swift in Sources */, - 28A283982037975600DDCB63 /* Saveable.swift in Sources */, 28AA50081EDCD51300AAF03D /* CoreDataRepository.swift in Sources */, 280300D71EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */, - 28A283A62038AFB100DDCB63 /* JirassicCell.swift in Sources */, - 4065D3DF1DD3B44200B73201 /* SettingsInteractor.swift in Sources */, 28577EAF1E8ADC53002B07FD /* STask.swift in Sources */, - 28A5F2821E586426002BE564 /* DataSource.swift in Sources */, 28A928931E91104B0022AB55 /* CloudKitRepository+Tasks.swift in Sources */, - 566B9FA8217DE67800EAF324 /* TasksInteractor.swift in Sources */, 4065D3B01DD3B44200B73201 /* TaskFinder.swift in Sources */, 28A283952037874600DDCB63 /* ModuleGitLogs.swift in Sources */, 284192D82018841700E64A9A /* JProject.swift in Sources */, - 56D90CF920876F1100F24442 /* WizardJiraView.swift in Sources */, 28279AD221BBF08E00376304 /* InMemoryCoreDataRepository.swift in Sources */, - 28EDE9391E59EC1500B360A4 /* TasksView.swift in Sources */, 28A283AC203A93AB00DDCB63 /* GitBranchParser.swift in Sources */, 40FCE4301DFD7C8D00D4FD45 /* Animatable.swift in Sources */, 4065D3F21DD3B44200B73201 /* CloudKitRepository.swift in Sources */, @@ -2359,64 +2483,112 @@ 28A283A020385BE000DDCB63 /* GitCommitsParser.swift in Sources */, 4065D3DA1DD3B44200B73201 /* SleepNotifications.swift in Sources */, 280F507D1EC868B0007416AB /* StringArray.swift in Sources */, - 569C4C68202319930049FBF1 /* GitPresenter.swift in Sources */, - 28FB265C20224EA700AEA38D /* HookupCell.swift in Sources */, 2845B13A206703C5006EFB3B /* Keychain.swift in Sources */, 28AA500D1EDCD51300AAF03D /* CTask.swift in Sources */, 4065D3A81DD3B44200B73201 /* ViewControllerStoryboard.swift in Sources */, 28AA500C1EDCD51300AAF03D /* CSettings.swift in Sources */, - 5685C7691DE8721100CA545E /* LoginPresenter.swift in Sources */, - 2871952C20241B25001C237E /* TrackingView.swift in Sources */, 40FCE4261DF7646F00D4FD45 /* ExtensionsInteractor.swift in Sources */, 4051D5F91E0EA48A002042BB /* AppLauncher.swift in Sources */, - 28EE1F5F20EE8D1000C5C1D6 /* CalendarCell.swift in Sources */, 56ADBF6B21C3F625008350A6 /* GitUser.swift in Sources */, - 2845B167206AE468006EFB3B /* TasksHeaderView.swift in Sources */, 28AA500A1EDCD51300AAF03D /* CoreDataRepository+Settings.swift in Sources */, 280D70B11ECC09D9005D2689 /* AppTheme.swift in Sources */, - 5685C7671DE8721100CA545E /* CloudKitLoginViewController.swift in Sources */, 28AA500E1EDCD51300AAF03D /* CUser.swift in Sources */, - 2845B165206AE468006EFB3B /* TaskCellPresenter.swift in Sources */, - 2871951C2022ECF7001C237E /* OutputsScrollView.swift in Sources */, 2845B1362066C6E6006EFB3B /* AppleScriptProtocol.swift in Sources */, 28577EB11E8ADC53002B07FD /* SUser.swift in Sources */, 4065D3DE1DD3B44200B73201 /* NewTaskViewController.swift in Sources */, 4065D39C1DD3B44200B73201 /* Day.swift in Sources */, - 56D90CFC20876F2B00F24442 /* WizardGitView.swift in Sources */, - 40FCE42C1DFC3F8700D4FD45 /* WelcomeViewController.swift in Sources */, - 28EE1F6520EE92DA00C5C1D6 /* CalendarPresenter.swift in Sources */, - 2845B16B206AE468006EFB3B /* NonTaskCell.swift in Sources */, 4065D39D1DD3B44200B73201 /* Report.swift in Sources */, 40FCE4251DF7646F00D4FD45 /* AppleScript.swift in Sources */, 28A928781E8F78580022AB55 /* SQLiteSchema.swift in Sources */, - 28FE18AC207B375A00DF796E /* CocoaHookupPresenter.swift in Sources */, 4065D3AF1DD3B44200B73201 /* ReadTasksInteractor.swift in Sources */, 565E19F120A5E336003A5E2A /* RCSync.swift in Sources */, 28AA50091EDCD51300AAF03D /* CoreDataRepository+Tasks.swift in Sources */, - 287195162022E1E2001C237E /* HookupPresenter.swift in Sources */, 2892E812208E6275004E5298 /* LocalPreferences.swift in Sources */, - 4065D3E21DD3B44200B73201 /* CalendarScrollView.swift in Sources */, 4065D3AC1DD3B44200B73201 /* CreateReport.swift in Sources */, - 2845B161206AE468006EFB3B /* TaskCell.swift in Sources */, - 28FB265F2022DC2B00AEA38D /* JiraTempoPresenter.swift in Sources */, - 287195222022FD87001C237E /* InputsTableViewDataSource.swift in Sources */, 28577EB71E8AF08F002B07FD /* SQLTable.swift in Sources */, 28A928841E910E5E0022AB55 /* SqliteRepository+User.swift in Sources */, - 288BB67F2085252900CF720A /* WizardViewController.swift in Sources */, 2892B2A81F094C800085BAC2 /* JWorkAttribute.swift in Sources */, - 5685C76C1DE8724400CA545E /* AccountViewController.swift in Sources */, - 2845B152206AE3A8006EFB3B /* ReportsHeaderView.swift in Sources */, 2892B2991F094A170085BAC2 /* JiraRepository.swift in Sources */, 28A2839B20382BBB00DDCB63 /* GitCommit.swift in Sources */, 4065D3A91DD3B44200B73201 /* Conversions.swift in Sources */, - 405B15611DEF3D080009871C /* TaskSuggestionViewController.swift in Sources */, - 564E55F7202884DE00CE4C76 /* WorklogsPresenter.swift in Sources */, - 28279E9C1E8300E200EAF9FC /* PlaceholderViewController.swift in Sources */, - 28FB265220224E3A00AEA38D /* JiraTempoCell.swift in Sources */, - 287B358820FBAFC40022F43E /* WizardCalendarView.swift in Sources */, - 405B15631DEF3F2A0009871C /* TaskSuggestionPresenter.swift in Sources */, + 6D5BC6582C1C1970002DA29B /* JitCell.swift in Sources */, + 6D5BC6592C1C1970002DA29B /* TasksPresenter.swift in Sources */, + 6D5BC65A2C1C1970002DA29B /* ClosedDayCell.swift in Sources */, + 6D5BC65B2C1C1970002DA29B /* PlaceholderViewController.swift in Sources */, + 6D5BC65C2C1C1970002DA29B /* ReportsPresenter.swift in Sources */, + 6D5BC65D2C1C1970002DA29B /* WizardCalendarView.swift in Sources */, + 6D5BC65E2C1C1970002DA29B /* CocoaHookupPresenter.swift in Sources */, + 6D5BC65F2C1C1970002DA29B /* OutputsScrollView.swift in Sources */, + 6D5BC6602C1C1970002DA29B /* AccountViewController.swift in Sources */, + 6D5BC6612C1C1970002DA29B /* TaskCell.swift in Sources */, + 6D5BC6622C1C1970002DA29B /* SettingsPresenter.swift in Sources */, + 6D5BC6632C1C1970002DA29B /* ReportCellPresenter.swift in Sources */, + 6D5BC6642C1C1970002DA29B /* TasksInteractor.swift in Sources */, + 6D5BC6652C1C1970002DA29B /* ProjectsInteractor.swift in Sources */, + 6D5BC6662C1C1970002DA29B /* TaskSuggestionViewController.swift in Sources */, + 6D5BC6672C1C1970002DA29B /* ReportsViewController.swift in Sources */, + 6D5BC6682C1C1970002DA29B /* DataSource.swift in Sources */, + 6D5BC6692C1C1970002DA29B /* TimeBox.swift in Sources */, + 6D5BC66A2C1C1970002DA29B /* HookupCell.swift in Sources */, + 6D5BC66B2C1C1970002DA29B /* BrowserPresenter.swift in Sources */, + 6D5BC66C2C1C1970002DA29B /* JiraTempoPresenter.swift in Sources */, + 6D5BC66D2C1C1970002DA29B /* EditableTimeBox.swift in Sources */, + 6D5BC66E2C1C1970002DA29B /* CalendarDayCellView.swift in Sources */, + 6D5BC66F2C1C1970002DA29B /* CopyReportCell.swift in Sources */, + 6D5BC6702C1C1970002DA29B /* TrackingView.swift in Sources */, + 6D5BC6712C1C1970002DA29B /* SettingsInteractor.swift in Sources */, + 6D5BC6722C1C1970002DA29B /* BrowserCell.swift in Sources */, + 6D5BC6732C1C1970002DA29B /* InputsTableViewDataSource.swift in Sources */, + 6D5BC6742C1C1970002DA29B /* StoreView.swift in Sources */, + 6D5BC6752C1C1970002DA29B /* WizardGitView.swift in Sources */, + 6D5BC6762C1C1970002DA29B /* WizardViewController.swift in Sources */, + 6D5BC6772C1C1970002DA29B /* MainPresenter.swift in Sources */, + 6D5BC6782C1C1970002DA29B /* InputsScrollView.swift in Sources */, + 6D5BC6792C1C1970002DA29B /* CalendarViewController.swift in Sources */, + 6D5BC67A2C1C1970002DA29B /* CellProtocol.swift in Sources */, + 6D5BC67B2C1C1970002DA29B /* ReportCell.swift in Sources */, + 6D5BC67C2C1C1970002DA29B /* HookupPresenter.swift in Sources */, + 6D5BC67D2C1C1970002DA29B /* TaskCell_.swift in Sources */, + 6D5BC67E2C1C1970002DA29B /* ReportsDataSource.swift in Sources */, + 6D5BC67F2C1C1970002DA29B /* CalendarInteractor.swift in Sources */, + 6D5BC6802C1C1970002DA29B /* ProjectsPresenter.swift in Sources */, + 6D5BC6812C1C1970002DA29B /* CloudKitLoginViewController.swift in Sources */, + 6D5BC6822C1C1970002DA29B /* ProjectsViewController.swift in Sources */, + 6D5BC6832C1C1970002DA29B /* NewTaskViewController.swift in Sources */, + 6D5BC6842C1C1970002DA29B /* TaskSuggestionPresenter.swift in Sources */, + 6D5BC6852C1C1970002DA29B /* CalendarPresenter.swift in Sources */, + 6D5BC6862C1C1970002DA29B /* GitPresenter.swift in Sources */, + 6D5BC6872C1C1970002DA29B /* OutputTableViewDataSource.swift in Sources */, + 6D5BC6882C1C1970002DA29B /* TaskSuggestionTests.swift in Sources */, + 6D5BC6892C1C1970002DA29B /* ProjectDetailsViewController.swift in Sources */, + 6D5BC68A2C1C1970002DA29B /* SettingsViewController.swift in Sources */, + 6D5BC68B2C1C1970002DA29B /* JiraTempoCell.swift in Sources */, + 6D5BC68C2C1C1970002DA29B /* ProjectDetailsPresenter.swift in Sources */, + 6D5BC68D2C1C1970002DA29B /* TaskCellPresenter.swift in Sources */, + 6D5BC68E2C1C1970002DA29B /* CloseDayCell.swift in Sources */, + 6D5BC68F2C1C1970002DA29B /* Saveable.swift in Sources */, + 6D5BC6902C1C1970002DA29B /* ShellCell.swift in Sources */, + 6D5BC6912C1C1970002DA29B /* CalendarAppPresenter.swift in Sources */, + 6D5BC6922C1C1970002DA29B /* JirassicCell.swift in Sources */, + 6D5BC6932C1C1970002DA29B /* TasksDataSource.swift in Sources */, + 6D5BC6942C1C1970002DA29B /* WizardJiraView.swift in Sources */, + 6D5BC6952C1C1970002DA29B /* LoginViewController.swift in Sources */, + 6D5BC6962C1C1970002DA29B /* MonthReportsHeaderView.swift in Sources */, + 6D5BC6972C1C1970002DA29B /* CalendarCell.swift in Sources */, + 6D5BC6982C1C1970002DA29B /* LoginPresenter.swift in Sources */, + 6D5BC6992C1C1970002DA29B /* TimeBoxViewController.swift in Sources */, + 6D5BC69A2C1C1970002DA29B /* TasksView.swift in Sources */, + 6D5BC69B2C1C1970002DA29B /* GitCell.swift in Sources */, + 6D5BC69C2C1C1970002DA29B /* MainViewController.swift in Sources */, + 6D5BC69D2C1C1970002DA29B /* TasksScrollView.swift in Sources */, + 6D5BC69E2C1C1970002DA29B /* CocoaHookupCell.swift in Sources */, + 6D5BC69F2C1C1970002DA29B /* WizardAppleScriptView.swift in Sources */, + 6D5BC6A02C1C1970002DA29B /* ProjectsListViewController.swift in Sources */, + 6D5BC6A12C1C1970002DA29B /* TasksViewController.swift in Sources */, + 6D5BC6A22C1C1970002DA29B /* WelcomeViewController.swift in Sources */, + 6D5BC6A32C1C1970002DA29B /* WorklogsPresenter.swift in Sources */, + 6D5BC6A42C1C1970002DA29B /* WorklogsViewController.swift in Sources */, 4065D3D61DD3B44200B73201 /* MenuBarController.swift in Sources */, - 28A5F27E1E5789FC002BE564 /* TasksDataSource.swift in Sources */, 4065D3B61DD3B44200B73201 /* TaskTypeSelection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2429,12 +2601,10 @@ 56D069E1216CABCB000D051D /* CreateMonthReportTests.swift in Sources */, 287558451EFE5969009A2503 /* ReadDaysInteractorTests.swift in Sources */, 4065D4241DD4562900B73201 /* CreateReportTests.swift in Sources */, - 2845B16E206AE46F006EFB3B /* TaskCellTests.swift in Sources */, 4073184F1DE9B1B40046F409 /* ComputerWakeUpInteractorTests.swift in Sources */, 28A283AE203C069F00DDCB63 /* GitBranchParserTests.swift in Sources */, 4065D4271DD4562900B73201 /* TaskTypeEstimatorTests.swift in Sources */, 28CBB59A20474755006F9D3A /* ParseGitBranchTests.swift in Sources */, - 40FCE4291DFC1CB400D4FD45 /* TaskSuggestionTests.swift in Sources */, 4065D4211DD4562100B73201 /* PredictiveTimeTypingTests.swift in Sources */, 28A283A220385F8C00DDCB63 /* GitCommitsParserTests.swift in Sources */, 4065D4251DD4562900B73201 /* TaskFinderTests.swift in Sources */, @@ -2531,6 +2701,14 @@ path = ..; sourceTree = ""; }; + 6D5BC5452C1C1970002DA29B /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6D5BC5442C1C1970002DA29B /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/Jirassic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Jirassic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 0c67376..f9b0d7c 100644 --- a/Jirassic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/Jirassic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,5 +1,8 @@ - + + PreviewsEnabled + +