Repository: ralcr/Jirassic Branch: master Commit: a5f43dadd57a Files: 343 Total size: 12.3 MB Directory structure: gitextract_a9w4npag/ ├── .gitignore ├── .gitmodules ├── .travis.yml ├── App/ │ ├── Entities/ │ │ ├── Day.swift │ │ ├── GitCommit.swift │ │ ├── GitUser.swift │ │ ├── Report.swift │ │ ├── Settings.swift │ │ ├── Task.swift │ │ ├── User.swift │ │ └── Week.swift │ ├── Extensions/ │ │ ├── Conversions.swift │ │ ├── DateExtension.swift │ │ ├── DateExtensionTests.swift │ │ ├── StringArray.swift │ │ ├── StringIdGenerator.swift │ │ ├── TableViewCell.swift │ │ ├── ViewAutolayout.swift │ │ ├── ViewController.swift │ │ ├── ViewControllerStoryboard.swift │ │ └── ViewXib.swift │ ├── Notifications/ │ │ ├── ComputerWakeUpInteractor.swift │ │ └── ComputerWakeUpInteractorTests.swift │ ├── Parsing/ │ │ ├── ParseGitBranch.swift │ │ └── ParseGitBranchTests.swift │ ├── Reports/ │ │ ├── CreateDayReport.swift │ │ ├── CreateMonthReport.swift │ │ ├── CreateMonthReportTests.swift │ │ ├── CreateReport.swift │ │ └── CreateReportTests.swift │ ├── Statistics/ │ │ └── StatisticsInteractor.swift │ ├── Tasks/ │ │ ├── CloseDay.swift │ │ ├── MergeTasksInteractor.swift │ │ ├── MergeTasksInteractorTests.swift │ │ ├── ReadDaysInteractor.swift │ │ ├── ReadDaysInteractorTests.swift │ │ ├── ReadTasksInteractor.swift │ │ ├── RemoveDuplicate.swift │ │ ├── TaskFinder.swift │ │ ├── TaskFinderTests.swift │ │ ├── TaskInteractor.swift │ │ ├── TaskInteractorTests.swift │ │ ├── TaskTypeEstimator.swift │ │ ├── TaskTypeEstimatorTests.swift │ │ └── TaskTypeSelection.swift │ ├── Time/ │ │ ├── PredictiveTimeTyping.swift │ │ ├── PredictiveTimeTypingTests.swift │ │ └── TimeInteractor.swift │ └── User/ │ ├── RegisterUserInteractor.swift │ ├── UserInteractor.swift │ └── UserInteractorTests.swift ├── Delivery/ │ ├── iOS/ │ │ ├── AppDelegate.swift │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.xib │ │ │ └── Main.storyboard │ │ ├── DaysViewController.swift │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── SplashIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── Jirassic Scrum.entitlements │ │ ├── LoginViewController.swift │ │ ├── NonTaskCell.swift │ │ ├── TaskCell.swift │ │ └── TasksViewController.swift │ ├── macOS/ │ │ ├── Animations/ │ │ │ ├── Animatable.swift │ │ │ └── FlipAnimation.swift │ │ ├── App/ │ │ │ ├── AppDelegate.swift │ │ │ ├── AppLauncher.swift │ │ │ ├── AppTheme.swift │ │ │ ├── AppViewController.swift │ │ │ ├── AppWireframe.swift │ │ │ ├── LocalPreferences.swift │ │ │ └── Versioning.swift │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── Bridging-Header.h │ │ ├── BrowserSupport.scpt │ │ ├── BuildScript.sh │ │ ├── Components/ │ │ │ ├── Components.storyboard │ │ │ ├── EditableTimeBox.swift │ │ │ ├── GitUsersViewController.swift │ │ │ ├── NewTaskViewController.swift │ │ │ ├── TimeBox.swift │ │ │ └── TimeBoxViewController.swift │ │ ├── External/ │ │ │ ├── AppleScript.swift │ │ │ ├── AppleScriptProtocol.swift │ │ │ ├── ExtensionsInstallerInteractor.swift │ │ │ ├── ExtensionsInteractor.swift │ │ │ └── SandboxedAppleScript.swift │ │ ├── IAP/ │ │ │ ├── IAPHelper.swift │ │ │ └── Store.swift │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── AppStoreIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── ButClose.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ButDisabled.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ButMinimize.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── CalendarIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── GitIcon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── MenuBarIcon-Normal.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── MenuBarIcon-Selected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Plus.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── WarningButton.imageset/ │ │ │ │ └── Contents.json │ │ │ └── WarningIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── Jirassic.entitlements │ │ ├── Menu/ │ │ │ ├── MenuBarController.swift │ │ │ └── MenuBarIconView.swift │ │ ├── Modules/ │ │ │ ├── CalendarEvents/ │ │ │ │ └── ModuleCalendar.swift │ │ │ ├── GitLogs/ │ │ │ │ ├── GitBranchParser.swift │ │ │ │ ├── GitBranchParserTests.swift │ │ │ │ ├── GitCommitsParser.swift │ │ │ │ ├── GitCommitsParserTests.swift │ │ │ │ ├── GitUserParser.swift │ │ │ │ └── ModuleGitLogs.swift │ │ │ ├── Hookup/ │ │ │ │ └── ModuleHookup.swift │ │ │ └── JiraTempo/ │ │ │ └── ModuleJiraTempo.swift │ │ ├── Notifications/ │ │ │ ├── BrowserNotification.swift │ │ │ ├── InternalNotifications.swift │ │ │ ├── SleepNotifications.swift │ │ │ └── UserNotifications.swift │ │ ├── Screens/ │ │ │ ├── Account/ │ │ │ │ ├── AccountViewController.swift │ │ │ │ ├── CloudKitLoginViewController.swift │ │ │ │ ├── Login.storyboard │ │ │ │ ├── LoginPresenter.swift │ │ │ │ └── LoginViewController.swift │ │ │ ├── Calendar/ │ │ │ │ └── CalendarScrollView.swift │ │ │ ├── Onboarding/ │ │ │ │ ├── Welcome.storyboard │ │ │ │ ├── WelcomeViewController.swift │ │ │ │ ├── WizardAppleScriptView.swift │ │ │ │ ├── WizardAppleScriptView.xib │ │ │ │ ├── WizardCalendarView.swift │ │ │ │ ├── WizardCalendarView.xib │ │ │ │ ├── WizardGitView.swift │ │ │ │ ├── WizardGitView.xib │ │ │ │ ├── WizardJiraView.swift │ │ │ │ ├── WizardJiraView.xib │ │ │ │ └── WizardViewController.swift │ │ │ ├── Placeholder/ │ │ │ │ ├── Placeholder.storyboard │ │ │ │ └── PlaceholderViewController.swift │ │ │ ├── Settings/ │ │ │ │ ├── Input/ │ │ │ │ │ ├── Browser/ │ │ │ │ │ │ ├── BrowserCell.swift │ │ │ │ │ │ ├── BrowserCell.xib │ │ │ │ │ │ └── BrowserPresenter.swift │ │ │ │ │ ├── Calendar/ │ │ │ │ │ │ ├── CalendarCell.swift │ │ │ │ │ │ ├── CalendarCell.xib │ │ │ │ │ │ └── CalendarPresenter.swift │ │ │ │ │ ├── Git/ │ │ │ │ │ │ ├── GitCell.swift │ │ │ │ │ │ ├── GitCell.xib │ │ │ │ │ │ └── GitPresenter.swift │ │ │ │ │ ├── InputsScrollView.swift │ │ │ │ │ ├── InputsScrollView.xib │ │ │ │ │ ├── InputsTableViewDataSource.swift │ │ │ │ │ ├── JirassicCmd/ │ │ │ │ │ │ ├── JirassicCell.swift │ │ │ │ │ │ └── JirassicCell.xib │ │ │ │ │ ├── Jit/ │ │ │ │ │ │ ├── JitCell.swift │ │ │ │ │ │ └── JitCell.xib │ │ │ │ │ └── Shell/ │ │ │ │ │ ├── ShellCell.swift │ │ │ │ │ └── ShellCell.xib │ │ │ │ ├── Output/ │ │ │ │ │ ├── CocoaHookup/ │ │ │ │ │ │ ├── CocoaHookupCell.swift │ │ │ │ │ │ ├── CocoaHookupCell.xib │ │ │ │ │ │ └── CocoaHookupPresenter.swift │ │ │ │ │ ├── Hookup/ │ │ │ │ │ │ ├── HookupCell.swift │ │ │ │ │ │ ├── HookupCell.xib │ │ │ │ │ │ └── HookupPresenter.swift │ │ │ │ │ ├── JiraTempo/ │ │ │ │ │ │ ├── JiraTempoCell.swift │ │ │ │ │ │ ├── JiraTempoCell.xib │ │ │ │ │ │ └── JiraTempoPresenter.swift │ │ │ │ │ ├── OutputTableViewDataSource.swift │ │ │ │ │ ├── OutputsScrollView.swift │ │ │ │ │ └── OutputsScrollView.xib │ │ │ │ ├── Saveable.swift │ │ │ │ ├── Settings.storyboard │ │ │ │ ├── SettingsInteractor.swift │ │ │ │ ├── SettingsPresenter.swift │ │ │ │ ├── SettingsViewController.swift │ │ │ │ ├── Store/ │ │ │ │ │ ├── StoreView.swift │ │ │ │ │ └── StoreView.xib │ │ │ │ └── Tracking/ │ │ │ │ ├── TrackingView.swift │ │ │ │ └── TrackingView.xib │ │ │ ├── TaskSuggestion/ │ │ │ │ ├── TaskSuggestionPresenter.swift │ │ │ │ ├── TaskSuggestionTests.swift │ │ │ │ └── TaskSuggestionViewController.swift │ │ │ ├── Tasks/ │ │ │ │ ├── AllTasks/ │ │ │ │ │ ├── CellProtocol.swift │ │ │ │ │ ├── HeaderView/ │ │ │ │ │ │ ├── TasksHeaderView.swift │ │ │ │ │ │ └── TasksHeaderView.xib │ │ │ │ │ ├── NonTaskCell/ │ │ │ │ │ │ ├── NonTaskCell.swift │ │ │ │ │ │ └── NonTaskCell.xib │ │ │ │ │ ├── TaskCell/ │ │ │ │ │ │ ├── TaskCell.swift │ │ │ │ │ │ ├── TaskCell.xib │ │ │ │ │ │ ├── TaskCellPresenter.swift │ │ │ │ │ │ └── TaskCellTests.swift │ │ │ │ │ └── TasksDataSource.swift │ │ │ │ ├── DataSource.swift │ │ │ │ ├── MonthReports/ │ │ │ │ │ ├── MonthReportsHeaderView.swift │ │ │ │ │ └── MonthReportsHeaderView.xib │ │ │ │ ├── Reports/ │ │ │ │ │ ├── HeaderView/ │ │ │ │ │ │ ├── ReportsHeaderView.swift │ │ │ │ │ │ └── ReportsHeaderView.xib │ │ │ │ │ ├── ReportCell/ │ │ │ │ │ │ ├── ReportCell.swift │ │ │ │ │ │ ├── ReportCell.xib │ │ │ │ │ │ └── ReportCellPresenter.swift │ │ │ │ │ └── ReportsDataSource.swift │ │ │ │ ├── Tasks.storyboard │ │ │ │ ├── TasksInteractor.swift │ │ │ │ ├── TasksPresenter.swift │ │ │ │ ├── TasksScrollView.swift │ │ │ │ ├── TasksView.swift │ │ │ │ └── TasksViewController.swift │ │ │ └── Worklogs/ │ │ │ ├── Worklogs.storyboard │ │ │ ├── WorklogsPresenter.swift │ │ │ └── WorklogsViewController.swift │ │ ├── ShellSupport.scpt │ │ ├── jirassic.sdef │ │ └── jit │ ├── macOS-cmd/ │ │ └── main.swift │ └── macOS-launcher/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj/ │ │ └── MainMenu.xib │ ├── Info.plist │ ├── JirassicLauncher.entitlements │ └── main.m ├── External/ │ ├── AppleScriptCommands/ │ │ └── NewTaskCommand.swift │ ├── CloudKit/ │ │ ├── CloudKitRepository+Settings.swift │ │ ├── CloudKitRepository+Tasks.swift │ │ ├── CloudKitRepository+User.swift │ │ ├── CloudKitRepository.swift │ │ └── UserDefaults+token.swift │ ├── CoreData/ │ │ ├── CSettings.swift │ │ ├── CTask.swift │ │ ├── CUser.swift │ │ ├── CoreDataRepository+Settings.swift │ │ ├── CoreDataRepository+Tasks.swift │ │ ├── CoreDataRepository+User.swift │ │ ├── CoreDataRepository.swift │ │ └── Jirassic.xcdatamodeld/ │ │ ├── .xccurrentversion │ │ └── Jirassic.xcdatamodel/ │ │ └── contents │ ├── InMemoryStorage/ │ │ └── InMemoryCoreDataRepository.swift │ ├── Jira/ │ │ ├── JProject.swift │ │ ├── JProjectIssue.swift │ │ ├── JReport.swift │ │ ├── JWorkAttribute.swift │ │ ├── JiraRepository+Projects.swift │ │ ├── JiraRepository+Reports.swift │ │ └── JiraRepository.swift │ ├── Keychain/ │ │ └── Keychain.swift │ ├── RCSync.swift │ ├── Realm/ │ │ ├── RSettings.swift │ │ ├── RTask.swift │ │ ├── RUser.swift │ │ ├── Realm.framework/ │ │ │ └── Versions/ │ │ │ └── A/ │ │ │ ├── Headers/ │ │ │ │ ├── NSError+RLMSync.h │ │ │ │ ├── RLMArray.h │ │ │ │ ├── RLMCollection.h │ │ │ │ ├── RLMConstants.h │ │ │ │ ├── RLMMigration.h │ │ │ │ ├── RLMObject.h │ │ │ │ ├── RLMObjectBase.h │ │ │ │ ├── RLMObjectBase_Dynamic.h │ │ │ │ ├── RLMObjectSchema.h │ │ │ │ ├── RLMPlatform.h │ │ │ │ ├── RLMProperty.h │ │ │ │ ├── RLMRealm.h │ │ │ │ ├── RLMRealmConfiguration+Sync.h │ │ │ │ ├── RLMRealmConfiguration.h │ │ │ │ ├── RLMRealm_Dynamic.h │ │ │ │ ├── RLMResults.h │ │ │ │ ├── RLMSchema.h │ │ │ │ ├── RLMSyncConfiguration.h │ │ │ │ ├── RLMSyncCredentials.h │ │ │ │ ├── RLMSyncManager.h │ │ │ │ ├── RLMSyncPermission.h │ │ │ │ ├── RLMSyncPermissionChange.h │ │ │ │ ├── RLMSyncPermissionOffer.h │ │ │ │ ├── RLMSyncPermissionOfferResponse.h │ │ │ │ ├── RLMSyncSession.h │ │ │ │ ├── RLMSyncUser.h │ │ │ │ ├── RLMSyncUtil.h │ │ │ │ ├── RLMThreadSafeReference.h │ │ │ │ └── Realm.h │ │ │ ├── Modules/ │ │ │ │ └── module.modulemap │ │ │ ├── PrivateHeaders/ │ │ │ │ ├── RLMAccessor.h │ │ │ │ ├── RLMArray_Private.h │ │ │ │ ├── RLMListBase.h │ │ │ │ ├── RLMMigration_Private.h │ │ │ │ ├── RLMObjectSchema_Private.h │ │ │ │ ├── RLMObjectStore.h │ │ │ │ ├── RLMObject_Private.h │ │ │ │ ├── RLMOptionalBase.h │ │ │ │ ├── RLMProperty_Private.h │ │ │ │ ├── RLMRealmConfiguration_Private.h │ │ │ │ ├── RLMRealm_Private.h │ │ │ │ ├── RLMResults_Private.h │ │ │ │ ├── RLMSchema_Private.h │ │ │ │ ├── RLMSyncConfiguration_Private.h │ │ │ │ ├── RLMSyncPermissionChange_Private.h │ │ │ │ ├── RLMSyncPermissionOfferResponse_Private.h │ │ │ │ ├── RLMSyncPermissionOffer_Private.h │ │ │ │ ├── RLMSyncPermission_Private.h │ │ │ │ └── RLMSyncUtil_Private.h │ │ │ ├── Realm │ │ │ └── Resources/ │ │ │ ├── CHANGELOG.md │ │ │ ├── Info.plist │ │ │ ├── LICENSE │ │ │ └── strip-frameworks.sh │ │ ├── RealmRepository.swift │ │ └── RealmSwift.framework/ │ │ └── Versions/ │ │ └── A/ │ │ ├── Headers/ │ │ │ └── RealmSwift-Swift.h │ │ ├── Modules/ │ │ │ ├── RealmSwift.swiftmodule/ │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftmodule │ │ │ └── module.modulemap │ │ ├── RealmSwift │ │ └── Resources/ │ │ └── Info.plist │ ├── Repository.swift │ ├── RepositoryInteractor.swift │ └── sqlite/ │ ├── SQLTable.swift │ ├── SQLiteDB.swift │ ├── SQLiteSchema.swift │ ├── SSettings.swift │ ├── STask.swift │ ├── SUser.swift │ ├── SqliteRepository+Settings.swift │ ├── SqliteRepository+Tasks.swift │ ├── SqliteRepository+User.swift │ ├── SqliteRepository.swift │ └── UserDefaults+uploadToken.swift ├── Jirassic macOS.entitlements ├── Jirassic.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm/ │ │ └── Package.resolved │ └── xcshareddata/ │ └── xcschemes/ │ ├── Jirassic AppStore.xcscheme │ ├── Jirassic iOS.xcscheme │ ├── Jirassic macOS.xcscheme │ ├── JirassicLauncher.xcscheme │ └── jirassic-cmd.xcscheme ├── JirassicTests/ │ └── Info.plist ├── MyPlayground.playground/ │ ├── Contents.swift │ └── contents.xcplayground ├── README.md ├── TestGit.scpt ├── TestScriptability.scpt ├── build/ │ └── jirassic ├── licence.txt └── scripts/ ├── add-key.sh ├── certs/ │ └── MacDeveloper.p12 └── profile/ ├── jirassic_dev.provisionprofile └── jirassic_launcher_dev.provisionprofile ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.xcuserstate .DS_Store *.xcworkspacedata *.xccheckout xcuserdata web fastlane travis_signing ================================================ FILE: .gitmodules ================================================ ================================================ FILE: .travis.yml ================================================ language: swift os: osx osx_image: xcode10.1 branches: only: - master cache: bundler xcode_project: Jirassic.xcodeproj # path to your xcodeproj folder xcode_scheme: Jirassic macOS xcode_destination: platform=macOS before_script: - chmod a+x ./scripts/add-key.sh - sh ./scripts/add-key.sh script: - xcodebuild clean test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" -project Jirassic.xcodeproj -scheme "Jirassic macOS" # - xcodebuild clean test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" -project Jirassic.xcodeproj -scheme "Jirassic iOS" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone XS,OS=12.1' # - xcodebuild clean test -project Jirassic.xcodeproj -scheme "Jirassic macOS" ================================================ FILE: App/Entities/Day.swift ================================================ // // Day.swift // Jirassic // // Created by Baluta Cristian on 30/12/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation // Represents a day in the calendar. class Day: CustomStringConvertible { /// The date of the first task in the day let dateStart: Date /// The date of the last task in the day if it was finished let dateEnd: Date? init (dateStart: Date, dateEnd: Date?) { self.dateStart = dateStart self.dateEnd = dateEnd } var description: String { return "" } } ================================================ FILE: App/Entities/GitCommit.swift ================================================ // // CommitResult.swift // Jirassic // // Created by Cristian Baluta on 17/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation // Git commit read from the command line struct GitCommit { var commitNumber: String var date: Date var authorEmail: String var message: String var branchName: String? } ================================================ FILE: App/Entities/GitUser.swift ================================================ // // GitUser.swift // Jirassic // // Created by Cristian Baluta on 14/12/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation // Users that made commits to the project struct GitUser { var name: String var email: String } ================================================ FILE: App/Entities/Report.swift ================================================ // // Report.swift // Jirassic // // Created by Cristian Baluta on 06/11/2016. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation struct Report { var taskNumber: String var title: String var notes: [String] var duration: TimeInterval } ================================================ FILE: App/Entities/Settings.swift ================================================ // // Settings.swift // Jirassic // // Created by Cristian Baluta on 17/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation enum TrackingMode: Int { case auto case notif } struct Settings { var enableBackup: Bool var settingsTracking: SettingsTracking var settingsBrowser: SettingsBrowser } struct SettingsTracking { var autotrack: Bool var autotrackingMode: TrackingMode var trackLunch: Bool var trackScrum: Bool var trackMeetings: Bool var trackStartOfDay: Bool var startOfDayTime: Date var endOfDayTime: Date var lunchTime: Date var scrumTime: Date var minSleepDuration: Int } struct SettingsBrowser { var trackCodeReviews: Bool var trackWastedTime: Bool var minCodeRevDuration: Int var codeRevLink: String var minWasteDuration: Int var wasteLinks: [String] } ================================================ FILE: App/Entities/Task.swift ================================================ // // Task.swift // Jirassic // // Created by Baluta Cristian on 21/11/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation // Never change the indexes because they are already stored in the database enum TaskType: Int { case issue = 0 case startDay = 1 case scrum = 2 case lunch = 3 case meeting = 4 case gitCommit = 5 case waste = 6 case learning = 7 case coderev = 8 case endDay = 9 case calendar = 10 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 "" } } // Used to group reports 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 } } } // Object representing a task struct Task { /// Is the date when this object was last modified by the server (iCloud) /// When created locally this field does not have a value. var lastModifiedDate: Date? /// Start date of the task, needed by tasks like meetings which have a known start and end var startDate: Date? var endDate: Date var notes: String? /// Task number is the issue number from Jira (eg. AA-1234) /// For other type of tasks should be nil var taskNumber: String? /// Task title is usually the title that follows the task number in Jira var taskTitle: String? var taskType: TaskType /// 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? } extension Task { init () { self.init (endDate: Date(), type: .issue) } init (endDate: Date, type: TaskType) { self.endDate = endDate self.taskType = type self.objectId = String.generateId() } init (startDate: Date?, endDate: Date, type: TaskType) { self.startDate = startDate self.endDate = endDate self.taskType = type self.objectId = String.generateId() } } /// Object used to pass task data to and from the cell, for displaying and editing typealias TaskCreationData = ( dateStart: Date?, dateEnd: Date, taskNumber: String?, notes: String?, taskType: TaskType ) ================================================ FILE: App/Entities/User.swift ================================================ // // User.swift // Jirassic // // Created by Baluta Cristian on 21/11/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation struct User { var email: String? var userId: String? } typealias UserCredentials = (email: String, password: String) ================================================ FILE: App/Entities/Week.swift ================================================ // // Week.swift // Jirassic // // Created by Baluta Cristian on 30/12/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation // Represents a week in the calendar class Week { let date: Date var days = [Day]() init (date: Date) { self.date = date } } ================================================ FILE: App/Extensions/Conversions.swift ================================================ // // Conversions.swift // Jirassic // // Created by Cristian Baluta on 06/11/2016. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation extension Double { var minToSec: Double { return self * 60 } var monthsToSec: Double { return self * 30 * 24.hoursToSec } var hoursToSec: Double { return self * 3600 } var secToHours: Double { return self / 3600 } /// Transforms a number of seconds into 'hours minutes'. The hours can be over 24 var secToHoursAndMin: String { let h = floor(self / 3600) let secondsRemaining = self - h * 3600 let m = secondsRemaining / 60 let hours = Int(h) let minutes = Int(m) return "\(hours)h \(minutes)m" } /// One hour equals to 1 percent var secToPercent: Double { return Double(Darwin.round((self / 3600) * 100) / 100) } } ================================================ FILE: App/Extensions/DateExtension.swift ================================================ // // DateExtension.swift // Spoto // // Created by Baluta Cristian on 05/12/14. // Copyright (c) 2014 Baluta Cristian. All rights reserved. // import Foundation let ymdUnitFlags: Set = [.year, .month, .day] let ymdhmsUnitFlags: Set = [.year, .month, .weekday, .day, .hour, .minute, .second] let gregorian = Calendar(identifier: Calendar.Identifier.gregorian) extension Date { init (hour: Int, minute: Int, second: Int = 0) { self.init(date: Date(), hour: hour, minute: minute, second: second) } init (date: Date, hour: Int, minute: Int, second: Int = 0) { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: date) comps.hour = hour comps.minute = minute comps.second = 0 self.init(timeInterval: 0, since: gregorian.date(from: comps)!) } init (year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int = 0) { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: Date()) comps.year = year comps.month = month comps.day = day comps.hour = hour comps.minute = minute comps.second = second self.init(timeInterval: 0, since: gregorian.date(from: comps)!) } init (YYYYMMddString: String) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "YYYY.MM.dd" let date = dateFormatter.date(from: YYYYMMddString) self.init(timeInterval: 0, since: date!) } } extension Date { func HHmmddMM() -> String { let f = DateFormatter() f.dateFormat = "HH:mm • dd MMM" return f.string(from: self) } func MMMMdd() -> String { let f = DateFormatter() f.dateFormat = "MMMM dd" return f.string(from: self) } func HHmm() -> String { let f = DateFormatter() f.dateFormat = "HH:mm" return f.string(from: self) } func HHmmGMT() -> String { let f = DateFormatter() f.timeZone = TimeZone(abbreviation: "GMT") f.dateFormat = "HH:mm" return f.string(from: self) } func EEMMMMdd() -> String { let f = DateFormatter() f.dateFormat = "EE, MMMM dd" return f.string(from: self) } func ddEEE() -> String { let f = DateFormatter() f.dateFormat = "dd • EEE" return f.string(from: self) } // eg. Thursday, February 01 func EEEEMMMMdd() -> String { let f = DateFormatter() f.dateFormat = "EEEE, MMMM dd" return f.string(from: self) } // eg. Thursday, Feb 01 func EEEEMMMdd() -> String { let f = DateFormatter() f.dateFormat = "EEEE, MMM dd" return f.string(from: self) } func YYYYMMddHHmmss() -> String { let f = DateFormatter() f.dateFormat = "yyyy-MM-dd HH:mm:ss" return f.string(from: self) } func YYYYMMddHHmmssGMT() -> String { let f = DateFormatter() f.timeZone = TimeZone(abbreviation: "GMT") f.dateFormat = "yyyy-MM-dd HH:mm:ss" return f.string(from: self) } #warning("if the date is right at the begining of the day the conversion returns the previous day because of the timezone") func YYYYMMddT00() -> String { let f = DateFormatter() // f.timeZone = TimeZone(abbreviation: "GMT") f.dateFormat = "YYYY-MM-dd" let day = f.string(from: self) // f.dateFormat = "HH:mm" let hour = "00:00"//f.string(from: self) return day + "T" + hour + ":00.000+0000" } func YY() -> String { let f = DateFormatter() f.dateFormat = "YY" return f.string(from: self) } func YYYY() -> String { let f = DateFormatter() f.dateFormat = "YYYY" return f.string(from: self) } } extension Date { func weekInterval() -> String { let bounds = self.weekBounds() let f = DateFormatter() f.dateFormat = "MMM dd" if bounds.0.isSameMonthAs(bounds.1) { return "\(f.string(from: bounds.0)) - \(bounds.1.day()) '\(bounds.0.YY())" } return "\(f.string(from: bounds.0)) - \(f.string(from: bounds.1)) '\(bounds.0.YY())" } } extension Date { @inline(__always) func isSameMonthAs (_ month: Date) -> Bool { return self.year() == month.year() && self.month() == month.month() } @inline(__always) func isSameWeekAs (_ month: Date) -> Bool { return self.year() == month.year() && self.week() == month.week() } @inline(__always) func isSameDayAs (_ date: Date) -> Bool { return NSCalendar.current.isDate(self, inSameDayAs: date) } @inline(__always) func isAlmostSameHourAs (_ date: Date, devianceSeconds: Double = 600.0) -> Bool { let timestampDiff = date.timeIntervalSince(self) return abs(timestampDiff) <= devianceSeconds } @inline(__always) func isToday() -> Bool { return isSameDayAs( Date() ) } func isWeekend() -> Bool { return gregorian.isDateInWeekend(self) } func daysInMonth() -> Int { let daysRange = gregorian.range(of: Calendar.Component.day, in: Calendar.Component.month, for: self) return daysRange!.count as Int } func components() -> (hour: Int, minute: Int) { let comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) return (hour: comps.hour!, minute: comps.minute!) } @inline(__always) func year() -> Int { let comps = gregorian.dateComponents(ymdUnitFlags, from: self) return comps.year! } @inline(__always) func month() -> Int { let comps = gregorian.dateComponents(ymdUnitFlags, from: self) return comps.month! } @inline(__always) func day() -> Int { let comps = gregorian.dateComponents(ymdUnitFlags, from: self) return comps.day! } @inline(__always) func week() -> Int { let comps = gregorian.dateComponents([Calendar.Component.weekOfYear], from: self) return comps.weekOfYear! } func round (minutesPrecision precision: Int = 6) -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) let hm = minutesToHours(minutes: comps.minute!, resultingMinutesPrecision: precision) comps.hour = comps.hour! + hm.hour comps.minute = hm.min comps.second = 0 return gregorian.date(from: comps)! } func dateByUpdating (hour: Int, minute: Int, second: Int = 0) -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.hour = hour comps.minute = minute comps.second = second return gregorian.date(from: comps)! } func dateByKeepingTime() -> Date { let comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) return Date().dateByUpdating(hour: comps.hour!, minute: comps.minute!) } static func parseHHmm (_ hhmm: String) -> (hour: Int, min: Int) { let hm = hhmm.components(separatedBy: ":") let hh = Int(hm[0]) ?? 0 let mm = Int(hm[1]) ?? 0 return (hour: hh, min: mm) } } extension Date { static func getMonthsBetween (startDate: Date, endDate: Date) -> Array { var dates: [Date] = [Date]() var monthDifference = DateComponents() var monthOffset: Int = 0 var nextDate = startDate while nextDate.compare(endDate) == ComparisonResult.orderedAscending { monthOffset += 1 monthDifference.month = monthOffset nextDate = gregorian.date(byAdding: monthDifference, to: startDate)! dates.append(nextDate) } return dates; } func startOfMonth() -> Date { return gregorian.date(from: gregorian.dateComponents([.year, .month], from: gregorian.startOfDay(for: self)))! } func endOfMonth() -> Date { return gregorian.date(byAdding: DateComponents(month: 1, day: -1, hour: 23, minute: 59, second: 59), to: self.startOfMonth())! } } extension Date { func startOfWeek() -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.day = comps.day! - (comps.weekday! - 1) + 1// 1 because weekday starts with 1, and 1 because weekday starts sunday comps.hour = 0 comps.minute = 0 comps.second = 0 comps.weekday = 1 return gregorian.date(from: comps)! } func endOfWeek() -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.day = comps.day! + (7 - comps.weekday!) + 1 comps.hour = 23 comps.minute = 59 comps.second = 59 comps.weekday = 7 return gregorian.date(from: comps)! } func weekBounds() -> (Date, Date) { return (self.startOfWeek(), self.endOfWeek()) } } extension Date { func startOfDay() -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.hour = 0 comps.minute = 0 comps.second = 0 return gregorian.date(from: comps)! } func startOfNextDay() -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.day = comps.day! + 1 comps.hour = 0 comps.minute = 0 comps.second = 0 return gregorian.date(from: comps)! } func endOfDay() -> Date { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: self) comps.hour = 23 comps.minute = 59 comps.second = 59 return gregorian.date(from: comps)! } func dayBounds() -> (Date, Date) { return (self.startOfDay(), self.endOfDay()) } } extension Date { private func minutesToHours (minutes: Int, resultingMinutesPrecision precision: Int) -> (hour: Int, min: Int) { let rest = minutes % precision let newMin = rest == 0 ? minutes : (minutes + precision - rest) return (newMin >= 60 ? 1 : 0, newMin >= 60 ? 0 : newMin) } } ================================================ FILE: App/Extensions/DateExtensionTests.swift ================================================ // // DateTests.swift // Jirassic // // Created by Baluta Cristian on 01/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class DateTests: XCTestCase { func testDateByKeepingTime() { let d1 = Date() let d2 = Date(year: 2016, month: 5, day: 5, hour: 11, minute: 30) let d3 = d2.dateByKeepingTime() let components1 = gregorian.dateComponents(ymdhmsUnitFlags, from: d1) let components2 = gregorian.dateComponents(ymdhmsUnitFlags, from: d2) let components3 = gregorian.dateComponents(ymdhmsUnitFlags, from: d3) XCTAssertTrue(components1.year == components3.year, "Should keep the ymd from current date d1") XCTAssertTrue(components1.month == components3.month) XCTAssertTrue(components1.day == components3.day) XCTAssertTrue(components2.hour == components3.hour, "Should keep the hm from custom date d2") XCTAssertTrue(components2.minute == components3.minute) } func testFirstDayThisMonth() { let sndOfMay2015 = Date(timeIntervalSince1970: 1430589737) let fstOfMay2015 = sndOfMay2015.startOfMonth() let components = gregorian.dateComponents(ymdhmsUnitFlags, from: fstOfMay2015) XCTAssertTrue(components.year == 2015, "Test failed, check firstDateThisMonth method") XCTAssertTrue(components.month == 5, "Test failed, check firstDateThisMonth method") XCTAssertTrue(components.day == 1, "Test failed, check firstDateThisMonth method") XCTAssertTrue(components.hour == 0, "Test failed, check firstDateThisMonth method") XCTAssertTrue(components.minute == 0, "Test failed, check firstDateThisMonth method") XCTAssertTrue(components.second == 0, "Test failed, check firstDateThisMonth method") } func testRoundDateUpToNearestQuarter() { var date = Date(hour: 13, minute: 8) XCTAssertTrue(date.round().compare(Date(hour: 13, minute: 12)) == .orderedSame, "") date = Date(hour: 13, minute: 17) XCTAssertTrue(date.round().compare(Date(hour: 13, minute: 18)) == .orderedSame, "") date = Date(hour: 13, minute: 28) XCTAssertTrue(date.round().compare(Date(hour: 13, minute: 30)) == .orderedSame, "") date = Date(hour: 13, minute: 31) XCTAssertTrue(date.round().compare(Date(hour: 13, minute: 36)) == .orderedSame, "") date = Date(hour: 13, minute: 44) XCTAssertTrue(date.round().compare(Date(hour: 13, minute: 48)) == .orderedSame, "") date = Date(hour: 13, minute: 55) XCTAssertTrue(date.round().compare(Date(hour: 14, minute: 0)) == .orderedSame, "") } func testWeek() { let date = Date(year: 2016, month: 1, day: 9, hour: 10, minute: 0) let weekBounds = date.weekBounds() XCTAssertTrue(weekBounds.0.compare(Date(year: 2016, month: 1, day: 4, hour: 0, minute: 0)) == .orderedSame, "") XCTAssertTrue(weekBounds.1.compare(Date(year: 2016, month: 1, day: 10, hour: 23, minute: 59, second: 59)) == .orderedSame, "") } func testIsSameDay() { let date1 = Date(year: 2016, month: 1, day: 9, hour: 23, minute: 50) let date2 = Date(year: 2016, month: 1, day: 10, hour: 0, minute: 30) XCTAssertFalse(date1.isSameDayAs(date2)) } func testIsAlmostSameHour() { var date1 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 00) var date2 = Date(year: 2016, month: 1, day: 9, hour: 11, minute: 50) XCTAssert(date1.isAlmostSameHourAs(date2)) date1 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 00) date2 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 10) XCTAssert(date1.isAlmostSameHourAs(date2)) date1 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 00) date2 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 11) XCTAssertFalse(date1.isAlmostSameHourAs(date2)) date1 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 00) date2 = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 1) XCTAssert(date1.isAlmostSameHourAs(date2, devianceSeconds: 60.0)) } func testRoundTo6() { let date = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 14) let roundedDate = date.round() let components = gregorian.dateComponents(ymdhmsUnitFlags, from: roundedDate) XCTAssertTrue(components.hour == 12) XCTAssertTrue(components.minute == 18) } func testRoundTo30() { let date = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 8) let roundedDate = date.round(minutesPrecision: 30) let components = gregorian.dateComponents(ymdhmsUnitFlags, from: roundedDate) XCTAssertTrue(components.hour == 12) XCTAssertTrue(components.minute == 30) } func testRoundToNextHour() { let date = Date(year: 2016, month: 1, day: 9, hour: 12, minute: 48) let roundedDate = date.round(minutesPrecision: 30) let components = gregorian.dateComponents(ymdhmsUnitFlags, from: roundedDate) XCTAssertTrue(components.hour == 13) XCTAssertTrue(components.minute == 0) } } ================================================ FILE: App/Extensions/StringArray.swift ================================================ // // StringArray.swift // Jirassic // // Created by Cristian Baluta on 14/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation extension String { func toArray() -> [String] { return self.components(separatedBy: ",") } } extension Array where Iterator.Element == String { func toString() -> String { return self.joined(separator: ",") } } ================================================ FILE: App/Extensions/StringIdGenerator.swift ================================================ // // StringIdGenerator.swift // Jirassic // // Created by Cristian Baluta on 03/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation extension String { static func generateId (_ length: Int = 20) -> String { let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var randomString = "" for _ in 0.. (in tableView: TableView, type: T.Type) { #if os(iOS) // return UIStoryboard(name: name, bundle: nil).instantiateViewControllerWithIdentifier(self.className) as! T #else let className = String(describing: self) assert(NSNib(nibNamed: className, bundle: Bundle.main) != nil, "err") if let nib = NSNib(nibNamed: className, bundle: Bundle.main) { tableView.register(nib, forIdentifier: NSUserInterfaceItemIdentifier(rawValue: className)) } #endif } class func instantiate (in tableView: TableView) -> Self { return instantiate(in: tableView, type: self) } private class func instantiate (in tableView: TableView, type: T.Type) -> T { let className = String(describing: self) #if os(iOS) // 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") } return cell #endif } } ================================================ FILE: App/Extensions/ViewAutolayout.swift ================================================ // // NSViewAutolayout.swift // Spoto // // Created by Baluta Cristian on 15/07/15. // Copyright (c) 2015 Baluta Cristian. All rights reserved. // #if os(iOS) import UIKit typealias View = UIView #else import Cocoa typealias View = NSView #endif extension View { func removeAutoresizing() { self.translatesAutoresizingMaskIntoConstraints = false } func constrainToSuperview() { self.removeAutoresizing() self.constrainToSuperviewWidth() self.constrainToSuperviewHeight() } func constrainToSuperviewWidth() { self.removeAutoresizing() let viewsDictionary = ["view": self] self.superview!.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|-0-[view]-0-|", options: [], metrics: nil, views: viewsDictionary)) } func constrainToSuperviewHeight (_ top: CGFloat=0.0, bottom: CGFloat=0.0) { self.removeAutoresizing() let viewsDictionary = ["view": self] let metricsDictionary: [String : NSNumber] = ["top": NSNumber(value: Float(top)), "bottom": NSNumber(value: Float(bottom))] self.superview!.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|-top-[view]-bottom-|", options: [], metrics: metricsDictionary, views: viewsDictionary)) } func constrainHorizontally (_ leftView: View, rightView: View, distance: CGFloat) { let viewsDictionary = ["leftView": leftView, "rightView": rightView] let metricsDictionary: [String : NSNumber] = ["distance": NSNumber(value: Float(distance))] self.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:[leftView]-distance-[rightView]", options: [], metrics: metricsDictionary, views: viewsDictionary)) } func constraintVertically (_ topView: View, bottomView: View, distance: CGFloat) { let viewsDictionary = ["topView": topView, "bottomView": bottomView] let metrics: [String : NSNumber] = ["gap": NSNumber(value: Float(distance))] self.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:[topView]-gap-[bottomView]", options: [], metrics: metrics, views: viewsDictionary)) } func constraintToTop (_ view: View, distance: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: view, attribute: topAttribute(), relatedBy: equalRelation(), toItem: self, attribute: topAttribute(), multiplier: 1, constant: distance) self.addConstraint(constraint) return constraint } func constraintToBottom (_ view: View, distance: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: view, attribute: bottomAttribute(), relatedBy: equalRelation(), toItem: self, attribute: bottomAttribute(), multiplier: 1, constant: distance) self.addConstraint(constraint) return constraint } func constraintToLeft (_ view: View, distance: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: view, attribute: leftAttribute(), relatedBy: equalRelation(), toItem: self, attribute: leftAttribute(), multiplier: 1, constant: distance) self.addConstraint(constraint) return constraint } func constraintToRight (_ view: View, distance: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: view, attribute: rightAttribute(), relatedBy: equalRelation(), toItem: self, attribute: rightAttribute(), multiplier: 1, constant: distance) self.addConstraint(constraint) return constraint } func constrainToWidth (_ width: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: self, attribute: widthAttribute(), relatedBy: equalRelation(), toItem: nil, attribute: noAttribute(), multiplier: 1, constant: width) self.superview!.addConstraint(constraint) return constraint } func constrainToHeight (_ height: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: self, attribute: heightAttribute(), relatedBy: equalRelation(), toItem: nil, attribute: noAttribute(), multiplier: 1, constant: height) self.superview!.addConstraint(constraint) return constraint } func centerX (_ offset: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: self, attribute: centerXAttribute(), relatedBy: equalRelation(), toItem: self.superview!, attribute: centerXAttribute(), multiplier: 1, constant: offset) self.superview!.addConstraint(constraint) return constraint } func centerY (_ offset: CGFloat) -> NSLayoutConstraint { let constraint = NSLayoutConstraint(item: self, attribute: centerYAttribute(), relatedBy: equalRelation(), toItem: self.superview!, attribute: centerYAttribute(), multiplier: 1, constant: offset) self.superview!.addConstraint(constraint) return constraint } #if os(iOS) func leftAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.left } func rightAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.right } func topAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.top } func bottomAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.bottom } func widthAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.width } func heightAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.height } func centerXAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.centerX } func centerYAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.centerY } func noAttribute() -> NSLayoutAttribute { return NSLayoutAttribute.notAnAttribute } func equalRelation() -> NSLayoutRelation { return NSLayoutRelation.equal } #else func leftAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.left } func rightAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.right } func topAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.top } func bottomAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.bottom } func widthAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.width } func heightAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.height } func centerXAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.centerX } func centerYAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.centerY } func noAttribute() -> NSLayoutConstraint.Attribute { return NSLayoutConstraint.Attribute.notAnAttribute } func equalRelation() -> NSLayoutConstraint.Relation { return NSLayoutConstraint.Relation.equal } #endif } ================================================ FILE: App/Extensions/ViewController.swift ================================================ // // ViewController.swift // Jirassic // // Created by Cristian Baluta on 06/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Cocoa extension NSViewController { func removeFromSuperview() { self.view.removeFromSuperview() } } ================================================ FILE: App/Extensions/ViewControllerStoryboard.swift ================================================ // // UIViewControllerStoryboard.swift // Jirassic // // Created by Cristian Baluta on 02/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // #if os(iOS) import UIKit typealias ViewController = UIViewController #else import Cocoa typealias ViewController = NSViewController #endif extension ViewController { class func instantiateFromStoryboard (_ name: String) -> Self { return instantiateFromStoryboard(name, type: self) } private class func instantiateFromStoryboard (_ name: String, type: T.Type) -> T { #if os(iOS) return UIStoryboard(name: name, bundle: nil).instantiateViewControllerWithIdentifier(self.className) as! T #else return NSStoryboard(name: name, bundle: nil).instantiateController(withIdentifier: String(describing: self)) as! T #endif } } ================================================ FILE: App/Extensions/ViewXib.swift ================================================ // // ViewXib.swift // Jirassic // // Created by Cristian Baluta on 18/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // #if os(iOS) import UIKit typealias AView = UIView #else import Cocoa typealias AView = NSView #endif extension AView { class func instantiateFromXib() -> Self { return instantiateFromXib(type: self) } private class func instantiateFromXib (type: T.Type) -> T { #if os(iOS) // return UIStoryboard(name: name, bundle: nil).instantiateViewControllerWithIdentifier(self.className) as! T #else var view: T? var views: NSArray? Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, topLevelObjects: &views) if let v = views { for _v in v { if let __v = _v as? T { view = __v } } } return view! #endif } } ================================================ FILE: App/Notifications/ComputerWakeUpInteractor.swift ================================================ // // ComputerWakeUpInteractor.swift // Jirassic // // Created by Baluta Cristian on 27/12/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation class ComputerWakeUpInteractor: RepositoryInteractor { var settings: Settings! let typeEstimator = TaskTypeEstimator() let reader: ReadTasksInteractor! init (repository: Repository, remoteRepository: Repository?, settings: Settings) { self.settings = settings reader = ReadTasksInteractor(repository: repository, remoteRepository: remoteRepository) super.init(repository: repository, remoteRepository: remoteRepository) } func runWith (lastSleepDate: Date?, currentDate: Date) { guard let lastSleepDate = lastSleepDate else { return } if let type = estimationForDate(lastSleepDate, currentDate: currentDate) { if type == .startDay { if settings.settingsTracking.trackStartOfDay { let startDate = settings.settingsTracking.startOfDayTime.dateByKeepingTime() if currentDate > startDate { let task = Task(endDate: currentDate, type: .startDay) save(task: task) } } } else if (type == .scrum && settings.settingsTracking.trackScrum) || (type == .lunch && settings.settingsTracking.trackLunch) { var task = Task(endDate: currentDate, type: type) task.startDate = lastSleepDate save(task: task) } } } internal func estimationForDate (_ date: Date, currentDate: Date) -> TaskType? { let existingTasks = reader.tasksInDay(currentDate) guard existingTasks.count > 0 else { return TaskType.startDay } let estimatedType: TaskType = typeEstimator.taskTypeAroundDate(date, withSettings: settings) switch estimatedType { case .scrum: if !TaskFinder().scrumExists(existingTasks) { return TaskType.scrum } case .lunch: if !TaskFinder().lunchExists(existingTasks) { return TaskType.lunch } case .meeting: if settings.settingsTracking.trackMeetings { return TaskType.meeting } default: return nil } return nil } internal func save (task: Task) { let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: self.remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in guard let task = savedTask else { return } InternalNotifications.notifyAboutNewlyAddedTask(task) }) } } ================================================ FILE: App/Notifications/ComputerWakeUpInteractorTests.swift ================================================ // // ComputerWakeUpInteractorTests.swift // Jirassic // // Created by Cristian Baluta on 26/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class ComputerWakeUpInteractorMock: ComputerWakeUpInteractor { var log_called = false var taskType_received: TaskType? override func save (task: Task) { log_called = true taskType_received = task.taskType } } class ComputerWakeUpInteractorTests: XCTestCase { func testScrumAndLunch() { let repository = InMemoryCoreDataRepository() let settings = Settings(enableBackup: false, settingsTracking: SettingsTracking(autotrack: true, autotrackingMode: TrackingMode.auto, trackLunch: true, trackScrum: true, trackMeetings: true, trackStartOfDay: true, startOfDayTime: Date(hour: 9, minute: 0), endOfDayTime: Date(hour: 17, minute: 0), lunchTime: Date(hour: 12, minute: 30), scrumTime: Date(hour: 10, minute: 30), minSleepDuration: 10), settingsBrowser: SettingsBrowser(trackCodeReviews: true, trackWastedTime: true, minCodeRevDuration: 5, codeRevLink: "", minWasteDuration: 5, wasteLinks: [])) // Insert start of the day otherwise scrum can't be detected let task = Task(endDate: Date(hour: 9, minute: 0), type: .startDay) let saveInteractor = TaskInteractor(repository: repository, remoteRepository: nil) saveInteractor.saveTask(task, allowSyncing: false, completion: { task in }) let interactor = ComputerWakeUpInteractorMock(repository: repository, remoteRepository: nil, settings: settings) interactor.runWith(lastSleepDate: Date(hour: 10, minute: 30), currentDate: Date(hour: 10, minute: 55)) XCTAssert(interactor.log_called) XCTAssert(interactor.taskType_received == .scrum) interactor.log_called = false interactor.runWith(lastSleepDate: Date(hour: 12, minute: 45), currentDate: Date(hour: 13, minute: 30)) XCTAssert(interactor.log_called) XCTAssert(interactor.taskType_received == .lunch) } } ================================================ FILE: App/Parsing/ParseGitBranch.swift ================================================ // // ParseGitBranch.swift // Jirassic // // Created by Cristian Baluta on 27/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class ParseGitBranch { private let taskIdEreg = "(([A-Z])+-([0-9])+)" private let branchFromGitLogEreg = "origin(/([A-Za-z0-9_-])+)" private let branchFromGitMergeEreg = "from ([A-Za-z0-9_-])+ to" var branchName: String init(branchName: String) { self.branchName = branchName if let branch = self.parseGitMerge(branchName) { self.branchName = branch } // Eliminate folders self.branchName = self.branchName.components(separatedBy: "/").last?.components(separatedBy: ",").first ?? self.branchName // else if let branch = self.parseGitLog(branchName) { // self.branchName = branch // } } private func parseGitLog(_ branchName: String) -> String? { guard let regex = try? NSRegularExpression(pattern: self.branchFromGitLogEreg, options: []) else { return nil } let match = regex.firstMatch(in: branchName, options: [], range: NSRange(location: 0, length: branchName.count)) if let _ = match { // TODO: try this with ereg to eliminate the folders before a branch name and ignore what follows after spaces return branchName.components(separatedBy: "/").last?.components(separatedBy: ", ").first // return (branchName as NSString).substring(with: match.range).replacingOccurrences(of: "origin/", with: "") } return nil } private func parseGitMerge(_ branchName: String) -> String? { guard let regex = try? NSRegularExpression(pattern: self.branchFromGitMergeEreg, options: []) else { return nil } let match = regex.firstMatch(in: branchName, options: [], range: NSRange(location: 0, length: branchName.count)) if let match = match { return (branchName as NSString).substring(with: match.range) .replacingOccurrences(of: "from ", with: "") .replacingOccurrences(of: " to", with: "") } return nil } // Extracts from the branch name the taskNumber if exists func taskNumber() -> String? { var taskNumber: String? = nil guard let regex = try? NSRegularExpression(pattern: self.taskIdEreg, options: []) else { return taskNumber } let match = regex.firstMatch(in: branchName, options: [], range: NSRange(location: 0, length: branchName.count)) if let match = match { taskNumber = (branchName as NSString).substring(with: match.range) } return taskNumber } // Extracts from the branch name what is left after taskNumber is extracted, and dashes are removed func taskTitle() -> String { let taskNumber = self.taskNumber() return branchName.replacingOccurrences(of: taskNumber ?? "", with: "") .replacingOccurrences(of: "-", with: " ") .replacingOccurrences(of: "_", with: " ") .trimmingCharacters(in: NSCharacterSet.whitespaces) } } ================================================ FILE: App/Parsing/ParseGitBranchTests.swift ================================================ // // ParseGitBranchTests.swift // JirassicTests // // Created by Cristian Baluta on 28/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class ParseGitBranchTests: XCTestCase { func test() { var parser = ParseGitBranch(branchName: "AA-1234-branch-name") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "branch name") parser = ParseGitBranch(branchName: "AA-1234__branch_name") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "branch name") parser = ParseGitBranch(branchName: "some_branch_name") XCTAssertNil(parser.taskNumber()) XCTAssert(parser.taskTitle() == "some branch name") } func testCommitMessage() { let parser = ParseGitBranch(branchName: "APP-1234 Some commit message") XCTAssert(parser.taskNumber() == "APP-1234") XCTAssert(parser.taskTitle() == "Some commit message") } func testMergeCommitMessage() { let parser = ParseGitBranch(branchName: "Merge pull request #619 in MYAPP/proj from AA-1234_some_branch_name to master;") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "some branch name") // TODO: Not sur ethis case exists // parser = ParseGitBranch(branchName: "Merge pull request #619 in MYAPP/proj from origin/AA-1234_some_branch_name to master;") // XCTAssert(parser.taskNumber() == "AA-1234") // XCTAssert(parser.taskTitle() == "some branch name") } func testBranchesGivenByGitLog() { var parser = ParseGitBranch(branchName: "origin/some_branch_name, some_branch_name") XCTAssertNil(parser.taskNumber()) XCTAssert(parser.taskTitle() == "some branch name") parser = ParseGitBranch(branchName: "origin/AA-1234_some_branch_name") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "some branch name") parser = ParseGitBranch(branchName: "origin/bugfix/AA-1234_some_branch_name") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "some branch name") parser = ParseGitBranch(branchName: "bugfix/AA-1234_some_branch_name") XCTAssert(parser.taskNumber() == "AA-1234") XCTAssert(parser.taskTitle() == "some branch name") } } ================================================ FILE: App/Reports/CreateDayReport.swift ================================================ // // CreateDayReport.swift // Jirassic // // Created by Cristian Baluta on 13.06.2024. // Copyright © 2024 Imagin soft. All rights reserved. // import Foundation class CreateDayReport { private let createReport = CreateReport() func stringReports (_ reports: [Report]) -> String { var str = "" for report in reports { let notes: [String] = report.notes.compactMap { note in guard note.count > 0 else { return nil } return "• \(note)" } let notesJoined = notes.joined(separator: "\n") var taskNumber = report.taskNumber == "coderev" ? "Code reviews and fixes" : report.taskNumber taskNumber = taskNumber == "learning" ? "Learning" : taskNumber taskNumber = taskNumber == "meeting" ? "Meetings" : taskNumber var title = report.title .replacingOccurrences(of: "_", with: " ") .trimmingCharacters(in: .whitespacesAndNewlines) if report.taskNumber == "learning" || report.taskNumber == "meeting" { title = "" } var note = "(\(report.duration.secToHours)) \(taskNumber) \(title)" if report.notes.count > 0 { for n in notes { note += "\n \(n)" } } str += note + "\n" } return str } } ================================================ FILE: App/Reports/CreateMonthReport.swift ================================================ // // CreateMonthReport.swift // Jirassic // // Created by Cristian Baluta on 09/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class CreateMonthReport { private let createReport = CreateReport() /// Returns reports collected from all days in the month /// @parameters /// tasks - All tasks in a month /// targetHoursInDay - How many hours in a day /// roundHours - Round the reports to fixed hours func reports (fromTasks tasks: [Task], targetHoursInDay: Double?, roundHours: Bool) -> (byDays: [[Report]], byTasks: [Report]) { guard tasks.count > 1 else { return (byDays: [], byTasks: []) } // When we find a startDay we keep its date and start adding tasks in that day till endDay found or new startDay found // Tasks between end and start are invalid var startDayDate: Date? // Group tasks by days var tasksByDay = [[Task]]() var tasksInDay = [Task]() for task in tasks { if let date = startDayDate { // Start of day already found // Iterate till endDay or new startDay found // Days without a .startDay are ignored if task.taskType == .endDay { tasksInDay.append(task) tasksByDay.append(tasksInDay) tasksInDay = [] startDayDate = nil } else if !date.isSameDayAs(task.endDate) { // This task is from the next day tasksByDay.append(tasksInDay) tasksInDay = [task] startDayDate = task.taskType == .startDay ? task.endDate : nil } else { tasksInDay.append(task) } } else { // If no start of day found yet iterate till found if task.taskType == .startDay { startDayDate = task.endDate tasksInDay = [task] } } if task.objectId == tasks.last?.objectId && tasksInDay.count > 0 { tasksByDay.append(tasksInDay) } } // Iterate over days and create reports var reportsByDay = [[Report]]() for tasks in tasksByDay { let report = createReport.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay) reportsByDay.append(report) } // Group reports by task number // Acumulate durations // Join notes but only for meetings var reportsByTaskNumber = [String: Report]() for day in reportsByDay { var d = 0.0 for report in day { d += report.duration var monthReport = reportsByTaskNumber[report.taskNumber] if monthReport == nil { reportsByTaskNumber[report.taskNumber] = Report(taskNumber: report.taskNumber, title: report.title, notes: ["meeting", "learning"].contains(report.taskNumber) ? report.notes : [], duration: report.duration) } else { if ["meeting", "learning"].contains(report.taskNumber) { monthReport!.notes = Array(Set(monthReport!.notes + report.notes)) } monthReport!.duration += report.duration reportsByTaskNumber[report.taskNumber] = monthReport } } } return (byDays: reportsByDay, byTasks: Array(reportsByTaskNumber.values)) } // List of reports by task number func joinReports (_ reports: [Report]) -> (notes: String, totalDuration: Double) { var notes = "" var totalDuration = 0.0 for report in reports { totalDuration += report.duration notes += "• \(report.taskNumber)\(report.title) (" + report.duration.secToHoursAndMin + ")\n" if report.notes.count > 0 { for note in report.notes { notes += " - \(note)\n" } } } return (notes: notes, totalDuration: totalDuration) } func htmlReports (_ reports: [Report]) -> String { var notes = "" var totalDuration = 0.0 for report in reports { totalDuration += report.duration var note = "\(report.taskNumber) \(report.title)" if report.notes.count > 0 { note = "

\(note)

" note += "
    " for n in report.notes { note += "
  • \(n)
  • " } note += "
" } notes += "\(note)\(report.duration.secToHoursAndMin)\n" } return notes } func csvReports (_ reports: [Report]) -> String { let headers = [ "Issue Key", "Issue summary", "Hours", "Work date", "Username", "Full name", "Period", "Account Key", "Account Name", "Activity Name", "Component", "All Components", "Version Name", "Issue Type", "Issue Status", "Project Key", "Project Name", "Work Description", "Parent Key", "Reporter", "External Hours", "Billed Hours", "Issue Original Estimate", "Issue Remaining Estimate", "Epic Link", "Account [deprecated, this field is no longer being used]", "Office Space", "External issue ID", "External issue ID", "Department", "Location", "External issue summary", "External Issue ID", "External Issue ID" ] var csv = headers.joined(separator: ";") + "\n" for report in reports { var note = "\(report.taskNumber) \(report.title)" var component = "" if report.notes.count > 0 { for n in report.notes { csv += ";;\(report.duration.secToHours);;;;;;;;\(component);;;;;;GS1.1_BOSCH_eBike;\(note);;;;;;;;;;;;;;;;" csv += "\n" } } else { csv += ";;\(report.duration.secToHours);;;;;;;;;;;;;;GS1.1_BOSCH_eBike;\(note);;;;;;;;;;;;;;;;" csv += "\n" } } return csv } } ================================================ FILE: App/Reports/CreateMonthReportTests.swift ================================================ // // CreateMonthReportTests.swift // JirassicTests // // Created by Cristian Baluta on 09/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class CreateMonthReportTests: XCTestCase { var tasks = [Task]() let kLunchLength = Double(2760)//46min ~ 45min let targetHoursInDay = 8.0.hoursToSec override func setUp() { // day 1 let str1 = "|10.10||||1;" + "|10.25|Code reviews part 1|coderev||8;" + "10.30|10.47||||2;" + "12.45|13.51||||3;" + "|14.5|Note 1|TASK-1||0;" + "|14.50|Note 2|TASK-2||0;" + "16.10|16.36|Some meeting|meeting||4;" + "|18.0|Note 3|TASK-3||0;" + "|18.0||||9"// end day tasks = buildTasks(str1, date: "2018.10.10") // Add a meeting that is outside the start-end, it should be ignored by reports tasks += buildTasks("18.30|19.0|Meeting|calendar meeting||10", date: "2018.10.10") // day 2 - without endDay let str2 = "|9.20||||1;" + "|10.25|Code reviews part 1|coderev||8;" + "10.30|10.55||||2;" + "12.45|13.51||||3;" + "|14.5|Note 1|TASK-1||0;" + "|14.50|Note 4|TASK-4||0;" + "|17.30|Note 5|TASK-5||0" tasks += buildTasks(str2, date: "2018.10.11") // day 3 - with endDay let str3 = "|10.20||||1;" + "|14.5|Note 1|TASK-1||0;" + "|18.30||||9" tasks += buildTasks(str3, date: "2018.10.12") } override func tearDown() { tasks = [] super.tearDown() } func testGroupByTaskNumber() { let reports = CreateMonthReport().reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay, roundHours: false) var totalDuration = 0.0 XCTAssert(reports.byDays.count == 3, "There should be only 8 unique task numbers. Lunch and waste are ignored") XCTAssert(reports.byTasks.count == 8, "There should be only 8 unique task numbers. Lunch and waste are ignored") for i1 in 0.. [Report] { // .endDay task is not part of reports let filteredTasks = tasks.filter({ $0.taskType != .endDay }) guard filteredTasks.count > 1 else { return [] } var processedTasks = splitOverlappingTasks(filteredTasks) processedTasks = removeUntrackableTasks(processedTasks) guard processedTasks.count > 1 else { return [] } processedTasks = addExtraTimeToTasks(processedTasks, targetHoursInDay: targetHoursInDay) let groups = groupByTaskNumber(processedTasks) let reports = reportsFromGroups(groups.groups) return sortReports(reports, withOrder: groups.order) } func toString (_ reports: [Report]) -> String { let lines = reports.map({ (_ report: Report) -> String in let title = self.title(from: report) let notes = report.notes.map { (note) -> String in return "• \(note)" } let body = notes.joined(separator: "\n") return title + "\n" + body }) return lines.joined(separator: "\n\n") } private func title (from report: Report) -> String { let taskNumber = report.taskNumber let taskTitle = report.title switch taskNumber { case "meeting": return "Meetings:" case "coderev": return "" default: switch taskTitle { case "": return taskNumber default: return taskNumber + " - " + taskTitle } } } } extension CreateReport { private func splitOverlappingTasks (_ tasks: [Task]) -> [Task] { var arr = [Task]() var task = tasks.first! var previousEndDate = task.endDate arr.append(task) for i in 1.. [Task] { var arr = [Task]() var untrackedDuration = 0.0 var previousTaskOriginalEndDate = tasks.first!.endDate for task in tasks { if isTrackingAllowed(taskType: task.taskType) { var tempTask = task tempTask.endDate = task.endDate.addingTimeInterval(-untrackedDuration) arr.append(tempTask) } else { untrackedDuration += task.endDate.timeIntervalSince(previousTaskOriginalEndDate) } previousTaskOriginalEndDate = task.endDate } return arr } private func addExtraTimeToTasks (_ tasks: [Task], targetHoursInDay: Double?) -> [Task] { // How many tasks should be adjusted let numberOfTasksToAdjust = tasks.filter({ isAdjustable(taskType: $0.taskType) }).count guard numberOfTasksToAdjust > 0 else { return tasks } // Calculate the diff to targetHoursInDay let workedTime = tasks.last!.endDate.timeIntervalSince(tasks.first!.endDate) let requiredHours = targetHoursInDay != nil ? targetHoursInDay! : workedTime let missingTime = requiredHours - workedTime let extraTimePerTask = ceil( Double( Int(missingTime) / numberOfTasksToAdjust)) var roundedTasks = [Task]() var task = tasks.first! task.endDate = targetHoursInDay == nil ? task.endDate : task.endDate.round() roundedTasks.append(task) var previousDate = task.endDate var extraTimeToAdd = 0.0 // First task is start of the day and should not be modified for i in 1.. (groups: [String: [Task]], order: [String]) { var order = [String]() var groups = [String: [Task]]() for task in tasks { guard isDisplayingAllowed(taskType: task.taskType) else { continue } let taskNumber = task.taskNumber != nil && task.taskNumber != "" ? task.taskNumber! : (task.taskType.defaultTaskNumber ?? "") // Save to dictionary var tgroup: [Task]? = groups[taskNumber] if tgroup == nil { tgroup = [Task]() groups[taskNumber] = tgroup } tgroup?.append(task) groups[taskNumber] = tgroup! // Save order if !order.contains(taskNumber) { order.append(taskNumber) } } return (groups: groups, order: order) } private func reportsFromGroups (_ groups: [String: [Task]]) -> [Report] { var reportsMap = [String: Report]() for (taskNumber, tasks) in groups { for task in tasks { guard isTrackingAllowed(taskType: task.taskType) else { continue } guard let startDate = task.startDate else { // This shouldn't happen. It can happen only if there's no start of the day continue } var report = reportsMap[taskNumber] if report == nil { // New reports var title = task.taskTitle let comps = title?.components(separatedBy: taskNumber) title = comps?.last title = title?.replacingOccurrences(of: "_", with: " ") title = title?.replacingOccurrences(of: "-", with: " ") var notes = [String]() if let taskNotes = task.notes { notes = [taskNotes] } report = Report( taskNumber: taskNumber, title: title ?? "", notes: notes, duration: task.endDate.timeIntervalSince(startDate) ) } else { // Update existing reports report!.duration += task.endDate.timeIntervalSince(task.startDate!) if let taskNotes = task.notes { var notes = report!.notes if !notes.contains(taskNotes) { notes.append(taskNotes) report!.notes = notes } } } reportsMap[taskNumber] = report } } return Array(reportsMap.values) } private func sortReports (_ reports: [Report], withOrder order: [String]) -> [Report] { var orderedReports = [Report]() // TODO: sort the array with short lambdas if possible // let arr = reports.sorted { reports.index(of: $0) < order.index(of: $1.1) } for taskNumber in order { for report in reports { if report.taskNumber == taskNumber { orderedReports.append(report) } } } return orderedReports } } extension CreateReport { private func isTrackingAllowed (taskType: TaskType) -> Bool { switch taskType { case .lunch, .waste: return false default: return true } } /// Returns if the duration of this task type is adjustable private func isAdjustable (taskType: TaskType) -> Bool { switch taskType { case .startDay, .scrum, .meeting, .learning, .calendar: return false default: return true } } private func isDisplayingAllowed (taskType: TaskType) -> Bool { return taskType != .startDay } } ================================================ FILE: App/Reports/CreateReportTests.swift ================================================ // // CreateReportTests.swift // Jirassic // // Created by Baluta Cristian on 01/06/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud func buildTasks(_ str: String, date: String = "2018.10.10") -> [Task] { var tasks = [Task]() // startDate: Date? | endDate: Date | notes: String? | taskNumber: String? | taskTitle: String? | taskType: TaskType let lines = str.components(separatedBy: ";") for line in lines { let comps = line.components(separatedBy: "|") let dateFormatter = DateFormatter() dateFormatter.dateFormat = "YYYY.MM.dd.HH.mm" let endDate = dateFormatter.date(from: date + "." + comps[1]) var task = Task(endDate: endDate!, type: TaskType(rawValue: Int(comps[5])!)!) if comps[0] != "" { task.startDate = dateFormatter.date(from: date + "." + comps[0]) } // notes if comps[2] != "" { task.notes = comps[2] } // taskNumber if comps[3] != "" { task.taskNumber = comps[3] } // taskTitle if comps[4] != "" { task.taskTitle = comps[4] } tasks.append(task) } return tasks } class CreateReportTests: XCTestCase { let report = CreateReport() var tasks = [Task]() let kLunchLength = Double(2760)//46min ~ 45min let targetHoursInDay = 8.0.hoursToSec override func setUp() { super.setUp() let str = "|10.10||||1;" + "|10.25|Code reviews part 1|coderev||8;" + "10.30|10.47||||2;" + "12.45|13.51||||3;" + "|14.5|Note 2|APP-2||0;" +// begins before the scrum but ends after the scrum. Subtract the scrum duration "|14.50|Note 3|APP-3||0;" + "|15.6|Code reviews part 2|coderev||8;" + "16.10|16.36||||6;" +//waste "|18.0|Note 6|APP-4||0;" + "|18.0||||9" tasks = buildTasks(str) } override func tearDown() { tasks = [] super.tearDown() } func testGroupByTaskNumber() { let reports = report.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay) XCTAssert(reports.count == 5, "There should be only 5 unique task numbers. Lunch and waste are ignored") for i1 in 0.. 0, "Duration should always greater than 0") XCTAssert(reports[1].duration > 0, "Duration should always greater than 0") XCTAssert(reports[2].duration > 0, "Duration should always greater than 0") var totalDuration = 0.0 for report in reports { totalDuration += report.duration } XCTAssert(totalDuration == targetHoursInDay) } func testRealSituation2() { let str = "|10.00||||1;" + "13.0|13.10|socialmedia|waste||6;" + "|18.45|task 1|APP-3730||0;" + "|18.45||||9" tasks = buildTasks(str) let reports = report.reports(fromTasks: tasks, targetHoursInDay: nil) XCTAssert(reports.count == 1, "Only one valid task") XCTAssert(reports[0].duration == 8 * 3600 + 35 * 60, "8h 35m") } } ================================================ FILE: App/Statistics/StatisticsInteractor.swift ================================================ // // StatisticsInteractor.swift // Jirassic // // Created by Cristian Baluta on 28/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation class StatisticsInteractor { func duration (of tasks: [Task]) -> Double { guard tasks.count > 1 else { return 0.0 } let time = tasks.last!.endDate.timeIntervalSince(tasks.first!.endDate) return time } func duration (of reports: [Report]) -> Double { var time = 0.0 for report in reports { time += report.duration } return time } } ================================================ FILE: App/Tasks/CloseDay.swift ================================================ // // CloseDay.swift // Jirassic // // Created by Cristian Baluta on 05/11/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCLog class CloseDay { func close (with tasks: [Task]) { guard tasks.count > 1 else { return } let interactor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) // Find if the day ended already let endDayTask: Task? = tasks.filter({$0.taskType == .endDay}).first // 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 } } // 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 { var task = task RCLog("Unsaved task found \(task)") task.objectId = String.generateId() interactor.saveTask(task, allowSyncing: true) { savedTask in } } } } } ================================================ FILE: App/Tasks/MergeTasksInteractor.swift ================================================ // // MergeTasksInteractor.swift // Jirassic // // Created by Cristian Baluta on 20/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class MergeTasksInteractor { private let secondsAllowed = 5.0 /// Merge the two list of tasks, remove duplicates, and sort ascending func merge (tasks: [Task], with gitTasks: [Task]) -> [Task] { let all = tasks + gitTasks // Remove duplicates var unique = [Task]() for task in all { var originalHasTaskNumber = false var duplicateHasTaskNumber = false let isUnique = !unique.contains(where: { originalHasTaskNumber = task.taskNumber?.count ?? 0 > 0 let isDuplicate = task.taskType == $0.taskType && abs(task.endDate.timeIntervalSince($0.endDate)) < secondsAllowed if isDuplicate { duplicateHasTaskNumber = $0.taskNumber?.count ?? 0 > 0 } return isDuplicate }) if isUnique { unique.append(task) } else if originalHasTaskNumber && !duplicateHasTaskNumber { unique.removeAll(where: { abs(task.endDate.timeIntervalSince($0.endDate)) < secondsAllowed }) unique.append(task) } } // Sort by date unique.sort(by: { // If tasks have the same date might be the end of the day compared with the last task // In this case endDay should be the latter task guard $0.endDate != $1.endDate else { return $1.taskType == .endDay } return $0.endDate < $1.endDate }) return unique } } //fileprivate extension Array where Element == Task { // // fileprivate mutating func mergeElements(newElements: C) where C.Iterator.Element == Element { // // let filteredList = newElements.filter( { // let gitTask = $0 // return !self.contains(where: { gitTask.endDate.compare($0.endDate) == .orderedSame }) // }) // self += filteredList // } //} ================================================ FILE: App/Tasks/MergeTasksInteractorTests.swift ================================================ // // MergeTasksInteractorTests.swift // JirassicTests // // Created by Cristian Baluta on 20/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class MergeTasksInteractorTests: XCTestCase { func testMergedOrder() { var gitWithTaskNumber = Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 16, minute: 30, second: 50), type: .gitCommit) gitWithTaskNumber.taskNumber = "APP-1" let gitWithoutTaskNumber = Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 16, minute: 30, second: 50), type: .gitCommit) var scrum = Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 10, minute: 0, second: 0), type: .calendar) scrum.startDate = Date(year: 2018, month: 2, day: 20, hour: 9, minute: 45, second: 0) let tasks = [Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 9, minute: 0, second: 0), type: .startDay), Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 9, minute: 30, second: 50), type: .issue), Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 13, minute: 10, second: 0), type: .lunch), Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 14, minute: 30, second: 30), type: .gitCommit), gitWithoutTaskNumber, Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 18, minute: 0, second: 0), type: .endDay) ] let gitTasks = [Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 14, minute: 30, second: 30), type: .gitCommit),// duplicate gitWithTaskNumber,// duplicate gitWithoutTaskNumber,// Duplicate provided by git cmd when you have rebase done Task(endDate: Date(year: 2018, month: 2, day: 20, hour: 18, minute: 0, second: 0), type: .gitCommit) ] let calendarTasks = [scrum] var mergedTasks = MergeTasksInteractor().merge(tasks: tasks, with: gitTasks) mergedTasks = MergeTasksInteractor().merge(tasks: mergedTasks, with: calendarTasks) XCTAssert(mergedTasks.count == 8) XCTAssert(mergedTasks[0].objectId == tasks[0].objectId, "Should be start of the day") XCTAssert(mergedTasks[1].objectId == tasks[1].objectId, "Should be issue 1") XCTAssert(mergedTasks[2].objectId == scrum.objectId, "Should be scrum") XCTAssert(mergedTasks[3].objectId == tasks[2].objectId, "Should be lunch") XCTAssert(mergedTasks[4].objectId == tasks[3].objectId, "Should be first git from tasks") XCTAssert(mergedTasks[5].objectId == gitWithTaskNumber.objectId, "Between identical git should be the one with a valid taskNumber") XCTAssert(mergedTasks[5].taskNumber == "APP-1") XCTAssert(mergedTasks[6].objectId == gitTasks[3].objectId, "Should be last git") XCTAssert(mergedTasks[7].objectId == tasks[5].objectId, "Should be end of day") // Test sorting let _ = mergedTasks.sorted { (t1, t2) -> Bool in // t1 is the second object and t2 the first from the mergedTasks XCTAssert(t1.endDate >= t2.endDate) return true } } } ================================================ FILE: App/Tasks/ReadDaysInteractor.swift ================================================ // // ReadDaysInteractor.swift // Jirassic // // Created by Baluta Cristian on 21/11/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation // Interactor responsible for querying and building Days snd Weeks. // Only start and end tasks will be queried for performance reasons class ReadDaysInteractor: RepositoryInteractor { private var tasks = [Task]() /// Query all startDay and endDay objects from the local repository /// Then do a sync with the remote if enabled and query the local objects again /// @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 all startDay and endDay objects from the local repository /// Then do a sync with the remote if enabled and query the local objects again /// @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) { queryLocalTasks(startDate: startingDate, endDate: Date()) { [weak self] (tasks: [Task]) in guard let _self = self else { return } _self.tasks = tasks completion(_self.weeks()) if let remoteRepository = _self.remoteRepository { 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()) } } } } } private func queryLocalTasks (startDate: Date, endDate: Date, _ completion: @escaping (_ tasks: [Task]) -> Void) { let predicateWithStartAndEndDays = NSPredicate(format: "taskType == %i || taskType == %i", TaskType.startDay.rawValue, TaskType.endDay.rawValue) repository.queryTasks(startDate: startDate, endDate: endDate, predicate: predicateWithStartAndEndDays, completion: { [weak self] (tasks, error) in guard let _self = self else { return } let tasks = _self.sorted(tasks: tasks) completion(tasks) }) } private func weeks() -> [Week] { var objects = [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) } } return objects } 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 sorted (tasks: [Task]) -> [Task] { return tasks.sorted { (task1: Task, task2: Task) -> Bool in return task1.endDate.compare(task2.endDate) == .orderedDescending } } private func days (ofWeek week: Week) -> [Day] { var objects = [Day]() var activeDay: Day? var referenceDate = Date.distantFuture // Tasks are sorted descending for task in tasks { if task.endDate.isSameWeekAs(week.date) { if task.endDate.isSameDayAs(referenceDate) { // Check if we reached the begining of the day and recreate the object with the real startDate if task.taskType == .startDay { if objects.count > 0 { let tempDay = objects.removeLast() activeDay = Day(dateStart: task.endDate, dateEnd: tempDay.dateEnd) } else { activeDay = Day(dateStart: task.endDate, dateEnd: nil) } objects.append(activeDay!) } } else { var endDate: Date? if task.taskType == .endDay { endDate = task.endDate } referenceDate = task.endDate activeDay = Day(dateStart: task.endDate, dateEnd: endDate) objects.append(activeDay!) } } } return objects } } ================================================ FILE: App/Tasks/ReadDaysInteractorTests.swift ================================================ // // ReadDaysInteractorTests.swift // Jirassic // // Created by Cristian Baluta on 23/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class ReadDaysInteractorTests: XCTestCase { func testSplitDays_coredata() { let repository = InMemoryCoreDataRepository() remoteRepository = nil let task1 = Task(endDate: Date(year: 2017, month: 5, day: 23, hour: 23, minute: 50), type: TaskType.startDay) repository.saveTask(task1, completion: { task in }) let task2 = Task(endDate: Date(year: 2017, month: 5, day: 24, hour: 0, minute: 30), type: TaskType.startDay) repository.saveTask(task2, completion: { task in }) let task3 = Task(endDate: Date(year: 2017, month: 5, day: 24, hour: 10, minute: 0), type: TaskType.startDay) repository.saveTask(task3, completion: { task in }) let interactor = ReadDaysInteractor(repository: repository, remoteRepository: nil) // This is synchronous query interactor.queryAll { (weeks) in XCTAssertTrue(weeks.first!.days.count == 2) } } } ================================================ FILE: App/Tasks/ReadTasksInteractor.swift ================================================ // // ReadTasks.swift // Jirassic // // Created by Baluta Cristian on 28/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Foundation class ReadTasksInteractor: RepositoryInteractor { // Return a list of tasks sorted by date func tasksInDay (_ date: Date) -> [Task] { return self.repository.queryTasks(startDate: date.startOfDay(), endDate: date.endOfDay(), predicate: nil) } // Return a list of tasks sorted by date func tasksInMonth (_ date: Date) -> [Task] { return self.repository.queryTasks(startDate: date.startOfMonth(), endDate: date.endOfMonth(), predicate: nil) } // Return a list of tasks sorted by date func tasks (between dateStart: Date, and dateEnd: Date) -> [Task] { return self.repository.queryTasks(startDate: dateStart, endDate: dateEnd, predicate: nil) } } ================================================ FILE: App/Tasks/RemoveDuplicate.swift ================================================ // // RemoveDuplicate.swift // Jirassic // // Created by Cristian Baluta on 26/12/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation /// Removes the later start day class RemoveDuplicate: RepositoryInteractor { let date: Date init(repository: Repository, remoteRepository: Repository?, date: Date) { self.date = date super.init(repository: repository, remoteRepository: remoteRepository) } func execute() { let predicate = NSPredicate(format: "taskType == %i", TaskType.startDay.rawValue) repository.queryTasks(startDate: date.startOfDay(), endDate: date.endOfDay(), predicate: predicate, completion: { [weak self] (tasks, error) in guard let _self = self else { return } guard tasks.count > 1 else { return } let taskInteractor = TaskInteractor(repository: _self.repository, remoteRepository: _self.remoteRepository) for i in 1.. Bool { return tasks.filter({ $0.taskType == TaskType.scrum }).count > 0 } func lunchExists (_ tasks: [Task]) -> Bool { return tasks.filter({ $0.taskType == TaskType.lunch }).count > 0 } } ================================================ FILE: App/Tasks/TaskFinderTests.swift ================================================ // // TaskFinderTests.swift // Jirassic // // Created by Cristian Baluta on 11/06/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class TaskFinderTests: XCTestCase { func testMissingTasks() { let tasks = [ Task(endDate: Date(), type: TaskType.issue), Task(endDate: Date(), type: TaskType.startDay), Task(endDate: Date(), type: TaskType.meeting), Task(endDate: Date(), type: TaskType.gitCommit) ] let taskFinder = TaskFinder() XCTAssertFalse(taskFinder.scrumExists(tasks)) XCTAssertFalse(taskFinder.lunchExists(tasks)) } func testExistingTasks() { let tasks = [ Task(endDate: Date(), type: TaskType.issue), Task(endDate: Date(), type: TaskType.startDay), Task(endDate: Date(), type: TaskType.scrum), Task(endDate: Date(), type: TaskType.lunch), Task(endDate: Date(), type: TaskType.meeting), Task(endDate: Date(), type: TaskType.gitCommit) ] let taskFinder = TaskFinder() XCTAssertTrue(taskFinder.scrumExists(tasks)) XCTAssertTrue(taskFinder.lunchExists(tasks)) } } ================================================ FILE: App/Tasks/TaskInteractor.swift ================================================ // // TaskInteractor.swift // Jirassic // // Created by Cristian Baluta on 19/10/15. // Copyright © 2017 Cristian Baluta. All rights reserved. // import Foundation class TaskInteractor: RepositoryInteractor { func queryTask (withId objectId: String) -> Task? { return repository.queryTask(withId: objectId) } func saveTask (_ task: Task, allowSyncing: Bool, completion: @escaping (_ savedTask: Task?) -> Void) { guard task.objectId != nil else { fatalError("Cannot save a task without objectId") } var task = task task.lastModifiedDate = nil self.repository.saveTask(task, completion: { [weak self] savedTask in guard let localTask = savedTask else { completion(nil) return } if allowSyncing { // We don't care if the task doesn't get saved to server self?.syncTask(localTask, completion: { (task) in }) } completion(localTask) }) } func deleteTask (_ task: Task) { guard task.objectId != nil else { fatalError("Cannot delete a task without objectId") } self.repository.deleteTask(task, permanently: false, completion: { (success: Bool) -> Void in #if !CMD if let remoteRepository = self.remoteRepository { let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) sync.deleteTask(task, completion: { (success) in }) } #endif }) } private func syncTask (_ task: Task, completion: @escaping (_ uploadedTask: Task) -> Void) { #if !CMD if let remoteRepository = self.remoteRepository { let sync = RCSync(localRepository: self.repository, remoteRepository: remoteRepository) sync.uploadTask(task, completion: { (success) in DispatchQueue.main.async { completion(task) } }) } #endif } } ================================================ FILE: App/Tasks/TaskInteractorTests.swift ================================================ // // TaskInteractorTests.swift // Jirassic // // Created by Cristian Baluta on 07/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class TaskInteractorTests: XCTestCase { func testSaveDelete() { let repository = InMemoryCoreDataRepository() let interactor = TaskInteractor(repository: repository, remoteRepository: nil) let tasksBeforeInsert = repository.queryTasks(startDate: Date().startOfDay(), endDate: Date().endOfDay()) XCTAssert(tasksBeforeInsert.count == 0, "We added one task, we should receive one task") let task = Task(endDate: Date(), type: TaskType.issue) interactor.saveTask(task, allowSyncing: false, completion: { task in }) let tasks = repository.queryTasks(startDate: Date().startOfDay(), endDate: Date().endOfDay()) XCTAssert(tasks.count == 1, "We added one task, we should receive one task") let taskToDelete = tasks.first! interactor.deleteTask(taskToDelete) let tasksAfterDelete = repository.queryTasks(startDate: Date().startOfDay(), endDate: Date().endOfDay()) XCTAssert(tasksAfterDelete.count == 0, "There should be no tasks left") } } ================================================ FILE: App/Tasks/TaskTypeEstimator.swift ================================================ // // TaskTypeEstimator.swift // Jirassic // // Created by Baluta Cristian on 04/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Foundation class TaskTypeEstimator { private let scrumVariationAllowed = 20.0.minToSec private let lunchVariationAllowed = 60.0.minToSec func taskTypeAroundDate (_ date: Date, withSettings settings: Settings) -> TaskType { // Check if the date is around scrum time let settingsScrumTime = gregorian.dateComponents(ymdhmsUnitFlags, from: settings.settingsTracking.scrumTime) var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: date) comps.hour = settingsScrumTime.hour comps.minute = settingsScrumTime.minute comps.second = 0 let scrumDate = gregorian.date(from: comps) if date.isAlmostSameHourAs(scrumDate!, devianceSeconds: scrumVariationAllowed) { return TaskType.scrum } // Check if the date is around lunch break let settingsLunchTime = gregorian.dateComponents(ymdhmsUnitFlags, from: settings.settingsTracking.lunchTime) comps.hour = settingsLunchTime.hour comps.minute = settingsLunchTime.minute comps.second = 0 let lunchDate = gregorian.date(from: comps) if date.isAlmostSameHourAs(lunchDate!, devianceSeconds: lunchVariationAllowed) { return TaskType.lunch } // Check if enough time to be considered a meeting if abs(date.timeIntervalSinceNow) > Double(settings.settingsTracking.minSleepDuration) { return TaskType.meeting } return TaskType.issue } } ================================================ FILE: App/Tasks/TaskTypeEstimatorTests.swift ================================================ // // TaskTypeEstimatorTests.swift // Jirassic // // Created by Baluta Cristian on 11/06/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class TaskTypeEstimatorTests: XCTestCase { let estimator = TaskTypeEstimator() let settings = Settings(enableBackup: false, settingsTracking: SettingsTracking( autotrack: true, autotrackingMode: TrackingMode(rawValue: TrackingMode.auto.rawValue)!, trackLunch: true, trackScrum: true, trackMeetings: true, trackStartOfDay: true, startOfDayTime: Date(hour: 9, minute: 0), endOfDayTime: Date(hour: 17, minute: 0), lunchTime: Date(hour: 13, minute: 0), scrumTime: Date(hour: 10, minute: 30), minSleepDuration: 20 ), settingsBrowser: SettingsBrowser( trackCodeReviews: true, trackWastedTime: true, minCodeRevDuration: 5, codeRevLink: "bitbucket", minWasteDuration: 5, wasteLinks: ["facebook.com", "twitter.com"] ) ) func testScrumBeginAt10_30() { let date = Date(hour: 10, minute: 30) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.scrum, "") } func testScrumBeginAt_10_50() { let date = Date(hour: 10, minute: 50) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.scrum, "") } func testScrumBeginAt_10_10() { let date = Date(hour: 10, minute: 10) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.scrum, "") } func testScrumCantBeginAt_11() { let date = Date(hour: 11, minute: 0) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssertFalse(taskType == TaskType.scrum, "") } func testLunchBeginAt_12() { let date = Date(hour: 12, minute: 0) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.lunch, "") } func testLunchBeginAt_12_30() { let date = Date(hour: 12, minute: 30) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.lunch, "") } func testLunchBeginAt_14() { let date = Date(hour: 14, minute: 0) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssert(taskType == TaskType.lunch, "") } func testLunchTooLate() { let date = Date(hour: 15, minute: 0) let taskType = estimator.taskTypeAroundDate(date, withSettings: settings) XCTAssertFalse(taskType == TaskType.lunch, "") } } ================================================ FILE: App/Tasks/TaskTypeSelection.swift ================================================ // // TaskTypeSelection.swift // Jirassic // // Created by Cristian Baluta on 20/08/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation class TaskTypeSelection { fileprivate let kLastSelectedTabKey = "LastSelectedTabKey" func setType (_ type: ListType) { UserDefaults.standard.set(type.rawValue, forKey: kLastSelectedTabKey) UserDefaults.standard.synchronize() } func lastType() -> ListType { if let type = ListType(rawValue: UserDefaults.standard.integer(forKey: kLastSelectedTabKey)) { return type } return ListType.allTasks } } ================================================ FILE: App/Time/PredictiveTimeTyping.swift ================================================ // // PredictiveTimeTyping.swift // Jirassic // // Created by Baluta Cristian on 06/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Foundation class PredictiveTimeTyping { func timeByAdding (_ string: String, to: String) -> String { var returnString = string guard string != "" else { // Deal with backspace let charsToDelete = to.count == 3 || to.count == 5 ? 2 : 1 let rangeToKeep = to.startIndex..= 10) { minToAdd = "\(m)" } else if (m >= 6) { minToAdd = "0\(m)" } else if (m == 0) { minToAdd = "00" } else if (m == 1) { minToAdd = "15" } else if (m == 2) { minToAdd = "20" } else if (m == 3) { minToAdd = "30" } else if (m == 4) { minToAdd = "45" } else if (m == 5) { minToAdd = "50" } returnString = "\(hr):\(minToAdd)" } // Deal with hours else { let h = decimalValueOf(hr, newDigit: string) if (h >= 24) { returnString = "00:" } else if (h >= 10) { // Do not perform any edit on hours from 10 to 23 returnString = "\(h):" } else if (h >= 3) { returnString = "0\(h):" } else if (hr.count >= 1) { returnString = "0\(h):" } } return returnString } func dateFromStringHHmm (_ string: String) -> Date { let gregorian = Calendar(identifier: Calendar.Identifier.gregorian) var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: Date()) let hm = string.components(separatedBy: ":") if (string.count == 0) { comps.hour = 0 comps.minute = 0 } else if (hm.count > 1) { comps.hour = Int(hm[0])! comps.minute = Int(hm[1])! } else { comps.hour = (hm.count == 1) ? Int(hm[0])! : 19; comps.minute = 0 } return gregorian.date(from: comps)! } // This method will combine strings and convert the result to number func decimalValueOf (_ existingText: String, newDigit: String) -> Int { if existingText.count > 0 && newDigit.count > 0 { return Int(existingText)! * 10 + Int(newDigit)! } if let d = Int(newDigit) { return d } else { return 0 } } } ================================================ FILE: App/Time/PredictiveTimeTypingTests.swift ================================================ // // PredictiveTimeTypingTests.swift // Jirassic // // Created by Baluta Cristian on 28/09/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class PredictiveTimeTypingTests: XCTestCase { let predictor = PredictiveTimeTyping() func testStringToDecimal() { XCTAssertFalse(predictor.decimalValueOf("", newDigit: "2") == 1, "") XCTAssert(predictor.decimalValueOf("", newDigit: "2") == 2, "") XCTAssert(predictor.decimalValueOf("", newDigit: "") == 0, "") XCTAssert(predictor.decimalValueOf("1", newDigit: "2") == 12, "") XCTAssert(predictor.decimalValueOf("59", newDigit: "3") == 593, "") } func testDateFromString() { var date = predictor.dateFromStringHHmm("12:25") XCTAssert(date.HHmm() == "12:25", "") date = predictor.dateFromStringHHmm("12") XCTAssert(date.HHmm() == "12:00", "") date = predictor.dateFromStringHHmm("") XCTAssert(date.HHmm() == "00:00", "") } func testHourPredictor() { XCTAssert(predictor.timeByAdding("0", to: "") == "0", "") XCTAssert(predictor.timeByAdding("1", to: "") == "1", "") XCTAssert(predictor.timeByAdding("2", to: "") == "2", "") XCTAssert(predictor.timeByAdding("3", to: "") == "03:", "Adding first h digit greater or equal to 3 can mean only 03: in the morning") XCTAssert(predictor.timeByAdding("", to: "03:") == "0", "No char means deleting. Deleting last h digit deletes the : as well") XCTAssert(predictor.timeByAdding("", to: "0") == "", "") XCTAssert(predictor.timeByAdding("0", to: "0") == "00:", "") XCTAssert(predictor.timeByAdding("2", to: "0") == "02:", "") XCTAssert(predictor.timeByAdding("3", to: "0") == "03:", "") XCTAssert(predictor.timeByAdding("0", to: "1") == "10:", "") XCTAssert(predictor.timeByAdding("9", to: "2") == "00:", "Adding more than 24h sets it to 00") } // Minutes are predicted to the first greater quarter or 10th func testMinutesPredictor() { XCTAssert(predictor.timeByAdding("0", to: "05:") == "05:00", "") XCTAssert(predictor.timeByAdding("1", to: "05:") == "05:15", "") XCTAssert(predictor.timeByAdding("2", to: "05:") == "05:20", "") XCTAssert(predictor.timeByAdding("3", to: "05:") == "05:30", "") XCTAssert(predictor.timeByAdding("4", to: "05:") == "05:45", "") XCTAssert(predictor.timeByAdding("5", to: "05:") == "05:50", "") XCTAssert(predictor.timeByAdding("6", to: "05:") == "05:06", "If minutes begins with 6 or greater, use it as final value") XCTAssert(predictor.timeByAdding("9", to: "05:") == "05:09", "") XCTAssert(predictor.timeByAdding("9", to: "05:1") == "05:19", "") XCTAssert(predictor.timeByAdding("9", to: "05:18") == "05:19", "") XCTAssert(predictor.timeByAdding("6", to: "05:09") == "05:06", "When the time is complete but you still add digits, replace the last digit with new value") XCTAssert(predictor.timeByAdding("", to: "05:09") == "05:", "Deleting minutes deletes both digits") XCTAssert(predictor.timeByAdding("", to: "05:0") == "05:", "") } } ================================================ FILE: App/Time/TimeInteractor.swift ================================================ // // TimeInteractor.swift // Jirassic // // Created by Cristian Baluta on 05/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class TimeInteractor { let settings: Settings! init(settings: Settings) { self.settings = settings } func workingDayLength() -> TimeInterval { return settings.settingsTracking.endOfDayTime.dateByKeepingTime().timeIntervalSince( settings.settingsTracking.startOfDayTime.dateByKeepingTime()) } func workedDayLength() -> TimeInterval { return Date().timeIntervalSince( settings.settingsTracking.startOfDayTime.dateByKeepingTime() ) } } ================================================ FILE: App/User/RegisterUserInteractor.swift ================================================ // // RegisterUserInteractor.swift // Jirassic // // Created by Baluta Cristian on 14/06/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Foundation import RCLog class RegisterUserInteractor: RepositoryInteractor { var onRegisterSuccess: (() -> ())? var onRegisterFailure: (() -> ())? func registerWithCredentials (_ credentials: UserCredentials) { self.repository.registerWithCredentials(credentials) { [weak self] (error) in if let error = error { let errorString = error.userInfo["error"] as? NSString RCLogO(errorString) self?.onRegisterFailure?() } else { self?.onRegisterSuccess?() } } } } ================================================ FILE: App/User/UserInteractor.swift ================================================ // // UserInteractor.swift // Jirassic // // Created by Cristian Baluta on 03/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RCLog class UserInteractor: RepositoryInteractor { var onLoginSuccess: (() -> ())? var onLoginFailure: (() -> ())? func getUser(_ completion: @escaping ((_ user: User?) -> Void)) { self.repository.getUser(completion) } func loginWithCredentials (_ credentials: UserCredentials) { self.repository.loginWithCredentials(credentials) { [weak self] (error: NSError?) in if let error = error { let errorString = error.userInfo["error"] as? NSString RCLogO(errorString) self?.register(credentials) } else { self?.onLoginSuccess?() } } } func logout() { self.repository.logout() } fileprivate func register (_ credentials: UserCredentials) { let registerInteractor = RegisterUserInteractor(repository: self.repository, remoteRepository: self.remoteRepository) registerInteractor.onRegisterSuccess = { [weak self] in self?.onLoginSuccess?() } registerInteractor.onRegisterFailure = { [weak self] in self?.onLoginFailure?() } registerInteractor.registerWithCredentials(credentials) } } ================================================ FILE: App/User/UserInteractorTests.swift ================================================ // // UserInteractorTests.swift // Jirassic // // Created by Cristian Baluta on 03/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class UserInteractorTests: XCTestCase { // func testLogout() { // // let repository = InMemoryCoreDataRepository() // // let task = Task(dateEnd: Date(), type: TaskType.issue) // let _ = repository.saveTask(task) { (success) in // // } // // let tasks = repository.queryTasksInDay(Date()) // XCTAssert(tasks.count == 1, "We added one task, we should receive one task") // // UserInteractor(repository: repository).logout() // // let tasksAfterLogout = repository.queryTasksInDay(Date()) // XCTAssert(tasksAfterLogout.count == 0, "After logging out there should be no task left") // } } ================================================ FILE: Delivery/iOS/AppDelegate.swift ================================================ // // AppDelegate.swift // Jirassic-Scrum // // Created by Baluta Cristian on 13/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit var localRepository: Repository! var remoteRepository: Repository? let appRed = UIColor(red: 240.0/255, green: 40.0/255, blue: 40.0/255, alpha: 1.0) @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application (_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // UserDefaults.standard.localChangeDate = nil // UserDefaults.standard.remoteChangeToken = nil localRepository = CoreDataRepository() remoteRepository = CloudKitRepository() remoteRepository?.getUser({ (user) in if user == nil { remoteRepository = nil } }) self.window?.tintColor = appRed return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: Delivery/iOS/Base.lproj/LaunchScreen.xib ================================================ Avenir-Medium ================================================ FILE: Delivery/iOS/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Delivery/iOS/DaysViewController.swift ================================================ // // MasterViewController.swift // Scrum // // Created by Baluta Cristian on 05/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit class DaysViewController: UITableViewController { var weeks = [Week]() override func viewDidLoad() { super.viewDidLoad() let refreshControl = UIRefreshControl() refreshControl.tintColor = appRed refreshControl.addTarget(self, action: #selector(reloadData), for: .valueChanged) self.refreshControl = refreshControl reloadData() } @objc func reloadData() { let interactor = ReadDaysInteractor(repository: localRepository, remoteRepository: remoteRepository) interactor.queryAll { weeks in self.weeks = weeks DispatchQueue.main.async { self.tableView.reloadData() self.refreshControl?.endRefreshing() } } } // MARK: - Segues override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ShowTasksSegue" { if let indexPath = self.tableView.indexPathForSelectedRow { (segue.destination as! TasksViewController).currentDay = weeks[indexPath.section].days[indexPath.row] } } } } extension DaysViewController { override func numberOfSections(in tableView: UITableView) -> Int { return weeks.count } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return weeks[section].date.weekInterval() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return weeks[section].days.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DayCell", for: indexPath) let day = weeks[indexPath.section].days[indexPath.row] cell.textLabel!.text = day.dateStart.EEEEMMMMdd() return cell } } ================================================ FILE: Delivery/iOS/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "logo40.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "logo60.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "logo58.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "logo87.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "logo80.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "logo120-1.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "logo120.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "logo180.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/iOS/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/iOS/Images.xcassets/SplashIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "logo400.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "logo600.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/iOS/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName Jirassic CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait ================================================ FILE: Delivery/iOS/Jirassic Scrum.entitlements ================================================ com.apple.developer.icloud-container-identifiers iCloud.com.jirassic.macos com.apple.developer.icloud-services CloudKit com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) ================================================ FILE: Delivery/iOS/LoginViewController.swift ================================================ // // LoginViewController.swift // Jirassic // // Created by Baluta Cristian on 25/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit class LoginViewController: UIViewController { @IBOutlet private var butLogin: UIButton! @IBOutlet private var infoTextField: UILabel! override func viewDidLoad() { super.viewDidLoad() self.title = "Jirassic" handleLoginButton(butLogin) } @IBAction func handleLoginButton (_ sender: UIButton) { remoteRepository?.getUser({ (user) in if user != nil { self.performSegue(withIdentifier: "ShowDaysSegue", sender: nil) } }) } } ================================================ FILE: Delivery/iOS/NonTaskCell.swift ================================================ // // NonTaskCell.swift // Jirassic // // Created by Baluta Cristian on 15/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit class NonTaskCell: UITableViewCell { @IBOutlet var dateLabel: UILabel? @IBOutlet var notesLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() } override func setSelected (_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: Delivery/iOS/TaskCell.swift ================================================ // // TaskTableViewCell.swift // Jirassic // // Created by Baluta Cristian on 14/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit class TaskCell: UITableViewCell { @IBOutlet var circleWhite: UIView? @IBOutlet var circleDark: UIView? @IBOutlet var taskNrLabel: UILabel? @IBOutlet var dateLabel: UILabel? @IBOutlet var titleLabel: UILabel? @IBOutlet var notesLabel: UILabel? override func awakeFromNib() { super.awakeFromNib() // Initialization code circleWhite?.layer.cornerRadius = circleWhite!.frame.size.width / 2 circleDark?.layer.cornerRadius = circleDark!.frame.size.width / 2 } } ================================================ FILE: Delivery/iOS/TasksViewController.swift ================================================ // // DetailViewController.swift // Scrum // // Created by Baluta Cristian on 05/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import UIKit class TasksViewController: UITableViewController { var currentDay: Day? private var tasks = [Task]() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // self.navigationItem.leftBarButtonItem = self.editButtonItem() // let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:") // self.navigationItem.rightBarButtonItem = addButton self.title = currentDay!.dateStart.EEEEMMMMdd() let reader = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) tasks = reader.tasksInDay(currentDay!.dateStart) // let reportInteractor = CreateReport() // let reports = reportInteractor.reports(fromTasks: currentTasks, targetHoursInDay: nil) // let currentReports = reports.reversed() // } tableView.reloadData() } // func insertNewObject(sender: AnyObject) { // objects.insert(Date(), atIndex: 0) // let indexPath = NSIndexPath(forRow: 0, inSection: 0) // self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) // } } extension TasksViewController { override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tasks.count } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return UITableViewAutomaticDimension } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let theTask = tasks[indexPath.row] // RCLog(theData) if theTask.taskType == .issue || theTask.taskType == .gitCommit { let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell cell.taskNrLabel!.text = theTask.taskNumber cell.dateLabel!.text = theTask.endDate.HHmm() cell.titleLabel!.text = (theTask.taskTitle ?? "").replacingOccurrences(of: "_", with: " ").replacingOccurrences(of: theTask.taskNumber!, with: "").trimmingCharacters(in: .whitespaces) cell.notesLabel!.text = theTask.notes?.trimmingCharacters(in: .whitespaces) return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: "NonTaskCell", for: indexPath) as! NonTaskCell var notes = theTask.notes ?? theTask.taskType.defaultNotes if theTask.taskType == .coderev { notes = "\(theTask.taskType.defaultNotes): \(notes)" } if let startDate = theTask.startDate { cell.notesLabel!.text = "\(startDate.HHmm()) - \(theTask.endDate.HHmm()) \(notes)" } else { cell.notesLabel!.text = "\(theTask.endDate.HHmm()) \(notes)" } return cell } } // override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { // // Return false if you do not want the specified item to be editable. // return true // } // // override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { // if editingStyle == .delete { // tasks.remove(at: indexPath.row) // tableView.deleteRows(at: [indexPath], with: .fade) // } else if editingStyle == .insert { // // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. // } // } } ================================================ FILE: Delivery/macOS/Animations/Animatable.swift ================================================ // // Animatable.swift // Jirassic // // Created by Cristian Baluta on 11/12/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa protocol Animatable { // Animatable views need a CALayer which should be created in this method func createLayer() } ================================================ FILE: Delivery/macOS/Animations/FlipAnimation.swift ================================================ // // Flip.swift // Jirassic // // Created by Baluta Cristian on 24/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa class FlipAnimation: NSObject { var animationReachedMiddle: (() -> ())? var animationFinished: (() -> ())? weak var layer: CALayer? func startWithLayer (_ layer: CALayer) { // Create CAAnimation let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.y") rotationAnimation.fromValue = 0.0 rotationAnimation.toValue = 3.14/2 rotationAnimation.duration = 0.2 rotationAnimation.repeatCount = 1.0 rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) rotationAnimation.fillMode = CAMediaTimingFillMode.forwards rotationAnimation.isRemovedOnCompletion = false rotationAnimation.setValue("flipAnimationInwards", forKey: "flip") rotationAnimation.delegate = self // Add perspective var perpectiveTransform = CATransform3DIdentity perpectiveTransform.m34 = CGFloat(1.0 / 1000) layer.transform = perpectiveTransform layer.anchorPoint = CGPoint(x: 0.5, y: 0) layer.add(rotationAnimation, forKey:"flip") self.layer = layer } func animatePhase2 (_ anim: CAAnimation!) { let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.y") rotationAnimation.fromValue = -3.14/2 rotationAnimation.toValue = 0.0 rotationAnimation.duration = 0.2 rotationAnimation.repeatCount = 1.0 rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) rotationAnimation.fillMode = CAMediaTimingFillMode.forwards rotationAnimation.isRemovedOnCompletion = false rotationAnimation.setValue("flipAnimationOutwards", forKey: "flip") rotationAnimation.delegate = self // Add perspective // var perpectiveTransform = CATransform3DIdentity // perpectiveTransform.m34 = CGFloat(1.0 / 1000) // self.layer?.transform = perpectiveTransform self.layer?.add(rotationAnimation, forKey:"flip") } func clean() { self.animationReachedMiddle = nil self.animationFinished = nil self.layer = nil } } extension FlipAnimation: CAAnimationDelegate { func animationDidStop (_ anim: CAAnimation, finished flag: Bool) { if anim.value(forKey: "flip") as! String == "flipAnimationInwards" { self.animationReachedMiddle!() self.animatePhase2(anim) } else if anim.value(forKey: "flip") as! String == "flipAnimationOutwards" { self.animationFinished!() clean() } } } ================================================ FILE: Delivery/macOS/App/AppDelegate.swift ================================================ // // AppDelegate.swift // Jirassic // // Created by Cristian Baluta on 24/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences import RCLog var localRepository: Repository! var remoteRepository: Repository? let hookup = ModuleHookup() @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet var window: NSWindow? var activePopover: NSPopover? let appWireframe = AppWireframe() let sleep = SleepNotifications() let theme = AppTheme() let menu = MenuBarController() private let browser = BrowserNotification() private let pref = RCPreferences() private var animatesOpen = true class func sharedApp() -> AppDelegate { return NSApplication.shared.delegate as! AppDelegate } override init() { super.init() #if DEBUG // Simulate a freshly installed app by resetting the preferences // localPreferences.reset() // UserDefaults.standard.serverChangeToken = nil // pref.reset(.wizardSteps) // pref.set("", forKey: .appVersion) // UserDefaults.standard.set(5, forKey: "wizardStep") // localPreferences.set(false, forKey: .enableGit) #else disableTraces() #endif self.window?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.floatingWindow))) localRepository = SqliteRepository() #if APPSTORE if SettingsInteractor().getAppSettings().enableBackup { remoteRepository = CloudKitRepository() remoteRepository?.getUser({ (user) in if user == nil { remoteRepository = nil } }) } // _ = Store.shared #else RCLog("Icloud is not supported in this target, continuing without...") #endif menu.isDark = theme.isDark menu.onOpen = { self.removeActivePopup() let isFirstLaunchOfThisVersion = self.pref.string(.appVersion) != Versioning.appVersion let wizardSteps: [Int] = self.pref.get(.wizardSteps) if isFirstLaunchOfThisVersion { self.presentWelcomePopup() } else if wizardSteps.count < WizardStep.allCases.count { self.presentWizard() } else { self.presentTasksPopup(animated: self.animatesOpen) self.animatesOpen = false } } menu.onClose = { self.removeActivePopup() } theme.onChange = { self.menu.isDark = self.theme.isDark } sleep.computerWentToSleep = { self.menu.triggerClose() self.browser.stop() } sleep.computerWakeUp = { let tasks = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) .tasksInDay(Date()) let isWeekend = Date().isWeekend() let isDayStarted = tasks.count > 0 let isDayEnded = tasks.contains(where: { $0.taskType == .endDay }) guard !isWeekend || (isDayStarted && isWeekend) else { RCLog(">>>>>>> It's weekend, won't track weekends unless the day was started <<<<<<<<") return } guard !isDayEnded else { RCLog(">>>>>>> Day ended, won't analyze further <<<<<<<<") return } self.browser.start() let settings: Settings = SettingsInteractor().getAppSettings() guard settings.settingsTracking.autotrack else { RCLog(">>>>>>> Autotracking disabled, won't present suggestions to user <<<<<<<<") return } let sleepDuration = Date().timeIntervalSince(self.sleep.lastSleepDate ?? Date()) guard sleepDuration >= Double(settings.settingsTracking.minSleepDuration).minToSec else { RCLog(">>>>>>> Sleep duration is shorter than the minimum required: \(sleepDuration) < \(Double(settings.settingsTracking.minSleepDuration).minToSec) <<<<<<<<") return } let startDate = settings.settingsTracking.startOfDayTime.dateByKeepingTime() guard Date() > startDate else { RCLog(">>>>>>> Woke up earlier than the predefined startDay, won't analyze further <<<<<<<<") return } let timeInteractor = TimeInteractor(settings: settings) let dayDuration = timeInteractor.workingDayLength() let workedDuration = timeInteractor.workedDayLength() guard workedDuration < dayDuration else { // Do not track time exceeded the working duration return } switch settings.settingsTracking.autotrackingMode { case .notif: self.removeActivePopup() self.presentTaskSuggestionPopup() break case .auto: ComputerWakeUpInteractor(repository: localRepository, remoteRepository: remoteRepository, settings: settings) .runWith(lastSleepDate: self.sleep.lastSleepDate, currentDate: Date()) break } } browser.codeReviewDidStart = { RCLog("Start code review \(Date())") } browser.codeReviewDidEnd = { RCLog("End code review \(Date())") let task = Task( lastModifiedDate: nil, startDate: self.browser.startDate, endDate: self.browser.endDate!, notes: self.browser.reviewedTasks.joined(separator: ", "), taskNumber: nil, taskTitle: nil, taskType: .coderev, objectId: String.generateId() ) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in }) } browser.wastingTimeDidStart = { RCLog("Start wasting time \(Date())") } browser.wastingTimeDidEnd = { RCLog("End wasting time \(Date())") let task = Task(startDate: self.browser.startDate, endDate: self.browser.endDate!, type: .waste) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in }) } } func applicationDidFinishLaunching (_ aNotification: Notification) { self.killLauncher() // Open with a delay because the popup doesn't play well otherwise let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(1.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { if !UserDefaults.standard.bool(forKey: "launchedByLauncher") { self.menu.triggerOpen() } else { self.presentTaskSuggestionPopup() } }) NSUserNotificationCenter.default.delegate = self NSEvent.addGlobalMonitorForEvents(matching: .rightMouseDown, handler: { event in self.menu.triggerClose() }) } func applicationWillTerminate (_ aNotification: Notification) { } } extension AppDelegate { private func presentWelcomePopup() { let popover = NSPopover() activePopover = popover popover.contentViewController = appWireframe.appViewController appWireframe.removeCurrentController() _ = appWireframe.presentWelcomeController() appWireframe.showPopover(popover, fromIcon: menu.iconView) } private func presentWizard() { let popover = NSPopover() activePopover = popover popover.contentViewController = appWireframe.appViewController appWireframe.removeCurrentController() _ = appWireframe.presentWizardController() appWireframe.showPopover(popover, fromIcon: menu.iconView) } private func presentTasksPopup (animated: Bool) { let popover = NSPopover() activePopover = popover popover.contentViewController = appWireframe.appViewController popover.animates = true appWireframe.removeCurrentController() _ = appWireframe.presentTasksController() appWireframe.showPopover(popover, fromIcon: menu.iconView) } func removeActivePopup() { if let popover = activePopover { activePopover = nil appWireframe.hidePopover(popover) appWireframe.removeEndDayController() appWireframe.removePlaceholder() appWireframe.removeCurrentController() } } private func presentTaskSuggestionPopup() { let popover = NSPopover() activePopover = popover popover.contentViewController = appWireframe.appViewController _ = appWireframe.presentTaskSuggestionController (startSleepDate: sleep.lastSleepDate, endSleepDate: Date()) appWireframe.showPopover(popover, fromIcon: menu.iconView) } } extension AppDelegate: NSUserNotificationCenterDelegate { func userNotificationCenter (_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { return true } } ================================================ FILE: Delivery/macOS/App/AppLauncher.swift ================================================ // // AppLauncher.swift // Jirassic // // Created by Cristian Baluta on 24/12/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa let launcherIdentifier = "com.jirassic.macos.launcher" extension AppDelegate { // func launchAtStartup() { // // guard InternalSettings().launchAtStartup else { // return // } // InternalSettings().launchAtStartup = true // killLauncher() // } func killLauncher() { for app in NSWorkspace.shared.runningApplications { if app.bundleIdentifier == launcherIdentifier { DistributedNotificationCenter.default() .postNotificationName(NSNotification.Name(rawValue: "killme"), object: Bundle.main.bundleIdentifier!, userInfo: nil, deliverImmediately: true) break } } } } ================================================ FILE: Delivery/macOS/App/AppTheme.swift ================================================ // // AppTheme.swift // Jirassic // // Created by Cristian Baluta on 17/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import Cocoa class AppTheme { var isDark: Bool { get { return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" } } var onChange: (() -> ())? var textColor: NSColor { get { return isDark ? NSColor(red: 240/255.0, green: 190/255.0, blue: 80/255.0, alpha: 1.0) : NSColor(red: 200/255.0, green: 120/255.0, blue: 180/255.0, alpha: 1.0) } } var lineColor: NSColor { get { return isDark ? NSColor(calibratedWhite: 1.0, alpha: 0.2) : NSColor(calibratedWhite: 0.0, alpha: 0.2) } } var highlightLineColor: NSColor { get { return isDark ? NSColor(calibratedWhite: 1.0, alpha: 1.0) : NSColor(calibratedWhite: 0.0, alpha: 1.0) } } init() { DistributedNotificationCenter.default.addObserver(self, selector: #selector(interfaceModeChanged(sender:)), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil) } @objc func interfaceModeChanged (sender: NSNotification) { onChange?() } } ================================================ FILE: Delivery/macOS/App/AppViewController.swift ================================================ // // AppViewController.swift // Jirassic // // Created by Cristian Baluta on 30/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa class AppViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() // Do view setup here. } } ================================================ FILE: Delivery/macOS/App/AppWireframe.swift ================================================ // // AppWireframe.swift // Jirassic // // Created by Baluta Cristian on 06/11/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Cocoa enum SplitViewColumn: Int { case calendar = 0 case tasks = 1 } class AppWireframe { private var _appViewController: AppViewController? private var currentController: NSViewController? private var _placeholderViewController: PlaceholderViewController? private var _worklogsViewController: WorklogsViewController? var appViewController: AppViewController { guard _appViewController == nil else { return _appViewController! } _appViewController = AppViewController.instantiateFromStoryboard("Main") return _appViewController! } private var welcomeViewController: WelcomeViewController { let controller = WelcomeViewController.instantiateFromStoryboard("Welcome") controller.appWireframe = self return controller } private var wizardViewController: WizardViewController { let controller = WizardViewController.instantiateFromStoryboard("Welcome") controller.appWireframe = self return controller } private var loginViewController: LoginViewController { let controller = LoginViewController.instantiateFromStoryboard("Login") let presenter = LoginPresenter() controller.loginPresenter = presenter presenter.userInterface = controller return controller } 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 var taskSuggestionViewController: TaskSuggestionViewController { let controller = TaskSuggestionViewController.instantiateFromStoryboard("Tasks") let presenter = TaskSuggestionPresenter() controller.presenter = presenter presenter.userInterface = controller return controller } private var settingsViewController: SettingsViewController { let controller = SettingsViewController.instantiateFromStoryboard("Settings") let presenter = SettingsPresenter() let interactor = SettingsInteractor() presenter.userInterface = controller presenter.interactor = interactor interactor.presenter = presenter controller.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 500, height: 500)) controller.presenter = presenter controller.appWireframe = self return controller } private var placeholderViewController: PlaceholderViewController { return PlaceholderViewController.instantiateFromStoryboard("Placeholder") } private var worklogsViewController: WorklogsViewController { let controller = WorklogsViewController.instantiateFromStoryboard("Worklogs") let presenter = WorklogsPresenter() controller.presenter = presenter controller.appWireframe = self presenter.userInterface = controller return controller } } extension AppWireframe { func showPopover (_ popover: NSPopover, fromIcon icon: NSView) { let edge = NSRectEdge.minY let rect = icon.frame popover.show(relativeTo: rect, of: icon, preferredEdge: edge) } func hidePopover (_ popover: NSPopover) { popover.close() } func removeCurrentController() { if let c = currentController { removeController(c) currentController = nil } } private func addController (_ controller: NSViewController) { appViewController.addChild(controller) appViewController.view.addSubview(controller.view) controller.view.constrainToSuperview() } private func removeController (_ controller: NSViewController) { controller.removeFromParent() controller.view.removeFromSuperview() } private func layerToAnimate() -> CALayer { return appViewController.view.superview!.layer! } } extension AppWireframe { func presentWelcomeController() -> WelcomeViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) let controller = self.welcomeViewController addController(controller) currentController = controller return controller } func presentWizardController() -> WizardViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) let controller = self.wizardViewController addController(controller) currentController = controller return controller } func presentLoginController() -> LoginViewController { let controller = self.loginViewController addController(controller) currentController = controller return controller } func presentTasksController() -> TasksViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 560, height: 500)) let controller = self.tasksViewController addController(controller) currentController = controller return controller } func presentTaskSuggestionController (startSleepDate: Date?, endSleepDate: Date) -> TaskSuggestionViewController { appViewController.view.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 450, height: 150)) let controller = self.taskSuggestionViewController controller.startSleepDate = startSleepDate controller.endSleepDate = endSleepDate addController(controller) currentController = controller return controller } // Placeholder func presentPlaceholder (_ message: MessageViewModel, intoSplitView splitView: NSSplitView) -> PlaceholderViewController { var controller = _placeholderViewController if controller == nil { controller = self.placeholderViewController appViewController.addChild(controller!) _placeholderViewController = controller } splitView.subviews[SplitViewColumn.tasks.rawValue].addSubview(controller!.view) controller!.view.constrainToSuperview() controller!.viewModel = message return controller! } func removePlaceholder() { if let controller = _placeholderViewController { removeController(controller) _placeholderViewController = nil } } // EndDay func presentEndDayController (date: Date, tasks: [Task]) -> WorklogsViewController { let controller = self.worklogsViewController controller.date = date controller.tasks = tasks addController(controller) controller.view.constrainToSuperview() _worklogsViewController = controller return controller } func removeEndDayController() { if let controller = _worklogsViewController { removeController(controller) _worklogsViewController = nil } } } extension AppWireframe { func flipToTasksController() { let tasksController = self.tasksViewController let flip = FlipAnimation() flip.animationReachedMiddle = { self.removeCurrentController() self.addController(tasksController) self.currentController = tasksController } flip.animationFinished = {} flip.startWithLayer(layerToAnimate()) } 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()) } func flipToLoginController() { let loginController = self.loginViewController let flip = FlipAnimation() flip.animationReachedMiddle = { self.removeController(self.currentController!) self.addController(loginController) self.currentController = loginController } flip.animationFinished = {} flip.startWithLayer(layerToAnimate()) } func flipToWizardController() { let wizardController = self.wizardViewController let flip = FlipAnimation() flip.animationReachedMiddle = { self.removeCurrentController() self.removePlaceholder() self.removeEndDayController() self.addController(wizardController) self.currentController = wizardController } flip.animationFinished = {} flip.startWithLayer(layerToAnimate()) } } ================================================ FILE: Delivery/macOS/App/LocalPreferences.swift ================================================ // // LocalPreferences.swift // Jirassic // // Created by Cristian Baluta on 23/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences enum LocalPreferences: String, RCPreferencesProtocol { case launchAtStartup = "launchAtStartup" case usePercents = "usePercents" case useDuration = "useDuration" // Currently installed app version. In case of an update this is the version of the previous app case appVersion = "appVersion" case wizardSteps = "wizardSteps" case settingsActiveTab = "settingsActiveTab" case settingsJiraUrl = "settingsJiraUrl" case settingsJiraUser = "settingsJiraUser" case settingsJiraProjectId = "settingsJiraProjectId" case settingsJiraProjectKey = "settingsJiraProjectKey" case settingsJiraProjectIssueKey = "settingsJiraProjectIssueKey" case settingsHookupCmdName = "settingsHookupCmdName" case settingsHookupAppName = "settingsHookupAppName" case settingsGitPaths = "settingsGitPaths" case settingsGitAuthors = "settingsGitAuthors" case settingsSelectedCalendars = "settingsSelectedCalendars" case enableGit = "enableGit" case enableJit = "enableJit" case enableRoundingDay = "enableRoundingDay" case enableHookup = "enableHookup" case enableCocoaHookup = "enableCocoaHookup" case enableHookupCredentials = "enableHookupCredentials" case enableCalendar = "enableCalendar" case copyWorklogsAsHtml = "copyWorklogsAsHtml" func defaultValue() -> Any { switch self { case .launchAtStartup: return false case .usePercents: return true case .useDuration: return false case .appVersion: return "" case .wizardSteps: return [] case .settingsActiveTab: return SettingsTab.tracking.rawValue case .settingsJiraUrl: return "" case .settingsJiraUser: return "" case .settingsJiraProjectId: return "" case .settingsJiraProjectKey: return "" 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 case .enableRoundingDay: return false case .enableHookup: return false case .enableCocoaHookup: return false case .enableHookupCredentials: return false case .enableCalendar: return false case .copyWorklogsAsHtml: return false } } } ================================================ FILE: Delivery/macOS/App/Versioning.swift ================================================ // // Versioning.swift // Jirassic // // Created by Cristian Baluta on 11/02/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation typealias Versions = (shellScript: String, browserScript: String, jirassicCmd: String, jitCmd: String) typealias Compatibility = (currentVersion: String, minVersion: String, available: Bool, compatible: Bool) class Versioning { static let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String private let compatibilityMaps: [String: Versions] = [ "17.06.14": (shellScript: "1.0", browserScript: "1.0", jirassicCmd: "17.06.14", jitCmd: "17.06.14"), "18.04.04": (shellScript: "1.0", browserScript: "1.1", jirassicCmd: "17.06.14", jitCmd: "17.06.14"), "18.04.25": (shellScript: "1.0", browserScript: "1.1", jirassicCmd: "18.04.25", jitCmd: "18.04.25"), "18.12.12": (shellScript: "1.0", browserScript: "1.1", jirassicCmd: "18.12.12", jitCmd: "18.12.12") // Add a new compatibility for each app version that needs one ] private let versions: Versions private let minVersions: Versions var shellScript: Compatibility { return (currentVersion: versions.shellScript, minVersion: minVersions.shellScript, available: versions.shellScript != "", compatible: versions.shellScript >= minVersions.shellScript) } var jirassic: Compatibility { return (currentVersion: versions.jirassicCmd, minVersion: minVersions.jirassicCmd, available: versions.jirassicCmd != "", compatible: versions.jirassicCmd >= minVersions.jirassicCmd) } var jit: Compatibility { return (currentVersion: versions.jitCmd, minVersion: minVersions.jitCmd, available: versions.jitCmd != "", compatible: versions.jitCmd >= minVersions.jitCmd) } var browser: Compatibility { return (currentVersion: versions.browserScript, minVersion: minVersions.browserScript, available: versions.browserScript != "", compatible: versions.browserScript >= minVersions.browserScript) } init (versions: Versions) { self.versions = versions // Find the current compatibility map var currentCompatibilityMap = compatibilityMaps[Versioning.appVersion] if currentCompatibilityMap == nil { let sortedKeys = compatibilityMaps.keys.sorted() let lastKey = sortedKeys.last! currentCompatibilityMap = compatibilityMaps[lastKey] } self.minVersions = currentCompatibilityMap! } } ================================================ FILE: Delivery/macOS/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Delivery/macOS/Bridging-Header.h ================================================ // // BridgingHeader.h // Jirassic // // Created by Cristian Baluta on 28/03/2017. // Copyright © 2017 Imagin soft. All rights reserved. // #ifndef Bridging_Header_h #define Bridging_Header_h #import #endif /* BridgingHeader_h */ ================================================ FILE: Delivery/macOS/BuildScript.sh ================================================ #!/bin/sh #if [ ${CONFIGURATION} == "Release" ]; then COMMIT_COUNT=$(git rev-list HEAD --count); BUNDLE_VERSION="$COMMIT_COUNT" now="$(date +'%y.%m.%d')" SHORT_VERSION_STRING="$now" echo "APP VERSION: $SHORT_VERSION_STRING - BUILD: $COMMIT_COUNT" /usr/libexec/Plistbuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION_STRING" "${INFOPLIST_FILE}" /usr/libexec/Plistbuddy -c "Set :CFBundleVersion $BUNDLE_VERSION" "${INFOPLIST_FILE}" #fi; ================================================ FILE: Delivery/macOS/Components/Components.storyboard ================================================ Usually Jira task ids which are of form LETTER-NUMBER, but can be anything really, important is that same task should have same id ================================================ FILE: Delivery/macOS/Components/EditableTimeBox.swift ================================================ // // EditableTimeBox.swift // Jirassic // // Created by Cristian Baluta on 28/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class EditableTimeBox: TimeBox { var isEditing = false var wasEdited = false var didBeginEditing: (() -> Void)? var didEndEditing: (() -> Void)? private var partialValue = "" private let predictor = PredictiveTimeTyping() override func awakeFromNib() { super.awakeFromNib() timeTextField?.delegate = self } } extension EditableTimeBox: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true partialValue = stringValue self.borderColor = NSColor.darkGray timeTextField?.textColor = NSColor.black return true } public func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if wasEdited { wasEdited = false didEndEditing?() } self.borderColor = NSColor.white timeTextField?.textColor = NSColor.darkGray return true } public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { // Detect Enter key if wasEdited && commandSelector == #selector(NSResponder.insertNewline(_:)) { didEndEditing?() wasEdited = false } return false } // override func controlTextDidBeginEditing (_ obj: Notification) { // isEditing = true // partialValue = stringValue // self.borderColor = NSColor.darkGray // timeTextField?.textColor = NSColor.black // } func controlTextDidChange (_ obj: Notification) { wasEdited = true let comps = stringValue.components(separatedBy: partialValue) let newDigit = (comps.count == 1 && partialValue != "") ? "" : comps.last partialValue = predictor.timeByAdding(newDigit!, to: partialValue) stringValue = partialValue } // override func controlTextDidEndEditing (_ obj: Notification) { // if wasEdited { // wasEdited = false // didEndEditing?() // } // self.borderColor = NSColor.white // timeTextField?.textColor = NSColor.darkGray // } } ================================================ FILE: Delivery/macOS/Components/GitUsersViewController.swift ================================================ // // GitUsersViewController.swift // Jirassic // // Created by Cristian Baluta on 16/12/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class GitUsersViewController: NSViewController { @IBOutlet private weak var scrollView: NSScrollView! @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] = [] override func viewDidLoad() { super.viewDidLoad() gitModule.fetchUsers { [weak self] users in self?.users = users self?.tableView.reloadData() } tableView.headerView = nil } @IBAction func handleDoneButton(_ sender: NSButton) { onDone?() } } extension GitUsersViewController: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { return users.count } } extension GitUsersViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { let user = users[row] if (tableColumn?.identifier)?.rawValue == "isSelected" { let allowedAuthors: [String] = pref.string(.settingsGitAuthors).split(separator: ",").map { String($0) } let isSelected = allowedAuthors.contains(user.email) return NSNumber(booleanLiteral: isSelected) } if (tableColumn?.identifier)?.rawValue == "email" { return user.email } return nil } func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { let user = users[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) } else { allowedAuthors = allowedAuthors.filter({$0 != user.email}) } pref.set(allowedAuthors.joined(separator: ","), forKey: .settingsGitAuthors) } } } ================================================ FILE: Delivery/macOS/Components/NewTaskViewController.swift ================================================ // // NewTaskViewController.swift // Jirassic // // Created by Baluta Cristian on 06/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences class NewTaskViewController: NSViewController { @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 startDateTextField: NSTextField! @IBOutlet private weak var startDateButton: NSButton! var onSave: ((_ taskData: TaskCreationData) -> Void)? var onCancel: (() -> Void)? private var activeEditingTextFieldContent = "" private var issueTypes = [String]() private let predictor = PredictiveTimeTyping() private let pref = RCPreferences() var dateStart: Date? { get { if pref.bool(.useDuration) { return self.duration > 0 ? self.dateEnd.addingTimeInterval(-self.duration) : nil } else { if self.startDateTextField!.stringValue == "" { return nil } let hm = Date.parseHHmm(self.startDateTextField!.stringValue) return self.initialDate.dateByUpdating(hour: hm.hour, minute: hm.min) } } set { if let startDate = newValue { self.startDateTextField.stringValue = startDate.HHmm() } } } var initialDate = Date() var dateEnd: Date { get { let hm = Date.parseHHmm(self.endDateTextField!.stringValue) return self.initialDate.dateByUpdating(hour: hm.hour, minute: hm.min) } set { self.initialDate = newValue self.endDateTextField.stringValue = newValue.HHmm() self.estimateTaskType() } } var duration: TimeInterval { get { if self.startDateTextField.stringValue == "" { return 0.0 } let hm = Date.parseHHmm(self.startDateTextField.stringValue) return Double(hm.min).minToSec + Double(hm.hour).hoursToSec } } var notes: String { get { return notesTextField.stringValue } set { self.notesTextField.stringValue = newValue } } var taskNumber: String { get { return issueIdTextField.stringValue } set { self.issueIdTextField.stringValue = newValue } } override func viewDidLoad() { super.viewDidLoad() taskTypeSelector.removeAllItems() taskTypeSelector.addItems(withTitles: ["Task", "Scrum", "Meeting", "Food", "Social & Media", "Learning", "Code review"]) taskTypeSelector.selectItem(at: 0) setupStartDateButtonTitle() startDateTextField.toolTip = "Predictive Time Typing (use digits and backspace):\n • First digit if between 2 and 9 means AM hours.\n • Leaving minutes empty, defaults to 00.\n • Last digit replaces itself, no need to delete." endDateTextField.toolTip = startDateTextField.toolTip } private func selectedTaskType() -> TaskType { switch taskTypeSelector.indexOfSelectedItem { case 0: return .issue case 1: return .scrum case 2: return .meeting case 3: return .lunch case 4: return .waste case 5: return .learning case 6: return .coderev default: return .issue } } 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!) } } private func setupStartDateButtonTitle() { startDateButton.title = pref.bool(.useDuration) ? "Duration" : "Date start" } } extension NewTaskViewController: NSTextFieldDelegate { func controlTextDidBeginEditing (_ obj: Notification) { if let textField = obj.object as? NSTextField { guard 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 } } } extension NewTaskViewController { @IBAction func handleTaskTypeSelector (_ sender: NSPopUpButton) { issueIdTextField.isEnabled = selectedTaskType() == .issue } @IBAction func handleSaveButton (_ sender: NSButton) { let taskData = TaskCreationData( dateStart: self.dateStart, dateEnd: self.dateEnd, taskNumber: self.taskNumber != "" ? self.taskNumber : nil, notes: self.notes != "" ? self.notes : nil, taskType: selectedTaskType() ) self.onSave?(taskData) } @IBAction func handleCancelButton (_ sender: NSButton) { self.onCancel?() } @IBAction func handleDurationButton (_ sender: NSButton) { pref.set(!pref.bool(.useDuration), forKey: .useDuration) setupStartDateButtonTitle() } } extension NewTaskViewController { override func mouseDown(with event: NSEvent) { // RCLog(event) } } ================================================ FILE: Delivery/macOS/Components/TimeBox.swift ================================================ // // TimeBox.swift // Jirassic // // Created by Cristian Baluta on 19/03/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class TimeBox: NSBox { internal var timeTextField: NSTextField? var stringValue: String { get { return timeTextField!.stringValue } set { timeTextField!.stringValue = newValue } } var isDark: Bool = false { 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 } else { 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 } } } var onClick: (() -> Void)? init() { super.init(frame: NSRect.zero) stringValue = "" } required init?(coder decoder: NSCoder) { super.init(coder: decoder) } override func awakeFromNib() { super.awakeFromNib() self.borderType = .noBorder timeTextField = NSTextField() timeTextField?.font = NSFont.boldSystemFont(ofSize: 10) timeTextField?.textColor = NSColor.darkGray timeTextField?.backgroundColor = NSColor.clear timeTextField?.drawsBackground = false timeTextField?.alignment = .center timeTextField?.focusRingType = .none timeTextField?.placeholderString = "00:00" timeTextField?.isEnabled = false timeTextField?.isEditable = false self.addSubview(timeTextField!) timeTextField?.translatesAutoresizingMaskIntoConstraints = false let viewsDictionary = ["view": timeTextField!] 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)) } override func mouseDown(with event: NSEvent) { onClick?() } override func updateTrackingAreas() { super.updateTrackingAreas() let trackingArea = NSTrackingArea(rect: NSZeroRect, options: [ NSTrackingArea.Options.inVisibleRect, NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited ], owner: self, userInfo: nil) if !self.trackingAreas.contains(trackingArea) { self.addTrackingArea(trackingArea) } } } ================================================ FILE: Delivery/macOS/Components/TimeBoxViewController.swift ================================================ // // TimeBoxViewController.swift // Jirassic // // Created by Cristian Baluta on 27/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class TimeBoxViewController: NSViewController { @IBOutlet private weak var timeTextField: NSTextField! @IBOutlet private weak var instructionsTextField: NSTextField! @IBOutlet private weak var disclosureButton: NSButton! var didCancel: (() -> Void)? var didSave: (() -> Void)? private var partialValue = "" private var isEditing = false private var wasEdited = false private let predictor = PredictiveTimeTyping() var stringValue: String { get { return timeTextField.stringValue } set { timeTextField.stringValue = newValue } } override func viewDidLoad() { super.viewDidLoad() disclosureButton.state = .on instructionsTextField.stringValue = "Predictive Time Typing (use digits and backspace):\n • First digit if between 2 and 9 means AM hours.\n • Leaving minutes empty, defaults to 00.\n • Last digit replaces itself, no need to delete." } } extension TimeBoxViewController { @IBAction func handleCancelButton (_ sender: NSButton) { didCancel?() } @IBAction func handleSaveButton (_ sender: NSButton) { didSave?() } @IBAction func handleDisclosureButton (_ sender: NSButton) { instructionsTextField.stringValue = sender.state == .off ? "" : "Predictive time typing:\n • First digit between 2 and 9 means AM.\n • Leaving minutes empty defaults to 00.\n • Last digit replaces itself." } } extension TimeBoxViewController: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true partialValue = stringValue return true } public func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if wasEdited { wasEdited = false } return true } public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { // Detect Enter key if wasEdited && commandSelector == #selector(NSResponder.insertNewline(_:)) { wasEdited = false } return false } func controlTextDidChange (_ obj: Notification) { wasEdited = true let comps = stringValue.components(separatedBy: partialValue) // The difference between original string and new string is the last digit var newDigit = comps.last // If textfield was selected, entering a new digit replaces everything and it has the length 1 if stringValue.count == 1 && partialValue.count > 2 { partialValue = "" } // Detect backspace else if comps.count == 1 && partialValue != "" { newDigit = nil } partialValue = predictor.timeByAdding(newDigit ?? "", to: partialValue) stringValue = partialValue } } ================================================ FILE: Delivery/macOS/External/AppleScript.swift ================================================ // // AppleScript.swift // Jirassic // // Created by Cristian Baluta on 20/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Foundation import CoreServices import Carbon.OpenScripting import RCLog fileprivate let commandRunShellScript = "runShellScript" fileprivate let commandGetScriptVersion = "getScriptVersion" fileprivate let commandGetBrowserInfo = "getBrowserInfo" class AppleScript: AppleScriptProtocol { private func validateTarget() { #if APPSTORE fatalError("For sandboxed apps, SandboxedAppleScript must be used") #endif } var scriptsDirectory: URL? { validateTarget() return Bundle.main.resourceURL } var binPaths: [String] { // Paths given by the app: /Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin // Paths given by Terminal echo $PATH: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin // There is surely something weird here // Lets just remove paths matching xcode and insert usr/local/bin if does not exist let localBin = "/usr/local/bin" let envPaths = ProcessInfo.processInfo.environment["PATH"] ?? "" var paths = envPaths.split(separator: ":").map({ String($0) }) paths = paths.filter({ !$0.contains("Xcode.app") }) if !paths.contains(localBin) { paths.insert(localBin, at: 0) } return paths } func run (command: String, completion: @escaping (String?) -> Void) { RCLog("-------------------------------------------------") RCLog("Running command: \(command)") let args = NSAppleEventDescriptor.list() args.insert(NSAppleEventDescriptor(string: command), at: 1) run (command: commandRunShellScript, scriptNamed: kShellSupportScriptName, args: args, completion: { descriptor in if let descriptor = descriptor, let result = descriptor.stringValue { RCLog("Result: \(result)") RCLog("-------------------------------------------------") completion(result) } else { completion(nil) } }) } func getScriptVersion (script: String, completion: @escaping (String) -> Void) { run (command: commandGetScriptVersion, scriptNamed: script, args: nil, completion: { descriptor in if let descriptor = descriptor, let result = descriptor.stringValue { completion(result) } else { completion("") } }) } func getJitInfo (completion: @escaping ([String: String]) -> Void) { getJitInfo (paths: binPaths, completion: completion) } private func getJitInfo (paths: [String], completion: @escaping ([String: String]) -> Void) { guard paths.count > 0 else { completion([:]) return } var remainingPaths = paths let path = remainingPaths.removeFirst() let command = "\(path)/jit info" let args = NSAppleEventDescriptor.list() args.insert(NSAppleEventDescriptor(string: command), at: 1) run (command: commandRunShellScript, scriptNamed: kShellSupportScriptName, args: args, completion: { descriptor in if let descriptor = descriptor, let rawJson = descriptor.stringValue { // json received from jit contains ' instead " because otherwise is not valid when passed 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 = d { dict = _d } } } completion(dict) } else { self.getJitInfo (paths: remainingPaths, completion: completion) } }) } func getJirassicVersion (completion: @escaping (String) -> Void) { getJirassicVersion(paths: binPaths, completion: completion) } private func getJirassicVersion (paths: [String], completion: @escaping (String) -> Void) { guard paths.count > 0 else { completion("") return } var remainingPaths = paths let path = remainingPaths.removeFirst() let command = "\(path)/jirassic version" let args = NSAppleEventDescriptor.list() args.insert(NSAppleEventDescriptor(string: command), at: 1) run (command: commandRunShellScript, scriptNamed: kShellSupportScriptName, args: args, completion: { descriptor in if let descriptor = descriptor, let result = descriptor.stringValue { completion(result) } else { self.getJirassicVersion(paths: remainingPaths, completion: completion) } }) } func getBrowserInfo (browserId: String, browserName: String, completion: @escaping (String, String) -> Void) { let args = NSAppleEventDescriptor.list() args.insert(NSAppleEventDescriptor(string: browserId), at: 1) args.insert(NSAppleEventDescriptor(string: browserName), at: 2) run (command: commandGetBrowserInfo, scriptNamed: kBrowserSupportScriptName, args: args, completion: { descriptor in if let descriptor = descriptor { let url = descriptor.atIndex(1)?.stringValue ?? "" let title = descriptor.atIndex(2)?.stringValue ?? "" completion(url, title) } else { RCLog("Cannot get browser info") completion("", "") } }) } func downloadFile (from: String, to: String, completion: @escaping (Bool) -> Void) { // let asc = NSAppleScript(source: "do shell script \"sudo cp \(from) \(to)\" with administrator privileges") let sessionConfig = URLSessionConfiguration.default let session = URLSession(configuration: sessionConfig) let request = URLRequest(url: URL(string: from)!) let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in if let tempLocalUrl = tempLocalUrl, error == nil { // Success if let statusCode = (response as? HTTPURLResponse)?.statusCode { RCLog("Success: \(statusCode)") } // do { // try FileManager.default.copyItem(at: tempLocalUrl, to: URL(fileURLWithPath: to)) // completion(true) // } catch (let writeError) { // print("error writing file \(to) : \(writeError)") // completion(false) // } RCLog(from) RCLog(to) let asc = NSAppleScript(source: "do shell script \"sudo cp \(tempLocalUrl.path) \(to)\" with administrator privileges") if let response = asc?.executeAndReturnError(nil) { RCLog(response) let asc = NSAppleScript(source: "chmod +x \(to)") if let response = asc?.executeAndReturnError(nil) { RCLog(response) completion(true) } else { RCLog("Could not download Jit from \(from) to \(to)") completion(false) } } else { RCLog("Could not download Jit from \(from) to \(to)") completion(false) } } else { RCLog("Failure: \(error!.localizedDescription)") } } task.resume() } func removeFile (from: String, completion: @escaping (Bool) -> Void) { } } extension AppleScript { fileprivate func run (command: String, scriptNamed: String, args: NSAppleEventDescriptor?, completion: @escaping (NSAppleEventDescriptor?) -> Void) { guard let scriptsDirectory = self.scriptsDirectory else { completion(nil) return } let scriptURL = scriptsDirectory.appendingPathComponent(scriptNamed + ".scpt") do { var pid = ProcessInfo.processInfo.processIdentifier let targetDescriptor = NSAppleEventDescriptor(descriptorType: typeKernelProcessID, bytes: &pid, length: MemoryLayout.size(ofValue: pid)) let theEvent = NSAppleEventDescriptor.appleEvent(withEventClass: AEEventClass(kASAppleScriptSuite),//kCoreEventClass, eventID: AEEventID(kASSubroutineEvent),//kAEOpenDocuments, targetDescriptor: targetDescriptor, returnID: AEReturnID(kAutoGenerateReturnID), transactionID: AETransactionID(kAnyTransactionID)) let commandDescriptor = NSAppleEventDescriptor(string: command) theEvent.setDescriptor(commandDescriptor, forKeyword: AEKeyword(keyASSubroutineName)) if let args = args { theEvent.setDescriptor(args, forKeyword: keyDirectObject) } let result = try NSUserAppleScriptTask(url: scriptURL) result.execute(withAppleEvent: theEvent, completionHandler: { (descriptor, error) in //RCLogO(descriptor) RCLogErrorO(error) DispatchQueue.main.sync { completion(descriptor) } }) } catch { completion(nil) } } } ================================================ FILE: Delivery/macOS/External/AppleScriptProtocol.swift ================================================ // // AppleScriptProtocol.swift // Jirassic // // Created by Cristian Baluta on 24/03/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation protocol AppleScriptProtocol { var scriptsDirectory: URL? {get} func run (command: String, completion: @escaping (String?) -> Void) func getScriptVersion (script: String, completion: @escaping (String) -> Void) func getJitInfo (completion: @escaping ([String: String]) -> Void) func getJirassicVersion (completion: @escaping (String) -> Void) func getBrowserInfo (browserId: String, browserName: String, completion: @escaping (String, String) -> Void) func downloadFile (from: String, to: String, completion: @escaping (Bool) -> Void) func removeFile (from: String, completion: @escaping (Bool) -> Void) } ================================================ FILE: Delivery/macOS/External/ExtensionsInstallerInteractor.swift ================================================ // // ExtensionsInstallerInteractor.swift // Jirassic // // Created by Cristian Baluta on 14/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import Cocoa class ExtensionsInstallerInteractor: ExtensionsInteractor { fileprivate let scripts: AppleScriptProtocol = AppleScript() fileprivate let fileManager = FileManager.default // func saveJiraSettings (_ settings: JiraSettings, completion: @escaping (Bool) -> Void) { // // var values = "jira_url=\(settings.url!)\njira_user=\(settings.user!)" // if let password = settings.password { // values += "\njira_password=\(password)" // } // // scripts.setupJitWithValues(values, completion: { success in // completion(success) // }) // } func installJirassic (_ completion: @escaping (Bool) -> Void) { scripts.downloadFile(from: "https://raw.githubusercontent.com/ralcr/Jit/master/build/jit", to: "/usr/local/bin/jirassic", completion: { success in completion(success) }) } func installJit (_ completion: @escaping (Bool) -> Void) { scripts.downloadFile(from: "https://raw.githubusercontent.com/ralcr/Jit/master/build/jit", to: "/usr/local/bin/jit", completion: { success in completion(success) }) } func uninstallTools (_ completion: @escaping (Bool) -> Void) { if isShellSupportInstalled() { uninstallCmds({ success in if success { if let bookmark = UserDefaults.standard.object(forKey: kShellSupportScriptName) as? NSData as Data? { var stale = false if let url = try? URL(resolvingBookmarkData: bookmark, options: URL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &stale) { let _ = url.startAccessingSecurityScopedResource() self.uninstallScript(atUrl: url, completion) url.stopAccessingSecurityScopedResource() } } // let scriptsDirectory = self.scripts.scriptsDirectory! // let scriptUrl = scriptsDirectory.appendingPathComponent("\(self.scriptsName).scpt") // self.uninstallScript(atUrl: scriptUrl, completion) } else { completion(false) } }) } else { completion(false) } } } extension ExtensionsInstallerInteractor { fileprivate func isShellSupportInstalled() -> Bool { let scriptsDirectory = scripts.scriptsDirectory! return fileManager.fileExists(atPath: scriptsDirectory.appendingPathComponent("\(kShellSupportScriptName).scpt").path) } fileprivate func isBrowserSupportInstalled() -> Bool { let scriptsDirectory = scripts.scriptsDirectory! return fileManager.fileExists(atPath: scriptsDirectory.appendingPathComponent("\(kBrowserSupportScriptName).scpt").path) } fileprivate func installScriptAndCmds (_ completion: @escaping (Bool) -> Void) { installScript(script: kShellSupportScriptName, { success in self.installJit(completion) }) } fileprivate func installScript (script: String, _ completion: @escaping (Bool) -> Void) { let panel = NSSavePanel() panel.nameFieldStringValue = "\(script).scpt" panel.directoryURL = scripts.scriptsDirectory! panel.message = "Please select: User / Library / Application Scripts / com.ralcr.Jirassic.osx" panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) panel.begin { (result) in if result.rawValue == NSFileHandlingPanelOKButton { let scriptPath = Bundle.main.url(forResource: script, withExtension: ".scpt") do { try? self.fileManager.copyItem(at: scriptPath!, to: panel.url!) let bookmark = try? panel.url!.bookmarkData(options: URL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) UserDefaults.standard.set(bookmark, forKey: kShellSupportScriptName) UserDefaults.standard.synchronize() completion(true) } } else { completion(false) } } } fileprivate func uninstallScript (atUrl url: URL, _ completion: @escaping (Bool) -> Void) { try? fileManager.removeItem(at: url) completion(true) } // fileprivate func installJit (_ completion: @escaping (Bool) -> Void) { // // let bundlePath = Bundle.main.url(forResource: "jit", withExtension: nil)!.deletingLastPathComponent() // // scripts.downloadFile(from: bundlePath.path + "/", to: kLocalBinPath, completion: { success in // completion(success) // }) // } fileprivate func uninstallCmds (_ completion: @escaping (Bool) -> Void) { scripts.removeFile(from: kLocalBinPath, completion: { success in completion(success) }) } } ================================================ FILE: Delivery/macOS/External/ExtensionsInteractor.swift ================================================ // // CMDToolsInstaller.swift // Jirassic // // Created by Cristian Baluta on 04/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation let kShellSupportScriptName = "ShellSupport" let kBrowserSupportScriptName = "BrowserSupport" let kLocalBinPath = "/usr/local/bin/" class ExtensionsInteractor { fileprivate let scripts: AppleScriptProtocol! init() { #if APPSTORE scripts = SandboxedAppleScript() #else scripts = AppleScript() #endif } func getJiraSettings (completion: @escaping ([String: String]) -> Void) { scripts.getJitInfo(completion: { dict in completion(dict) }) } func getBrowserInfo (browserId: String, browserName: String, completion: @escaping (String, String) -> Void) { scripts.getBrowserInfo(browserId: browserId, browserName: browserName, completion: completion) } func run (command: String, completion: @escaping (String?) -> Void) { scripts.run(command: command, completion: completion) } func getVersions (completion: @escaping (_ versions: Versions) -> Void) { self.scripts.getScriptVersion (script: kShellSupportScriptName, completion: { shellSupportScriptVersion in self.scripts.getScriptVersion (script: kBrowserSupportScriptName, completion: { browserSupportScriptVersion in self.scripts.getJirassicVersion (completion: { jirassicVersion in self.scripts.getJitInfo (completion: { dict in let jitVersion = dict["version"] ?? "" let versions = Versions(shellScript: shellSupportScriptVersion, browserScript: browserSupportScriptVersion, jirassicCmd: jirassicVersion, jitCmd: jitVersion ) completion(versions) }) }) }) }) } } ================================================ FILE: Delivery/macOS/External/SandboxedAppleScript.swift ================================================ // // SandboxedAppleScriptt.swift // Jirassic // // Created by Cristian Baluta on 20/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Foundation class SandboxedAppleScript: AppleScript { override var scriptsDirectory: URL? { return try? FileManager.default.url(for: .applicationScriptsDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true) } override func downloadFile (from: String, to: String, completion: @escaping (Bool) -> Void) { fatalError("File manipulation not supported in AppStore") // let args = NSAppleEventDescriptor.list() // args.insert(NSAppleEventDescriptor(string: from), at: 1) // args.insert(NSAppleEventDescriptor(string: to), at: 2) // // run (command: "install", scriptNamed: kShellSupportScriptName, args: args, completion: { descriptor in // completion(descriptor != nil) // }) } override func removeFile (from: String, completion: @escaping (Bool) -> Void) { fatalError("File manipulation not supported in AppStore") // let args = NSAppleEventDescriptor.list() // args.insert(NSAppleEventDescriptor(string: from), at: 1) // // run (command: "uninstall", scriptNamed: kShellSupportScriptName, args: args, completion: { descriptor in // completion(descriptor != nil) // }) } } ================================================ FILE: Delivery/macOS/IAP/IAPHelper.swift ================================================ // // IAPHelper.swift // IAPDemo // // Created by Jason Zheng on 8/24/16. // Copyright © 2016 Jason Zheng. All rights reserved. // import StoreKit extension SKProduct { public func localizedPrice() -> String? { let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.locale = self.priceLocale return formatter.string(from: self.price) } } public let IAP = IAPHelper.sharedInstance public typealias ProductIdentifier = String public typealias ProductWithExpireDate = [ProductIdentifier: Date] public typealias ProductsRequestHandler = (_ response: SKProductsResponse?, _ error: Error?) -> () public typealias PurchaseHandler = (_ productIdentifier: ProductIdentifier?, _ error: Error?) -> () public typealias RestoreHandler = (_ productIdentifiers: Set, _ error: Error?) -> () public typealias ValidateHandler = (_ statusCode: Int?, _ products: ProductWithExpireDate?, _ json: [String: Any]?) -> () public class IAPHelper: NSObject { private override init() { super.init() addObserver() } static let sharedInstance = IAPHelper() fileprivate var productsRequest: SKProductsRequest? fileprivate var productsRequestHandler: ProductsRequestHandler? fileprivate var purchaseHandler: PurchaseHandler? fileprivate var restoreHandler: RestoreHandler? private var observerAdded = false public func addObserver() { if !observerAdded { observerAdded = true SKPaymentQueue.default().add(self) } } public func removeObserver() { if observerAdded { observerAdded = false SKPaymentQueue.default().remove(self) } } } // MARK: StoreKit API extension IAPHelper { public func requestProducts(_ productIdentifiers: Set, handler: @escaping ProductsRequestHandler) { productsRequest?.cancel() productsRequestHandler = handler productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers) productsRequest?.delegate = self productsRequest?.start() } public func purchaseProduct(_ productIdentifier: ProductIdentifier, handler: @escaping PurchaseHandler) { purchaseHandler = handler let payment = SKMutablePayment() payment.productIdentifier = productIdentifier SKPaymentQueue.default().add(payment) } public func restorePurchases(_ handler: @escaping RestoreHandler) { restoreHandler = handler SKPaymentQueue.default().restoreCompletedTransactions() } /* * password: Only used for receipts that contain auto-renewable subscriptions. * It's your app’s shared secret (a hexadecimal string) which was generated on iTunesConnect. */ public func validateReceipt(_ password: String? = nil, handler: @escaping ValidateHandler) { validateReceiptInternal(true, password: password) { (statusCode, products, json) in if let statusCode = statusCode , statusCode == ReceiptStatus.testReceipt.rawValue { self.validateReceiptInternal(false, password: password, handler: { (statusCode, products, json) in handler(statusCode, products, json) }) } else { handler(statusCode, products, json) } } } } // MARK: SKProductsRequestDelegate extension IAPHelper: SKProductsRequestDelegate { public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { productsRequestHandler?(response, nil) clearRequestAndHandler() } public func request(_ request: SKRequest, didFailWithError error: Error) { productsRequestHandler?(nil, error) clearRequestAndHandler() } private func clearRequestAndHandler() { productsRequest = nil productsRequestHandler = nil } } // MARK: SKPaymentTransactionObserver extension IAPHelper: SKPaymentTransactionObserver { public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch (transaction.transactionState) { case SKPaymentTransactionState.purchased: completePurchaseTransaction(transaction) case SKPaymentTransactionState.restored: finishTransaction(transaction) case SKPaymentTransactionState.failed: failedTransaction(transaction) case SKPaymentTransactionState.purchasing, SKPaymentTransactionState.deferred: break } } } public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { completeRestoreTransactions(queue, error: nil) } public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { completeRestoreTransactions(queue, error: error) } private func completePurchaseTransaction(_ transaction: SKPaymentTransaction) { purchaseHandler?(transaction.payment.productIdentifier, transaction.error) purchaseHandler = nil finishTransaction(transaction) } private func completeRestoreTransactions(_ queue: SKPaymentQueue, error: Error?) { var productIdentifiers = Set() for transaction in queue.transactions { if let productIdentifier = transaction.original?.payment.productIdentifier { productIdentifiers.insert(productIdentifier) } finishTransaction(transaction) } restoreHandler?(productIdentifiers, error) restoreHandler = nil } private func failedTransaction(_ transaction: SKPaymentTransaction) { // NOTE: Both purchase and restore may come to this state. So need to deal with both handlers. purchaseHandler?(nil, transaction.error) purchaseHandler = nil restoreHandler?(Set(), transaction.error) restoreHandler = nil finishTransaction(transaction) } // MARK: Helper private func finishTransaction(_ transaction: SKPaymentTransaction) { switch transaction.transactionState { case SKPaymentTransactionState.purchased, SKPaymentTransactionState.restored, SKPaymentTransactionState.failed: SKPaymentQueue.default().finishTransaction(transaction) default: break } } } // MARK: Validate Receipt extension IAPHelper { fileprivate func validateReceiptInternal(_ isProduction: Bool, password: String?, handler: @escaping ValidateHandler) { let serverURL = isProduction ? "https://buy.itunes.apple.com/verifyReceipt" : "https://sandbox.itunes.apple.com/verifyReceipt" let appStoreReceiptURL = Bundle.main.appStoreReceiptURL guard let receiptData = receiptData(appStoreReceiptURL, password: password), let url = URL(string: serverURL) else { handler(ReceiptStatus.noRecipt.rawValue, nil, nil) return } let request = NSMutableURLRequest(url: url) request.httpMethod = "POST" request.httpBody = receiptData let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in guard let data = data, error == nil else { handler(nil, nil, nil) return } do { let json = try JSONSerialization.jsonObject(with: data, options:[]) as? [String: Any] let statusCode = json?["status"] as? Int let products = self.parseValidateResultJSON(json) handler(statusCode, products, json) } catch { handler(nil, nil, nil) } } task.resume() } internal func parseValidateResultJSON(_ json: [String: Any]?) -> ProductWithExpireDate? { var products = ProductWithExpireDate() var canceledProducts = ProductWithExpireDate() var productDateDict = [String: [ProductDateHelper]]() let dateOf5000 = Date(timeIntervalSince1970: 95617584000) // 5000-01-01 var totalInAppPurchaseList = [[String: Any]]() if let receipt = json?["receipt"] as? [String: Any], let inAppPurchaseList = receipt["in_app"] as? [[String: Any]] { totalInAppPurchaseList += inAppPurchaseList } if let inAppPurchaseList = json?["latest_receipt_info"] as? [[String: Any]] { totalInAppPurchaseList += inAppPurchaseList } for inAppPurchase in totalInAppPurchaseList { if let productID = inAppPurchase["product_id"] as? String, let purchaseDate = parseDate(inAppPurchase["purchase_date_ms"] as? String) { let expiresDate = parseDate(inAppPurchase["expires_date_ms"] as? String) let cancellationDate = parseDate(inAppPurchase["cancellation_date_ms"] as? String) let productDateHelper = ProductDateHelper(purchaseDate: purchaseDate, expiresDate: expiresDate, canceledDate: cancellationDate) if productDateDict[productID] == nil { productDateDict[productID] = [productDateHelper] } else { productDateDict[productID]?.append(productDateHelper) } if let cancellationDate = cancellationDate { if let lastCanceledDate = canceledProducts[productID] { if lastCanceledDate.timeIntervalSince1970 < cancellationDate.timeIntervalSince1970 { canceledProducts[productID] = cancellationDate } } else { canceledProducts[productID] = cancellationDate } } } } for (productID, productDateHelpers) in productDateDict { var date = Date(timeIntervalSince1970: 0) let lastCanceledDate = canceledProducts[productID] for productDateHelper in productDateHelpers { let validDate = productDateHelper.getValidDate(lastCanceledDate: lastCanceledDate, unlimitedDate: dateOf5000) if date.timeIntervalSince1970 < validDate.timeIntervalSince1970 { date = validDate } } products[productID] = date } return products.isEmpty ? nil : products } private func receiptData(_ appStoreReceiptURL: URL?, password: String?) -> Data? { guard let receiptURL = appStoreReceiptURL, let receipt = try? Data(contentsOf: receiptURL) else { return nil } do { let receiptData = receipt.base64EncodedString() var requestContents = ["receipt-data": receiptData] if let password = password { requestContents["password"] = password } let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: []) return requestData } catch let error { NSLog("\(error)") } return nil } private func parseDate(_ str: String?) -> Date? { guard let str = str, let msTimeInterval = TimeInterval(str) else { return nil } return Date(timeIntervalSince1970: msTimeInterval / 1000) } } internal struct ProductDateHelper { var purchaseDate = Date(timeIntervalSince1970: 0) var expiresDate: Date? = nil var canceledDate: Date? = nil func getValidDate(lastCanceledDate: Date?, unlimitedDate: Date) -> Date { if let lastCanceledDate = lastCanceledDate { return (purchaseDate.timeIntervalSince1970 > lastCanceledDate.timeIntervalSince1970) ? (expiresDate ?? unlimitedDate) : lastCanceledDate } if let canceledDate = canceledDate { return canceledDate } else if let expiresDate = expiresDate { return expiresDate } else { return unlimitedDate } } } public enum ReceiptStatus: Int { case noRecipt = -999 case valid = 0 case testReceipt = 21007 } ================================================ FILE: Delivery/macOS/IAP/Store.swift ================================================ // // Created by Cristian Baluta on 27/06/2018. // Copyright © 2018 Baluta Cristian. All rights reserved. // import Foundation import StoreKit import RCPreferences import RCLog enum StoreProduct: String, RCPreferencesProtocol { case git = "com.jirassic.macos.git.6months" case jiraTempo = "com.jirassic.macos.jiratempo.6months" case full = "com.jirassic.macos.full.6months" func defaultValue() -> Any { switch self { case .git: return false case .jiraTempo: return false case .full: return false } } } class Store { static let shared = Store() private let localPref = RCPreferences() private var products = [SKProduct]() init() { // getProducts { (success) in } // let receiptUrl = Bundle.main.appStoreReceiptURL } var isGitPurchased: Bool { return true // return localPref.bool(.full) || localPref.bool(.git) } var isJiraTempoPurchased: Bool { return true // return localPref.bool(.full) || localPref.bool(.jiraTempo) } func getProduct(_ product: StoreProduct, _ completion: @escaping (SKProduct?) -> Void) { guard let skProduct = products.filter({$0.productIdentifier == product.rawValue}).first else { getProducts { (success) in if success { self.getProduct(product, completion) } else { completion(nil) } } return } completion(skProduct) } private func getProducts(_ completion: @escaping (Bool) -> Void) { var productIdentifiers = Set() productIdentifiers.insert(StoreProduct.git.rawValue) productIdentifiers.insert(StoreProduct.jiraTempo.rawValue) productIdentifiers.insert(StoreProduct.full.rawValue) IAP.requestProducts(productIdentifiers) { (response, error) in if let products = response?.products, !products.isEmpty { self.products = products RCLog(products) completion(true) } else if let _ = response?.invalidProductIdentifiers { completion(false) } else { // Some error happened completion(false) } } } func purchase(product: StoreProduct, _ completion: @escaping (Bool) -> Void) { IAP.purchaseProduct(product.rawValue, handler: { (productIdentifier, error) in if let product = StoreProduct(rawValue: productIdentifier ?? "") { self.localPref.set(true, forKey: product) completion(true) } else if let error = error as NSError? { if error.code == SKError.Code.paymentCancelled.rawValue { // User cancelled RCLog("purchase cancelled") } else { // Some error happened } completion(false) } }) } func restore(_ completion: @escaping (Bool) -> Void) { IAP.restorePurchases({ (productIdentifiers, error) in RCLog("Restored products: \(productIdentifiers)") if let error = error as NSError? { if error.code == SKError.Code.paymentCancelled.rawValue { // User cancelled RCLog("canceled") } else { // Some error happened } completion(false) } else { // Reset all products self.localPref.set(false, forKey: .full) // Set purchased to true for purchased items for productIdentifier in productIdentifiers.reversed() { if let product = StoreProduct(rawValue: productIdentifier) { self.localPref.set(true, forKey: product) } } completion(true) } }) } } ================================================ FILE: Delivery/macOS/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "icon_jirassic_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "icon_jirassic_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon_jirassic_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon_jirassic_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon_jirassic_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon_jirassic_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon_jirassic_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon_jirassic_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon_jirassic_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon_jirassic_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/AppStoreIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "icon_jirassic_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "icon_jirassic_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon_jirassic_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "icon_jirassic_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon_jirassic_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "icon_jirassic_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon_jirassic_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "icon_jirassic_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon_jirassic_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "icon_jirassic_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/ButClose.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "ButClose.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "ButClose@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/ButDisabled.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "ButDisabled.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "ButDisabled@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/ButMinimize.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "ButMinimize.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "ButMinimize@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/CalendarIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "Calendar-2.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "Calendar-3.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "GitIcon.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/MenuBarIcon-Normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "MenuBarIcon-Normal.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "MenuBarIcon-Normal@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Delivery/macOS/Images.xcassets/MenuBarIcon-Selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "MenuBarIcon-Selected.png", "scale" : "1x" }, { "idiom" : "mac", "filename" : "MenuBarIcon-Selected@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/Plus.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "AddMailboxTemplate-2.pdf" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Delivery/macOS/Images.xcassets/WarningButton.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "scale" : "1x" }, { "idiom" : "mac", "filename" : "warning.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Images.xcassets/WarningIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "scale" : "1x" }, { "idiom" : "mac", "filename" : "warning.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Delivery/macOS/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile Jirassic CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 18.12.12 CFBundleSignature ???? CFBundleVersion 11 LSApplicationCategoryType public.app-category.productivity LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement NSAppleScriptEnabled NSCalendarsUsageDescription Needed to list meetings NSCalendarsFullAccessUsageDescription Needed to list meetings NSHumanReadableCopyright Copyright © 2018 Imagin Soft. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass NSApplication OSAScriptingDefinition jirassic.sdef ================================================ FILE: Delivery/macOS/Jirassic.entitlements ================================================ com.apple.developer.icloud-container-identifiers iCloud.$(CFBundleIdentifier) com.apple.developer.icloud-services CloudKit com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox com.apple.security.files.user-selected.read-only com.apple.security.network.client com.apple.security.network.server com.apple.security.personal-information.calendars ================================================ FILE: Delivery/macOS/Menu/MenuBarController.swift ================================================ // // MenuBarController.swift // Jirassic // // Created by Baluta Cristian on 26/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa class MenuBarController: NSObject { fileprivate let bar = NSStatusBar.system fileprivate var item: NSStatusItem! var iconView: MenuBarIconView! var onOpen: (() -> ())? var onClose: (() -> ())? var appearsDisabled: Bool? { set { iconView.alphaValue = newValue == true ? 0.4 : 1.0 } get { return iconView.alphaValue != 1.0 } } var isDark: Bool? { didSet { iconView.isDark = isDark } } override init() { super.init() let length: CGFloat = -1 //NSVariableStatusItemLength item = bar.statusItem(withLength: length) iconView = MenuBarIconView(item: item) iconView.onMouseDown = { if self.iconView.isSelected { self.onOpen?() } else { self.onClose?() } } item.view = iconView } func handleQuitButton() { NSApplication.shared.terminate(nil) } func triggerOpen() { if iconView.isSelected == false { iconView.mouseDown(with: NSEvent()) } } func triggerClose() { if iconView.isSelected == true { iconView.mouseDown(with: NSEvent()) } } } ================================================ FILE: Delivery/macOS/Menu/MenuBarIconView.swift ================================================ // // IconView.swift // SwiftStatusBarApplication // // Created by Tommy Leung on 6/7/14. // Copyright (c) 2014 Tommy Leung. All rights reserved. // import Cocoa class MenuBarIconView : NSView { private(set) var image: NSImage private let item: NSStatusItem var onMouseDown: (() -> ())? var _isSelected = false var isSelected: Bool { get { return _isSelected } set { _isSelected = newValue self.image = NSImage(named: isDark == true || _isSelected ? "MenuBarIcon-Selected" : "MenuBarIcon-Normal")! self.needsDisplay = true } } var isDark: Bool? { didSet { self.image = NSImage(named: isDark == true ? "MenuBarIcon-Selected" : "MenuBarIcon-Normal")! self.needsDisplay = true } } init (item: NSStatusItem) { self.item = item self.image = NSImage(named: "MenuBarIcon-Normal")! let thickness = NSStatusBar.system.thickness let rect = CGRect(x: 0, y: 0, width: thickness, height: thickness) super.init(frame: rect) } required init? (coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw (_ dirtyRect: NSRect) { self.item.drawStatusBarBackground(in: dirtyRect, withHighlight: self.isSelected) let size = self.image.size let rect = CGRect(x: 2, y: 2, width: size.width, height: size.height) image.draw(in: rect) } override func mouseDown (with theEvent: NSEvent) { self.isSelected = !_isSelected onMouseDown?() } override func mouseUp (with theEvent: NSEvent) { } } ================================================ FILE: Delivery/macOS/Modules/CalendarEvents/ModuleCalendar.swift ================================================ // // ModuleCalendar.swift // Jirassic // // Created by Cristian Baluta on 04/07/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import EventKit import RCPreferences class ModuleCalendar { private var eventStore = EKEventStore() private let pref = RCPreferences() var isAuthorizationDetermined: Bool { return EKEventStore.authorizationStatus(for: .event) != .notDetermined } var isAuthorized: Bool { if #available(macOS 14.0, *) { return EKEventStore.authorizationStatus(for: .event) == .fullAccess } else { return EKEventStore.authorizationStatus(for: .event) == .authorized } } var selectedCalendars: [String] { get { return pref.string(.settingsSelectedCalendars).split(separator: ",").map({String($0)}) } set { pref.set(newValue.joined(separator: ","), forKey: .settingsSelectedCalendars) } } func authorize(_ completion: @escaping (Bool) -> Void) { if #available(macOS 14.0, *) { eventStore.requestFullAccessToEvents { granted, error in self.eventStore = EKEventStore() completion(granted) } } else { eventStore.requestAccess(to: .event) { granted, error in self.eventStore = EKEventStore() completion(granted) } } } func allCalendars() -> [EKCalendar] { return eventStore.calendars(for: .event) } func allCalendarsTitles() -> [String] { return allCalendars().map({$0.title}) } func events (dateStart: Date, dateEnd: Date, completion: @escaping (([Task]) -> Void)) { guard isAuthorized else { completion([]) return } var tasks = [Task]() authorize { (granted) in guard granted else { completion([]) return } for calendar in self.allCalendars() { if self.selectedCalendars.contains(calendar.title) { let eventsPredicate = self.eventStore.predicateForEvents(withStart: dateStart, end: dateEnd, calendars: [calendar]) let events: [EKEvent] = self.eventStore.events(matching: eventsPredicate) for event in events { // Create a task without id, this will tell the app that is not saved in db let task = Task(lastModifiedDate: nil, startDate: event.startDate, endDate: event.endDate, notes: event.title, taskNumber: nil, taskTitle: event.title, taskType: .calendar, objectId: nil) tasks.append(task) } DispatchQueue.main.async { completion(tasks) } } } } } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/GitBranchParser.swift ================================================ // // GitBranchParser.swift // Jirassic // // Created by Cristian Baluta on 19/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class GitBranchParser { private var raw: String init (raw: String) { self.raw = raw } func firstBranchName() -> String { return branches().first ?? "" } func branches() -> [String] { var arr = [String]() let r = raw.replacingOccurrences(of: "\r", with: "\n") .replacingOccurrences(of: "*", with: "") .replacingOccurrences(of: " ", with: "") let results = r.split(separator: "\n").map { String($0) } for result in results { if result.count > 0 { arr.append(result) } } return arr } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/GitBranchParserTests.swift ================================================ // // GitBranchParserTests.swift // JirassicTests // // Created by Cristian Baluta on 20/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class GitBranchParserTests: XCTestCase { let raw = " branch1\r" + "branch2\n" + "* branch3\r\n" + " \r" + " branch4" func testBranches() { let parser = GitBranchParser(raw: raw) let branches = parser.branches() XCTAssert(branches.count == 4) XCTAssert(branches[0] == "branch1") XCTAssert(branches[1] == "branch2") XCTAssert(branches[2] == "branch3") XCTAssert(branches[3] == "branch4") } func testBranchName() { let parser = GitBranchParser(raw: raw) let branchName = parser.firstBranchName() XCTAssert(branchName == "branch1") } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/GitCommitsParser.swift ================================================ // // GitCommitsParser.swift // Jirassic // // Created by Cristian Baluta on 17/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class GitCommitsParser { private var raw: String init (raw: String) { self.raw = raw } func toGitCommits() -> [GitCommit] { var commits = [GitCommit]() let r = raw.replacingOccurrences(of: "\r", with: "\n") let results = r.split(separator: "\n").map { String($0) } for result in results { if result != "" { commits.append( self.parseCommit(result) ) } } return commits } private func parseCommit (_ commit: String) -> GitCommit { var comps = commit.split(separator: ";").map { String($0) } let commitNumber = comps.count > 0 ? comps.removeFirst() : "" let timestamp = comps.count > 0 ? comps.removeFirst() : "0" let date = Date(timeIntervalSince1970: TimeInterval(timestamp)!) let email = comps.count > 0 ? comps.removeFirst() : "" let message = comps.count > 0 ? comps.removeFirst() : "" let branchName = comps.count > 0 ? comps.removeFirst() : nil return GitCommit(commitNumber: commitNumber, date: date, authorEmail: email, message: message, branchName: branchName) } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/GitCommitsParserTests.swift ================================================ // // GitCommitsParserTests.swift // JirassicTests // // Created by Cristian Baluta on 17/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud class GitCommitsParserTests: XCTestCase { func testParser() { let raw = "0;1517923183;me@email.com;AAA-3007 Refactor;\n" + "0;1517922963;you@email.com;AAA-3007 Refactor;\r" + "0;1517922230;me@email.com;AAA-3007 Refactor;(AAA-3007_Branchname)\r\n" + "0;1517922230;me@email.com;AAA-3007 Refactor;(AAA-3007_Branchname)\n\r" + "0;1517922230;me@email.com;AAA-3007 Refactor;(AAA-3007_Branchname)" let parser = GitCommitsParser(raw: raw) let commits = parser.toGitCommits() XCTAssert(commits.count == 5) let commit1 = commits[0] XCTAssert(commit1.authorEmail == "me@email.com") XCTAssert(commit1.message == "AAA-3007 Refactor") XCTAssertNil(commit1.branchName) } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/GitUserParser.swift ================================================ // // GitUserParser.swift // Jirassic // // Created by Cristian Baluta on 14/12/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class GitUserParser { private var raw: String init (raw: String) { self.raw = raw } func toGitUsers() -> [GitUser] { var users = [GitUser]() 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) ) } } return users } 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() : "" return GitUser(name: name, email: email) } } ================================================ FILE: Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift ================================================ // // ModuleGitLogs.swift // Jirassic // // Created by Cristian Baluta on 16/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences import RCLog class ModuleGitLogs { private let extensions = ExtensionsInteractor() private let pref = RCPreferences() // func isReachable (completion: @escaping (Bool) -> Void) { // checkIfGitInstalled(completion: completion) // } func fetchLogs (dateStart: Date, dateEnd: Date, completion: @escaping (([Task]) -> Void)) { // Because of a bug in applescripts split the call into smaller chunks for months if dateEnd.timeIntervalSince(dateStart) > 7*24.hoursToSec { let chunks = ceil(dateEnd.timeIntervalSince(dateStart) / (7*24.hoursToSec)) var dates = [(dateStart: Date, dateEnd: Date)]() for i in 0.. dateEnd { date.dateEnd = dateEnd } dates.append(date) } logsChunk(dates: dates, completion: completion) } else { logsChunk(dates: [(dateStart: dateStart, dateEnd: dateEnd)], completion: completion) } } /// Returns a list of commiters emails func fetchUsers(completion: @escaping (([GitUser]) -> Void)) { let paths = pref.string(.settingsGitPaths).split(separator: ",").map { String($0) } users(paths: paths, previousUsers: []) { gitUsers in completion(gitUsers) } } private func users (paths: [String], previousUsers: [GitUser], completion: @escaping (([GitUser]) -> Void)) { var gitUsers = previousUsers var paths = paths guard let path = paths.first else { completion(gitUsers) return } paths.removeFirst() getGitUsers(at: path) { rawResults in let parser = GitUserParser(raw: rawResults) gitUsers += parser.toGitUsers() self.users(paths: paths, previousUsers: gitUsers, completion: completion) } } private func logsChunk (dates: [(dateStart: Date, dateEnd: Date)], previousTasks: [Task] = [], completion: @escaping (([Task]) -> Void)) { var tasks = previousTasks var dates = dates guard dates.count > 0 else { completion(tasks) return } let interval = dates.removeFirst() let paths = pref.string(.settingsGitPaths).split(separator: ",").map { String($0) } 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 guard interval.dateStart <= commit.date && commit.date <= interval.dateEnd else { RCLog("This commit is not in the provided interval, ignoring... \(commit)") continue } // If the branch sounds like invalid (empty or master) try to obtain the task number from commit message let str = commit.branchName != "" && commit.branchName != "master" ? commit.branchName : commit.message let branchParser = ParseGitBranch(branchName: str ?? "") let taskTitle = branchParser.taskTitle() let taskNumber = branchParser.taskNumber() ?? taskTitle // Remove task number from the beginning of a commit message var notes = "" if commit.message.contains("Merge pull request #") { notes = "Merge pull request" } else { notes = commit.message .replacingOccurrences(of: taskNumber, with: "") .trimmingCharacters(in: NSCharacterSet.whitespaces) } // Create a task without id, this will tell the app that is not saved in db let task = Task(lastModifiedDate: nil, startDate: nil, endDate: commit.date, notes: notes, taskNumber: taskNumber, taskTitle: taskTitle, taskType: .gitCommit, objectId: nil) tasks.append(task) } self.logsChunk(dates: dates, previousTasks: tasks, completion: completion) } } private func logs (dateStart: Date, dateEnd: Date, paths: [String], previousCommits: [GitCommit], completion: @escaping (([GitCommit]) -> Void)) { var commits = previousCommits var paths = paths guard let path = paths.first else { completion(commits) return } paths.removeFirst() let allowedAuthors: [String] = pref.string(.settingsGitAuthors).split(separator: ",").map { String($0) } getGitLogs(at: path, dateStart: dateStart, dateEnd: dateEnd, completion: { rawResults in let parser = GitCommitsParser(raw: rawResults) var rawCommits: [GitCommit] = parser.toGitCommits() // Filter out commits that don't belong to my users rawCommits = rawCommits.filter({ allowedAuthors.contains($0.authorEmail) }) // Obtain branch names where missing self.getBranchName(at: path, previousCommits: rawCommits, completion: { commitsWithBranches in commits += commitsWithBranches self.logs(dateStart: dateStart, dateEnd: dateEnd, paths: paths, previousCommits: commits, completion: completion) }) }) } private func getBranchName (at path: String, previousCommits: [GitCommit], completion: @escaping (([GitCommit]) -> Void)) { var i: Int = -1, j = 0 var commits = previousCommits for c in commits { if c.branchName == nil { i = j break } j += 1 } if i > -1 { var commitToFix = commits[i] getGitBranch(at: path, containing: commitToFix.commitNumber, completion: { rawBranches in let parser = GitBranchParser(raw: rawBranches) commitToFix.branchName = parser.firstBranchName() commits[i] = commitToFix self.getBranchName(at: path, previousCommits: commits, completion: completion) }) } else { completion(commits) } } } extension ModuleGitLogs { func checkIfGitInstalled (completion: @escaping (Bool) -> Void) { let command = "command -v git"// Returns the path to git if exists extensions.run (command: command, completion: { result in completion(result != nil) }) } // private func checkGitRepository (at path: String, completion: @escaping (Bool) -> Void) { // // let command = "git -C \(path) rev-parse --is-inside-work-tree" // extensions.run (command: command, completion: { result in // completion(result == "true") // }) // } private func getGitLogs (at path: String, dateStart: Date, dateEnd: Date, completion: @escaping (String) -> Void) { // https://www.kernel.org/pub/software/scm/git/docs/git-log.html#_pretty_formats // error "fatal: Not a git repository (or any of the parent directories): .git" number 128 // do shell script git -C ~/Documents/proj log --after="2018-2-6" --before="2018-2-7" --pretty=format:"%at;%ae;%s;%D" // Getting git logs based on dates looks unreliable, fetch all logs in a day and filter later let startDate = dateStart.startOfDay().YYYYMMddT00() let endDate = dateEnd.endOfDay().addingTimeInterval(1).YYYYMMddT00() let command = "git -C \(path) log --reflog --after=\"\(startDate)\" --before=\"\(endDate)\" --pretty=format:\"%h;%at;%ae;%s;%D\"" extensions.run (command: command, completion: { result in if let result = result { if result.contains("fatal: Not a git repository (or any of the parent directories): .git") { completion("") } else { completion(result) } } else { completion("") } }) } private func getGitBranch (at path: String, containing commitNumber: String, completion: @escaping (String) -> Void) { // let command = "git -C \(path) log \(commitNumber)..HEAD --ancestry-path --merges --oneline | tail -n 1" let command = "git -C \(path) branch --contains \(commitNumber)" extensions.run (command: command, completion: { result in if let result = result { completion(result) } else { completion("") } }) } // Returns the raw response for users private func getGitUsers (at path: String, completion: @escaping (String) -> Void) { // git log --pretty="%an %ae%n%cn %ce" | sort | uniq let command = "git -C \(path) log --pretty=format:\"%cn;%ce\" | sort | uniq" extensions.run (command: command, completion: { result in if let result = result { if result.contains("fatal: Not a git repository (or any of the parent directories): .git") { completion("") } else { completion(result) } } else { completion("") } }) } } ================================================ FILE: Delivery/macOS/Modules/Hookup/ModuleHookup.swift ================================================ // // Hookup.swift // Jirassic // // Created by Cristian Baluta on 26/11/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import RCPreferences import RCLog class ModuleHookup { private let extensions = ExtensionsInteractor() private let localPreferences = RCPreferences() func isCmdReachable (completion: @escaping (Bool) -> Void) { let cmd = localPreferences.string(.settingsHookupCmdName) checkIfCommandInstalled(cmd: cmd, completion: completion) } func isAppReachable (completion: @escaping (Bool) -> Void) { let appName = localPreferences.string(.settingsHookupAppName) checkIfAppInstalled(appName: appName, completion: completion) } func insert (task: Task, completion: ((_ success: Bool) -> Void)? = nil) { let isHookupEnabled = localPreferences.bool(.enableHookup) let isCocoaHookupEnabled = localPreferences.bool(.enableCocoaHookup) // Call cli hookup if isHookupEnabled { let cliName = localPreferences.string(.settingsHookupCmdName) let json = buildJson (task: task) let command = "\(cliName) insert \"\(json)\"" extensions.run (command: command, completion: { result in guard let validJson = result else { completion?(false) return } guard let data = validJson.data(using: String.Encoding.utf8) else { completion?(false) return } guard let jdict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String], let dict = jdict else { completion?(false) return } RCLog(dict) completion?(dict["success"] == "true") }) } // Call cocoa hookups // Only start/end types are supported if isCocoaHookupEnabled { let appName = localPreferences.string(.settingsHookupAppName) var command = "" if task.taskType == .startDay { command = "osascript -e \"tell application \\\"\(appName)\\\" to start\"" } else if task.taskType == .endDay { command = "osascript -e \"tell application \\\"\(appName)\\\" to stop\"" } if command != "" { extensions.run (command: command, completion: { result in // Do nothing }) } } } /// Json sent to shell to be valid must be a string with ' instead of " and no breaklines private func buildJson (task: Task) -> String { var jsonCredentials = "'credentials':{}" if localPreferences.bool(.enableHookupCredentials) { let url = localPreferences.string(.settingsJiraUrl) let user = localPreferences.string(.settingsJiraUser) let password = Keychain.getPassword() jsonCredentials = "'credentials':{'url':'\(url)', 'user':'\(user)', 'password':'\(password)'}" } let jsonTask = "'task':{'taskType':\(task.taskType.rawValue)}" return "{\(jsonCredentials), \(jsonTask)}" } } extension ModuleHookup { func checkIfCommandInstalled (cmd: String, completion: @escaping (Bool) -> Void) { let command = "command -v \(cmd)"// Returns the path to git if exists extensions.run (command: command, completion: { result in completion(result != nil) }) } func checkIfAppInstalled (appName: String, completion: @escaping (Bool) -> Void) { //let command = "command -v \(cmd)"// Returns the path to git if exists //extensions.run (command: command, completion: { result in completion(true) //}) } } ================================================ FILE: Delivery/macOS/Modules/JiraTempo/ModuleJiraTempo.swift ================================================ // // JiraTempo.swift // Jirassic // // Created by Cristian Baluta on 24/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences class ModuleJiraTempo { private var repository: JiraRepository? private let pref = RCPreferences() /// Returns true if credentials are all setup var isConfigured: Bool { return pref.string(.settingsJiraUrl) != "" && pref.string(.settingsJiraUser) != "" && Keychain.getPassword() != "" } var isProjectConfigured: Bool { return pref.string(.settingsJiraProjectKey) != "" && pref.string(.settingsJiraProjectIssueKey) != "" } func initRepository() { repository = JiraRepository(url: pref.string(.settingsJiraUrl), user: pref.string(.settingsJiraUser), password: Keychain.getPassword()) } func fetchProjects (success: @escaping ([JProject]) -> Void, failure: @escaping (Error) -> Void) { initRepository() repository!.fetchProjects(success: success, failure: failure) } func fetchProjectIssues (projectKey: String, success: @escaping ([JProjectIssue]) -> Void, failure: @escaping (Error) -> Void) { initRepository() repository!.fetchProjectIssues(projectKey: projectKey, success: success, failure: failure) } func postWorklog (worklog: String, duration: Double, date: Date, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { let project = JProject(id: pref.string(.settingsJiraProjectId), key: pref.string(.settingsJiraProjectKey), name: "", url: "") let projectIssue = JProjectIssue(id: "", key: pref.string(.settingsJiraProjectIssueKey), url: "") initRepository() repository!.postWorklog(worklog, duration: duration, in: project, to: projectIssue, date: date, success: { success() }, failure: { error in failure(error) }) } // func upload (reports: [Report]) { // var comment = "" // var duration = 0.0 // for report in reports { // comment += report.taskNumber + " - " + report.title + "\n" + report.notes + "\n\n" // duration += report.duration // } // } } ================================================ FILE: Delivery/macOS/Notifications/BrowserNotification.swift ================================================ // // BrowserNotifications.swift // Jirassic // // Created by Cristian Baluta on 09/05/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Cocoa import RCLog class BrowserNotification { var codeReviewDidStart: (() -> Void)? var codeReviewDidEnd: (() -> Void)? var wastingTimeDidStart: (() -> Void)? var wastingTimeDidEnd: (() -> Void)? var startDate: Date? var endDate: Date? var reviewedTasks = [String]() var taskType: TaskType? fileprivate let delay = 15.0 fileprivate let stashUrlEreg = "(http|https)://(.+)/projects/(.+)/repos/(.+)/pull-requests" fileprivate let browsersIds = ["com.apple.Safari", "com.apple.SafariTechnologyPreview", "com.google.Chrome", "com.google.Chrome.canary", "org.chromium.Chromium", "com.vivaldi.Vivaldi"] fileprivate var timer: Timer? fileprivate var extensionsInteractor = ExtensionsInteractor() fileprivate var reader: ReadTasksInteractor? init() { start() } func start() { stop() timer = Timer.scheduledTimer(timeInterval: delay, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true) } func stop() { timer?.invalidate() timer = nil } @objc func handleTimer (timer: Timer) { guard let app: NSRunningApplication = NSWorkspace.shared.frontmostApplication, let appId = app.bundleIdentifier, var appName = app.localizedName else { return } // Google Chrome Canary does not return the correct localizedName, so override it if appId == "com.google.Chrome.canary" { appName = "Google Chrome Canary" } // RCLogO(appId) // RCLogO(appName) guard browsersIds.contains(appId) else { if let taskType = self.taskType { switch taskType { case .waste: handleWastingTimeEnd() break case .coderev: handleCodeRevEnd() break default: break } } return } reader = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) let existingTasks = reader!.tasksInDay(Date()) let isDayStarted = existingTasks.count > 0 let isDayEnded = existingTasks.contains(where: { $0.taskType == .endDay }) guard let startDayTask = existingTasks.first else { return } guard isDayStarted && !isDayEnded else { RCLog("Day started but also ended, won't continue analyzing the url") return } let settings: Settings = SettingsInteractor().getAppSettings() let maxDuration = TimeInteractor(settings: settings).workingDayLength() let workedDuration = Date().timeIntervalSince( startDayTask.endDate ) guard workedDuration < maxDuration else { // Do not track browser events past working duration return } extensionsInteractor.getBrowserInfo(browserId: appId, browserName: appName, completion: { (url, title) in RCLog("Analyzing url: \(url), title: \(title)") if self.isCodeRevLink(url) { if self.isWastingTime() { self.handleWastingTimeEnd() } if !self.isInCodeRev() { self.handleCodeRevStart() } // Find the taskNumber of the branch you're reviewing let branchParser = ParseGitBranch(branchName: title) if let taskNumber = branchParser.taskNumber() { if !self.reviewedTasks.contains(taskNumber) { self.reviewedTasks.append(taskNumber) } } } else if self.isWastingTimeLink(url) { if self.isInCodeRev() { self.handleCodeRevEnd() } if !self.isWastingTime() { self.handleWastingTimeStart() } } else { if self.isInCodeRev() { self.handleCodeRevEnd() } if self.isWastingTime() { self.handleWastingTimeEnd() } } }) } } extension BrowserNotification { fileprivate func handleCodeRevStart() { guard !isInCodeRev() else { return } taskType = .coderev startDate = Date() reviewedTasks = [String]() codeReviewDidStart?() } fileprivate func handleCodeRevEnd() { guard isInCodeRev() else { return } self.endDate = Date() let settings = localRepository.settings() let duration = self.endDate!.timeIntervalSince(startDate!) if duration >= Double(settings.settingsBrowser.minCodeRevDuration).minToSec { self.codeReviewDidEnd?() } else { RCLog("Discard code review session with duration \(duration) < \(Double(settings.settingsBrowser.minCodeRevDuration).minToSec)") } startDate = nil } fileprivate func isCodeRevLink (_ url: String) -> Bool { let settings = localRepository.settings() let coderevLink = settings.settingsBrowser.codeRevLink != "" ? settings.settingsBrowser.codeRevLink : self.stashUrlEreg guard let coderevRegex = try? NSRegularExpression(pattern: coderevLink, options: []) else { return false } let matches = coderevRegex.matches(in: url, options: [], range: NSRange(location: 0, length: url.count)) return matches.count > 0 } fileprivate func isInCodeRev() -> Bool { return startDate != nil && taskType == .coderev } } extension BrowserNotification { fileprivate func handleWastingTimeStart() { guard !isWastingTime() else { return } taskType = .waste startDate = Date() wastingTimeDidStart?() } fileprivate func handleWastingTimeEnd() { guard isWastingTime() else { return } self.endDate = Date() let settings: Settings = SettingsInteractor().getAppSettings() let duration = self.endDate!.timeIntervalSince(startDate!) if duration >= Double(settings.settingsBrowser.minWasteDuration).minToSec { self.wastingTimeDidEnd?() } else { RCLog("Discard wasting time session with duration \(duration) < \(Double(settings.settingsBrowser.minWasteDuration).minToSec)") } startDate = nil } fileprivate func isWastingTimeLink (_ url: String) -> Bool { let settings = localRepository.settings() for link in settings.settingsBrowser.wasteLinks { guard link != "" else { continue } let regex = try! NSRegularExpression(pattern: link, options: []) let matches = regex.matches(in: url, options: [], range: NSRange(location: 0, length: url.count)) if matches.count > 0 { return true } } return false } fileprivate func isWastingTime() -> Bool { return startDate != nil && taskType == .waste } } ================================================ FILE: Delivery/macOS/Notifications/InternalNotifications.swift ================================================ // // InternalNotifications.swift // Jirassic // // Created by Baluta Cristian on 13/12/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation let kNewTaskWasAddedNotification = "NewTaskWasAddedNotification" class InternalNotifications: NSObject { class func notifyAboutNewlyAddedTask (_ task: Task) { NotificationCenter.default.post(name: Notification.Name(rawValue: kNewTaskWasAddedNotification), object: nil) } } ================================================ FILE: Delivery/macOS/Notifications/SleepNotifications.swift ================================================ // // SleepNotifications.swift // Jirassic // // Created by Baluta Cristian on 10/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCLog class SleepNotifications: NSObject { var computerWentToSleep: (() -> ())? var computerWakeUp: (() -> ())? var lastSleepDate: Date? var lastWakeDate: Date? override init() { super.init() // NSWorkspace.shared().notificationCenter.addObserver( // self, // selector: #selector(SleepNotifications.receiveSleepNotification(_:)), // name: NSNotification.Name.NSWorkspaceWillSleep, // object: nil) NSWorkspace.shared.notificationCenter.addObserver( self, selector: #selector(SleepNotifications.receiveSleepNotification(_:)), name: NSWorkspace.screensDidSleepNotification, object: nil) // NSWorkspace.shared().notificationCenter.addObserver( // self, // selector: #selector(SleepNotifications.receiveWakeNotification(_:)), // name: NSNotification.Name.NSWorkspaceDidWake, // object: nil) NSWorkspace.shared.notificationCenter.addObserver( self, selector: #selector(SleepNotifications.receiveWakeNotification(_:)), name: NSWorkspace.screensDidWakeNotification, object: nil) } deinit { NSWorkspace.shared.notificationCenter.removeObserver(self) } @objc func receiveSleepNotification (_ notif: Notification) { RCLogO(notif) lastSleepDate = Date() computerWentToSleep?() } @objc func receiveWakeNotification (_ notif: Notification) { RCLogO(notif) lastWakeDate = Date() computerWakeUp?() } } ================================================ FILE: Delivery/macOS/Notifications/UserNotifications.swift ================================================ // // LocalNotifications.swift // Jirassic // // Created by Baluta Cristian on 12/12/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Foundation class UserNotifications { func showNotification (_ title: String, informativeText: String) { let notification = NSUserNotification() notification.title = title notification.informativeText = informativeText NSUserNotificationCenter.default.deliver(notification) } } ================================================ FILE: Delivery/macOS/Screens/Account/AccountViewController.swift ================================================ // // AccountViewController.swift // Jirassic // // Created by Cristian Baluta on 25/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa class AccountViewController: NSViewController { @IBOutlet fileprivate var emailTextField: NSTextField? @IBOutlet fileprivate var passwordTextField: NSTextField? @IBOutlet fileprivate var butLogin: NSButton? @IBOutlet fileprivate var progressIndicator: NSProgressIndicator? var credentials: UserCredentials { get { return (email: self.emailTextField!.stringValue, password: self.passwordTextField!.stringValue) } set { self.emailTextField!.stringValue = newValue.email self.passwordTextField!.stringValue = newValue.password } } override func viewDidLoad() { super.viewDidLoad() // let container = CKContainer.default() // container.requestApplicationPermission(.userDiscoverability) { (status, error) in // guard error == nil else { return } // // if status == CKApplicationPermissionStatus.granted { // container.fetchUserRecordID { (recordID, error) in // guard error == nil else { return } // guard let recordID = recordID else { return } // // container.discoverUserInfo(withUserRecordID: recordID) { (info, fetchError) in // // use info.firstName and info.lastName however you need // print(info) // } // } // } // } } override func viewDidAppear() { super.viewDidAppear() // let user = UserInteractor().currentUser() // butLogin?.title = user.isLoggedIn ? "Logout" : "Login" // emailTextField?.stringValue = user.email! } @IBAction func handleLoginButton (_ sender: NSButton) { // presenter!.login(credentials) } func login (_ credentials: UserCredentials) { // let interactor = UserInteractor(data: localRepository) // interactor.onLoginSuccess = { // self.userInterface?.showLoadingIndicator(false) // } // let user = interactor.currentUser() // user.isLoggedIn ? interactor.logout() : interactor.loginWithCredentials(credentials) } func showLoadingIndicator (_ show: Bool) { if show { progressIndicator!.startAnimation(nil) } else { progressIndicator!.stopAnimation(nil) } } } ================================================ FILE: Delivery/macOS/Screens/Account/CloudKitLoginViewController.swift ================================================ // // CloudKitLoginViewController.swift // Jirassic // // Created by Cristian Baluta on 13/06/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Cocoa class CloudKitLoginViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() } } ================================================ FILE: Delivery/macOS/Screens/Account/Login.storyboard ================================================ You are currently using the app in annonymous mode. By logging in you ensure you never lose the data and you can sync with the phone. Preferably to register with your work e-mail NSAllRomanInputSourcesLocaleIdentifier Sign in to your iCloud account to keep the data in sync with other devices. Launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID. ================================================ FILE: Delivery/macOS/Screens/Account/LoginPresenter.swift ================================================ // // LoginPresenter.swift // Jirassic // // Created by Cristian Baluta on 01/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation protocol LoginPresenterInput { func loginWithCredentials (_ credentials: UserCredentials) func cancelScreen() } protocol LoginPresenterOutput { func showLoadingIndicator (_ show: Bool) } class LoginPresenter { var userInterface: LoginPresenterOutput? var appWireframe: AppWireframe? } extension LoginPresenter: LoginPresenterInput { func loginWithCredentials (_ credentials: UserCredentials) { userInterface?.showLoadingIndicator(true) if let repository = remoteRepository { let login = UserInteractor(repository: repository, remoteRepository: remoteRepository) login.onLoginSuccess = { self.userInterface?.showLoadingIndicator(false) _ = self.appWireframe?.presentTasksController() } login.onLoginFailure = { self.userInterface?.showLoadingIndicator(false) } login.loginWithCredentials(credentials) } } func cancelScreen() { _ = appWireframe?.presentTasksController() } } ================================================ FILE: Delivery/macOS/Screens/Account/LoginViewController.swift ================================================ // // LoginViewController.swift // Jirassic // // Created by Baluta Cristian on 07/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa class LoginViewController: NSViewController { @IBOutlet fileprivate var _label: NSTextField? @IBOutlet fileprivate var _emailTextField: NSTextField? @IBOutlet fileprivate var _passwordTextField: NSTextField? @IBOutlet fileprivate var _butLogin: NSButton? @IBOutlet fileprivate var _butCancel: NSButton? @IBOutlet fileprivate var _progressIndicator: NSProgressIndicator? var loginPresenter: LoginPresenterInput? var credentials: UserCredentials { get { return (email: self._emailTextField!.stringValue, password: self._passwordTextField!.stringValue) } set { self._emailTextField!.stringValue = newValue.email self._passwordTextField!.stringValue = newValue.password } } var isLoggedIn: Bool = false override func viewDidLoad() { super.viewDidLoad() if let repository = remoteRepository { UserInteractor(repository: repository, remoteRepository: remoteRepository).getUser({ (user) in }) // if user.isLoggedIn { // _butLogin?.title = "Logout" // _label?.stringValue = "You are already logged in." // self.credentials = (email: user.email!, password: "") // } else { // _butLogin?.title = "Login or Signup" // _label?.stringValue = "You are currently using the app in annonymous mode. By logging in you ensure you never lose the data and you can sync with the phone. Preferably to register with your work e-mail" // } } } // MARK: Actions @IBAction func handleLoginButton (_ sender: NSButton) { loginPresenter?.loginWithCredentials(credentials) } @IBAction func handleCancelButton (_ sender: NSButton) { loginPresenter?.cancelScreen() } } extension LoginViewController: LoginPresenterOutput { func showLoadingIndicator (_ show: Bool) { if show { _progressIndicator?.startAnimation(nil) } else { _progressIndicator?.stopAnimation(nil) } } } ================================================ FILE: Delivery/macOS/Screens/Calendar/CalendarScrollView.swift ================================================ // // 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) } } } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/Welcome.storyboard ================================================ ================================================ FILE: Delivery/macOS/Screens/Onboarding/WelcomeViewController.swift ================================================ // // WelcomeViewController.swift // Jirassic // // Created by Cristian Baluta on 10/12/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa import RCPreferences import RCLog class WelcomeViewController: NSViewController { weak var appWireframe: AppWireframe? @IBOutlet var boxSetupProgrammers: NSBox! @IBOutlet var boxSetupOthers: NSBox! @IBOutlet var boxWhatsNew: NSBox! @IBOutlet var whatsNewTextField: NSTextField! private let pref = RCPreferences() override func viewDidLoad() { super.viewDidLoad() createLayer() // if AppTheme().isDark { // box.fillColor = NSColor.clear // } // Do we need to display what's new box? // When app is an update from previous version let isFirstLaunchOfThisVersion = pref.string(.appVersion) != Versioning.appVersion // Deprecated userdefault, keep it for few versions, needed to present what's new screen let wizardStep = UserDefaults.standard.integer(forKey: "RCPreferences-wizardStep") let isAnUpdateFromAnotherVersion = pref.string(.appVersion) != "" || wizardStep > 0 if isFirstLaunchOfThisVersion && isAnUpdateFromAnotherVersion { boxWhatsNew.isHidden = false boxSetupProgrammers.isHidden = true boxSetupOthers.isHidden = true whatsNewTextField.stringValue = "• Import meetings from Calendar.app\n• Create monthly reports\n• Fixes bug in editing tasks. Git commits and calendar meetings can be edited after the day is closed\n• Extended calendar history to one year\n• Various UI improvements and fixes" } else { boxWhatsNew.isHidden = true boxSetupProgrammers.isHidden = false boxSetupOthers.isHidden = false } } deinit { RCLog("deinit") } @IBAction func handleSetupProgrammersButton (_ sender: NSButton) { // Set that we saw this version of the app launch pref.set(Versioning.appVersion, forKey: .appVersion) let stepsToSave: [Int] = [] pref.set(stepsToSave, forKey: .wizardSteps) appWireframe!.flipToWizardController() } @IBAction func handleSetupOthersButton (_ sender: NSButton) { pref.set(Versioning.appVersion, forKey: .appVersion) let stepsSeen = [WizardStep.shell, WizardStep.browser, WizardStep.git] let stepsToSave: [Int] = stepsSeen.map({ $0.rawValue }) pref.set(stepsToSave, forKey: .wizardSteps) appWireframe!.flipToWizardController() } @IBAction func handleWhatsNewButton (_ sender: NSButton) { pref.set(Versioning.appVersion, forKey: .appVersion) let stepsSeen = [WizardStep.shell, WizardStep.browser, WizardStep.git, WizardStep.jira] let stepsToSave: [Int] = stepsSeen.map({ $0.rawValue }) pref.set(stepsToSave, forKey: .wizardSteps) // Remove deprecated userdefault UserDefaults.standard.removeObject(forKey: "RCPreferences-wizardStep") appWireframe!.flipToWizardController() } @IBAction func handleQuitAppButton (_ sender: NSButton) { NSApplication.shared.terminate(nil) } @IBAction func handleMinimizeAppButton (_ sender: NSButton) { AppDelegate.sharedApp().menu.triggerClose() } } extension WelcomeViewController: Animatable { func createLayer() { view.layer = CALayer() view.wantsLayer = true } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardAppleScriptView.swift ================================================ // // WizardAppleScriptView.swift // Jirassic // // Created by Cristian Baluta on 18/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCLog class WizardAppleScriptView: NSView { @IBOutlet var titleLabel: NSTextField! @IBOutlet var subtitleLabel: NSTextField! @IBOutlet var butLink: NSButton! @IBOutlet var butSkip: NSButton! @IBOutlet var progressIndicator: NSProgressIndicator! var onSkip: (() -> Void)? var scriptName: String? { didSet { handleTimer() } } var subtitle: String? { didSet { subtitleLabel.stringValue = subtitle ?? "" } } private var timer: Timer? private let scripts: AppleScriptProtocol = SandboxedAppleScript() override func awakeFromNib() { super.awakeFromNib() } func invalidate() { timer?.invalidate() timer = nil } deinit { invalidate() RCLog("deinit") } @objc func handleTimer() { guard let scriptName = self.scriptName else { return } timer = WeakTimer.scheduledTimer(timeInterval: 1, target: self, repeats: true) { [weak self] timer in // Place your action code here. self?.scripts.getScriptVersion(script: scriptName, completion: { [weak self] scriptVersion in RCLog(scriptVersion) if scriptVersion != "" { self?.progressIndicator.stopAnimation(nil) self?.butLink.isHidden = true self?.subtitleLabel.textColor = NSColor.green self?.subtitleLabel.stringValue = "Installed successfully!" self?.subtitleLabel.font = NSFont.systemFont(ofSize: 26) } else { self?.progressIndicator.startAnimation(nil) self?.butLink.isHidden = false self?.subtitleLabel.textColor = NSColor.black self?.subtitleLabel.stringValue = self?.subtitle ?? "" self?.subtitleLabel.font = NSFont.systemFont(ofSize: 13) } }) } } @IBAction func handleInstructionsButton (_ sender: NSButton) { #if APPSTORE NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #else NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #endif } @IBAction func handleSkipButton (_ sender: NSButton) { onSkip?() } } final class WeakTimer { fileprivate weak var timer: Timer? fileprivate weak var target: AnyObject? fileprivate let action: (Timer) -> Void fileprivate init(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) { self.target = target self.action = action self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(fire), userInfo: nil, repeats: repeats) } class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) -> Timer { return WeakTimer(timeInterval: timeInterval, target: target, repeats: repeats, action: action).timer! } @objc fileprivate func fire(timer: Timer) { if target != nil { action(timer) } else { timer.invalidate() } } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardAppleScriptView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift ================================================ // // WizardCalendarView.swift // Jirassic // // Created by Cristian Baluta on 15/07/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class WizardCalendarView: NSView { @IBOutlet private var scrollView: NSScrollView! @IBOutlet private var butAuthorize: NSButton! @IBOutlet var butSkip: NSButton! var onSkip: (() -> Void)? private let pref = RCPreferences() private var calendarsButtons = [NSButton]() private var presenter: CalendarPresenterInput = CalendarPresenter() override func awakeFromNib() { super.awakeFromNib() (presenter as! CalendarPresenter).userInterface = self (presenter as! CalendarPresenter).refresh() } func save() { } @IBAction func handleSkipButton (_ sender: NSButton) { save() onSkip?() } @IBAction func handleAuthorizeButton (_ sender: NSButton) { // Enable the calendar so when it gets authorized will be able to read and display the list of calendars presenter.enable(true) presenter.authorize() } override func layout() { super.layout() var x = CGFloat(0) var y = CGFloat(0) for but in calendarsButtons { if x + but.frame.size.width > scrollView.frame.size.width { y += 26 x = CGFloat(0) } but.frame = CGRect(x: x, y: y, width: but.frame.size.width, height: but.frame.size.height) x += but.frame.size.width + 10 } scrollView.documentView?.setFrameSize(NSSize(width: 0, height: y + 26)) } } extension WizardCalendarView: CalendarPresenterOutput { func enable (_ enabled: Bool) { butAuthorize.isHidden = enabled } func setStatusImage (_ imageName: NSImage.Name) { } func setStatusText (_ text: String) { } func setDescriptionText (_ text: String) { } func setCalendarStatus (authorized: Bool, enabled: Bool) { } func setCalendars (_ calendars: [String], selected: [String]) { for but in calendarsButtons { but.removeFromSuperview() } calendarsButtons = [] for title in calendars { let but = NSButton() but.setButtonType(.switch) but.title = title but.sizeToFit() but.state = selected.contains(title) ? .on : .off but.target = self but.action = #selector(didClickCalendarButton) scrollView.addSubview(but) self.calendarsButtons.append(but) } self.needsLayout = true } @objc func didClickCalendarButton (_ sender: NSButton) { if sender.state == .on { presenter.enableCalendar(sender.title) } else { presenter.disableCalendar(sender.title) } } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardCalendarView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardGitView.swift ================================================ // // WizardGitView.swift // Jirassic // // Created by Cristian Baluta on 18/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class WizardGitView: NSView { @IBOutlet fileprivate var emailsTextField: NSTextField! @IBOutlet fileprivate var pathsTextField: NSTextField! @IBOutlet fileprivate var butPick: NSButton! @IBOutlet var butSkip: NSButton! var onSkip: (() -> Void)? private let pref = RCPreferences() private var emailClickGestureRecognizer: NSClickGestureRecognizer? private var gitUsersPopover: NSPopover? var presenter: GitPresenterInput = GitPresenter() override func awakeFromNib() { super.awakeFromNib() // Git is disabled by default, we need to enable in order to be able to make changes 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) { // If git was not setup completely, disable it if emailsTextField.stringValue == "" || pathsTextField.stringValue == "" { pref.set(false, forKey: .enableGit) } 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 { func setStatusImage (_ imageName: NSImage.Name) {} func setStatusText (_ text: String) {} func setDescriptionText (_ text: String) {} 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 } } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardGitView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardJiraView.swift ================================================ // // WizardJiraView.swift // Jirassic // // Created by Cristian Baluta on 18/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class WizardJiraView: NSView { @IBOutlet var butLogin: NSButton! @IBOutlet var butSkip: NSButton! @IBOutlet fileprivate var baseUrlTextField: NSTextField! @IBOutlet fileprivate var userTextField: NSTextField! @IBOutlet fileprivate var passwordTextField: NSTextField! @IBOutlet fileprivate var errorTextField: NSTextField! @IBOutlet fileprivate var projectNamePopup: NSPopUpButton! @IBOutlet fileprivate var projectIssueNamePopup: NSPopUpButton! @IBOutlet fileprivate var progressIndicator: NSProgressIndicator! fileprivate let localPreferences = RCPreferences() var presenter: JiraTempoPresenterInput = JiraTempoPresenter() var onSkip: (() -> Void)? override func awakeFromNib() { super.awakeFromNib() baseUrlTextField.stringValue = localPreferences.string(.settingsJiraUrl) userTextField.stringValue = localPreferences.string(.settingsJiraUser) passwordTextField.stringValue = Keychain.getPassword() projectNamePopup.target = self projectNamePopup.action = #selector(projectNamePopupSelected(_:)) projectIssueNamePopup.target = self projectIssueNamePopup.action = #selector(projectIssueNamePopupSelected(_:)) (presenter as! JiraTempoPresenter).userInterface = self presenter.setupUserInterface() handleLoginButton(butLogin) } func save() { presenter.save(url: baseUrlTextField.stringValue, user: userTextField.stringValue, password: passwordTextField.stringValue) } @IBAction func handleLoginButton (_ sender: NSButton) { save() guard baseUrlTextField.stringValue != "", userTextField.stringValue != "", passwordTextField.stringValue != "" else { return } presenter.checkCredentials() } @IBAction func handleSkipButton (_ sender: NSButton) { onSkip?() } @IBAction func projectNamePopupSelected (_ sender: NSPopUpButton) { if let title = sender.selectedItem?.title { localPreferences.set(title, forKey: .settingsJiraProjectKey) presenter.loadProjectIssues(for: title) } } @IBAction func projectIssueNamePopupSelected (_ sender: NSPopUpButton) { localPreferences.set(sender.selectedItem?.title ?? "", forKey: .settingsJiraProjectIssueKey) } } extension WizardJiraView: JiraTempoPresenterOutput { func setPurchased (_ purchased: Bool) { } func enableProgressIndicator (_ enabled: Bool) { enabled ? progressIndicator.startAnimation(nil) : progressIndicator.stopAnimation(nil) butLogin.isHidden = enabled } 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 } } ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardJiraView.xib ================================================ NSAllRomanInputSourcesLocaleIdentifier ================================================ FILE: Delivery/macOS/Screens/Onboarding/WizardViewController.swift ================================================ // // WizardViewController.swift // Jirassic // // Created by Cristian Baluta on 16/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences import RCLog enum WizardStep: Int, CaseIterable { case shell = 0 case browser = 1 case git = 2 case calendar = 3 case jira = 4 } class WizardViewController: NSViewController { weak var appWireframe: AppWireframe? @IBOutlet private var titleLabel: NSTextField! @IBOutlet private var subtitleLabel: NSTextField! @IBOutlet private var containerView: NSView! @IBOutlet private var containerViewHeightConstrain: NSLayoutConstraint! private var contentView: NSView? @IBOutlet private var butSkip: NSButton! @IBOutlet private var levelIndicator: NSLevelIndicator! private let pref = RCPreferences() private var step: WizardStep = WizardStep.shell private let steps: [WizardStep] = [.shell, .browser, .git, .calendar, .jira] private var stepsUnseen: [WizardStep] { let stepsSaved: [Int] = pref.get(.wizardSteps) let stepsSeen: [WizardStep] = stepsSaved.map({ WizardStep(rawValue: $0)! }) let unseen: [WizardStep] = steps.filter({ !stepsSeen.contains($0) }) return unseen } override func viewDidLoad() { super.viewDidLoad() createLayer() levelIndicator.maxValue = Double(WizardStep.allCases.count) if let wizardStep = stepsUnseen.first { goTo(step: wizardStep) } } override func viewDidDisappear() { super.viewDidDisappear() removeCurrentContent() } deinit { RCLog("deinit") } func goTo (step: WizardStep) { removeCurrentContent() self.step = step levelIndicator.intValue = Int32(step.rawValue + 1) switch step { case .shell: titleLabel.stringValue = "Shell Support" subtitleLabel.stringValue = "Jirassic needs shell support to communicate with Git and the Browser.\nThe shell is accessed through an AppleScript which for security reasons you need to install manually." let applescriptView = WizardAppleScriptView.instantiateFromXib() applescriptView.titleLabel.stringValue = "Install ShellSupport.scpt" applescriptView.subtitle = "Go to jirassic.com, copy the install script and run it in your Terminal.app. We'll wait!" applescriptView.scriptName = kShellSupportScriptName applescriptView.onSkip = { [weak self] in if let wself = self { wself.handleNextButton(wself.butSkip) } } containerView.addSubview(applescriptView) applescriptView.constrainToSuperview() contentView = applescriptView containerViewHeightConstrain.constant = 114 break case .browser: titleLabel.stringValue = "Browser Support" subtitleLabel.stringValue = "Jirassic will be able to read the url of the active browser and it will detect when you do code reviews and when you waste time on social media." let applescriptView = WizardAppleScriptView.instantiateFromXib() applescriptView.titleLabel.stringValue = "Install BrowserSupport.scpt" applescriptView.subtitle = "Go to jirassic.com, copy the install script and run it in your Terminal. We'll wait!" applescriptView.scriptName = kBrowserSupportScriptName applescriptView.onSkip = { [weak self] in if let wself = self { wself.handleNextButton(wself.butSkip) } } containerView.addSubview(applescriptView) applescriptView.constrainToSuperview() contentView = applescriptView containerViewHeightConstrain.constant = 114 break case .git: titleLabel.stringValue = "Git" subtitleLabel.stringValue = "Include git commits in reports, to help you write more accurate worklogs. Chose the users and projects you want to track!" let gitView = WizardGitView.instantiateFromXib() gitView.onSkip = { [weak self] in if let wself = self { wself.handleNextButton(wself.butSkip) } } containerView.addSubview(gitView) gitView.constrainToSuperview() contentView = gitView containerViewHeightConstrain.constant = 114 break case .calendar: titleLabel.stringValue = "Calendar.app" subtitleLabel.stringValue = "Include calendar events in the reports and treat them as meetings. Please enable and select the calendars you use!" let calendarView = WizardCalendarView.instantiateFromXib() calendarView.onSkip = { [weak self] in if let wself = self { wself.handleNextButton(wself.butSkip) } } containerView.addSubview(calendarView) calendarView.constrainToSuperview() contentView = calendarView containerViewHeightConstrain.constant = 200 break case .jira: titleLabel.stringValue = "Jira Tempo" subtitleLabel.stringValue = "Jirassic can post your worklogs directly to Jira Tempo, very convenient and very fast. Please login then select the project and issue you want to post the worklogs to." let jiraView = WizardJiraView.instantiateFromXib() jiraView.onSkip = { [weak self] in if let wself = self { wself.handleNextButton(wself.butSkip) } } containerView.addSubview(jiraView) jiraView.constrainToSuperview() contentView = jiraView containerViewHeightConstrain.constant = 114 butSkip.title = "Finish setup" break } } private func removeCurrentContent() { if let view = contentView { view.removeFromSuperview() if let v = view as? WizardAppleScriptView { v.invalidate() } contentView = nil } } @IBAction func handleNextButton (_ sender: NSButton) { var stepsUnseen = self.stepsUnseen let stepSeen = stepsUnseen.removeFirst() if let nextStep = stepsUnseen.first { goTo(step: nextStep) let stepsSaved: [Int] = pref.get(.wizardSteps) var stepsSeen: [WizardStep] = stepsSaved.map({ WizardStep(rawValue: $0)! }) stepsSeen.append(stepSeen) stepsSeen.sort { (w1, w2) -> Bool in w1.rawValue < w2.rawValue } let stepsToSave: [Int] = stepsSeen.map({ $0.rawValue }) pref.set(stepsToSave, forKey: .wizardSteps) } else { handleSkipButton(sender) } } @IBAction func handleSkipButton (_ sender: NSButton) { let stepsToSave: [Int] = WizardStep.allCases.map({ $0.rawValue }) pref.set(stepsToSave, forKey: .wizardSteps) appWireframe!.flipToTasksController() } @IBAction func handleQuitAppButton (_ sender: NSButton) { NSApplication.shared.terminate(nil) } @IBAction func handleMinimizeAppButton (_ sender: NSButton) { AppDelegate.sharedApp().menu.triggerClose() } } extension WizardViewController: Animatable { func createLayer() { view.layer = CALayer() view.wantsLayer = true } } ================================================ FILE: Delivery/macOS/Screens/Placeholder/Placeholder.storyboard ================================================ ================================================ FILE: Delivery/macOS/Screens/Placeholder/PlaceholderViewController.swift ================================================ // // PlaceholderViewController.swift // Jirassic // // Created by Baluta Cristian on 06/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCLog typealias MessageViewModel = (title: String?, message: String?, buttonTitle: String?) class PlaceholderViewController: NSViewController { @IBOutlet fileprivate weak var titleLabel: NSTextField! @IBOutlet fileprivate weak var messageLabel: NSTextField! @IBOutlet fileprivate weak var button: NSButton! var didPressButton: (() -> ())? var viewModel: MessageViewModel? { didSet { guard self.titleLabel != nil else { fatalError("viewModel should be set after the VC is presented") } if let title = viewModel?.title { self.titleLabel?.stringValue = title } if let message = viewModel?.message { self.messageLabel?.stringValue = message } if let buttonTitle = viewModel?.buttonTitle { button?.isHidden = false self.button?.title = buttonTitle } else { button?.isHidden = true } } } override func viewDidLoad() { super.viewDidLoad() } } extension PlaceholderViewController { @IBAction func handleStartButton (_ sender: NSButton) { RCLog(sender) self.didPressButton?() } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Browser/BrowserCell.swift ================================================ // // BrowserCell.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class BrowserCell: NSTableRowView { static let height = CGFloat(270) @IBOutlet private var coderevImageView: NSImageView! @IBOutlet private var coderevTextField: NSTextField! @IBOutlet private var butInstallCoderev: NSButton! @IBOutlet private var butTrackCodeReviews: NSButton! @IBOutlet private var butTrackWastedTime: NSButton! @IBOutlet private var codeReviewsLinkTextField: NSTextField! @IBOutlet private var minCodeRevDurationLabel: NSTextField! @IBOutlet private var minCodeRevDurationSlider: NSSlider! @IBOutlet private var wastedTimeLinksTextField: NSTextField! @IBOutlet private var minWasteDurationLabel: NSTextField! @IBOutlet private var minWasteDurationSlider: NSSlider! func showSettings (_ settings: SettingsBrowser) { butTrackCodeReviews.state = settings.trackCodeReviews ? NSControl.StateValue.on : NSControl.StateValue.off butTrackWastedTime.state = settings.trackWastedTime ? NSControl.StateValue.on : NSControl.StateValue.off codeReviewsLinkTextField.stringValue = settings.codeRevLink wastedTimeLinksTextField.stringValue = settings.wasteLinks.toString() minCodeRevDurationSlider.integerValue = settings.minCodeRevDuration minWasteDurationSlider.integerValue = settings.minWasteDuration handleMinCodeRevDuration( minCodeRevDurationSlider ) handleMinWasteDuration( minWasteDurationSlider ) enableCodeReview( settings.trackCodeReviews ) enableWastedTime( settings.trackWastedTime ) } func settings() -> SettingsBrowser { return SettingsBrowser( trackCodeReviews: butTrackCodeReviews.state == NSControl.StateValue.on, trackWastedTime: butTrackWastedTime.state == NSControl.StateValue.on, minCodeRevDuration: minCodeRevDurationSlider.integerValue, codeRevLink: codeReviewsLinkTextField.stringValue, minWasteDuration: minWasteDurationSlider.integerValue, wasteLinks: wastedTimeLinksTextField.stringValue.toArray() ) } func save() { } func setBrowserStatus (compatibility: Compatibility) { if compatibility.available { coderevImageView.image = NSImage(named: compatibility.compatible ? NSImage.statusAvailableName : NSImage.statusUnavailableName) coderevTextField.stringValue = compatibility.compatible ? "Jirassic can read the url of your browser and it will log time based on it" : (compatibility.available ? "Browser support installed but outdated, please update!" : "Browser support not installed, please install!") } else { coderevImageView.image = NSImage(named: NSImage.statusUnavailableName) coderevTextField.stringValue = "Not installed yet" } butInstallCoderev.isHidden = compatibility.available && compatibility.compatible } func enableCodeReview (_ enable: Bool) { codeReviewsLinkTextField.isEnabled = enable minCodeRevDurationSlider.isEnabled = enable } func enableWastedTime (_ enable: Bool) { wastedTimeLinksTextField.isEnabled = enable minWasteDurationSlider.isEnabled = enable } @IBAction func handleCodeReviewButton (_ sender: NSButton) { enableCodeReview(sender.state == NSControl.StateValue.on) } @IBAction func handleWastedTimeButton (_ sender: NSButton) { enableWastedTime(sender.state == NSControl.StateValue.on) } @IBAction func handleInstallBrowserSupportButton (_ sender: NSButton) { NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) } @IBAction func handleMinCodeRevDuration (_ sender: NSSlider) { minCodeRevDurationLabel.stringValue = "\(sender.integerValue) min" } @IBAction func handleMinWasteDuration (_ sender: NSSlider) { minWasteDurationLabel.stringValue = "\(sender.integerValue) min" } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Browser/BrowserCell.xib ================================================ When one of the supported browsers is active, Jirassic reads the url and when matches one defined below for at least the minimum defined duration, tracks the time accordingly. A spinner will show next to the app icon when url is read ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Browser/BrowserPresenter.swift ================================================ // // BrowserPresenter.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift ================================================ // // CalendarCell.swift // Jirassic // // Created by Cristian Baluta on 05/07/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class CalendarCell: NSTableRowView { static let height = CGFloat(200) @IBOutlet private var statusImageView: NSImageView! @IBOutlet private var statusTextField: NSTextField! @IBOutlet private var descriptionTextField: NSTextField! @IBOutlet private var butAuthorize: NSButton! @IBOutlet private var butEnable: NSButton! @IBOutlet private var scrollView: NSScrollView! private var calendarsButtons = [NSButton]() private var presenter: CalendarPresenterInput = CalendarPresenter() override func awakeFromNib() { super.awakeFromNib() (presenter as! CalendarPresenter).userInterface = self presenter.refresh() } func save() { // Nothing to save } @IBAction func handleAuthorizeButton (_ sender: NSButton) { presenter.authorize() } @IBAction func handleEnableButton (_ sender: NSButton) { presenter.enable(sender.state == .on) } override func layout() { super.layout() var x = CGFloat(0) var y = CGFloat(0) for but in calendarsButtons { if x + but.frame.size.width > scrollView.frame.size.width { y += 26 x = CGFloat(0) } but.frame = CGRect(x: x, y: y, width: but.frame.size.width, height: but.frame.size.height) x += but.frame.size.width + 10 } scrollView.documentView?.setFrameSize(NSSize(width: 0, height: y + 26)) } } extension CalendarCell: CalendarPresenterOutput { func enable (_ enabled: Bool) { for but in calendarsButtons { but.isEnabled = enabled } } func setStatusImage (_ imageName: NSImage.Name) { statusImageView.image = NSImage(named: imageName) } func setStatusText (_ text: String) { statusTextField.stringValue = text } func setDescriptionText (_ text: String) { descriptionTextField.stringValue = text } func setCalendarStatus (authorized: Bool, enabled: Bool) { statusImageView.isHidden = authorized butAuthorize.isHidden = authorized butEnable.isHidden = !authorized butEnable.state = enabled ? NSControl.StateValue.on : NSControl.StateValue.off } func setCalendars (_ calendars: [String], selected: [String]) { for but in calendarsButtons { but.removeFromSuperview() } calendarsButtons = [] for title in calendars { let but = NSButton() but.setButtonType(.switch) but.title = title but.sizeToFit() but.state = selected.contains(title) ? .on : .off but.target = self but.action = #selector(didClickCalendarButton) scrollView.addSubview(but) self.calendarsButtons.append(but) } self.needsLayout = true } @objc func didClickCalendarButton (_ sender: NSButton) { if sender.state == .on { presenter.enableCalendar(sender.title) } else { presenter.disableCalendar(sender.title) } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Calendar/CalendarPresenter.swift ================================================ // // CalendarPresenter.swift // Jirassic // // Created by Cristian Baluta on 05/07/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import Cocoa import RCPreferences protocol CalendarPresenterInput: class { func enable (_ enabled: Bool) func enableCalendar (_ calendarTitle: String) func disableCalendar (_ calendarTitle: String) func refresh () func authorize() } protocol CalendarPresenterOutput: class { func enable (_ enabled: Bool) func setStatusImage (_ imageName: NSImage.Name) func setStatusText (_ text: String) func setDescriptionText (_ text: String) func setCalendarStatus (authorized: Bool, enabled: Bool) func setCalendars (_ calendars: [String], selected: [String]) } class CalendarPresenter { weak var userInterface: CalendarPresenterOutput? private let calendarModule = ModuleCalendar() private let pref = RCPreferences() } extension CalendarPresenter: CalendarPresenterInput { func enable (_ enabled: Bool) { pref.set(enabled, forKey: .enableCalendar) userInterface!.enable(enabled) } func enableCalendar (_ calendarTitle: String) { var calendars = calendarModule.selectedCalendars calendars.append(calendarTitle) calendarModule.selectedCalendars = calendars } func disableCalendar (_ calendarTitle: String) { let calendars = calendarModule.selectedCalendars.filter() { $0 != calendarTitle } calendarModule.selectedCalendars = calendars } func authorize() { calendarModule.authorize { (authorized) in DispatchQueue.main.async { self.refresh() } } } func refresh() { if calendarModule.isAuthorizationDetermined { userInterface!.setStatusImage(calendarModule.isAuthorized ? NSImage.statusAvailableName : NSImage.statusPartiallyAvailableName) userInterface!.setStatusText(calendarModule.isAuthorized ? "Calendar.app is accessible and ready to use" : "Calendar.app is not authorized") userInterface!.setDescriptionText(calendarModule.isAuthorized ? "Events from the selected calendars will appear in Jirassic as tasks" : "Authorize from: System Preferences / Security&Privacy / Privacy / Calendars") userInterface!.setCalendarStatus (authorized: calendarModule.isAuthorized, enabled: pref.bool(.enableCalendar)) } else { userInterface!.setStatusImage(NSImage.statusUnavailableName) userInterface!.setStatusText("Calendar.app was not authorized yet") userInterface!.setCalendarStatus (authorized: calendarModule.isAuthorized, enabled: pref.bool(.enableCalendar)) } userInterface!.setCalendars(calendarModule.allCalendarsTitles(), selected: calendarModule.selectedCalendars) userInterface!.enable(calendarModule.isAuthorized && pref.bool(.enableCalendar)) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift ================================================ // // GitCell.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class GitCell: NSTableRowView, Saveable { static let height = CGFloat(195) @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) { #if APPSTORE NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #else NSWorkspace.shared.open( URL(string: "https://github.com/ralcr/Jit")!) #endif } @IBAction func handlePurchaseButton (_ sender: NSButton) { onPurchasePressed?() } @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 { func setStatusImage (_ imageName: NSImage.Name) { statusImageView.image = NSImage(named: imageName) } func setStatusText (_ text: String) { statusTextField.stringValue = text } func setDescriptionText (_ text: String) { descriptionTextField.stringValue = text } func setButInstall (enabled: Bool) { butInstall.isHidden = !enabled } func setButPurchase (enabled: Bool) { butPurchase.isHidden = !enabled } func setButEnable (on: Bool?, enabled: Bool?) { if let isOn = on { butEnable.state = isOn ? .on : .off } 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 } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib ================================================ 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 ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Git/GitPresenter.swift ================================================ // // GitPresenter.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import Cocoa import RCPreferences protocol GitPresenterInput: class { var isShellScriptInstalled: Bool? {get set} func enableGit (_ enabled: Bool) func refresh (withCommand command: String) func pickPath() func save (emails: String, paths: String) } protocol GitPresenterOutput: class { func setStatusImage (_ imageName: NSImage.Name) func setStatusText (_ text: String) func setDescriptionText (_ text: String) func setButInstall (enabled: Bool) func setButPurchase (enabled: Bool) func setButEnable (on: Bool?, enabled: Bool?) func setPaths (_ paths: String?, enabled: Bool?) func setEmails (_ emails: String?, enabled: Bool?) } enum GitCellState { // First thing to do is purchase it case needsPurchase // After purchasing you need to install the apple scripts case needsShellScript case needsGitScript // When purchased and reachable via shell you have the option to enable or disable case disabled case enabled } class GitPresenter { weak var userInterface: GitPresenterOutput? private let gitModule = ModuleGitLogs() private let localPreferences = RCPreferences() private let store = Store.shared var isShellScriptInstalled: Bool? { didSet { refresh() } } init() { } private func refresh() { userInterface?.setPaths(localPreferences.string(.settingsGitPaths), enabled: localPreferences.bool(.enableGit)) userInterface?.setEmails(localPreferences.string(.settingsGitAuthors), enabled: localPreferences.bool(.enableGit)) guard store.isGitPurchased else { refresh(state: .needsPurchase) return } gitModule.checkIfGitInstalled(completion: { [weak self] commandInstalled in guard let wself = self else { return } guard wself.isShellScriptInstalled == true else { wself.refresh(state: .needsShellScript) return } guard commandInstalled else { wself.refresh(state: .needsGitScript) return } wself.refresh(state: wself.localPreferences.bool(.enableGit) ? .enabled : .disabled) }) } private func refresh (state: GitCellState) { guard let userInterface = self.userInterface else { return } userInterface.setDescriptionText("You will see git commits right in the reports. They are saved to database and synced with iCloud only after closing the day. Note: Git commits are not always 100% reliable") switch state { case .needsPurchase: userInterface.setStatusImage(NSImage.statusUnavailableName) userInterface.setStatusText("Purchase Git support") userInterface.setButInstall(enabled: false) userInterface.setButPurchase(enabled: true) userInterface.setButEnable(on: false, enabled: false) case .needsShellScript: userInterface.setStatusImage(NSImage.statusUnavailableName) userInterface.setStatusText("Not possible to use Git, please install shell scripts first!") userInterface.setButInstall(enabled: true) userInterface.setButPurchase(enabled: false) case .needsGitScript: userInterface.setStatusImage(NSImage.statusPartiallyAvailableName) userInterface.setStatusText("Git plugin is not installed, please install.") userInterface.setButInstall(enabled: true) userInterface.setButPurchase(enabled: false) case .disabled: userInterface.setStatusText("Git plugin is installed and ready to use, please enable.") userInterface.setButEnable(on: false, enabled: true) userInterface.setButInstall(enabled: false) userInterface.setButPurchase(enabled: false) case .enabled: userInterface.setStatusText("Git plugin is installed and ready to use.") userInterface.setButEnable(on: true, enabled: true) userInterface.setButInstall(enabled: false) userInterface.setButPurchase(enabled: false) } } } extension GitPresenter: GitPresenterInput { func enableGit (_ enabled: Bool) { localPreferences.set(enabled, forKey: .enableGit) userInterface?.setPaths(nil, enabled: enabled) userInterface?.setEmails(nil, enabled: enabled) userInterface?.setButEnable(on: enabled, enabled: nil) } func refresh (withCommand command: String) { // Set the command to userDefaults and it will be read by the hokup module from there // localPreferences.set(command, forKey: .settingsHookupCmdName) refresh() } 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) -> Void in guard let self, let userInterface = self.userInterface else { return } if result == NSApplication.ModalResponse.OK { if let url = panel.urls.first { var path = url.absoluteString path = path.replacingOccurrences(of: "file://", with: "") path.removeLast() // TODO: Validate if the picked project is a git project let existingPaths = self.localPreferences.string(.settingsGitPaths) let updatedPaths = existingPaths == "" ? path : (existingPaths + "," + path) self.savePaths(updatedPaths) userInterface.setPaths(updatedPaths, enabled: self.localPreferences.bool(.enableGit)) } } } } func save (emails: String, paths: String) { saveEmails(emails) savePaths(paths) } private func saveEmails (_ emails: String) { localPreferences.set(emails, forKey: .settingsGitAuthors) } private func savePaths (_ paths: String) { localPreferences.set(paths, forKey: .settingsGitPaths) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/InputsScrollView.swift ================================================ // // InputsScrollView.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class InputsScrollView: NSScrollView { @IBOutlet private var tableView: NSTableView! var dataSource: InputsTableViewDataSource?// If not declared, the reference is released var onPurchasePressed: (() -> Void)? override func awakeFromNib() { super.awakeFromNib() tableView.selectionHighlightStyle = .none tableView.backgroundColor = .clear tableView.intercellSpacing = NSSize(width: 0, height: 30) dataSource = InputsTableViewDataSource(tableView: tableView) dataSource?.onPurchasePressed = { [weak self] in self?.onPurchasePressed?() } tableView.dataSource = dataSource tableView.delegate = dataSource tableView.reloadData() } func showSettings (_ settings: SettingsBrowser) { dataSource!.showSettingsBrowser(settings) } func settings() -> SettingsBrowser { return dataSource!.settingsBrowser() } func save() { dataSource?.shellCell?.save() dataSource?.jitCell?.save() dataSource?.gitCell?.save() dataSource?.browserCell?.save() dataSource?.calendarCell?.save() } func setShellStatus (compatibility: Compatibility) { dataSource?.shellCell?.setShellStatus (compatibility: compatibility) } func setJirassicStatus (compatibility: Compatibility) { dataSource?.jirassicCell?.setJirassicStatus (compatibility: compatibility) } func setJitStatus (compatibility: Compatibility) { dataSource?.jitCell?.setJitStatus (compatibility: compatibility) } func setGitStatus (available: Bool) { dataSource?.gitCell?.presenter.isShellScriptInstalled = available } func setBrowserStatus (compatibility: Compatibility) { dataSource?.browserCell?.setBrowserStatus (compatibility: compatibility) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/InputsScrollView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Input/InputsTableViewDataSource.swift ================================================ // // InputsTableViewDataSource.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa enum InputType { case shell case jirassic case git case jit case browser case calendar static var all: [InputType] = [.shell, .jirassic, .git, .jit, .browser, .calendar] } class InputsTableViewDataSource: NSObject { private let tableView: NSTableView private let cells: [InputType] = InputType.all var shellCell: ShellCell? var jirassicCell: JirassicCell? var jitCell: JitCell? var gitCell: GitCell? var browserCell: BrowserCell? var calendarCell: CalendarCell? var onPurchasePressed: (() -> Void)? init (tableView: NSTableView) { self.tableView = tableView super.init() ShellCell.register(in: tableView) JirassicCell.register(in: tableView) JitCell.register(in: tableView) GitCell.register(in: tableView) BrowserCell.register(in: tableView) CalendarCell.register(in: tableView) shellCell = ShellCell.instantiate(in: self.tableView) jirassicCell = JirassicCell.instantiate(in: self.tableView) jitCell = JitCell.instantiate(in: self.tableView) gitCell = GitCell.instantiate(in: self.tableView) browserCell = BrowserCell.instantiate(in: self.tableView) calendarCell = CalendarCell.instantiate(in: self.tableView) gitCell?.onPurchasePressed = { [weak self] in self?.onPurchasePressed?() } } func showSettingsBrowser (_ settings: SettingsBrowser) { browserCell!.showSettings(settings) } func settingsBrowser() -> SettingsBrowser { return browserCell!.settings() } } extension InputsTableViewDataSource: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { return cells.count } func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { let inputType = cells[row] switch inputType { case .shell: return ShellCell.height case .jirassic: return JirassicCell.height case .git: return GitCell.height case .jit: return JitCell.height case .browser: return BrowserCell.height case .calendar: return CalendarCell.height } } } extension InputsTableViewDataSource: NSTableViewDelegate { func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { switch cells[row] { case .shell: return shellCell! case .jirassic: return jirassicCell! case .git: return gitCell! case .jit: return jitCell! case .browser: return browserCell! case .calendar: return calendarCell! } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/JirassicCmd/JirassicCell.swift ================================================ // // JirassicCell.swift // Jirassic // // Created by Cristian Baluta on 17/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class JirassicCell: NSTableRowView { static let height = CGFloat(60) @IBOutlet private var statusImageView: NSImageView! @IBOutlet private var textField: NSTextField! @IBOutlet private var butInstall: NSButton! func setJirassicStatus (compatibility: Compatibility) { if compatibility.available { statusImageView.image = compatibility.compatible ? NSImage(named: NSImage.statusAvailableName) : NSImage(named: "WarningButton") textField.stringValue = compatibility.compatible ? "Jirassic \(compatibility.currentVersion) installed, type 'jirassic' in Terminal for more info" : "Version \(compatibility.currentVersion) installed, min required is \(compatibility.minVersion), please update" } else { statusImageView.image = NSImage(named: NSImage.statusUnavailableName) textField.stringValue = "Not installed, please follow instructions!" } butInstall.isHidden = compatibility.available && compatibility.compatible } func save() { } @IBAction func handleInstallButton (_ sender: NSButton) { #if APPSTORE NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #else // presenter?.installJirassic() NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #endif } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/JirassicCmd/JirassicCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Jit/JitCell.swift ================================================ // // JitCell.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class JitCell: NSTableRowView { static let height = CGFloat(135) @IBOutlet private var statusImageView: NSImageView! @IBOutlet private var statusImageViewGapToButEnableConstraint: NSLayoutConstraint! @IBOutlet private var textField: NSTextField! @IBOutlet private var butInstall: NSButton! @IBOutlet private var butEnable: NSButton! private let prefs = RCPreferences() override func awakeFromNib() { super.awakeFromNib() butEnable.isHidden = true statusImageView.isHidden = false } func save() { } func setJitStatus (compatibility: Compatibility) { if compatibility.available { statusImageView.image = compatibility.compatible ? NSImage(named: NSImage.statusAvailableName) : NSImage(named: "WarningButton") statusImageViewGapToButEnableConstraint.constant = compatibility.compatible ? -14 : 5 textField.stringValue = compatibility.compatible ? "Jit \(compatibility.currentVersion) installed, type 'jit' in Terminal for more info" : "Version \(compatibility.currentVersion) installed, min required is \(compatibility.minVersion), please update" butEnable.state = prefs.bool(.enableJit) ? .on : .off } else { statusImageView.image = NSImage(named: NSImage.statusUnavailableName) textField.stringValue = "Cannot determine Jit compatibility, install shell support first!" } butInstall.isHidden = compatibility.available && compatibility.compatible butEnable.isHidden = !compatibility.available statusImageView.isHidden = compatibility.available && compatibility.compatible } @IBAction func handleInstallButton (_ sender: NSButton) { #if APPSTORE NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #else // presenter?.installJit() NSWorkspace.shared.open( URL(string: "https://github.com/ralcr/Jit")!) #endif } @IBAction func handleEnableButton (_ sender: NSButton) { prefs.set(sender.state == .on, forKey: .enableJit) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Jit/JitCell.xib ================================================ Jit is a CLI wrapper over Git which facilitates working with Jira tasks and branches. If enabled, the commits made with Jit will be saved to Jirassic immediately, being more accurate and synced over iCloud. ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Shell/ShellCell.swift ================================================ // // ShellCell.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class ShellCell: NSTableRowView { static let height = CGFloat(60) @IBOutlet private var statusImageView: NSImageView! @IBOutlet private var textField: NSTextField! @IBOutlet private var butInstall: NSButton! override func awakeFromNib() { super.awakeFromNib() self.toolTip = "Shell scripts are needed for communicating with the plugins: reading git logs, reading browser url, finding compatibility." } func setShellStatus (compatibility: Compatibility) { if compatibility.available { statusImageView.image = NSImage(named: compatibility.compatible ? NSImage.statusAvailableName : NSImage.statusPartiallyAvailableName) textField.stringValue = compatibility.compatible ? "Version \(compatibility.currentVersion) installed, Jirassic is now able to communicate with the shell." : "Outdated, please update!" } else { statusImageView.image = NSImage(named: NSImage.statusUnavailableName) textField.stringValue = "Not installed yet" } butInstall.isHidden = compatibility.available && compatibility.compatible } func save() { // Nothing to save } @IBAction func handleInstallButton (_ sender: NSButton) { #if APPSTORE NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #else // presenter?.installJirassic() NSWorkspace.shared.open( URL(string: "http://www.jirassic.com/#extensions")!) #endif } } ================================================ FILE: Delivery/macOS/Screens/Settings/Input/Shell/ShellCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupCell.swift ================================================ // // CocoaHookupCell.swift // Jirassic // // Created by Cristian Baluta on 09/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class CocoaHookupCell: NSTableRowView, Saveable { static let height = CGFloat(95) @IBOutlet fileprivate var statusImageView: NSImageView! @IBOutlet fileprivate var statusTextField: NSTextField! @IBOutlet fileprivate var butEnable: NSButton! @IBOutlet fileprivate var hookupNameTextField: NSTextField! @IBOutlet fileprivate var butPick: NSButton! var presenter: CocoaHookupPresenterInput = CocoaHookupPresenter() override func awakeFromNib() { super.awakeFromNib() hookupNameTextField.delegate = self (presenter as! CocoaHookupPresenter).userInterface = self } func save() { // Already saved } @IBAction func handleEnableButton (_ sender: NSButton) { presenter.enableCocoaHookup(sender.state == .on) } @IBAction func handlePickButton (_ sender: NSButton) { presenter.pickApp() } } extension CocoaHookupCell: CocoaHookupPresenterOutput { func setStatusImage (_ imageName: NSImage.Name) { statusImageView.image = NSImage(named: imageName) } func setStatusText (_ text: String) { statusTextField.stringValue = text } func setButEnable (on: Bool?, enabled: Bool?) { if let isOn = on { butEnable.title = isOn ? "Enabled" : "Disabled" butEnable.state = isOn ? .on : .off } if let enabled = enabled { butEnable.isEnabled = enabled } } func setButPick (enabled: Bool) { butPick.isEnabled = enabled } func setApp (appName: String?, enabled: Bool?) { if let appName = appName { hookupNameTextField.stringValue = appName } if let enabled = enabled { hookupNameTextField.isEnabled = enabled } } } extension CocoaHookupCell: NSTextFieldDelegate { func controlTextDidEndEditing (_ obj: Notification) { presenter.refresh(withApp: hookupNameTextField.stringValue) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupPresenter.swift ================================================ // // CocoaHookupPresenter.swift // Jirassic // // Created by Cristian Baluta on 09/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import Cocoa import RCPreferences protocol CocoaHookupPresenterInput: class { var isShellScriptInstalled: Bool? {get set} func enableCocoaHookup (_ enabled: Bool) func refresh (withApp appName: String) func pickApp() } protocol CocoaHookupPresenterOutput: class { func setStatusImage (_ imageName: NSImage.Name) func setStatusText (_ text: String) func setButEnable (on: Bool?, enabled: Bool?) func setButPick (enabled: Bool) func setApp (appName: String?, enabled: Bool?) } class CocoaHookupPresenter { weak var userInterface: CocoaHookupPresenterOutput? let hookupModule = ModuleHookup() fileprivate let localPreferences = RCPreferences() var isShellScriptInstalled: Bool? { didSet { self.refresh() } } init() { } func refresh() { hookupModule.isAppReachable(completion: { [weak self] appInstalled in guard let wself = self, let userInterface = wself.userInterface else { return } if wself.isShellScriptInstalled == true { userInterface.setStatusImage(appInstalled ? NSImage.statusAvailableName : NSImage.statusPartiallyAvailableName) userInterface.setStatusText(appInstalled ? "Start/Stop AppleScripts will be sent to the selected Cocoa app" : "App not reachable") } else { userInterface.setStatusImage(NSImage.statusUnavailableName) userInterface.setStatusText("Not possible to use hookups, please install shell support first!") } userInterface.setApp(appName: wself.localPreferences.string(.settingsHookupAppName), enabled: wself.localPreferences.bool(.enableCocoaHookup)) userInterface.setButEnable(on: wself.localPreferences.bool(.enableCocoaHookup), enabled: appInstalled) userInterface.setButPick(enabled: wself.localPreferences.bool(.enableCocoaHookup)) }) } } extension CocoaHookupPresenter: CocoaHookupPresenterInput { func enableCocoaHookup (_ enabled: Bool) { localPreferences.set(enabled, forKey: .enableCocoaHookup) userInterface!.setApp(appName: nil, enabled: enabled) userInterface!.setButEnable(on: enabled, enabled: nil) userInterface!.setButPick(enabled: enabled) } func refresh (withApp appName: String) { // Set the command to userDefaults and it will be read by the hookup module from there localPreferences.set(appName, forKey: .settingsHookupAppName) refresh() } func pickApp() { let panel = NSOpenPanel() panel.canChooseFiles = true panel.canChooseDirectories = false panel.allowsMultipleSelection = false panel.showsHiddenFiles = false panel.allowedFileTypes = ["app"] panel.message = "Please select the custom .app with support for Jirassic AppleScript" panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) panel.begin { [weak self] (result) -> Void in guard let self else { return } if result == NSApplication.ModalResponse.OK { if let url = panel.urls.first { let appName = url.absoluteString .components(separatedBy: "/") .last? .replacingOccurrences(of: ".app", with: "") .replacingOccurrences(of: "%20", with: " ") self.refresh (withApp: appName ?? "") } } } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/Hookup/HookupCell.swift ================================================ // // HookupCell.swift // Jirassic // // Created by Cristian Baluta on 31/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class HookupCell: NSTableRowView, Saveable { static let height = CGFloat(160) @IBOutlet fileprivate var statusImageView: NSImageView! @IBOutlet fileprivate var statusTextField: NSTextField! @IBOutlet fileprivate var butEnable: NSButton! @IBOutlet fileprivate var hookupNameTextField: NSTextField! @IBOutlet fileprivate var butEnableCredentials: NSButton! @IBOutlet fileprivate var butPick: NSButton! var presenter: HookupPresenterInput = HookupPresenter() override func awakeFromNib() { super.awakeFromNib() hookupNameTextField.delegate = self (presenter as! HookupPresenter).userInterface = self } func save() { // Already saved } @IBAction func handleEnableButton (_ sender: NSButton) { presenter.enableHookup(sender.state == .on) } @IBAction func handleEnableCredentialsButton (_ sender: NSButton) { presenter.enableCredentials(sender.state == .on) } @IBAction func handlePickButton (_ sender: NSButton) { presenter.pickCLI() } } extension HookupCell: HookupPresenterOutput { func setStatusImage (_ imageName: NSImage.Name) { statusImageView.image = NSImage(named: imageName) } func setStatusText (_ text: String) { statusTextField.stringValue = text } func setButEnable (on: Bool?, enabled: Bool?) { if let isOn = on { butEnable.title = isOn ? "Enabled" : "Disabled" butEnable.state = isOn ? .on : .off } if let enabled = enabled { butEnable.isEnabled = enabled } } func setButEnableCredentials (on: Bool?, enabled: Bool?) { if let isOn = on { butEnableCredentials.state = isOn ? .on : .off } if let enabled = enabled { butEnableCredentials.isEnabled = enabled } } func setButPick (enabled: Bool) { butPick.isEnabled = enabled } func setCommand (path: String?, enabled: Bool?) { if let path = path { hookupNameTextField.stringValue = path } if let enabled = enabled { hookupNameTextField.isEnabled = enabled } } } extension HookupCell: NSTextFieldDelegate { func controlTextDidEndEditing (_ obj: Notification) { presenter.refresh(withCommand: hookupNameTextField.stringValue) } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/Hookup/HookupCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Output/Hookup/HookupPresenter.swift ================================================ // // HookupPresenter.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import Cocoa import RCPreferences protocol HookupPresenterInput: class { var isShellScriptInstalled: Bool? {get set} func enableHookup (_ enabled: Bool) func enableCredentials (_ enabled: Bool) func refresh (withCommand command: String) func pickCLI() } protocol HookupPresenterOutput: class { func setStatusImage (_ imageName: NSImage.Name) func setStatusText (_ text: String) func setButEnable (on: Bool?, enabled: Bool?) func setButEnableCredentials (on: Bool?, enabled: Bool?) func setButPick (enabled: Bool) func setCommand (path: String?, enabled: Bool?) } class HookupPresenter { weak var userInterface: HookupPresenterOutput? let hookupModule = ModuleHookup() fileprivate let localPreferences = RCPreferences() var isShellScriptInstalled: Bool? { didSet { self.refresh() } } init() { } func refresh() { hookupModule.isCmdReachable(completion: { [weak self] commandInstalled in guard let wself = self, let userInterface = wself.userInterface else { return } if wself.isShellScriptInstalled == true { userInterface.setStatusImage(commandInstalled ? NSImage.statusAvailableName : NSImage.statusPartiallyAvailableName) userInterface.setStatusText(commandInstalled ? "Start/End day actions will be sent to this custom cmd" : "Custom cmd is not reachable") } else { userInterface.setStatusImage(NSImage.statusUnavailableName) userInterface.setStatusText("Not possible to use custom cmd, please install shell support first!") } userInterface.setCommand(path: wself.localPreferences.string(.settingsHookupCmdName), enabled: wself.localPreferences.bool(.enableHookup)) userInterface.setButEnable(on: wself.localPreferences.bool(.enableHookup), enabled: commandInstalled) userInterface.setButEnableCredentials(on: wself.localPreferences.bool(.enableHookupCredentials), enabled: commandInstalled) userInterface.setButPick(enabled: wself.localPreferences.bool(.enableHookup)) }) } } extension HookupPresenter: HookupPresenterInput { func enableHookup (_ enabled: Bool) { localPreferences.set(enabled, forKey: .enableHookup) userInterface!.setCommand(path: nil, enabled: enabled) userInterface!.setButEnable(on: enabled, enabled: nil) userInterface!.setButPick(enabled: enabled) } func enableCredentials (_ enabled: Bool) { localPreferences.set(enabled, forKey: .enableHookupCredentials) } func refresh (withCommand command: String) { // Set the command to userDefaults and it will be read by the hookup module from there localPreferences.set(command, forKey: .settingsHookupCmdName) refresh() } func pickCLI() { let panel = NSOpenPanel() panel.canChooseFiles = true panel.canChooseDirectories = false panel.allowsMultipleSelection = false panel.showsHiddenFiles = true panel.allowedFileTypes = [""] panel.message = "Please select a CLI app" panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) panel.begin { [weak self] (result) -> Void in guard let wself = self else { return } if result == NSApplication.ModalResponse.OK { if let url = panel.urls.first { var path = url.absoluteString path = path.replacingOccurrences(of: "file://", with: "") wself.refresh (withCommand: path) } } } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoCell.swift ================================================ // // JiraTempoCell.swift // Jirassic // // Created by Cristian Baluta on 31/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class JiraTempoCell: NSTableRowView { static let height = CGFloat(250) @IBOutlet private var butPurchase: 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! private let localPreferences = RCPreferences() var presenter: JiraTempoPresenterInput = JiraTempoPresenter() var onPurchasePressed: (() -> Void)? override func awakeFromNib() { super.awakeFromNib() baseUrlTextField.delegate = self userTextField.delegate = self passwordTextField.delegate = self baseUrlTextField.stringValue = localPreferences.string(.settingsJiraUrl) userTextField.stringValue = localPreferences.string(.settingsJiraUser) passwordTextField.stringValue = Keychain.getPassword() projectNamePopup.target = self projectNamePopup.action = #selector(JiraTempoCell.projectNamePopupSelected(_:)) projectIssueNamePopup.target = self projectIssueNamePopup.action = #selector(JiraTempoCell.projectIssueNamePopupSelected(_:)) (presenter as! JiraTempoPresenter).userInterface = self presenter.setupUserInterface() presenter.checkCredentials() } func save() { presenter.save(url: baseUrlTextField.stringValue, user: userTextField.stringValue, password: passwordTextField.stringValue) } @IBAction func handlePurchaseButton (_ sender: NSButton) { onPurchasePressed?() } @IBAction func projectNamePopupSelected (_ sender: NSPopUpButton) { if let title = sender.selectedItem?.title { localPreferences.set(title, forKey: .settingsJiraProjectKey) presenter.loadProjectIssues(for: title) } } @IBAction func projectIssueNamePopupSelected (_ sender: NSPopUpButton) { localPreferences.set(sender.selectedItem?.title ?? "", forKey: .settingsJiraProjectIssueKey) } } extension JiraTempoCell: 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 } } extension JiraTempoCell: NSTextFieldDelegate { func controlTextDidEndEditing(_ obj: Notification) { guard baseUrlTextField.stringValue != "", userTextField.stringValue != "", passwordTextField.stringValue != "" else { // Fields are empty save() return } guard baseUrlTextField.stringValue != localPreferences.string(.settingsJiraUrl) || userTextField.stringValue != localPreferences.string(.settingsJiraUser) || passwordTextField.stringValue != Keychain.getPassword() else { // No change to the fields return } save() presenter.checkCredentials() } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoCell.xib ================================================ NSAllRomanInputSourcesLocaleIdentifier ================================================ FILE: Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoPresenter.swift ================================================ // // JiraTempoPresenter.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences import RCLog protocol JiraTempoPresenterInput: class { func setupUserInterface() func checkCredentials() func loadProjects() func loadProjectIssues (for projectKey: String) func save (url: String, user: String, password: String) } protocol JiraTempoPresenterOutput: class { func setPurchased (_ purchased: Bool) func enableProgressIndicator (_ enabled: Bool) func showProjects (_ projects: [String], selectedProject: String) func showProjectIssues (_ issues: [String], selectedIssue: String) func showErrorMessage (_ message: String) } class JiraTempoPresenter { weak var userInterface: JiraTempoPresenterOutput? private var moduleJira = ModuleJiraTempo() private let store = Store.shared private let pref = RCPreferences() } extension JiraTempoPresenter: JiraTempoPresenterInput { func checkCredentials() { loadProjects() } func setupUserInterface() { userInterface!.setPurchased(store.isJiraTempoPurchased) userInterface!.showErrorMessage("") let selectedProjectName = pref.string(.settingsJiraProjectKey) let projects = selectedProjectName != "" ? [selectedProjectName] : [] userInterface!.showProjects(projects, selectedProject: selectedProjectName) let selectedProjectIssueName = pref.string(.settingsJiraProjectIssueKey) let issues = selectedProjectIssueName != "" ? [selectedProjectIssueName] : [] userInterface!.showProjectIssues(issues, selectedIssue: selectedProjectIssueName) } func loadProjects() { // Start loading only if credentials are all setup, otherwise crashes will happen guard store.isJiraTempoPurchased && moduleJira.isConfigured else { return } userInterface!.enableProgressIndicator(true) userInterface!.showErrorMessage("") moduleJira.fetchProjects(success: { [weak self] (projects) in DispatchQueue.main.async { guard let wself = self, let userInterface = wself.userInterface else { return } userInterface.enableProgressIndicator(false) let titles = projects.map { $0.key } let selectedProjectKey = wself.pref.string(.settingsJiraProjectKey) userInterface.showProjects(titles, selectedProject: selectedProjectKey) if projects.count > 0 && selectedProjectKey != "" { wself.loadProjectIssues(for: selectedProjectKey) } } }, failure: { [weak self] (error) in DispatchQueue.main.async { self?.handleError(error) } }) } func loadProjectIssues (for projectKey: String) { userInterface!.enableProgressIndicator(true) userInterface!.showErrorMessage("") moduleJira.fetchProjectIssues (projectKey: projectKey, success: { [weak self] (issues) in DispatchQueue.main.async { guard let wself = self, let userInterface = wself.userInterface else { return } userInterface.enableProgressIndicator(false) let titles = issues.map { $0.key } userInterface.showProjectIssues(titles, selectedIssue: wself.pref.string(.settingsJiraProjectIssueKey)) } }, failure: { [weak self] (error) in DispatchQueue.main.async { self?.handleError(error) } }) } private func handleError(_ error: Error) { RCLog(error) var errorMessage = error.localizedDescription switch error._code { case -1001: errorMessage = "Server not reachable. Is your Jira limited to internal network?" case 1: errorMessage = "Unknown, please verify login via browser. Possible causes are wrong domain or you're using an expired password which is causing Jira to ask for captcha." default: errorMessage = error.localizedDescription } userInterface!.enableProgressIndicator(false) userInterface!.showErrorMessage("Error: \(errorMessage)") } func save (url: String, user: String, password: String) { pref.set(url, forKey: .settingsJiraUrl) pref.set(user, forKey: .settingsJiraUser) // Save password only if different than the existing one if password != Keychain.getPassword() { Keychain.setPassword(password) } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/OutputTableViewDataSource.swift ================================================ // // OutputTableViewDataSource.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa enum OutputType { case jiraTempo case cliHookup case cocoaHookup } class OutputTableViewDataSource: NSObject { private let tableView: NSTableView private let cells: [OutputType] = [.jiraTempo] var jiraCell: JiraTempoCell? var cliHookupCell: HookupCell? var cocoaHookupCell: CocoaHookupCell? var onPurchasePressed: (() -> Void)? init (tableView: NSTableView) { self.tableView = tableView super.init() JiraTempoCell.register(in: tableView) // HookupCell.register(in: tableView) // CocoaHookupCell.register(in: tableView) jiraCell = JiraTempoCell.instantiate(in: self.tableView) // cliHookupCell = HookupCell.instantiate(in: self.tableView) // cocoaHookupCell = CocoaHookupCell.instantiate(in: self.tableView) jiraCell?.onPurchasePressed = { [weak self] in self?.onPurchasePressed?() } } } extension OutputTableViewDataSource: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { return cells.count } func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { let outputType = cells[row] switch outputType { case .jiraTempo: return JiraTempoCell.height case .cliHookup: return HookupCell.height case .cocoaHookup: return CocoaHookupCell.height } } } extension OutputTableViewDataSource: NSTableViewDelegate { func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var cell: NSView! = nil let outputType = cells[row] switch outputType { case .jiraTempo: cell = jiraCell! case .cliHookup: cell = cliHookupCell! case .cocoaHookup: cell = cocoaHookupCell! } return cell } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/OutputsScrollView.swift ================================================ // // OutputsTableView.swift // Jirassic // // Created by Cristian Baluta on 01/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class OutputsScrollView: NSScrollView { @IBOutlet fileprivate var tableView: NSTableView! var dataSource: OutputTableViewDataSource?// If not declared, the reference is released var onPurchasePressed: (() -> Void)? override func awakeFromNib() { super.awakeFromNib() tableView.selectionHighlightStyle = .none tableView.backgroundColor = .clear tableView.intercellSpacing = NSSize(width: 0, height: 30) dataSource = OutputTableViewDataSource(tableView: tableView) dataSource?.onPurchasePressed = { [weak self] in self?.onPurchasePressed?() } tableView.dataSource = dataSource tableView.delegate = dataSource tableView.reloadData() } func save() { dataSource?.jiraCell?.save() dataSource?.cliHookupCell?.save() dataSource?.cocoaHookupCell?.save() } func setHookupStatus (available: Bool) { dataSource?.cliHookupCell?.presenter.isShellScriptInstalled = available dataSource?.cocoaHookupCell?.presenter.isShellScriptInstalled = available } } ================================================ FILE: Delivery/macOS/Screens/Settings/Output/OutputsScrollView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Saveable.swift ================================================ // // Saveable.swift // Jirassic // // Created by Cristian Baluta on 17/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation protocol Saveable { func save() } ================================================ FILE: Delivery/macOS/Screens/Settings/Settings.storyboard ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/SettingsInteractor.swift ================================================ // // SettingsInteractor.swift // Jirassic // // Created by Cristian Baluta on 06/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import ServiceManagement import RCPreferences import RCLog protocol SettingsInteractorInput: class { func getAppSettings() -> Settings func saveAppSettings (_ settings: Settings) func enabledLaunchAtStartup (_ enabled: Bool) } protocol SettingsInteractorOutput: class { } class SettingsInteractor { weak var presenter: SettingsInteractorOutput? fileprivate let localPreferences = RCPreferences() init() { } } extension SettingsInteractor: SettingsInteractorInput { func getAppSettings() -> Settings { return localRepository!.settings() } func saveAppSettings (_ settings: Settings) { RCLog("Saving \(settings)") localRepository!.saveSettings(settings) } func enabledLaunchAtStartup (_ enabled: Bool) { let launchAtStartup = SMLoginItemSetEnabled(launcherIdentifier as CFString, enabled) localPreferences.set(launchAtStartup ? enabled : false, forKey: .launchAtStartup) } } ================================================ FILE: Delivery/macOS/Screens/Settings/SettingsPresenter.swift ================================================ // // SettingsPresenter.swift // Jirassic // // Created by Cristian Baluta on 02/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RCPreferences protocol SettingsPresenterInput: class { func checkExtensions() func showSettings() func selectTab (_ tab: SettingsTab) func saveAppSettings (_ settings: Settings) func enableBackup (_ enabled: Bool) func enableLaunchAtStartup (_ enabled: Bool) func installJirassic() func installJit() } protocol SettingsPresenterOutput: class { func setShellStatus (compatibility: Compatibility) func setJirassicStatus (compatibility: Compatibility) func setJitStatus (compatibility: Compatibility) func setGitStatus (available: Bool) func setBrowserStatus (compatibility: Compatibility) func setHookupStatus (available: Bool) func showAppSettings (_ settings: Settings) func enableBackup (_ enabled: Bool, title: String) func enableLaunchAtStartup (_ enabled: Bool) func selectTab (_ tab: SettingsTab) } class SettingsPresenter { private var extensions = ExtensionsInteractor() #if !APPSTORE private var extensionsInstaller = ExtensionsInstallerInteractor() #endif weak var userInterface: SettingsPresenterOutput? var interactor: SettingsInteractorInput? private let pref = RCPreferences() } extension SettingsPresenter: SettingsPresenterInput { func checkExtensions() { extensions.getVersions { [weak self] (versions) in guard let userInterface = self?.userInterface else { return } let compatibility = Versioning(versions: versions) // Setup shell script userInterface.setShellStatus(compatibility: compatibility.shellScript) // Setup jirassic cmd userInterface.setJirassicStatus(compatibility: compatibility.jirassic) // Setup jit cmd userInterface.setJitStatus(compatibility: compatibility.jit) // Setup browser script userInterface.setBrowserStatus(compatibility: compatibility.browser) // Git requires extra call userInterface.setGitStatus(available: versions.shellScript != "") // Hookup requires extra call userInterface.setHookupStatus(available: versions.shellScript != "") } } func showSettings() { let settings = interactor!.getAppSettings() userInterface!.showAppSettings(settings) userInterface!.enableLaunchAtStartup( pref.bool(.launchAtStartup) ) enableBackup(settings.enableBackup) let lastActiveSettingsTab = SettingsTab(rawValue: pref.int(.settingsActiveTab))! selectTab(lastActiveSettingsTab) } func selectTab (_ tab: SettingsTab) { pref.set(tab.rawValue, forKey: .settingsActiveTab) userInterface!.selectTab(tab) } func saveAppSettings (_ settings: Settings) { interactor!.saveAppSettings(settings) } func enableBackup (_ enabled: Bool) { #if APPSTORE if enabled { // Init the global instance of remoteRepository in AppDelegate remoteRepository = CloudKitRepository() remoteRepository?.getUser({ (user) in if user == nil { self.userInterface?.enableBackup(false, title: "Syncing with iCloud not possible, you are not logged into iCloud") remoteRepository = nil } else { self.userInterface?.enableBackup(true, title: "Sync with iCloud and iOS app") } }) } else { remoteRepository = nil self.userInterface?.enableBackup(enabled, title: "Sync with iCloud and iOS app") } #endif } func enableLaunchAtStartup (_ enabled: Bool) { interactor!.enabledLaunchAtStartup(enabled) } func installJirassic() { #if !APPSTORE extensionsInstaller.installJirassic { (success) in // self.userInterface!.setJirassicStatus(compatibility: <#T##Compatibility#>) } #endif } func installJit() { #if !APPSTORE extensionsInstaller.installJit { (success) in // self.userInterface!.setJitStatus(compatibility: <#T##Compatibility#>) } #endif } } extension SettingsPresenter: SettingsInteractorOutput { } ================================================ FILE: Delivery/macOS/Screens/Settings/SettingsViewController.swift ================================================ // // SettingsViewController.swift // Jirassic // // Created by Baluta Cristian on 06/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences import RCLog enum SettingsTab: Int { case tracking = 0 case input = 1 case output = 2 case store = 3 } class SettingsViewController: NSViewController { @IBOutlet private var segmentedControl: NSSegmentedControl! @IBOutlet private var container: NSBox! @IBOutlet private var butBackup: NSButton! @IBOutlet private var butEnableLaunchAtStartup: NSButton! // Tracking tab private var trackingView: TrackingView? // Input tab private var inputsScrollView: InputsScrollView? // Output tab private var outputsScrollView: OutputsScrollView? // Store tab private var storeView: StoreView? weak var appWireframe: AppWireframe? var presenter: SettingsPresenterInput? private let localPreferences = RCPreferences() override func viewDidAppear() { super.viewDidAppear() createLayer() trackingView = TrackingView.instantiateFromXib() inputsScrollView = InputsScrollView.instantiateFromXib() outputsScrollView = OutputsScrollView.instantiateFromXib() // storeView = StoreView.instantiateFromXib() presenter!.checkExtensions() presenter!.showSettings() inputsScrollView?.onPurchasePressed = { [weak self] in self?.presenter?.selectTab(.store) } outputsScrollView?.onPurchasePressed = { [weak self] in self?.presenter?.selectTab(.store) } #if !APPSTORE butBackup.isEnabled = false butBackup.state = NSControl.StateValue.off butEnableLaunchAtStartup.isEnabled = false butEnableLaunchAtStartup.state = NSControl.StateValue.off #endif } override func viewWillDisappear() { super.viewWillDisappear() let settings = Settings( enableBackup: butBackup.state == NSControl.StateValue.on, settingsTracking: trackingView!.settings(), settingsBrowser: inputsScrollView!.settings() ) presenter!.saveAppSettings(settings) trackingView?.save() inputsScrollView?.save() outputsScrollView?.save() } deinit { RCLog("deinit") } func removeSelectedTabView() { trackingView!.removeFromSuperview() inputsScrollView!.removeFromSuperview() outputsScrollView!.removeFromSuperview() // storeView!.removeFromSuperview() } } extension SettingsViewController { @IBAction func handleSaveButton (_ sender: NSButton) { appWireframe!.flipToTasksController() } @IBAction func handleBackupButton (_ sender: NSButton) { presenter!.enableBackup(sender.state == NSControl.StateValue.on) } @IBAction func handleLaunchAtStartupButton (_ sender: NSButton) { presenter!.enableLaunchAtStartup(sender.state == NSControl.StateValue.on) } @IBAction func handleSegmentedControl (_ sender: NSSegmentedControl) { let tab = SettingsTab(rawValue: sender.selectedSegment)! presenter!.selectTab(tab) } @IBAction func handleQuitAppButton (_ sender: NSButton) { NSApplication.shared.terminate(nil) } @IBAction func handleMinimizeAppButton (_ sender: NSButton) { AppDelegate.sharedApp().menu.triggerClose() } } extension SettingsViewController: Animatable { func createLayer() { view.layer = CALayer() view.wantsLayer = true } } extension SettingsViewController: SettingsPresenterOutput { func setShellStatus (compatibility: Compatibility) { inputsScrollView?.setShellStatus (compatibility: compatibility) } func setJirassicStatus (compatibility: Compatibility) { inputsScrollView?.setJirassicStatus (compatibility: compatibility) } func setJitStatus (compatibility: Compatibility) { inputsScrollView?.setJitStatus (compatibility: compatibility) } func setGitStatus (available: Bool) { inputsScrollView?.setGitStatus (available: available) } func setBrowserStatus (compatibility: Compatibility) { inputsScrollView?.setBrowserStatus (compatibility: compatibility) } func setHookupStatus (available: Bool) { outputsScrollView?.setHookupStatus (available: available) } func showAppSettings (_ settings: Settings) { trackingView?.showSettings(settings.settingsTracking) inputsScrollView?.showSettings(settings.settingsBrowser) butBackup.state = settings.enableBackup ? NSControl.StateValue.on : NSControl.StateValue.off } func enableLaunchAtStartup (_ enabled: Bool) { butEnableLaunchAtStartup.state = enabled ? NSControl.StateValue.on : NSControl.StateValue.off } func enableBackup (_ enabled: Bool, title: String) { butBackup.state = enabled ? NSControl.StateValue.on : NSControl.StateValue.off butBackup.title = title } func selectTab (_ tab: SettingsTab) { removeSelectedTabView() segmentedControl!.selectedSegment = tab.rawValue switch tab { case .tracking: container.addSubview(trackingView!) trackingView!.constrainToSuperview() case .input: container.addSubview(inputsScrollView!) inputsScrollView!.constrainToSuperview() case .output: container.addSubview(outputsScrollView!) outputsScrollView!.constrainToSuperview() case .store: break // container.addSubview(storeView!) // storeView!.constrainToSuperview() } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Store/StoreView.swift ================================================ // // StoreView.swift // Jirassic // // Created by Cristian Baluta on 18/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences import RCLog class StoreView: NSView { @IBOutlet private var butBuy: NSButton! @IBOutlet private var butCancel: NSButton! @IBOutlet private var butRestore: NSButton! @IBOutlet private var progressIndicatorAll: NSProgressIndicator! @IBOutlet private var progressIndicatorRestore: NSProgressIndicator! @IBOutlet private var priceTextField: NSTextField! @IBOutlet private var descriptionAllTextField: NSTextField! private let localPreferences = RCPreferences() private let store = Store.shared override func awakeFromNib() { super.awakeFromNib() progressIndicatorAll.isHidden = true progressIndicatorRestore.isHidden = true refresh() } @IBAction func handleBuyButton (_ sender: NSButton) { butBuy.isHidden = true progressIndicatorAll.isHidden = false progressIndicatorAll.startAnimation(sender) store.purchase(product: .full) { [weak self] (success) in DispatchQueue.main.async { if success { self?.progressIndicatorAll.isHidden = true self?.progressIndicatorAll.stopAnimation(nil) self?.refresh() } } } } @IBAction func handleCancelButton (_ sender: NSButton) { if let url = URL(string: "https://apps.apple.com/account/subscriptions"), NSWorkspace.shared.open(url) { RCLog("default browser was successfully opened") } } @IBAction func handleRestoreButton (_ sender: NSButton) { butRestore.isHidden = true progressIndicatorRestore.isHidden = false progressIndicatorRestore.startAnimation(sender) store.restore() { [weak self] (success) in DispatchQueue.main.async { if success { self?.butRestore.isHidden = false self?.progressIndicatorRestore.isHidden = true self?.progressIndicatorRestore.stopAnimation(nil) } } } } private func refresh() { butBuy.isHidden = store.isGitPurchased && store.isJiraTempoPurchased butCancel.isHidden = !butBuy.isHidden butRestore.isHidden = store.isGitPurchased && store.isJiraTempoPurchased descriptionAllTextField.stringValue = "Try for 30 days for free, after that you will be charged every 6 months. The subscription can be disabled from your App Store account.\n\nWhat's included in the subscription:\n• Git plugin will let you see commits made with Git in the daily reports\n• Jira Tempo plugin will let you save daily reports directly to Jira Tempo" store.getProduct(.full) { [weak self] skProduct in if let product = skProduct { DispatchQueue.main.async { self?.priceTextField.stringValue = product.localizedPrice() ?? "" } } } } } ================================================ FILE: Delivery/macOS/Screens/Settings/Store/StoreView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Settings/Tracking/TrackingView.swift ================================================ // // TrackingView.swift // Jirassic // // Created by Cristian Baluta on 02/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class TrackingView: NSView { @IBOutlet fileprivate var butAutotrack: NSButton! @IBOutlet fileprivate var autotrackingModeSegmentedControl: NSSegmentedControl! @IBOutlet fileprivate var butTrackStartOfDay: NSButton! @IBOutlet fileprivate var butTrackLunch: NSButton! @IBOutlet fileprivate var butTrackScrum: NSButton! // @IBOutlet fileprivate var butTrackMeetings: NSButton! @IBOutlet fileprivate var startOfDayTimePicker: NSDatePicker! @IBOutlet fileprivate var endOfDayTimePicker: NSDatePicker! @IBOutlet fileprivate var lunchTimePicker: NSDatePicker! @IBOutlet fileprivate var scrumTimePicker: NSDatePicker! @IBOutlet fileprivate var minSleepDurationLabel: NSTextField! @IBOutlet fileprivate var minSleepDurationSlider: NSSlider! @IBAction func handleAutoTrackButton (_ sender: NSButton) { autotrackingModeSegmentedControl.isEnabled = sender.state == NSControl.StateValue.on } @IBAction func handleMinSleepDuration (_ sender: NSSlider) { minSleepDurationLabel.stringValue = "Ignore sleeps shorter than \(sender.integerValue) minutes" } func showSettings (_ settings: SettingsTracking) { butAutotrack.state = settings.autotrack ? NSControl.StateValue.on : NSControl.StateValue.off autotrackingModeSegmentedControl.selectedSegment = settings.autotrackingMode.rawValue minSleepDurationSlider.integerValue = settings.minSleepDuration handleMinSleepDuration(minSleepDurationSlider) butTrackStartOfDay.state = settings.trackStartOfDay ? NSControl.StateValue.on : NSControl.StateValue.off butTrackLunch.state = settings.trackLunch ? NSControl.StateValue.on : NSControl.StateValue.off butTrackScrum.state = settings.trackScrum ? NSControl.StateValue.on : NSControl.StateValue.off startOfDayTimePicker.dateValue = settings.startOfDayTime endOfDayTimePicker.dateValue = settings.endOfDayTime lunchTimePicker.dateValue = settings.lunchTime scrumTimePicker.dateValue = settings.scrumTime } func settings() -> SettingsTracking { return SettingsTracking( autotrack: butAutotrack.state == NSControl.StateValue.on, autotrackingMode: TrackingMode(rawValue: autotrackingModeSegmentedControl.selectedSegment)!, trackLunch: butTrackLunch.state == NSControl.StateValue.on, trackScrum: butTrackScrum.state == NSControl.StateValue.on, trackMeetings: true,//butTrackMeetings.state == NSControl.StateValue.on, trackStartOfDay: butTrackStartOfDay.state == NSControl.StateValue.on, startOfDayTime: startOfDayTimePicker.dateValue, endOfDayTime: endOfDayTimePicker.dateValue, lunchTime: lunchTimePicker.dateValue, scrumTime: scrumTimePicker.dateValue, minSleepDuration: minSleepDurationSlider.integerValue ) } func save() { } } ================================================ FILE: Delivery/macOS/Screens/Settings/Tracking/TrackingView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionPresenter.swift ================================================ // // TaskSuggestionPresenter.swift // Jirassic // // Created by Cristian Baluta on 30/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Foundation protocol TaskSuggestionPresenterInput: class { func setup (startSleepDate: Date?, endSleepDate: Date?) func selectSegment (atIndex index: Int) func save (selectedSegment: Int, notes: String, startSleepDate: Date?, endSleepDate: Date?) } protocol TaskSuggestionPresenterOutput: class { func selectSegment (atIndex index: Int) func setTime (_ notes: String) func setNotes (_ notes: String) func hideTaskTypes() } class TaskSuggestionPresenter { weak var userInterface: TaskSuggestionPresenterOutput? private var isStartOfDay = false private let startWorkText = "Good morning, ready to start working?" private let moduleCalendar = ModuleCalendar() private func taskType (forIndex index: Int) -> TaskType { switch index { case 0: return .scrum case 1: return .meeting case 2: return .lunch case 3: return .waste case 4: return .learning default: return .issue } } private func selectedSegment (forTaskType taskType: TaskType) -> Int { switch taskType { case .scrum: return 0 case .meeting: return 1 case .lunch: return 2 case .waste: return 3 case .learning: return 4 default: return -1 } } } extension TaskSuggestionPresenter: TaskSuggestionPresenterInput { func setup (startSleepDate: Date?, endSleepDate: Date?) { var time = "" if let startDate = startSleepDate { time += "Away between \(startDate.HHmm()) - " } time += endSleepDate!.HHmm() userInterface!.setTime(time) if let startDate = startSleepDate { let settings: Settings = SettingsInteractor().getAppSettings() let interactor = ComputerWakeUpInteractor(repository: localRepository, remoteRepository: remoteRepository, settings: settings) if let type = interactor.estimationForDate(startDate, currentDate: endSleepDate ?? Date()) { if type == .startDay { isStartOfDay = true userInterface!.setNotes(startWorkText) userInterface!.hideTaskTypes() } else { let index = selectedSegment(forTaskType: type) selectSegment(atIndex: index) // If selected segment is meeting, try to see if the meeting is a calendar event and populate with that data if type == .meeting { moduleCalendar.events(dateStart: startDate, dateEnd: startDate.endOfDay()) { (tasks) in for task in tasks { if task.startDate!.isAlmostSameHourAs(startDate) && task.endDate.isAlmostSameHourAs(endSleepDate!, devianceSeconds: 30.0.minToSec) { self.userInterface!.setNotes(task.notes ?? "") break } } } } } } } else { isStartOfDay = true userInterface!.setNotes(startWorkText) userInterface!.hideTaskTypes() } } func selectSegment (atIndex index: Int) { let type = taskType(forIndex: index) userInterface!.setNotes(type.defaultNotes) userInterface!.selectSegment(atIndex: index) } func save (selectedSegment: Int, notes: String, startSleepDate: Date?, endSleepDate: Date?) { var task: Task if isStartOfDay { task = Task(endDate: endSleepDate!, type: .startDay) } else { let type = taskType(forIndex: selectedSegment) task = Task(endDate: endSleepDate!, type: type) task.notes = notes task.startDate = startSleepDate } let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in }) if task.taskType == .startDay { ModuleHookup().insert(task: task) } } } ================================================ FILE: Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionTests.swift ================================================ // // TaskSuggestionTests.swift // Jirassic // // Created by Cristian Baluta on 10/12/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import XCTest @testable import Jirassic_no_cloud fileprivate class TaskSuggestionPresenterMock: TaskSuggestionPresenterOutput { var selectSegment_called = false var setTime_called = false var setNotes_called = false var hideTaskTypes_called = false func selectSegment (atIndex index: Int) { selectSegment_called = true } func setTime (_ notes: String) { setTime_called = true } func setNotes (_ notes: String) { setNotes_called = true } func hideTaskTypes() { hideTaskTypes_called = true } } class TaskSuggestionTests: XCTestCase { override func setUp() { super.setUp() localRepository = InMemoryCoreDataRepository() } override func tearDown() { super.tearDown() } func testMethod_setup() { let presenter = TaskSuggestionPresenter() let controller = TaskSuggestionPresenterMock() presenter.userInterface = controller presenter.setup (startSleepDate: Date(), endSleepDate: Date()) XCTAssert(controller.setTime_called) XCTAssert(controller.setNotes_called) XCTAssert(controller.hideTaskTypes_called) XCTAssertFalse(controller.selectSegment_called) } func testMethod_setTime() { let presenter = TaskSuggestionPresenter() let controller = TaskSuggestionPresenterMock() presenter.userInterface = controller presenter.selectSegment (atIndex: 0) XCTAssertFalse(controller.setTime_called) XCTAssertFalse(controller.hideTaskTypes_called) XCTAssert(controller.setNotes_called) XCTAssert(controller.selectSegment_called) } func testMethod_save() { let presenter = TaskSuggestionPresenter() let controller = TaskSuggestionPresenterMock() presenter.userInterface = controller presenter.save (selectedSegment: 0, notes: "", startSleepDate: Date(), endSleepDate: Date()) // XCTAssert(controller.setNotes_called) } } ================================================ FILE: Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionViewController.swift ================================================ // // TaskSuggestionViewController.swift // Jirassic // // Created by Cristian Baluta on 30/11/2016. // Copyright © 2016 Imagin soft. All rights reserved. // import Cocoa class TaskSuggestionViewController: NSViewController { @IBOutlet private weak var segmentedControl: NSSegmentedControl? @IBOutlet private weak var titleTextField: NSTextField! @IBOutlet private weak var notesTextField: NSTextField! var presenter: TaskSuggestionPresenterInput? var startSleepDate: Date? var endSleepDate: Date? override func viewDidLoad() { super.viewDidLoad() presenter!.setup(startSleepDate: startSleepDate, endSleepDate: endSleepDate) } @IBAction func handleSegmentedControl (_ sender: NSSegmentedControl) { presenter!.selectSegment(atIndex: sender.selectedSegment) } @IBAction func handleIgnoreButton (_ sender: NSButton) { AppDelegate.sharedApp().removeActivePopup() } @IBAction func handleSaveButton (_ sender: NSButton) { presenter!.save(selectedSegment: segmentedControl != nil ? segmentedControl!.selectedSegment : -1, notes: notesTextField.stringValue, startSleepDate: startSleepDate, endSleepDate: endSleepDate) AppDelegate.sharedApp().removeActivePopup() } } extension TaskSuggestionViewController: TaskSuggestionPresenterOutput { func selectSegment (atIndex index: Int) { segmentedControl!.selectedSegment = index } func setTime (_ notes: String) { titleTextField.stringValue = notes } func setNotes (_ notes: String) { notesTextField.stringValue = notes } func hideTaskTypes() { segmentedControl!.removeFromSuperview() segmentedControl = nil // notesTextField.removeAutoresizing() // _ = notesTextField.constraintToBottom(self.view, distance: CGFloat(0)) } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/CellProtocol.swift ================================================ // // CellProtocol.swift // Jirassic // // Created by Baluta Cristian on 10/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa protocol CellProtocol { var statusImage: NSImageView? {get} var data: TaskCreationData {get set} var duration: String {get set} var isDark: Bool {get set} var isEditable: Bool {get set} var isRemovable: Bool {get set} var isIgnored: Bool {get set} var color: NSColor {get set} var timeToolTip: String? {get set} var didEndEditingCell: ((_ cell: CellProtocol) -> ())? {get set} var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? {get set} var didClickAddCell: ((_ cell: CellProtocol) -> ())? {get set} var didCopyContentCell: ((_ cell: CellProtocol) -> ())? {get set} } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/HeaderView/TasksHeaderView.swift ================================================ // // FooterCell.swift // Jirassic // // Created by Cristian Baluta on 30/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class TasksHeaderView: NSTableHeaderView { @IBOutlet private var backgroundView: NSVisualEffectView! @IBOutlet private var butAdd: NSButton! @IBOutlet private var butCloseDay: NSButton! @IBOutlet private var butWorklogs: NSButton! @IBOutlet private var butJiraSetup: NSButton! private unowned let appWireframe = AppDelegate.sharedApp().appWireframe private var store = Store.shared private var moduleJira = ModuleJiraTempo() private let pref = RCPreferences() var didClickAddTask: (() -> Void)? var didClickCloseDay: (() -> Void)? var didClickSaveWorklogs: (() -> Void)? var isDayEnded: Bool = false { didSet { let isJiraAvailable = store.isJiraTempoPurchased && moduleJira.isConfigured && moduleJira.isProjectConfigured self.butAdd.isHidden = isDayEnded self.butCloseDay.isHidden = isDayEnded self.butWorklogs.isHidden = !isDayEnded self.butWorklogs.isEnabled = isJiraAvailable self.butJiraSetup.isHidden = isDayEnded ? isJiraAvailable : true } } override func awakeFromNib() { super.awakeFromNib() if #available(OSX 10.14, *) { // In OS14 there is already a default blurry background backgroundView.isHidden = true } } override func headerRect(ofColumn column: Int) -> NSRect { // This will prevent for a label to appear in the middle of the header return NSRect.zero } // Overriding this in OS14 removes default blurry background and the custom one adds ugly edges to buttons // override func draw (_ dirtyRect: NSRect) { // } @IBAction func handleAddButton (_ sender: NSButton) { didClickAddTask?() } @IBAction func handleCloseDayButton (_ sender: NSButton) { didClickCloseDay?() } @IBAction func handleWorklogsButton (_ sender: NSButton) { didClickSaveWorklogs?() } @IBAction func handleJiraSetupButton (_ sender: NSButton) { pref.set(SettingsTab.output.rawValue, forKey: .settingsActiveTab) appWireframe.flipToSettingsController() } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/HeaderView/TasksHeaderView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.swift ================================================ // // NonTaskCell.swift // Jirassic // // Created by Baluta Cristian on 07/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa class NonTaskCell: 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! private var isEditing = false private var wasEdited = false private var trackingArea: NSTrackingArea? private var activeTimeboxPopover: NSPopover? var didEndEditingCell: ((_ cell: CellProtocol) -> ())? var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? var didClickAddCell: ((_ cell: CellProtocol) -> ())? var didCopyContentCell: ((_ cell: CellProtocol) -> ())? private var _data: TaskCreationData? var data: TaskCreationData { get { if let dateStart = _data?.dateStart { let newHM = Date.parseHHmm(self.dateStartTextField.stringValue) let date = dateStart.dateByUpdating(hour: newHM.hour, minute: newHM.min) _data?.dateStart = date } let newHM = Date.parseHHmm(self.dateEndTextField.stringValue) let dateEnd = _data!.dateEnd.dateByUpdating(hour: newHM.hour, minute: newHM.min) _data?.dateEnd = dateEnd if self.notesTextField.stringValue != "" { _data?.notes = self.notesTextField.stringValue } return _data! } set { _data = newValue if let dateStart = newValue.dateStart { self.dateStartTextField.stringValue = dateStart.HHmm() self.dateStartTextField.isHidden = false self.dateStartTextFieldLeadingContraint.constant = 14 } else { self.dateStartTextField.isHidden = true self.dateStartTextFieldLeadingContraint.constant = 14 - 36 - 4 } self.dateEndTextField.stringValue = newValue.dateEnd.HHmm() self.notesTextField.stringValue = newValue.notes ?? "" } } var duration: String { get { return "" } set { fatalError("Not available") } } var isDark: Bool = false { didSet { dateStartTextField.isDark = isDark dateEndTextField.isDark = isDark } } var isEditable: Bool = true { didSet { notesTextField.isEditable = isEditable } } var isRemovable: Bool = true var isIgnored: Bool = false { didSet { self.notesTextField!.alphaValue = isIgnored ? 0.5 : 1.0 } } var color: NSColor = NSColor.black { didSet { self.notesTextField!.textColor = color } } var timeToolTip: String? { didSet { dateStartTextField.toolTip = timeToolTip dateEndTextField.toolTip = timeToolTip } } override func awakeFromNib() { super.awakeFromNib() butRemove.isHidden = true butAdd.isHidden = true butRemove.wantsLayer = true dateStartTextField.onClick = { self.createTimeboxPopover(timebox: self.dateStartTextField) } dateEndTextField.onClick = { self.createTimeboxPopover(timebox: self.dateEndTextField) } 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() } private func createTimeboxPopover (timebox: TimeBox) { guard activeTimeboxPopover == nil, isEditable else { return } let popover = NSPopover() let view = TimeBoxViewController.instantiateFromStoryboard("Components") view.didSave = { let hasChanges = timebox.stringValue != view.stringValue timebox.stringValue = view.stringValue popover.performClose(nil) if hasChanges { self.didEndEditingCell?(self) } } view.didCancel = { popover.performClose(nil) self.activeTimeboxPopover = nil } popover.contentViewController = view popover.show(relativeTo: timebox.frame, of: self, preferredEdge: NSRectEdge.maxY) view.stringValue = timebox.stringValue activeTimeboxPopover = popover } } extension NonTaskCell { @IBAction func handleRemoveButton (_ sender: NSButton) { didClickRemoveCell?(self) } @IBAction func handleAddButton (_ sender: NSButton) { didClickAddCell?(self) } } extension NonTaskCell { 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) } // 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) } func ensureTrackingArea() { if trackingArea == nil { trackingArea = NSTrackingArea(rect: NSZeroRect, options: [ NSTrackingArea.Options.inVisibleRect, NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited ], owner: self, userInfo: nil) } } override func updateTrackingAreas() { super.updateTrackingAreas() self.ensureTrackingArea() if !self.trackingAreas.contains(self.trackingArea!) { self.addTrackingArea(self.trackingArea!) } } } extension NonTaskCell: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true return true } public func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if wasEdited { wasEdited = false didEndEditingCell?(self) } return true } public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { // Detect Enter key if wasEdited && commandSelector == #selector(NSResponder.insertNewline(_:)) { didEndEditingCell?(self) wasEdited = false } return false } func controlTextDidChange (_ obj: Notification) { wasEdited = true } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.swift ================================================ // // TaskCell.swift // Jirassic // // Created by Baluta Cristian on 28/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa class TaskCell: NSTableRowView, CellProtocol { @IBOutlet var contentView: NSView! @IBOutlet var statusImage: NSImageView? @IBOutlet private var dateEndTextField: TimeBox! @IBOutlet private var issueNrTextField: NSTextField! @IBOutlet private var notesTextField: NSTextField! @IBOutlet private var notesTextFieldRightConstrain: NSLayoutConstraint! @IBOutlet private var butAdd: NSButton! @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 private var trackingArea: NSTrackingArea? private var activeTimeboxPopover: NSPopover? var didEndEditingCell: ((_ cell: CellProtocol) -> ())? var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? var didClickAddCell: ((_ cell: CellProtocol) -> ())? var didCopyContentCell: ((_ cell: CellProtocol) -> ())? // Sets data to the cell var _dateEnd = Date() var _dateEndHHmm = "" var data: TaskCreationData { get { let hm = Date.parseHHmm(self.dateEndTextField!.stringValue) let date = _dateEnd.dateByUpdating(hour: hm.hour, minute: hm.min) return ( dateStart: nil, dateEnd: date, taskNumber: self.issueNrTextField.stringValue, notes: self.notesTextField.stringValue, taskType: .issue ) } set { _dateEnd = newValue.dateEnd _dateEndHHmm = _dateEnd.HHmm() dateEndTextField.stringValue = _dateEndHHmm issueNrTextField.stringValue = newValue.taskNumber ?? "" notesTextField.stringValue = newValue.notes ?? "" } } var duration: String { get { return "" } set { fatalError("Not available") } } var isDark: Bool = false { didSet { dateEndTextField.isDark = isDark } } var isEditable: Bool = true { didSet { issueNrTextField.isEditable = isEditable notesTextField.isEditable = isEditable } } var isRemovable: Bool = true { didSet { butRemove.isEnabled = isRemovable } } var isIgnored: Bool = false var color: NSColor = NSColor.black { didSet { self.notesTextField!.textColor = color } } var timeToolTip: String? { didSet { self.toolTip = nil dateEndTextField.toolTip = timeToolTip } } override func awakeFromNib() { super.awakeFromNib() showMouseOverControls(false) notesTextFieldRightConstrain.constant = 0 dateEndTextField.onClick = { [weak self] in if let wself = self { wself.createTimeboxPopover(timebox: wself.dateEndTextField) } } } private func createTimeboxPopover (timebox: TimeBox) { guard activeTimeboxPopover == nil else { return } let popover = NSPopover() let view = TimeBoxViewController.instantiateFromStoryboard("Components") view.didSave = { let hasChanges = timebox.stringValue != view.stringValue timebox.stringValue = view.stringValue popover.performClose(nil) if hasChanges { self.didEndEditingCell?(self) } } view.didCancel = { popover.performClose(nil) self.activeTimeboxPopover = nil } popover.contentViewController = view popover.show(relativeTo: timebox.frame, of: self, preferredEdge: NSRectEdge.maxY) view.stringValue = timebox.stringValue activeTimeboxPopover = popover } } extension TaskCell { @IBAction func handleRemoveButton (_ sender: NSButton) { didClickRemoveCell?(self) } @IBAction func handleAddButton (_ sender: NSButton) { didClickAddCell?(self) } } extension TaskCell { override func drawBackground (in dirtyRect: NSRect) { let width = dirtyRect.size.width - kCellLeftPadding * 2 let height = dirtyRect.size.height - kGapBetweenCells - 2 if isEditing { let selectionRect = NSRect(x: kCellLeftPadding, y: 2, width: width, height: height) NSColor.red.setStroke() let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6) selectionPath.stroke() } else if self.mouseInside { notesTextFieldRightConstrain!.constant = isRemovable ? 90 : 40 butRemoveWidthConstraint.constant = isRemovable ? 40 : 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() // NSColor(calibratedRed: 0.3, green: 0.1, blue: 0.1, alpha: 1.0).setStroke() let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6) // selectionPath.fill() selectionPath.stroke() } else { notesTextFieldRightConstrain!.constant = 0 let selectionRect = NSRect(x: kCellLeftPadding, y: 2, width: width, height: height) // NSColor(calibratedWhite: 1.0, alpha: 1.0).setFill() AppDelegate.sharedApp().theme.lineColor.setStroke() let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6) // selectionPath.fill() selectionPath.stroke() } } override func mouseEntered (with theEvent: NSEvent) { super.mouseEntered(with: theEvent) self.mouseInside = true self.showMouseOverControls(self.mouseInside) self.setNeedsDisplay(self.frame) } override func mouseExited (with theEvent: NSEvent) { super.mouseExited(with: theEvent) self.mouseInside = false self.showMouseOverControls(self.mouseInside) self.setNeedsDisplay(self.frame) } func showMouseOverControls (_ show: Bool) { butRemove.isHidden = !show butAdd.isHidden = !show line1.isHidden = !show line2.isHidden = !show } func ensureTrackingArea() { if trackingArea == nil { trackingArea = NSTrackingArea( rect: NSZeroRect, options: [NSTrackingArea.Options.inVisibleRect, NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited], owner: self, userInfo: nil ) } } override func updateTrackingAreas() { super.updateTrackingAreas() self.ensureTrackingArea() if !self.trackingAreas.contains(self.trackingArea!) { self.addTrackingArea(self.trackingArea!) } } } extension TaskCell: NSTextFieldDelegate { public func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { isEditing = true self.setNeedsDisplay(self.frame) return true } public func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if wasEdited { wasEdited = false isEditing = false didEndEditingCell?(self) self.setNeedsDisplay(self.frame) } return true } public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { // Detect Enter key if wasEdited && commandSelector == #selector(NSResponder.insertNewline(_:)) { wasEdited = false isEditing = false didEndEditingCell?(self) self.setNeedsDisplay(self.frame) } return false } func controlTextDidChange (_ obj: Notification) { wasEdited = true } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellPresenter.swift ================================================ // // TaskCellPresenter.swift // Jirassic // // Created by Baluta Cristian on 27/11/15. // Copyright © 2015 Cristian Baluta. All rights reserved. // import Cocoa class TaskCellPresenter: NSObject { var cell: CellProtocol! convenience init (cell: CellProtocol) { self.init() self.cell = cell } func present (previousTask: Task?, currentTask theTask: Task) { cell.statusImage?.image = nil switch theTask.taskType { case .issue: cell.statusImage?.image = NSImage(named: NSImage.statusAvailableName) case .gitCommit: cell.statusImage?.image = NSImage(named: "GitIcon") case .coderev: cell.color = NSColor.systemGray case .startDay, .endDay: cell.color = NSColor.systemBlue case .calendar: cell.color = NSColor.systemGray cell.statusImage?.image = NSImage(named: "CalendarIcon") default: cell.color = NSColor.systemGray } var notes = theTask.notes ?? "" if notes == "" { notes = theTask.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)" } cell.data = ( dateStart: theTask.startDate, dateEnd: theTask.endDate, taskNumber: theTask.taskNumber, notes: notes, taskType: theTask.taskType ) 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" } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellTests.swift ================================================ // // 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() // } } ================================================ FILE: Delivery/macOS/Screens/Tasks/AllTasks/TasksDataSource.swift ================================================ // // 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 } } ================================================ FILE: Delivery/macOS/Screens/Tasks/DataSource.swift ================================================ // // DataSource.swift // Jirassic // // Created by Cristian Baluta on 18/02/2017. // Copyright © 2017 Imagin soft. All rights reserved. // 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) } typealias DataSource = NSTableViewDataSource & NSTableViewDelegate & TasksAndReportsDataSource ================================================ FILE: Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.swift ================================================ // // MonthReportsHeaderView.swift // Jirassic // // Created by Cristian Baluta on 25/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Cocoa class MonthReportsHeaderView: ReportsHeaderView { @IBOutlet private var butCopyAll: NSButton! @IBOutlet private var butCopyAsHtml: NSButton! @IBOutlet private var totalDaysTextField: NSTextField! var numberOfDays: Int { get { return 0 } set { totalDaysTextField.stringValue = "Number of days: \(newValue)" } } override func awakeFromNib() { super.awakeFromNib() butCopyAsHtml.state = pref.bool(.copyWorklogsAsHtml) ? .on : .off butCopyAsHtml.toolTip = "This can be set in 'Settings/Tracking/Working between'" } } extension MonthReportsHeaderView { @IBAction func handleHtmlButton (_ sender: NSButton) { pref.set(sender.state == .on, forKey: .copyWorklogsAsHtml) } } ================================================ FILE: Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.swift ================================================ // // ReportsHeaderView.swift // Jirassic // // Created by Cristian Baluta on 19/02/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Cocoa import RCPreferences class ReportsHeaderView: NSTableHeaderView { @IBOutlet private var backgroundView: NSVisualEffectView! @IBOutlet private var butPercents: NSButton! @IBOutlet private var butRound: NSButton! @IBOutlet private var totalTimeTextField: NSTextField! internal let pref = RCPreferences() var didChangeSettings: (() -> Void)? var didClickCopyAll: ((Bool) -> Void)? // In hours var workedTime: String { get { return "" } set { totalTimeTextField.stringValue = newValue } } var workdayTime: Double { get { return 0.0 } set { butRound.title = "Round to \(newValue) hours" } } override func awakeFromNib() { super.awakeFromNib() butPercents.title = "Show time in percents" butPercents.state = pref.bool(.usePercents) ? .on : .off 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 } } // Overriding this in OS14 removes default blurry background and the custom one adds ugly edges to buttons // override func draw (_ dirtyRect: NSRect) { // } override func headerRect(ofColumn column: Int) -> NSRect { // This will prevent for a label to appear in the middle of the header return NSRect.zero } } extension ReportsHeaderView { @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?() } @IBAction func handleCopyAllButton (_ sender: NSButton) { didClickCopyAll?(pref.bool(.copyWorklogsAsHtml)) } } ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.swift ================================================ // // ReportCell.swift // Jirassic // // Created by Cristian Baluta on 04/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Cocoa class ReportCell: NSTableRowView, CellProtocol { var statusImage: NSImageView? @IBOutlet fileprivate var durationTextField: NSTextField? @IBOutlet fileprivate var taskNrTextField: NSTextField? @IBOutlet fileprivate var notesTextField: NSTextField? @IBOutlet fileprivate var butCopy: NSButton? @IBOutlet fileprivate var butCopyWidthConstraint: NSLayoutConstraint? fileprivate var trackingArea: NSTrackingArea? fileprivate var bgColor: NSColor = NSColor.clear var didEndEditingCell: ((_ cell: CellProtocol) -> ())? var didClickRemoveCell: ((_ cell: CellProtocol) -> ())? var didClickAddCell: ((_ cell: CellProtocol) -> ())? var didCopyContentCell: ((_ cell: CellProtocol) -> ())? var data: TaskCreationData { get { return ( dateStart: nil, dateEnd: Date(), taskNumber: self.taskNrTextField!.stringValue, notes: self.notesTextField!.stringValue, taskType: .issue ) } set { self.taskNrTextField!.stringValue = newValue.taskNumber ?? "" self.notesTextField!.stringValue = newValue.notes ?? "" } } var duration: String { get { return durationTextField!.stringValue } set { self.durationTextField!.stringValue = newValue } } var heightThatFits: CGFloat { get { let titleTextField = self.taskNrTextField!.sizeThatFits( NSSize(width: self.frame.size.width - 60 - 70, height: 1000) ) let notesTextField = self.notesTextField!.sizeThatFits( NSSize(width: self.frame.size.width - self.notesTextField!.frame.origin.x, height: 1000) ) return 6 + titleTextField.height + 4 + notesTextField.height + 6 } } var isDark: Bool = false var isEditable: Bool = false var isRemovable: Bool = false var isIgnored: Bool = false var color: NSColor = NSColor.black var timeToolTip: String? override func awakeFromNib() { super.awakeFromNib() butCopyWidthConstraint?.constant = 0 taskNrTextField?.preferredMaxLayoutWidth = CGFloat(300) ensureTrackingArea() } override func draw (_ dirtyRect: NSRect) { bgColor.set() dirtyRect.fill() } @IBAction func handleCopyButton (_ sender: NSButton) { let string = "\(taskNrTextField!.stringValue)\n\(notesTextField!.stringValue)" NSPasteboard.general.clearContents() NSPasteboard.general.writeObjects([string as NSPasteboardWriting]) } } extension ReportCell { override func mouseEntered (with theEvent: NSEvent) { butCopyWidthConstraint?.constant = 47 bgColor = NSColor.white butCopy!.needsLayout = true self.needsDisplay = true if isDark == true { taskNrTextField?.textColor = NSColor.black notesTextField?.textColor = NSColor.darkGray durationTextField?.textColor = NSColor.darkGray } } override func mouseExited (with theEvent: NSEvent) { butCopyWidthConstraint?.constant = 0 bgColor = NSColor.clear butCopy!.needsLayout = true self.needsDisplay = true if isDark == true { taskNrTextField?.textColor = NSColor.white notesTextField?.textColor = NSColor.lightGray durationTextField?.textColor = NSColor.lightGray } } override func updateTrackingAreas() { super.updateTrackingAreas() self.ensureTrackingArea() if !(self.trackingAreas as NSArray).contains(self.trackingArea!) { self.addTrackingArea(self.trackingArea!); } } func ensureTrackingArea() { if trackingArea == nil { trackingArea = NSTrackingArea( rect: self.bounds, options: [NSTrackingArea.Options.inVisibleRect, NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited], owner: self, userInfo: nil ) } } } ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.xib ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCellPresenter.swift ================================================ // // ReportCellPresenter.swift // Jirassic // // Created by Cristian Baluta on 06/11/2016. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences class ReportCellPresenter: NSObject { var cell: CellProtocol! private let pref = RCPreferences() convenience init (cell: CellProtocol) { self.init() self.cell = cell } func present (theReport: Report) { let notes: [String] = theReport.notes.compactMap { note in guard note.count > 0 else { return nil } return "• \(note)" } let notesJoined = notes.joined(separator: "\n") var taskNumber = theReport.taskNumber == "coderev" ? "Code reviews" : theReport.taskNumber taskNumber = taskNumber == "learning" ? "Learning" : taskNumber taskNumber = taskNumber == "meeting" ? "Meetings" : taskNumber var title = theReport.title .replacingOccurrences(of: "_", with: " ") .trimmingCharacters(in: .whitespacesAndNewlines) if theReport.taskNumber == "learning" || theReport.taskNumber == "meeting" { title = "" } cell.data = ( dateStart: nil, dateEnd: Date(), taskNumber: taskNumber + " " + title, notes: notesJoined, taskType: .issue ) cell.duration = pref.bool(.usePercents) ? "\(theReport.duration.secToPercent)" : theReport.duration.secToHoursAndMin// Date(timeIntervalSince1970: theReport.duration).HHmmGMT() cell.statusImage?.image = NSImage(named: NSImage.statusAvailableName) cell.isDark = AppDelegate.sharedApp().theme.isDark } } ================================================ FILE: Delivery/macOS/Screens/Tasks/Reports/ReportsDataSource.swift ================================================ // // ReportsDataSource.swift // Jirassic // // Created by Cristian Baluta on 17/02/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Cocoa class ReportsDataSource: NSObject, TasksAndReportsDataSource { var tableView: NSTableView! { didSet { if #available(OSX 10.13, *) { tableView.usesAutomaticRowHeights = true } ReportCell.register(in: tableView) } } var didClickAddRow: ((_ row: Int) -> Void)? var didClickRemoveRow: ((_ row: Int) -> Void)? private var tempCell: ReportCell? let numberOfDays: Int var reports: [Report] init (reports: [Report], numberOfDays: Int) { self.reports = reports self.numberOfDays = numberOfDays } func addTask (_ task: Task, at row: Int) { } func removeTask (at row: Int) { } } extension ReportsDataSource: NSTableViewDataSource { func numberOfRows (in aTableView: NSTableView) -> Int { return reports.count } func tableView (_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { if #available(OSX 10.13, *) { // This version of osx supports cell autoresizing return CGFloat(50) } let theData = reports[row] // Calculate height to fit content if tempCell == nil { tempCell = ReportCell.instantiate(in: tableView) tempCell?.frame = NSRect(x: 0, y: 0, width: tableView.frame.size.width, height: 50) } ReportCellPresenter(cell: tempCell!).present(theReport: theData) return tempCell!.heightThatFits } } extension ReportsDataSource: NSTableViewDelegate { func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let theData = reports[row] let cell: CellProtocol = ReportCell.instantiate(in: self.tableView) ReportCellPresenter(cell: cell).present(theReport: theData) return cell as? NSView } } ================================================ FILE: Delivery/macOS/Screens/Tasks/Tasks.storyboard ================================================ ================================================ FILE: Delivery/macOS/Screens/Tasks/TasksInteractor.swift ================================================ // // TasksInteractor.swift // Jirassic // // Created by Cristian Baluta on 22/10/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences import RCLog protocol TasksInteractorInput: class { func reloadCalendar() func reloadTasks (inDay day: Day) func reloadTasks (inMonth day: Day) } protocol TasksInteractorOutput: class { func calendarDidLoad (_ weeks: [Week]) func tasksDidLoad (_ tasks: [Task]) } class TasksInteractor { weak var presenter: TasksPresenter? private let daysReader: ReadDaysInteractor! private let tasksReader: ReadTasksInteractor! private let moduleGit = ModuleGitLogs() private let moduleCalendar = ModuleCalendar() private let pref = RCPreferences() private var currentTasks = [Task]() 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 let dateEnd = day.dateEnd ?? dateStart.endOfDay() currentDateStart = dateStart reloadTasks(dateStart: dateStart, dateEnd: dateEnd) } func reloadTasks (inMonth day: Day) { let dateStart = day.dateStart.startOfMonth() let dateEnd = dateStart.endOfMonth() currentDateStart = dateStart reloadTasks(dateStart: dateStart, dateEnd: dateEnd) } private func reloadTasks (dateStart: Date, dateEnd: Date) { self.currentTasks = [] self.addLocalTasks(dateStart: dateStart, dateEnd: dateEnd) { [weak self] in guard let self, !self.currentTasks.isEmpty else { self?.presenter?.tasksDidLoad([]) return } let isMonthRead = dateEnd.timeIntervalSince(dateStart) > 24.hoursToSec if isMonthRead || self.currentTasks.contains(where: { $0.taskType == .endDay }) { self.presenter?.tasksDidLoad(self.currentTasks) return } self.addGitLogs(dateStart: dateStart, dateEnd: dateEnd) { self.addCalendarEvents(dateStart: dateStart, dateEnd: dateEnd) { self.presenter?.tasksDidLoad(self.currentTasks) } } } } private func addLocalTasks (dateStart: Date, dateEnd: Date, completion: () -> Void) { currentTasks = tasksReader.tasks(between: dateStart, and: dateEnd) // Sort by date currentTasks.sort(by: { // If tasks have the same date might be the end of the day compared with the last task // In this case endDay should be the latter task guard $0.endDate != $1.endDate else { return $1.taskType == .endDay } return $0.endDate < $1.endDate }) completion() } private func addGitLogs (dateStart: Date, dateEnd: Date, completion: @escaping () -> Void) { guard pref.bool(.enableGit) else { completion() return } moduleGit.fetchLogs(dateStart: dateStart, dateEnd: dateEnd) { [weak self] gitTasks in guard let self, self.currentDateStart == dateStart else { RCLog("Different day was selected than the one loading") return } self.currentTasks = MergeTasksInteractor().merge(tasks: self.currentTasks, with: gitTasks) completion() } } private func addCalendarEvents (dateStart: Date, dateEnd: Date, completion: @escaping () -> Void) { guard pref.bool(.enableCalendar) else { completion() return } moduleCalendar.events(dateStart: dateStart, dateEnd: dateEnd) { [weak self] calendarTasks in guard let self, self.currentDateStart == dateStart else { RCLog("Different day was selected than the one loading") return } self.currentTasks = MergeTasksInteractor().merge(tasks: self.currentTasks, with: calendarTasks) completion() } } } ================================================ FILE: Delivery/macOS/Screens/Tasks/TasksPresenter.swift ================================================ // // TasksPresenter.swift // Jirassic // // Created by Cristian Baluta on 05/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences protocol TasksPresenterInput: class { func initUI() func syncData() func reloadData() func reloadTasksOnDay (_ day: Day, listType: ListType) func updateNoTasksState() func messageButtonDidPress() func startDay() func closeDay (shouldSaveToJira: Bool) func insertTaskWithData (_ taskData: TaskCreationData) func insertTask (after row: Int) func removeTask (at row: Int) } 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 } class TasksPresenter { weak var appWireframe: AppWireframe? weak var ui: TasksPresenterOutput? 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? } 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 reloadData() { ui!.removeTasksController() ui!.showLoadingIndicator(true) interactor!.reloadCalendar() } func reloadTasksOnDay (_ day: Day, listType: ListType) { ui!.removeTasksController() ui!.showLoadingIndicator(true) lastSelectedDay = day selectedListType = listType switch selectedListType { case .allTasks, .report: interactor!.reloadTasks(inDay: day) case .monthlyReports: interactor!.reloadTasks(inMonth: day) } } func updateNoTasksState() { if currentTasks.count == 0 { ui!.showMessage(( title: "Good morning!", message: "Ready to start working today?", 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() { if currentTasks.count == 0 { startDay() } else { ui!.presentNewTaskController(date: Date()) } } 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?.reloadData() }) ModuleHookup().insert(task: task) } func closeDay (shouldSaveToJira: Bool) { let closeDay = CloseDay() closeDay.close(with: currentTasks) if shouldSaveToJira { // Reload data will be called after save with success ui!.presentEndDayController(date: lastSelectedDay?.dateStart ?? Date(), tasks: currentTasks) } else { reloadData() } } func insertTaskWithData (_ 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 let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: false, completion: { savedTask in }) } func insertTask (after row: Int) { guard currentTasks.count > row + 1 else { // Insert task at the end let taskBefore = currentTasks[row] let nextDate = taskBefore.endDate.isSameDayAs(Date()) ? Date() : taskBefore.endDate.addingTimeInterval(3600) ui!.presentNewTaskController(date: nextDate) return } // Insert task between 2 other tasks let taskBefore = currentTasks[row] let taskAfter = currentTasks[row+1] let middleTimestamp = taskAfter.endDate.timeIntervalSince(taskBefore.endDate) / 2 let middleDate = taskBefore.endDate.addingTimeInterval(middleTimestamp) ui!.presentNewTaskController(date: middleDate) } func removeTask (at row: Int) { let task = currentTasks[row] currentTasks.remove(at: row) 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) } } } } 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]) { guard let ui = self.ui else { return } ui.showLoadingIndicator(false) ui.removeTasksController() currentTasks = tasks switch selectedListType { case .allTasks: ui.showTasks(currentTasks) case .report: 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) case .monthlyReports: 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, type: selectedListType) break } updateNoTasksState() } } ================================================ FILE: Delivery/macOS/Screens/Tasks/TasksScrollView.swift ================================================ // // TasksScrollView.swift // Jirassic // // Created by Baluta Cristian on 28/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences class TasksScrollView: NSScrollView { private var tableView: NSTableView! private var dataSource: DataSource! private var listType: ListType! private let pref = RCPreferences() var didClickAddRow: ((_ row: Int, _ rect: CGRect?) -> Void)? var didClickRemoveRow: ((_ row: Int) -> Void)? var didClickCloseDay: ((_ tasks: [Task], _ shouldSaveToJira: Bool) -> Void)? var didClickCopyDailyReport: (() -> Void)? var didClickCopyMonthlyReport: ((_ asHtml: Bool) -> Void)? var didChangeSettings: (() -> Void)? required init?(coder: NSCoder) { super.init(coder: coder) } override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupTableView() } convenience init (dataSource: DataSource, listType: ListType) { self.init(frame: NSRect.zero) self.setupTableView() self.listType = listType reloadDataSource(dataSource) } func reloadData() { tableView!.reloadData() } func reloadDataSource (_ dataSource: DataSource) { self.dataSource = dataSource self.dataSource.tableView = tableView tableView.dataSource = self.dataSource tableView.delegate = self.dataSource addHeader() self.dataSource.didClickAddRow = { [weak self] row in self?.didClickAddRow!(row, nil) } self.dataSource.didClickRemoveRow = { [weak self] row in self?.didClickRemoveRow!(row) } } private func setupTableView() { self.automaticallyAdjustsContentInsets = false self.contentInsets = NSEdgeInsetsMake(0, 0, 0, 0) self.drawsBackground = false self.hasVerticalScroller = true tableView = NSTableView(frame: self.frame) tableView.selectionHighlightStyle = NSTableView.SelectionHighlightStyle.none tableView.backgroundColor = NSColor.clear tableView.headerView = nil let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "taskColumn")) column.width = 400 tableView.addTableColumn(column) self.documentView = tableView! } private func addHeader() { if let dataSource = self.dataSource as? TasksDataSource { guard dataSource.tasks.count > 0 else { tableView.headerView = nil return } guard tableView.headerView == nil else { return } let headerView = TasksHeaderView.instantiateFromXib() headerView.didClickCloseDay = { [weak self] in self?.didClickCloseDay!(dataSource.tasks, false) } headerView.didClickAddTask = { [weak self] in if let wself = self { wself.didClickAddRow!(wself.dataSource.numberOfRows!(in: wself.tableView) - 1, CGRect(x: 60, y: headerView.frame.size.height + wself.contentView.documentVisibleRect.origin.y, width: 1, height: 1)) } } headerView.didClickSaveWorklogs = { [weak self] in self?.didClickCloseDay!(dataSource.tasks, true) } headerView.isDayEnded = dataSource.isDayEnded tableView.headerView = headerView } else if let dataSource = self.dataSource as? ReportsDataSource { guard dataSource.reports.count > 0 else { tableView.headerView = nil return } guard tableView.headerView == nil else { return } let settings = SettingsInteractor().getAppSettings() let workingDayLength = TimeInteractor(settings: settings).workingDayLength() let totalTime = StatisticsInteractor().duration(of: dataSource.reports) var headerView: ReportsHeaderView! switch listType! { case .report: headerView = ReportsHeaderView.instantiateFromXib() headerView.didClickCopyAll = { asHtml in self.didClickCopyDailyReport?() } case .monthlyReports: let monthHeaderView = MonthReportsHeaderView.instantiateFromXib() monthHeaderView.numberOfDays = dataSource.numberOfDays monthHeaderView.didClickCopyAll = { asHtml in self.didClickCopyMonthlyReport?(asHtml) } headerView = monthHeaderView default: break } headerView.workdayTime = workingDayLength.secToPercent headerView.workedTime = pref.bool(.usePercents) ? String(describing: totalTime.secToPercent) : totalTime.secToHoursAndMin headerView.didChangeSettings = { [weak self] in self?.didChangeSettings!() } tableView.headerView = headerView } } func addTask (_ task: Task, at row: Int) { if let dataSource = tableView.dataSource as? TasksDataSource { dataSource.addTask(task, at: row) addHeader() } } func removeTask (at row: Int) { if let dataSource = tableView.dataSource as? TasksDataSource { dataSource.removeTask(at: row) let rowsToRemoveFromTable = IndexSet(integer: row) tableView.removeRows(at: rowsToRemoveFromTable, withAnimation: .effectFade) addHeader() } } func frameOfCell (atRow row: Int) -> NSRect { return tableView.frameOfCell(atColumn: 0, row: row) } func view() -> NSTableView { return self.tableView } } ================================================ FILE: Delivery/macOS/Screens/Tasks/TasksView.swift ================================================ // // TasksView.swift // Jirassic // // Created by Cristian Baluta on 19/02/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Cocoa class TasksView: NSView { override func mouseUp (with theEvent: NSEvent) { if theEvent.clickCount == 2 { AppDelegate.sharedApp().menu.triggerClose() } } } ================================================ FILE: Delivery/macOS/Screens/Tasks/TasksViewController.swift ================================================ // // TasksViewController.swift // Jirassic // // Created by Baluta Cristian on 28/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Cocoa import RCPreferences import RCLog class TasksViewController: NSViewController { @IBOutlet private var splitView: NSSplitView! @IBOutlet private var calendarScrollView: CalendarScrollView! private var tasksScrollView: TasksScrollView? @IBOutlet private var listSegmentedControl: NSSegmentedControl! @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! @IBOutlet private var loadingTasksIndicator: NSProgressIndicator! @IBOutlet private var syncIndicator: NSProgressIndicator! weak var appWireframe: AppWireframe? var presenter: TasksPresenterInput? /// Property to keep a reference to the active cell rect private var rectToDisplayPopoverAt: NSRect? private var activePopover: NSPopover? override func awakeFromNib() { super.awakeFromNib() createLayer() } override func viewDidLoad() { super.viewDidLoad() registerForNotifications() listSegmentedControl!.selectedSegment = TaskTypeSelection().lastType().rawValue hideControls(false) calendarScrollView!.didSelectDay = { [weak self] (day: Day) in guard let strongSelf = self else { return } let selectedListType = ListType(rawValue: strongSelf.listSegmentedControl!.selectedSegment)! strongSelf.presenter!.reloadTasksOnDay(day, listType: selectedListType) } } override func viewDidAppear() { super.viewDidAppear() presenter!.initUI() } 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 TasksViewController: Animatable { func createLayer() { view.layer = CALayer() view.wantsLayer = true view.layer?.backgroundColor = .white } } extension TasksViewController { @IBAction func handleSegmentedControl (_ sender: NSSegmentedControl) { let listType = ListType(rawValue: sender.selectedSegment)! TaskTypeSelection().setType(listType) if let selectedDay = calendarScrollView!.selectedDay { presenter!.reloadTasksOnDay(selectedDay, 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 TasksViewController: TasksPresenterOutput { 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, intoSplitView: splitView!) controller.didPressButton = { self.presenter?.messageButtonDidPress() } } func showCalendar (_ weeks: [Week]) { calendarScrollView.weeks = weeks calendarScrollView.reloadData() } func showTasks (_ tasks: [Task]) { let dataSource = TasksDataSource(tasks: tasks) var frame = splitView!.subviews[SplitViewColumn.tasks.rawValue].frame frame.origin = NSPoint.zero let scrollView = TasksScrollView(dataSource: dataSource, listType: .allTasks) scrollView.frame = frame splitView.subviews[SplitViewColumn.tasks.rawValue].addSubview(scrollView) scrollView.constrainToSuperview() scrollView.didClickAddRow = { [weak self] (row, rect) in RCLogO("Add item after row \(row)") if row >= 0 { self?.rectToDisplayPopoverAt = rect ?? self?.tasksScrollView?.frameOfCell(atRow: row) self?.presenter!.insertTask(after: row) } } scrollView.didClickRemoveRow = { [weak self] row in RCLogO("Remove item at row \(row)") if row >= 0 { self?.presenter!.removeTask(at: row) self?.tasksScrollView!.removeTask(at: row) } } scrollView.didClickCloseDay = { [weak self] (tasks, shouldSaveToJira) in self?.presenter!.closeDay(shouldSaveToJira: shouldSaveToJira) } scrollView.reloadData() tasksScrollView = scrollView } func showReports (_ reports: [Report], numberOfDays: Int, type: ListType) { let dataSource = ReportsDataSource(reports: reports, numberOfDays: numberOfDays) var frame = splitView.subviews[SplitViewColumn.tasks.rawValue].frame frame.origin = NSPoint.zero let scrollView = TasksScrollView(dataSource: dataSource, listType: type) scrollView.frame = frame splitView!.subviews[SplitViewColumn.tasks.rawValue].addSubview(scrollView) scrollView.constrainToSuperview() scrollView.reloadData() scrollView.didChangeSettings = { [weak self] in if let strongSelf = self { strongSelf.handleSegmentedControl(strongSelf.listSegmentedControl) } } scrollView.didClickCopyDailyReport = { let interactor = CreateDayReport() let string = interactor.stringReports(dataSource.reports) print(string) NSPasteboard.general.clearContents() NSPasteboard.general.writeObjects([string as NSPasteboardWriting]) } scrollView.didClickCopyMonthlyReport = { asHtml in var string = "" let interactor = CreateMonthReport() if asHtml { // string = interactor.htmlReports(dataSource.reports) string = interactor.csvReports(dataSource.reports) } else { let joined = interactor.joinReports(dataSource.reports) string = joined.notes + "\n\n" + joined.totalDuration.secToHoursAndMin } NSPasteboard.general.clearContents() NSPasteboard.general.writeObjects([string as NSPasteboardWriting]) } tasksScrollView = scrollView } func removeTasksController() { if tasksScrollView != nil { tasksScrollView?.removeFromSuperview() tasksScrollView = nil } } func selectDay (_ day: Day) { calendarScrollView.selectDay(day) } func presentNewTaskController (date: Date) { let popover = NSPopover() let controller = NewTaskViewController.instantiateFromStoryboard("Components") controller.onSave = { [weak self] (taskData: TaskCreationData) -> Void in if let self { self.presenter!.insertTaskWithData(taskData) self.presenter!.updateNoTasksState() self.presenter!.reloadData() popover.performClose(nil) } } controller.onCancel = { [weak self] in if let self { popover.performClose(nil) self.activePopover = nil self.presenter!.updateNoTasksState() } } popover.contentViewController = controller popover.show(relativeTo: rectToDisplayPopoverAt!, of: self.tasksScrollView!.view(), preferredEdge: NSRectEdge.maxY) // Add data after popover is presented controller.dateStart = nil// TODO Add scrum start date when around scrum date controller.dateEnd = date activePopover = popover } func presentEndDayController (date: Date, tasks: [Task]) { splitView.isHidden = true appWireframe!.removePlaceholder() hideControls(true) let controller = appWireframe!.presentEndDayController(date: date, tasks: tasks) controller.onSave = { [weak self] in if let strongSelf = self { strongSelf.appWireframe!.removeEndDayController() strongSelf.splitView.isHidden = false strongSelf.hideControls(false) strongSelf.presenter!.reloadData() } } controller.onCancel = { [weak self] in if let strongSelf = self { strongSelf.appWireframe!.removeEndDayController() strongSelf.splitView.isHidden = false strongSelf.hideControls(false) strongSelf.presenter!.updateNoTasksState() } } } } extension TasksViewController { func registerForNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(TasksViewController.handleNewTaskAdded(_:)), name: NSNotification.Name(rawValue: kNewTaskWasAddedNotification), object: nil) } @objc func handleNewTaskAdded (_ notif: Notification) { presenter!.reloadData() } } ================================================ FILE: Delivery/macOS/Screens/Worklogs/Worklogs.storyboard ================================================ ================================================ FILE: Delivery/macOS/Screens/Worklogs/WorklogsPresenter.swift ================================================ // // WorklogsPresenter.swift // Jirassic // // Created by Cristian Baluta on 05/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences protocol WorklogsPresenterInput: class { func setup (date: Date, tasks: [Task]) func save (worklog: String) func enableRounding (_ enabled: Bool) } protocol WorklogsPresenterOutput: class { func showRounding (enabled: Bool, title: String) func showDuration (_ duration: Double) func showWorklog (_ worklog: String) func showProgressIndicator (_ show: Bool) func showJiraMessage (_ message: String, isError: Bool) func saveSuccess() } class WorklogsPresenter { weak var userInterface: WorklogsPresenterOutput? var date: Date? private let pref = RCPreferences() private var moduleJira = ModuleJiraTempo() private let reportsInteractor = CreateReport() private var workdayLength = 0.0 private var workedLength = 0.0 private var tasks: [Task] = [] } extension WorklogsPresenter: WorklogsPresenterInput { func setup (date: Date, tasks: [Task]) { self.date = date self.tasks = tasks show(tasks: tasks) } private func show (tasks: [Task]) { // Find the real number of worked hours let reports = reportsInteractor.reports(fromTasks: tasks, targetHoursInDay: nil) let message = reportsInteractor.toString(reports) let settings = SettingsInteractor().getAppSettings() workdayLength = TimeInteractor(settings: settings).workingDayLength() workedLength = StatisticsInteractor().duration(of: reports) let duration = (pref.bool(.enableRoundingDay) ? workdayLength : workedLength).secToPercent userInterface!.showDuration(duration) userInterface!.showWorklog(message) setupRoundingButton(workdayLength: workdayLength.secToPercent, workedLength: workedLength.secToPercent) } private func setupRoundingButton (workdayLength: TimeInterval, workedLength: TimeInterval) { userInterface!.showRounding(enabled: pref.bool(.enableRoundingDay), title: "Round worklogs duration to \(String(describing: workdayLength)) hours") } func save (worklog: String) { userInterface!.showJiraMessage("", isError: false) // Save to jira tempo let isRoundingEnabled = pref.bool(.enableRoundingDay) userInterface!.showProgressIndicator(true) let duration = isRoundingEnabled ? workdayLength : workedLength moduleJira.postWorklog(worklog: worklog, duration: duration, 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() } } }, failure: { [weak self] error in DispatchQueue.main.async { if let userInterface = self?.userInterface { userInterface.showProgressIndicator(false) userInterface.showJiraMessage(error.localizedDescription, isError: true) } } }) } func enableRounding (_ enabled: Bool) { pref.set(enabled, forKey: .enableRoundingDay) let duration = enabled ? workdayLength : workedLength userInterface!.showDuration(duration.secToPercent) } } ================================================ FILE: Delivery/macOS/Screens/Worklogs/WorklogsViewController.swift ================================================ // // WorklogsViewController.swift // Jirassic // // Created by Cristian Baluta on 05/02/2018. // Copyright © 2018 Imagin soft. All rights reserved. // 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! @IBOutlet private var jiraErrorTextField: NSTextField! @IBOutlet private var butRound: NSButton! @IBOutlet private var butSave: NSButton! var onSave: (() -> Void)? var onCancel: (() -> Void)? var presenter: WorklogsPresenterInput? var date: Date? var tasks: [Task]? weak var appWireframe: AppWireframe? override func viewDidLoad() { super.viewDidLoad() dateTextField.stringValue = date!.EEEEMMMdd() jiraErrorTextField.stringValue = "" worklogTextView.drawsBackground = false worklogTextView.backgroundColor = NSColor.clear presenter!.setup(date: date!, tasks: tasks!) } @IBAction func handleCancelButton (_ sender: NSButton) { self.onCancel?() } @IBAction func handleSaveButton (_ sender: NSButton) { presenter!.save(worklog: worklogTextView.string) } @IBAction func handleRoundButton (_ sender: NSButton) { presenter!.enableRounding(sender.state == .on) } } extension WorklogsViewController: WorklogsPresenterOutput { func showRounding (enabled: Bool, title: String) { butRound.state = enabled ? .on : .off butRound.title = title } func showDuration (_ duration: Double) { durationTextField.stringValue = String(describing: duration) } func showWorklog (_ worklog: String) { worklogTextView.string = worklog } func showProgressIndicator (_ show: Bool) { show ? progressIndicator.startAnimation(nil) : progressIndicator.stopAnimation(nil) } func showJiraMessage (_ message: String, isError: Bool) { jiraErrorTextField.stringValue = message jiraErrorTextField.textColor = isError ? NSColor.red : NSColor(red: 76/255, green: 172/255, blue: 44/255, alpha: 1.0) } func saveSuccess() { self.onSave?() } } ================================================ FILE: Delivery/macOS/jirassic.sdef ================================================ ================================================ FILE: Delivery/macOS-cmd/main.swift ================================================ // // jirassic // // Created by Baluta Cristian on 06/01/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation var shouldKeepRunning = true let theRL = RunLoop.current let appVersion = "18.12.12" //while shouldKeepRunning && theRL.run(mode: .defaultRunLoopMode, before: .distantFuture) {} enum ArgType { case taskNr case notes } enum Command: String { case list = "list" case reports = "reports" case insert = "insert" case scrum = "scrum" case lunch = "lunch" case meeting = "meeting" case waste = "waste" case learning = "learning" case coderev = "coderev" case version = "version" } func printHelp() { print("") print("jirassic \(appVersion) - (c)2018 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(" insert -nr -notes -duration ") print(" scrum|lunch|meeting|waste|learning|coderev Duration in minutes") print("") } let urls = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask) let libraryDirectory = urls.first! let jirassicSandboxedAppSupportDir = libraryDirectory.appendingPathComponent("Containers/com.jirassic.macos/Data/Library/Application Support/Jirassic") //print(urls) //print(jirassicSandboxedAppSupportDir) // /Users/Cristian/Library/Containers/com.ralcr.Jirassic.osx/Data/Library/Application%20Support/Jirassic/ // /Users/Cristian/Library/Application%20Support/Jirassic/ let localRepository: Repository! = SqliteRepository(documentsDirectory: jirassicSandboxedAppSupportDir) var remoteRepository: Repository? let reader = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) //let settings = localRepository!.settings() //print(currentTasks) // Insert the task var arguments = ProcessInfo.processInfo.arguments //arguments.append("list") //arguments.append("2018.11.09") //arguments.append("reports") //arguments.append("2018.11.09") //arguments.append("6") //print(arguments) arguments.remove(at: 0)// First arg is the filepath and needs to be removed guard arguments.count > 0 else { printHelp() exit(0) } func dayStarted() -> Bool { let currentTasks = reader.tasksInDay(Date()) guard currentTasks.count > 0 else { print("Working day was not started yet, start it with 'jirassic start'") return false } return true } func list (dayOnDate date: Date) { print("") let tasks = reader.tasksInDay(date) if tasks.count > 0 { for task in tasks { let startTime = task.startDate != nil ? (task.startDate!.HHmm() + " ") : "" let notes = task.notes ?? task.taskType.defaultNotes print("• " + startTime + task.endDate.HHmm() + " " + notes) } } else { print("No tasks!") } print("") } func reports (forDay date: Date, targetHoursInDay: Double?) { print("") let tasks = reader.tasksInDay(date) let reportsInteractor = CreateReport() let duration: Double? = targetHoursInDay != nil ? targetHoursInDay!.hoursToSec : nil let reports = reportsInteractor.reports(fromTasks: tasks, targetHoursInDay: duration) if reports.count > 0 { let message = reportsInteractor.toString(reports) print(message) print("") let workedLength = StatisticsInteractor().duration(of: reports) print("Total duration: \(workedLength.secToHoursAndMin)") } else { print("No tasks!") } print("") } func reports (forMonth date: Date, targetHoursInDay: Double?) { print("") print("Reports for the month of \(date.startOfMonth().MMMMdd()) - \(date.endOfMonth().MMMMdd()) \(date.YYYY())") print("") let tasks = reader.tasksInMonth(date) let monthReportsInteractor = CreateMonthReport() let duration: Double? = targetHoursInDay != nil ? targetHoursInDay!.hoursToSec : nil let result = monthReportsInteractor.reports(fromTasks: tasks, targetHoursInDay: duration) if result.byTasks.count > 0 { let joined = monthReportsInteractor.joinReports(result.byTasks) print(joined.notes) print("") print("Total duration: \(joined.totalDuration.secToHoursAndMin)") } else { print("No tasks!") } print("") } func insertIssue (arguments: [String]) { guard dayStarted() else { return } var argType: ArgType? var taskNumber: String? let taskType = TaskType.issue var notes: String? for arg in arguments { if arg.hasPrefix("-") { switch arg { case "-nr": argType = .taskNr break case "-notes": argType = .notes break default: print("Unsupported argument \(arg). Is it a typo?") break } } else if let argType = argType { switch argType { case .taskNr: taskNumber = arg break case .notes: if notes == nil { notes = arg } else { notes = "\(notes!) \(arg)" } break } } } let task = Task( lastModifiedDate: nil, startDate: nil, endDate: Date(), notes: notes, taskNumber: taskNumber, taskTitle: nil, taskType: taskType, objectId: String.generateId() ) // print(task) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: false, completion: {_ in }) print("Task saved") } func insert (taskType: Command, arguments: [String]) { guard dayStarted() 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 } if let duration = arguments.first { if let d = Double(duration) { if d > 0 { let startDate = task?.endDate.addingTimeInterval(d) task?.startDate = startDate } } } // print(task!) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: nil) saveInteractor.saveTask(task!, allowSyncing: false, completion: { _ in }) print(taskType.rawValue.capitalized + " saved") } let commandStr = arguments.remove(at: 0) if let command = Command(rawValue: commandStr) { switch command { case .list: var date = Date() if arguments.count > 0 { let arg = arguments.remove(at: 0) date = Date(YYYYMMddString: arg) } list (dayOnDate: date) break case .reports: var date = Date() var duration: Double? = nil if arguments.count >= 2 { 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) break } else if arg.components(separatedBy: ".").count == 3 { // We have year, month and day date = Date(YYYYMMddString: arg) } } reports (forDay: date, targetHoursInDay: duration) break case .insert: insertIssue (arguments: arguments) break case .scrum, .lunch, .meeting, .waste, .learning, .coderev: insert (taskType: command, arguments: arguments) break case .version: print(appVersion) } } else { print("\nUnsupported command.\n") printHelp() } exit(0) ================================================ FILE: Delivery/macOS-launcher/AppDelegate.h ================================================ // // AppDelegate.h // // Created by Cristian Baluta on 18/03/2017. // Copyright © 2017 Imagin soft. All rights reserved. // #import @interface AppDelegate : NSObject @end ================================================ FILE: Delivery/macOS-launcher/AppDelegate.m ================================================ // // AppDelegate.m // // Created by Cristian Baluta on 18/03/2017. // Copyright © 2017 Imagin soft. All rights reserved. // #import "AppDelegate.h" @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSString *appIdentifier = @"com.jirassic.macos"; BOOL alreadyRunning = NO; for (NSRunningApplication *app in [NSWorkspace sharedWorkspace].runningApplications) { if ([app.bundleIdentifier isEqualToString:appIdentifier]) { alreadyRunning = YES; break; } } if (!alreadyRunning) { [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(terminate) name:@"killme" object:appIdentifier]; BOOL launched = [[NSWorkspace sharedWorkspace] launchApplication:@"Jirassic.app"]; NSLog(@"Jirassic launched %i", launched); // NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // NSURL *url = [NSURL fileURLWithPath:[workspace fullPathForApplication:@"Jirassic.app"]]; // NSLog(@"Jirassic url %@", url); // NSError *error = nil; //// NSArray *arguments = @[@"launchedByLauncher"]; // NSDictionary *config = @{NSWorkspaceLaunchConfigurationEnvironment: @{@"launchedByLauncher": @YES}}; // [workspace launchApplicationAtURL:url // options:NSWorkspaceLaunchDefault // configuration:config // error:&error]; } else { [self terminate]; } } - (void)terminate { [NSApp terminate:nil]; } @end ================================================ FILE: Delivery/macOS-launcher/Base.lproj/MainMenu.xib ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Delivery/macOS-launcher/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSApplicationCategoryType public.app-category.productivity LSBackgroundOnly LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2016 Imagin soft. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: Delivery/macOS-launcher/JirassicLauncher.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: Delivery/macOS-launcher/main.m ================================================ // // main.m // // Created by Cristian Baluta on 18/03/2017. // Copyright © 2017 Imagin soft. All rights reserved. // #import int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); } ================================================ FILE: External/AppleScriptCommands/NewTaskCommand.swift ================================================ // // NewTaskCommand.swift // Jirassic // // Created by Cristian Baluta on 08/04/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCPreferences import RCLog class NewTaskCommand: NSScriptCommand { let pref = RCPreferences() override func execute() -> Any? { guard pref.bool(.enableJit) else { RCLogErrorO("Jit is not enabled.") return nil } guard let json = arguments?[""] as? String else { RCLogErrorO("Invalid argument") return nil } RCLog(json) let validJson = json.replacingOccurrences(of: "'", with: "\"") 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 { return nil } RCLog(dict) let notes = dict["notes"] ?? "" let taskTitle = dict["branchName"] ?? "" let taskNumber = dict["taskNumber"] != "null" ? dict["taskNumber"]! : taskTitle let taskType = dict["taskType"] != nil ? TaskType(rawValue: Int(dict["taskType"]!)!)! : TaskType.gitCommit let informativeText = "\(taskNumber): \(notes)" // If the day was not started, start it now let reader = ReadTasksInteractor(repository: localRepository, remoteRepository: remoteRepository) let currentTasks = reader.tasksInDay(Date()) if currentTasks.count == 0 { startDay() } // Save task let task = Task( lastModifiedDate: nil, startDate: nil, endDate: Date(), notes: notes, taskNumber: taskNumber, taskTitle: taskTitle, taskType: taskType, objectId: String.generateId() ) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(task, allowSyncing: true, completion: { savedTask in }) // Notify app UserNotifications().showNotification("Git commit added", informativeText: informativeText) InternalNotifications.notifyAboutNewlyAddedTask(task) return nil } private func startDay() { var startDate = AppDelegate.sharedApp().sleep.lastWakeDate if startDate == nil { let settings: Settings = SettingsInteractor().getAppSettings() startDate = settings.settingsTracking.startOfDayTime.dateByKeepingTime() } let comps = startDate!.components() let startDayMark = Task(endDate: Date(hour: comps.hour, minute: comps.minute), type: TaskType.startDay) let saveInteractor = TaskInteractor(repository: localRepository, remoteRepository: remoteRepository) saveInteractor.saveTask(startDayMark, allowSyncing: true, completion: { savedTask in }) } } ================================================ FILE: External/CloudKit/CloudKitRepository+Settings.swift ================================================ // // CloudKitRepository+Settings.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation extension CloudKitRepository: RepositorySettings { func settings() -> Settings { fatalError("Not applicable") } func saveSettings (_ settings: Settings) { fatalError("Not applicable") } } ================================================ FILE: External/CloudKit/CloudKitRepository+Tasks.swift ================================================ // // CloudKitRepository+Tasks.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import CloudKit import RCLog extension CloudKitRepository: RepositoryTasks { func queryTask (withId objectId: String) -> Task? { fatalError("This method is not applicable to CloudKitRepository") } func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil) -> [Task] { fatalError("This method is not applicable to CloudKitRepository") } 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 completion(self.tasksFromRecords(records ?? []), nil) } } func queryUnsyncedTasks() -> [Task] { fatalError("This method is not applicable to CloudKitRepository") } func queryDeletedTasks (_ completion: @escaping ([Task]) -> Void) { fatalError("This method is not applicable to CloudKitRepository") } func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { let changeToken = UserDefaults.standard.serverChangeToken fetchChangedRecords(token: changeToken, previousRecords: [], previousDeletedRecordsIds: [], completion: { (changedRecords, deletedRecordsIds) in completion(self.tasksFromRecords(changedRecords), self.stringIdsFromCKRecordIds(deletedRecordsIds), nil) }) } func deleteTask (_ task: Task, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { guard let privateDB = self.privateDB else { RCLog("Not logged in") return } fetchCKRecordOfTask(task) { (record) in if let cktask = record { privateDB.delete(withRecordID: cktask.recordID, completionHandler: { (recordID, error) in RCLogO(recordID) RCLogErrorO(error) completion(error != nil) }) } else { completion(false) } } } func deleteTask (objectId: String, completion: @escaping ((_ success: Bool) -> Void)) { } 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 { RCLog("Can't save, not logged in to iCloud") return } // Query for the task from server if exists fetchCKRecordOfTask(task) { record in 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) record = CKRecord(recordType: "Task", recordID: recordId) } record = self.updatedRecord(record!, withTask: task) privateDB.save(record!, completionHandler: { savedRecord, error in RCLog("2. Record after saving to CloudKit") RCLogO(savedRecord) RCLogErrorO(error) if let record = savedRecord { let uploadedTask = self.taskFromRecord(record) completion(uploadedTask) } 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) } }) } } } extension CloudKitRepository { func fetchCKRecordOfTask (_ task: Task, completion: @escaping ((_ ctask: CKRecord?) -> Void)) { guard let customZone = self.customZone, 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 RCLogErrorO(error) if let result = results?.first { completion(result) } else { completion(nil) } } } 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 } } ================================================ FILE: External/CloudKit/CloudKitRepository+User.swift ================================================ // // CloudKitRepository+User.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import CloudKit extension CloudKitRepository: RepositoryUser { func getUser (_ completion: @escaping ((_ user: User?) -> Void)) { CKContainer.default().accountStatus(completionHandler: { (accountStatus, error) in DispatchQueue.main.async { if accountStatus == .available { completion( User(email: nil, userId: nil) ) } else { completion( nil ) } } }) } func loginWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { } func registerWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { } func logout() { user = nil privateDB = nil customZone = nil } } ================================================ FILE: External/CloudKit/CloudKitRepository.swift ================================================ // // CloudKitRepository.swift // Jirassic // // Created by Cristian Baluta on 09/06/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import CloudKit import RCLog class CloudKitRepository { internal var user: User? internal let container = CKContainer(identifier: "iCloud.com.jirassic.macos") internal var privateDB: CKDatabase? internal var customZone: CKRecordZone? init() { getUser { [weak self] (user) in if let user = user { self?.user = user self?.initDB() } } } 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") 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) } 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) } } } } ================================================ FILE: External/CloudKit/UserDefaults+token.swift ================================================ // // 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") } } } } ================================================ FILE: External/CoreData/CSettings.swift ================================================ // // CSettings.swift // Jirassic // // Created by Cristian Baluta on 17/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import CoreData class CSettings: NSManagedObject { @NSManaged var autotrack: NSNumber? @NSManaged var autotrackingMode: NSNumber? @NSManaged var trackLunch: NSNumber? @NSManaged var trackScrum: NSNumber? @NSManaged var trackMeetings: NSNumber? @NSManaged var trackCodeReviews: NSNumber? @NSManaged var trackWastedTime: NSNumber? @NSManaged var trackStartOfDay: NSNumber? @NSManaged var enableBackup: NSNumber? @NSManaged var startOfDayTime: Date? @NSManaged var endOfDayTime: Date? @NSManaged var lunchTime: Date? @NSManaged var scrumTime: Date? @NSManaged var minSleepDuration: NSNumber? @NSManaged var minCodeRevDuration: NSNumber? @NSManaged var codeRevLink: String? @NSManaged var minWasteDuration: NSNumber? @NSManaged var wasteLinks: [String]? } ================================================ FILE: External/CoreData/CTask.swift ================================================ // // CTask.swift // Jirassic // // Created by Cristian Baluta on 01/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import CoreData class CTask: NSManagedObject { @NSManaged var lastModifiedDate: Date? @NSManaged var markedForDeletion: NSNumber? @NSManaged var startDate: Date? @NSManaged var endDate: Date? @NSManaged var notes: String? @NSManaged var taskNumber: String? @NSManaged var taskTitle: String? @NSManaged var taskType: NSNumber? @NSManaged var objectId: String? } ================================================ FILE: External/CoreData/CUser.swift ================================================ // // CUser.swift // Jirassic // // Created by Cristian Baluta on 04/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import CoreData class CUser: NSManagedObject { @NSManaged var userId: String? @NSManaged var email: String? } ================================================ FILE: External/CoreData/CoreDataRepository+Settings.swift ================================================ // // CoreDataRepository+Settings.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import CoreData extension CoreDataRepository: RepositorySettings { func settings() -> Settings { let results: [CSettings] = queryWithPredicate(nil, sortDescriptors: nil) var csettings: CSettings? = results.first if csettings == nil { // Default values csettings = NSEntityDescription.insertNewObject(forEntityName: String(describing: CSettings.self), into: managedObjectContext!) as? CSettings csettings?.autotrack = 1 csettings?.autotrackingMode = 1 csettings?.trackLunch = 1 csettings?.trackScrum = 1 csettings?.trackMeetings = 1 csettings?.trackCodeReviews = 1 csettings?.trackWastedTime = 1 csettings?.trackStartOfDay = 1 csettings?.enableBackup = 1 csettings?.startOfDayTime = Date(hour: 9, minute: 0) csettings?.endOfDayTime = Date(hour: 17, minute: 0) csettings?.lunchTime = Date(hour: 13, minute: 0) csettings?.scrumTime = Date(hour: 10, minute: 30) csettings?.minSleepDuration = NSNumber(value: 13) csettings?.minCodeRevDuration = NSNumber(value: 3) csettings?.minWasteDuration = NSNumber(value: 5) csettings?.codeRevLink = "(http|https)://(.+)/projects/(.+)/repos/(.+)/pull-requests" csettings?.wasteLinks = ["facebook.com", "youtube.com", "twitter.com"] saveContext() } return settingsFromCSettings(csettings!) } func saveSettings (_ settings: Settings) { let _ = csettingsFromSettings(settings) saveContext() } fileprivate func settingsFromCSettings (_ csettings: CSettings) -> Settings { return Settings( enableBackup: csettings.enableBackup!.boolValue, settingsTracking: SettingsTracking( autotrack: csettings.autotrack!.boolValue, autotrackingMode: TrackingMode(rawValue: csettings.autotrackingMode!.intValue)!, trackLunch: csettings.trackLunch!.boolValue, trackScrum: csettings.trackScrum!.boolValue, trackMeetings: csettings.trackMeetings!.boolValue, trackStartOfDay: csettings.trackStartOfDay!.boolValue, startOfDayTime: csettings.startOfDayTime!, endOfDayTime: csettings.endOfDayTime!, lunchTime: csettings.lunchTime!, scrumTime: csettings.scrumTime!, minSleepDuration: csettings.minSleepDuration!.intValue ), settingsBrowser: SettingsBrowser( trackCodeReviews: csettings.trackCodeReviews!.boolValue, trackWastedTime: csettings.trackWastedTime!.boolValue, minCodeRevDuration: csettings.minCodeRevDuration!.intValue, codeRevLink: csettings.codeRevLink!, minWasteDuration: csettings.minWasteDuration!.intValue, wasteLinks: csettings.wasteLinks! ) ) } fileprivate func csettingsFromSettings (_ settings: Settings) -> CSettings { let results: [CSettings] = queryWithPredicate(nil, sortDescriptors: nil) var csettings: CSettings? = results.first if csettings == nil { csettings = NSEntityDescription.insertNewObject(forEntityName: String(describing: CSettings.self), into: managedObjectContext!) as? CSettings } csettings?.autotrack = NSNumber(value: settings.settingsTracking.autotrack) csettings?.autotrackingMode = NSNumber(value: settings.settingsTracking.autotrackingMode.rawValue) csettings?.trackLunch = NSNumber(value: settings.settingsTracking.trackLunch) csettings?.trackScrum = NSNumber(value: settings.settingsTracking.trackScrum) csettings?.trackMeetings = NSNumber(value: settings.settingsTracking.trackMeetings) csettings?.trackCodeReviews = NSNumber(value: settings.settingsBrowser.trackCodeReviews) csettings?.trackWastedTime = NSNumber(value: settings.settingsBrowser.trackWastedTime) csettings?.trackStartOfDay = NSNumber(value: settings.settingsTracking.trackStartOfDay) csettings?.enableBackup = NSNumber(value: settings.enableBackup) csettings?.startOfDayTime = settings.settingsTracking.startOfDayTime csettings?.endOfDayTime = settings.settingsTracking.endOfDayTime csettings?.lunchTime = settings.settingsTracking.lunchTime csettings?.scrumTime = settings.settingsTracking.scrumTime csettings?.minSleepDuration = NSNumber(value: settings.settingsTracking.minSleepDuration) csettings?.minCodeRevDuration = NSNumber(value: settings.settingsBrowser.minCodeRevDuration) csettings?.codeRevLink = settings.settingsBrowser.codeRevLink csettings?.minWasteDuration = NSNumber(value: settings.settingsBrowser.minWasteDuration) csettings?.wasteLinks = settings.settingsBrowser.wasteLinks return csettings! } } ================================================ FILE: External/CoreData/CoreDataRepository+Tasks.swift ================================================ // // CoreDataRepository+Tasks.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import CoreData extension CoreDataRepository: RepositoryTasks { func queryTask (withId objectId: String) -> Task? { #warning("To be implemented for ios") return nil } func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil) -> [Task] { let tasks = tasksBetween(startDate: startDate, endDate: endDate) return tasks } func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil, completion: @escaping ([Task], NSError?) -> Void) { let tasks = tasksBetween(startDate: startDate, endDate: endDate) completion(tasks, nil) } func queryUnsyncedTasks() -> [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)) } else { subpredicates.append(NSPredicate(format: "lastModifiedDate == nil")) } let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: subpredicates) let results: [CTask] = queryWithPredicate(compoundPredicate, sortDescriptors: nil) let tasks = tasksFromCTasks(results) return tasks } func queryDeletedTasks (_ completion: @escaping ([Task]) -> Void) { let predicate = NSPredicate(format: "markedForDeletion == YES") let results: [CTask] = queryWithPredicate(predicate, sortDescriptors: nil) let tasks = tasksFromCTasks(results) completion(tasks) } func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { completion(queryUnsyncedTasks(), [], nil) } func deleteTask (_ task: Task, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { guard let context = managedObjectContext else { return } let ctask = ctaskFromTask(task) if permanently { context.delete(ctask) } else { ctask.markedForDeletion = NSNumber(value: true) } saveContext() completion(true) } func deleteTask (objectId: String, completion: @escaping ((_ success: Bool) -> Void)) { } func saveTask (_ task: Task, completion: @escaping ((_ task: Task?) -> Void)) { let ctask = ctaskFromTask(task) saveContext() completion( taskFromCTask(ctask)) } } extension CoreDataRepository { fileprivate func tasksBetween (startDate: Date, endDate: Date) -> [Task] { let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ NSPredicate(format: "endDate >= %@ AND endDate <= %@", startDate as CVarArg, endDate as CVarArg), NSPredicate(format: "markedForDeletion == NO || markedForDeletion == nil") ]) let sortDescriptors = [NSSortDescriptor(key: "endDate", ascending: true)] let results: [CTask] = queryWithPredicate(compoundPredicate, sortDescriptors: sortDescriptors) let tasks = tasksFromCTasks(results) return tasks } fileprivate func taskFromCTask (_ ctask: CTask) -> Task { return Task(lastModifiedDate: ctask.lastModifiedDate, startDate: ctask.startDate, endDate: ctask.endDate!, notes: ctask.notes, taskNumber: ctask.taskNumber, taskTitle: ctask.taskTitle, taskType: TaskType(rawValue: ctask.taskType!.intValue)!, objectId: ctask.objectId! ) } fileprivate func tasksFromCTasks (_ ctasks: [CTask]) -> [Task] { var tasks = [Task]() for ctask in ctasks { tasks.append(self.taskFromCTask(ctask)) } return tasks } fileprivate func ctaskFromTask (_ task: Task) -> CTask { let taskPredicate = NSPredicate(format: "objectId == %@", task.objectId!) let tasks: [CTask] = queryWithPredicate(taskPredicate, sortDescriptors: nil) var ctask: CTask? = tasks.first if ctask == nil { ctask = NSEntityDescription.insertNewObject(forEntityName: String(describing: CTask.self), into: managedObjectContext!) as? CTask ctask?.objectId = task.objectId } return updatedCTask(ctask!, withTask: task) } // Update only updatable properties. objectId can't be updated fileprivate func updatedCTask (_ ctask: CTask, withTask task: Task) -> CTask { ctask.taskNumber = task.taskNumber ctask.taskType = NSNumber(value: task.taskType.rawValue) ctask.taskTitle = task.taskTitle ctask.notes = task.notes ctask.startDate = task.startDate ctask.endDate = task.endDate ctask.lastModifiedDate = task.lastModifiedDate return ctask } } ================================================ FILE: External/CoreData/CoreDataRepository+User.swift ================================================ // // CoreDataRepository+User.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import CoreData extension CoreDataRepository: RepositoryUser { func getUser (_ completion: @escaping ((_ user: User?) -> Void)) { fatalError("This method is not applicable to local Repository") } func loginWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to local Repository") } func registerWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to local Repository") } func logout() { fatalError("This method is not applicable to local Repository") } } ================================================ FILE: External/CoreData/CoreDataRepository.swift ================================================ // // CoreDataRepository.swift // Jirassic // // Created by Cristian Baluta on 15/04/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import CoreData import RCLog class CoreDataRepository { internal let databaseName = "Jirassic" internal lazy var applicationDocumentsDirectory: URL = { #if os(iOS) let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return urls.last! #else let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) let baseUrl = urls.last! let url = baseUrl.appendingPathComponent(self.databaseName) RCLog(url) if !FileManager.default.fileExists(atPath: url.path) { do { try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil) } catch _ { return baseUrl } } return url #endif }() lazy var managedObjectContext: NSManagedObjectContext? = { return self.persistentContainer.viewContext }() lazy var persistentContainer: NSPersistentContainer = { return container() }() internal func container() -> NSPersistentContainer { let url = self.applicationDocumentsDirectory.appendingPathComponent("\(self.databaseName).coredata") let storeDescriptor = NSPersistentStoreDescription(url: url) storeDescriptor.shouldMigrateStoreAutomatically = true storeDescriptor.shouldInferMappingModelAutomatically = true storeDescriptor.type = NSSQLiteStoreType let container = NSPersistentContainer(name: self.databaseName) container.persistentStoreDescriptions = [storeDescriptor] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { print("Unresolved error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true return container } func saveContext () { if let moc = self.managedObjectContext, moc.hasChanges { try? moc.save() } } convenience init (documentsDirectory: String) { self.init() applicationDocumentsDirectory = URL(fileURLWithPath: documentsDirectory) } } extension CoreDataRepository { internal func queryWithPredicate (_ predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?) -> [T] { guard let context = managedObjectContext else { return [] } let request = NSFetchRequest(entityName: String(describing: T.self)) //let request = T.fetchRequest() request.returnsObjectsAsFaults = false request.predicate = predicate request.sortDescriptors = sortDescriptors do { let results = try context.fetch(request) return results } catch _ { return [] } } } ================================================ FILE: External/CoreData/Jirassic.xcdatamodeld/.xccurrentversion ================================================ _XCCurrentVersionName Jirassic.xcdatamodel ================================================ FILE: External/CoreData/Jirassic.xcdatamodeld/Jirassic.xcdatamodel/contents ================================================ ================================================ FILE: External/InMemoryStorage/InMemoryCoreDataRepository.swift ================================================ // // InMemoryRepository.swift // Jirassic // // Created by Baluta Cristian on 29/03/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import CoreData //@testable import Jirassic_no_cloud // TODO try to configure this so the model does not need to be in the Jirassic Appstore target but in the tests target class InMemoryCoreDataRepository: CoreDataRepository { override func container() -> NSPersistentContainer { let storeDescriptor = NSPersistentStoreDescription() storeDescriptor.shouldAddStoreAsynchronously = false storeDescriptor.type = NSInMemoryStoreType let container = NSPersistentContainer(name: self.databaseName, managedObjectModel: self.managedObjectModel) container.persistentStoreDescriptions = [storeDescriptor] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { print("Unresolved error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true return container } lazy var managedObjectModel: NSManagedObjectModel = { let testBundle = Bundle(for: type(of: self)) // return NSManagedObjectModel.mergedModel(from: nil)! let managedObjectModel = NSManagedObjectModel.mergedModel(from: [testBundle]) return managedObjectModel! }() } ================================================ FILE: External/Jira/JProject.swift ================================================ // // JProject.swift // Jirassic // // Created by Cristian Baluta on 24/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation struct JProject { var id: String var key: String var name: String var url: String } ================================================ FILE: External/Jira/JProjectIssue.swift ================================================ // // JProjectIssue.swift // Jirassic // // Created by Cristian Baluta on 24/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation struct JProjectIssue { var id: String var key: String var url: String } ================================================ FILE: External/Jira/JReport.swift ================================================ // // JReport.swift // Jirassic // // Created by Cristian Baluta on 02/07/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation struct JReport { var comment: String var dateStarted: String//YYYY-MM-ddT00:00:00.000+0000 var timeSpentSeconds: Int // var author: JAuthor // var issue: JIssue var workAttributeValues: [JWorkAttribute] } ================================================ FILE: External/Jira/JWorkAttribute.swift ================================================ // // JWorkAttribute.swift // Jirassic // // Created by Cristian Baluta on 02/07/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation struct JWorkAttribute { var value: String } ================================================ FILE: External/Jira/JiraRepository+Projects.swift ================================================ // // JiraRepository+Projects.swift // Jirassic // // Created by Cristian Baluta on 24/01/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import RCHttp extension JiraRepository { // GET https://.../rest/api/2/project // Returns an array of projects func fetchProjects (success: @escaping ([JProject]) -> Void, failure: @escaping (Error) -> Void) { let path = "rest/api/2/project" request?.get(at: path, success: { responseData in guard let responseJson = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments), let projects = responseJson as? [[String: Any]] else { failure(RCHttpError(errorDescription: "Invalid json response")) return } var jprojects: [JProject] = [] for project in projects { let jproject = JProject(id: project["id"] as? String ?? "", key: project["key"] as? String ?? "", name: project["name"] as? String ?? "", url: project["self"] as? String ?? "") jprojects.append(jproject) } success(jprojects) }, failure: { err in failure(err) }) } // GET https://.../rest/api/2/search?jql=project=PROJECT_KEY&fields=*none&maxResults=-1 // Returns a dictionary that includes an array of issues 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 guard let responseJson = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments), let response = responseJson as? [String: Any], let issues = response["issues"] as? [[String: Any]] else { failure(RCHttpError(errorDescription: "Invalid json response")) return } var jissues: [JProjectIssue] = [] for issue in issues { let jissue = JProjectIssue(id: issue["id"] as? String ?? "", key: issue["key"] as? String ?? "", url: issue["self"] as? String ?? "") jissues.append(jissue) } success(jissues) }, failure: { err in failure(err) }) } } ================================================ FILE: External/Jira/JiraRepository+Reports.swift ================================================ // // Jira+Reports.swift // Jirassic // // Created by Cristian Baluta on 02/07/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import RCHttp extension JiraRepository { // // https://.../rest/tempo-timesheets/3/worklogs/?dateFrom=2017-06-01&dateTo=2017-06-30&username=cristianbal // func fetchReports (ofDay day: Date, completion: (() -> Void)?) { // // // } // // func deleteReports (ofDay day: Date, completion: ((Bool) -> Void)?) { // // } // POST https://.../rest/tempo-timesheets/3/worklogs/ func postWorklog (_ worklog: String, duration: Double, in project: JProject, to issue: JProjectIssue, date: Date, success: @escaping (() -> Void), failure: @escaping (Error) -> Void) { let dateStarted = date.YYYYMMddT00()//"2017-07-03T00:00:00.000+0000" let path = "rest/tempo-timesheets/3/worklogs" let parameters: [String: Any] = [ "issue": [ "key": issue.key, "projectId": project.id ], "author": [ "name": self.user ], "comment": worklog, "dateStarted": dateStarted, "timeSpentSeconds": duration ] request?.post(at: path, parameters: parameters, success: { responseData in guard let _ = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) else { failure(RCHttpError(errorDescription: "Invalid json response")) return } success() }, failure: { (err) in failure(err) }) } } //extension JiraRepository { // // fileprivate func reportsToJReports(_ reports: [Report]) -> [JReport] { // return reports.map({ reportToJReport($0) }) // } // // fileprivate func reportToJReport(_ report: Report) -> JReport { // // let author = JAuthor(name: "self.user") // let issue = JIssue(key: "", projectId: 0) // let jreport = JReport(comment: report.notes, // dateStarted: "", // timeSpentSeconds: Int(report.duration), // author: author, // issue: issue, // workAttributeValues: []) // // return jreport // } // // fileprivate func serializedJReports (_ jreports: [JReport]) -> [String: Any] { // // return [String: Any]() // } //} ================================================ FILE: External/Jira/JiraRepository.swift ================================================ // // JiraRepository.swift // Jirassic // // Created by Cristian Baluta on 02/07/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import RCHttp class JiraRepository { var request: RCHttp? var user: String! init (url: String, user: String, password: String) { self.user = user self.request = RCHttp(baseURL: url) self.request?.authenticate(user: user, password: password) } } ================================================ FILE: External/Keychain/Keychain.swift ================================================ // // Keychain.swift // Jirassic // // Created by Cristian Baluta on 25/03/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation import SwiftKeychainWrapper class Keychain { private static let key = "jira_password" class func getPassword() -> String { return KeychainWrapper.standard.string(forKey: key) ?? "" } class func setPassword(_ password: String?) { if let password = password { _ = KeychainWrapper.standard.set(password, forKey: key) } else { KeychainWrapper.standard.removeObject(forKey: key) } } } ================================================ FILE: External/RCSync.swift ================================================ // // SyncTasks.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import RCLog class RCSync { private let localRepository: Repository! private let remoteRepository: Repository! private var objectsToUpload = [Task]() private var objectsToDelete = [Task]() init (localRepository: Repository, remoteRepository: Repository) { self.localRepository = localRepository self.remoteRepository = remoteRepository } func start (_ completion: @escaping ((_ hasIncomingChanges: Bool) -> Void)) { RCLog("1. Start iCloud sync") guard objectsToUpload.count == 0 || objectsToDelete.count == 0 else { RCLog("1. Sync already in progress, not starting it again") return } objectsToUpload = localRepository.queryUnsyncedTasks() 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.getLatestServerChanges(completion) } } } // 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 self.syncNext(completion) }) } else { task = objectsToDelete.first if task != nil { objectsToDelete.remove(at: 0) deleteTask(task!, completion: { (success) in self.syncNext(completion) }) } else { UserDefaults.standard.lastSyncDateWithRemote = Date() completion(true) } } } func uploadTask (_ task: Task, completion: @escaping ((_ success: Bool) -> Void)) { RCLog("1.1 >>> Save \(task)") _ = remoteRepository.saveTask(task) { uploadedTask in guard let uploadedTask = uploadedTask else { completion(false) return } RCLog("1.1 Save <<< uploadedTask \(String(describing: uploadedTask.objectId))") // 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) }) } } 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 completion(true) }) } } 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)") for task in changedTasks { self.localRepository.saveTask(task, completion: { (task) in RCLog("2. Saved to local db \(String(describing: task?.objectId))") }) } RCLog("2. Number of deletes: \(deletedTasksIds.count)") for remoteId in deletedTasksIds { self.localRepository.deleteTask(objectId: remoteId, completion: { (success) in RCLog("2. Deleted from local db: \(remoteId) \(success)") }) } completion(changedTasks.count > 0 || deletedTasksIds.count > 0) } } } ================================================ FILE: External/Realm/RSettings.swift ================================================ // // CSettings.swift // Jirassic // // Created by Cristian Baluta on 17/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RealmSwift class RSettings: Object { dynamic var startOfDayEnabled: Bool = false dynamic var lunchEnabled: Bool = false dynamic var scrumEnabled: Bool = false dynamic var meetingEnabled: Bool = false dynamic var autoTrackEnabled: Bool = false dynamic var trackingMode: Int = 0 dynamic var startOfDayTime: Date? dynamic var endOfDayTime: Date? dynamic var lunchTime: Date? dynamic var scrumTime: Date? dynamic var minSleepDuration: Date? } ================================================ FILE: External/Realm/RTask.swift ================================================ // // CTask.swift // Jirassic // // Created by Cristian Baluta on 01/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RealmSwift class RTask: Object { dynamic var lastModifiedDate: Date? dynamic var creationDate: Date? dynamic var startDate: Date? dynamic var endDate: Date? dynamic var notes: String? dynamic var taskNumber: String? dynamic var taskType: Int = 0 dynamic var objectId: String? } ================================================ FILE: External/Realm/RUser.swift ================================================ // // CUser.swift // Jirassic // // Created by Cristian Baluta on 04/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RealmSwift class RUser: Object { dynamic var userId: String? dynamic var email: String? dynamic var lastSyncDate: Date? dynamic var isLoggedIn: Bool = false } ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/NSError+RLMSync.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2017 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN /// NSError category extension providing methods to get data out of Realm's /// "client reset" error. @interface NSError (RLMSync) /** Given a Realm Object Server client reset error, return the block that can be called to manually initiate the client reset process, or nil if the error isn't a client reset error. */ - (nullable void(^)(void))rlmSync_clientResetBlock NS_REFINED_FOR_SWIFT; /** Given a Realm Object Server client reset error, return the path where the backup copy of the Realm will be placed once the client reset process is complete. */ - (nullable NSString *)rlmSync_clientResetBackedUpRealmPath NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMArray.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN @class RLMObject, RLMRealm, RLMResults, RLMNotificationToken; /** `RLMArray` is the container type in Realm used to define to-many relationships. Unlike an `NSArray`, `RLMArray`s hold a single type, specified by the `objectClassName` property. This is referred to in these docs as the “type” of the array. When declaring an `RLMArray` property, the type must be marked as conforming to a protocol by the same name as the objects it should contain (see the `RLM_ARRAY_TYPE` macro). In addition, the property can be declared using Objective-C generics for better compile-time type safety. RLM_ARRAY_TYPE(ObjectType) ... @property RLMArray *arrayOfObjectTypes; `RLMArray`s can be queried with the same predicates as `RLMObject` and `RLMResult`s. `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed, or can be obtained by querying a Realm. ### Key-Value Observing `RLMArray` supports array key-value observing on `RLMArray` properties on `RLMObject` subclasses, and the `invalidated` property on `RLMArray` instances themselves is key-value observing compliant when the `RLMArray` is attached to a managed `RLMObject` (`RLMArray`s on unmanaged `RLMObject`s will never become invalidated). Because `RLMArray`s are attached to the object which they are a property of, they do not require using the mutable collection proxy objects from `-mutableArrayValueForKey:` or KVC-compatible mutation methods on the containing object. Instead, you can call the mutation methods on the `RLMArray` directly. */ @interface RLMArray : NSObject #pragma mark - Properties /** The number of objects in the array. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The class name (i.e. type) of the `RLMObject`s contained in the array. */ @property (nonatomic, readonly, copy) NSString *objectClassName; /** The Realm which manages the array. Returns `nil` for unmanaged arrays. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** Indicates if the array can no longer be accessed. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Accessing Objects from an Array /** Returns the object at the index specified. @param index The index to look up. @return An `RLMObject` of the type contained in the array. */ - (RLMObjectType)objectAtIndex:(NSUInteger)index; /** Returns the first object in the array. Returns `nil` if called on an empty array. @return An `RLMObject` of the type contained in the array. */ - (nullable RLMObjectType)firstObject; /** Returns the last object in the array. Returns `nil` if called on an empty array. @return An `RLMObject` of the type contained in the array. */ - (nullable RLMObjectType)lastObject; #pragma mark - Adding, Removing, and Replacing Objects in an Array /** Adds an object to the end of the array. @warning This method may only be called during a write transaction. @param object An `RLMObject` of the type contained in the array. */ - (void)addObject:(RLMObjectType)object; /** Adds an array of objects to the end of the array. @warning This method may only be called during a write transaction. @param objects An enumerable object such as `NSArray` or `RLMResults` which contains objects of the same class as the array. */ - (void)addObjects:(id)objects; /** Inserts an object at the given index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param anObject An `RLMObject` of the type contained in the array. @param index The index at which to insert the object. */ - (void)insertObject:(RLMObjectType)anObject atIndex:(NSUInteger)index; /** Removes an object at the given index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index The array index identifying the object to be removed. */ - (void)removeObjectAtIndex:(NSUInteger)index; /** Removes the last object in the array. @warning This method may only be called during a write transaction. */ - (void)removeLastObject; /** Removes all objects from the array. @warning This method may only be called during a write transaction. */ - (void)removeAllObjects; /** Replaces an object at the given index with a new object. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index The index of the object to be replaced. @param anObject An object (of the same type as returned from the `objectClassName` selector). */ - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(RLMObjectType)anObject; /** Moves the object at the given source index to the given destination index. Throws an exception if the index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param sourceIndex The index of the object to be moved. @param destinationIndex The index to which the object at `sourceIndex` should be moved. */ - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex; /** Exchanges the objects in the array at given indices. Throws an exception if either index exceeds the bounds of the array. @warning This method may only be called during a write transaction. @param index1 The index of the object which should replace the object at index `index2`. @param index2 The index of the object which should replace the object at index `index1`. */ - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2; #pragma mark - Querying an Array /** Returns the index of an object in the array. Returns `NSNotFound` if the object is not found in the array. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(RLMObjectType)object; /** Returns the index of the first object in the array matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the array. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the array matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the array. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; /** Returns all the objects matching the given predicate in the array. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all the objects matching the given predicate in the array. @param predicate The predicate with which to filter the objects. @return An `RLMResults` of objects that match the given predicate */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from the array. @param keyPath The key path to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from the array. @param property The property name to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified property. */ - (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending __deprecated_msg("Use `-sortedResultsUsingKeyPath:ascending:`"); /** Returns a sorted `RLMResults` from the array. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /// :nodoc: - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; /// :nodoc: - (void)setObject:(RLMObjectType)newValue atIndexedSubscript:(NSUInteger)index; #pragma mark - Notifications /** Registers a block to be called each time the array changes. The block will be asynchronously called with the initial array, and then called again after each write transaction which changes any of the objects in the array, which objects are in the results, or the order of the objects in the array. The `changes` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the array were added, removed or modified. If a write transaction did not modify any objects in the array, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. If an error occurs the block will be called with `nil` for the results parameter and a non-`nil` error. Currently the only errors that can occur are when opening the Realm on the background worker thread. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. Person *person = [[Person allObjectsInRealm:realm] firstObject]; NSLog(@"person.dogs.count: %zu", person.dogs.count); // => 0 self.token = [person.dogs addNotificationBlock(RLMArray *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count) // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [person.dogs addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-stop` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @warning This method may only be called on a managed array. @param block The block to be called each time the array changes. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *__nullable array, RLMCollectionChange *__nullable changes, NSError *__nullable error))block __attribute__((warn_unused_result)); #pragma mark - Unavailable Methods /** `-[RLMArray init]` is not available because `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed, or can be obtained by querying a Realm. */ - (instancetype)init __attribute__((unavailable("RLMArrays cannot be created directly"))); /** `+[RLMArray new]` is not available because `RLMArray`s cannot be created directly. `RLMArray` properties on `RLMObject`s are lazily created when accessed, or can be obtained by querying a Realm. */ + (instancetype)new __attribute__((unavailable("RLMArrays cannot be created directly"))); @end /// :nodoc: @interface RLMArray (Swift) // for use only in Swift class definitions - (instancetype)initWithObjectClassName:(NSString *)objectClassName; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMCollection.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMThreadSafeReference.h" NS_ASSUME_NONNULL_BEGIN @class RLMRealm, RLMResults, RLMObject, RLMSortDescriptor, RLMNotificationToken, RLMCollectionChange; /** A homogenous collection of `RLMObject` instances. Examples of conforming types include `RLMArray`, `RLMResults`, and `RLMLinkingObjects`. */ @protocol RLMCollection @required #pragma mark - Properties /** The number of objects in the collection. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The class name (i.e. type) of the `RLMObject`s contained in the collection. */ @property (nonatomic, readonly, copy) NSString *objectClassName; /** The Realm which manages the collection, or `nil` for unmanaged collections. */ @property (nonatomic, readonly) RLMRealm *realm; #pragma mark - Accessing Objects from a Collection /** Returns the object at the index specified. @param index The index to look up. @return An `RLMObject` of the type contained in the collection. */ - (id)objectAtIndex:(NSUInteger)index; /** Returns the first object in the collection. Returns `nil` if called on an empty collection. @return An `RLMObject` of the type contained in the collection. */ - (nullable id)firstObject; /** Returns the last object in the collection. Returns `nil` if called on an empty collection. @return An `RLMObject` of the type contained in the collection. */ - (nullable id)lastObject; #pragma mark - Querying a Collection /** Returns the index of an object in the collection. Returns `NSNotFound` if the object is not found in the collection. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(RLMObject *)object; /** Returns the index of the first object in the collection matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the collection. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the collection matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the collection. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; /** Returns all objects matching the given predicate in the collection. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects matching the given predicate in the collection. @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing objects that match the given predicate. */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from the collection. @param keyPath The keyPath to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from the collection. @param property The property name to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified property. */ - (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending __deprecated_msg("Use `-sortedResultsUsingKeyPath:ascending:`"); /** Returns a sorted `RLMResults` from the collection. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; /// :nodoc: - (id)objectAtIndexedSubscript:(NSUInteger)index; /** Returns an `NSArray` containing the results of invoking `valueForKey:` using `key` on each of the collection's objects. @param key The name of the property. @return An `NSArray` containing results. */ - (nullable id)valueForKey:(NSString *)key; /** Invokes `setValue:forKey:` on each of the collection's objects using the specified `value` and `key`. @warning This method may only be called during a write transaction. @param value The object value. @param key The name of the property. */ - (void)setValue:(nullable id)value forKey:(NSString *)key; #pragma mark - Notifications /** Registers a block to be called each time the collection changes. The block will be asynchronously called with the initial collection, and then called again after each write transaction which changes either any of the objects in the collection, or which objects are in the collection. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the collection were added, removed or modified. If a write transaction did not modify any objects in this collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. If an error occurs the block will be called with `nil` for the collection parameter and a non-`nil` error. Currently the only errors that can occur are when opening the Realm on the background worker thread. At the time when the block is called, the collection object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial collection. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. id collection = [Dog allObjects]; NSLog(@"dogs.count: %zu", dogs.count); // => 0 self.token = [collection addNotificationBlock:^(id dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-stop` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @param block The block to be called each time the collection changes. @return A token which must be held for as long as you want collection notifications to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(id __nullable collection, RLMCollectionChange *__nullable change, NSError *__nullable error))block __attribute__((warn_unused_result)); @end /** An `RLMSortDescriptor` stores a property name and a sort order for use with `sortedResultsUsingDescriptors:`. It is similar to `NSSortDescriptor`, but supports only the subset of functionality which can be efficiently run by Realm's query engine. `RLMSortDescriptor` instances are immutable. */ @interface RLMSortDescriptor : NSObject #pragma mark - Properties /** The key path which the sort descriptor orders results by. */ @property (nonatomic, readonly) NSString *keyPath; /** Whether the descriptor sorts in ascending or descending order. */ @property (nonatomic, readonly) BOOL ascending; #pragma mark - Methods /** Returns a new sort descriptor for the given key path and sort direction. */ + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a copy of the receiver with the sort direction reversed. */ - (instancetype)reversedSortDescriptor; #pragma mark - Deprecated /** The name of the property which the sort descriptor orders results by. */ @property (nonatomic, readonly) NSString *property __deprecated_msg("Use `-keyPath`"); /** Returns a new sort descriptor for the given property name and sort direction. */ + (instancetype)sortDescriptorWithProperty:(NSString *)propertyName ascending:(BOOL)ascending __deprecated_msg("Use `+sortDescriptorWithKeyPath:ascending:`"); @end /** A `RLMCollectionChange` object encapsulates information about changes to collections that are reported by Realm notifications. `RLMCollectionChange` is passed to the notification blocks registered with `-addNotificationBlock` on `RLMArray` and `RLMResults`, and reports what rows in the collection changed since the last time the notification block was called. The change information is available in two formats: a simple array of row indices in the collection for each type of change, and an array of index paths in a requested section suitable for passing directly to `UITableView`'s batch update methods. A complete example of updating a `UITableView` named `tv`: [tv beginUpdates]; [tv deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tv endUpdates]; All of the arrays in an `RLMCollectionChange` are always sorted in ascending order. */ @interface RLMCollectionChange : NSObject /// The indices of objects in the previous version of the collection which have /// been removed from this one. @property (nonatomic, readonly) NSArray *deletions; /// The indices in the new version of the collection which were newly inserted. @property (nonatomic, readonly) NSArray *insertions; /** The indices in the new version of the collection which were modified. For `RLMResults`, this means that one or more of the properties of the object at that index were modified (or an object linked to by that object was modified). For `RLMArray`, the array itself being modified to contain a different object at that index will also be reported as a modification. */ @property (nonatomic, readonly) NSArray *modifications; /// Returns the index paths of the deletion indices in the given section. - (NSArray *)deletionsInSection:(NSUInteger)section; /// Returns the index paths of the insertion indices in the given section. - (NSArray *)insertionsInSection:(NSUInteger)section; /// Returns the index paths of the modification indices in the given section. - (NSArray *)modificationsInSection:(NSUInteger)section; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMConstants.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN // For compatibility with Xcode 7, before extensible string enums were introduced, #ifdef NS_EXTENSIBLE_STRING_ENUM #define RLM_EXTENSIBLE_STRING_ENUM NS_EXTENSIBLE_STRING_ENUM #define RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(_, extensible_string_enum) NS_SWIFT_NAME(extensible_string_enum) #else #define RLM_EXTENSIBLE_STRING_ENUM #define RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(fully_qualified, _) NS_SWIFT_NAME(fully_qualified) #endif #if __has_attribute(ns_error_domain) && (!defined(__cplusplus) || !__cplusplus || __cplusplus >= 201103L) #define RLM_ERROR_ENUM(type, name, domain) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wignored-attributes\"") \ NS_ENUM(type, __attribute__((ns_error_domain(domain))) name) \ _Pragma("clang diagnostic pop") #else #define RLM_ERROR_ENUM(type, name, domain) NS_ENUM(type, name) #endif #pragma mark - Enums /** `RLMPropertyType` is an enumeration describing all property types supported in Realm models. For more information, see [Realm Models](https://realm.io/docs/objc/latest/#models). */ // Make sure numbers match those in typedef NS_ENUM(int32_t, RLMPropertyType) { #pragma mark - Primitive types /** Integers: `NSInteger`, `int`, `long`, `Int` (Swift) */ RLMPropertyTypeInt = 0, /** Booleans: `BOOL`, `bool`, `Bool` (Swift) */ RLMPropertyTypeBool = 1, /** Floating-point numbers: `float`, `Float` (Swift) */ RLMPropertyTypeFloat = 9, /** Double-precision floating-point numbers: `double`, `Double` (Swift) */ RLMPropertyTypeDouble = 10, #pragma mark - Object types /** Strings: `NSString`, `String` (Swift) */ RLMPropertyTypeString = 2, /** Binary data: `NSData` */ RLMPropertyTypeData = 4, /** Any object: `id`. This property type is no longer supported for new models. However, old models with any-typed properties are still supported for migration purposes. */ RLMPropertyTypeAny = 6, /** Dates: `NSDate` */ RLMPropertyTypeDate = 8, #pragma mark - Array/Linked object types /** Realm model objects. See [Realm Models](https://realm.io/docs/objc/latest/#models) for more information. */ RLMPropertyTypeObject = 12, /** Realm arrays. See [Realm Models](https://realm.io/docs/objc/latest/#models) for more information. */ RLMPropertyTypeArray = 13, /** Realm linking objects. See [Realm Models](https://realm.io/docs/objc/latest/#models) for more information. */ RLMPropertyTypeLinkingObjects = 14, }; /** An error domain identifying Realm-specific errors. */ extern NSString * const RLMErrorDomain; /** An error domain identifying non-specific system errors. */ extern NSString * const RLMUnknownSystemErrorDomain; /** `RLMError` is an enumeration representing all recoverable errors. It is associated with the Realm error domain specified in `RLMErrorDomain`. */ typedef RLM_ERROR_ENUM(NSInteger, RLMError, RLMErrorDomain) { /** Denotes a general error that occurred when trying to open a Realm. */ RLMErrorFail = 1, /** Denotes a file I/O error that occurred when trying to open a Realm. */ RLMErrorFileAccess = 2, /** Denotes a file permission error that ocurred when trying to open a Realm. This error can occur if the user does not have permission to open or create the specified file in the specified access mode when opening a Realm. */ RLMErrorFilePermissionDenied = 3, /** Denotes an error where a file was to be written to disk, but another file with the same name already exists. */ RLMErrorFileExists = 4, /** Denotes an error that occurs if a file could not be found. This error may occur if a Realm file could not be found on disk when trying to open a Realm as read-only, or if the directory part of the specified path was not found when trying to write a copy. */ RLMErrorFileNotFound = 5, /** Denotes an error that occurs if a file format upgrade is required to open the file, but upgrades were explicitly disabled. */ RLMErrorFileFormatUpgradeRequired = 6, /** Denotes an error that occurs if the database file is currently open in another process which cannot share with the current process due to an architecture mismatch. This error may occur if trying to share a Realm file between an i386 (32-bit) iOS Simulator and the Realm Browser application. In this case, please use the 64-bit version of the iOS Simulator. */ RLMErrorIncompatibleLockFile = 8, /** Denotes an error that occurs when there is insufficient available address space. */ RLMErrorAddressSpaceExhausted = 9, /** Denotes an error that occurs if there is a schema version mismatch, so that a migration is required. */ RLMErrorSchemaMismatch = 10, }; #pragma mark - Constants #pragma mark - Notification Constants /** A notification indicating that changes were made to a Realm. */ typedef NSString * RLMNotification RLM_EXTENSIBLE_STRING_ENUM; /** This notification is posted by a Realm when the data in that Realm has changed. More specifically, this notification is posted after a Realm has been refreshed to reflect a write transaction. This can happen when an autorefresh occurs, when `-[RLMRealm refresh]` is called, after an implicit refresh from `-[RLMRealm beginWriteTransaction]`, or after a local write transaction is completed. */ extern RLMNotification const RLMRealmRefreshRequiredNotification RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(RLMRealmRefreshRequiredNotification, RefreshRequired); /** This notification is posted by a Realm when a write transaction has been committed to a Realm on a different thread for the same file. It is not posted if `-[RLMRealm autorefresh]` is enabled, or if the Realm is refreshed before the notification has a chance to run. Realms with autorefresh disabled should normally install a handler for this notification which calls `-[RLMRealm refresh]` after doing some work. Refreshing the Realm is optional, but not refreshing the Realm may lead to large Realm files. This is because Realm must keep an extra copy of the data for the stale Realm. */ extern RLMNotification const RLMRealmDidChangeNotification RLM_EXTENSIBLE_STRING_ENUM_CASE_SWIFT_NAME(RLMRealmDidChangeNotification, DidChange); #pragma mark - Other Constants /** The schema version used for uninitialized Realms */ extern const uint64_t RLMNotVersioned; /** The corresponding value is the name of an exception thrown by Realm. */ extern NSString * const RLMExceptionName; /** The corresponding value is a Realm file version. */ extern NSString * const RLMRealmVersionKey; /** The corresponding key is the version of the underlying database engine. */ extern NSString * const RLMRealmCoreVersionKey; /** The corresponding key is the Realm invalidated property name. */ extern NSString * const RLMInvalidatedKey; NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMMigration.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @class RLMSchema; @class RLMArray; @class RLMObject; /** A block type which provides both the old and new versions of an object in the Realm. Object properties can only be accessed using keyed subscripting. @see `-[RLMMigration enumerateObjects:block:]` @param oldObject The object from the original Realm (read-only). @param newObject The object from the migrated Realm (read-write). */ typedef void (^RLMObjectMigrationBlock)(RLMObject * __nullable oldObject, RLMObject * __nullable newObject); /** `RLMMigration` instances encapsulate information intended to facilitate a schema migration. A `RLMMigration` instance is passed into a user-defined `RLMMigrationBlock` block when updating the version of a Realm. This instance provides access to the old and new database schemas, the objects in the Realm, and provides functionality for modifying the Realm during the migration. */ @interface RLMMigration : NSObject #pragma mark - Properties /** Returns the old `RLMSchema`. This is the schema which describes the Realm before the migration is applied. */ @property (nonatomic, readonly) RLMSchema *oldSchema; /** Returns the new `RLMSchema`. This is the schema which describes the Realm after the migration is applied. */ @property (nonatomic, readonly) RLMSchema *newSchema; #pragma mark - Altering Objects during a Migration /** Enumerates all the objects of a given type in the Realm, providing both the old and new versions of each object. Within the block, object properties can only be accessed using keyed subscripting. @param className The name of the `RLMObject` class to enumerate. @warning All objects returned are of a type specific to the current migration and should not be cast to `className`. Instead, treat them as `RLMObject`s and use keyed subscripting to access properties. */ - (void)enumerateObjects:(NSString *)className block:(__attribute__((noescape)) RLMObjectMigrationBlock)block; /** Creates and returns an `RLMObject` instance of type `className` in the Realm being migrated. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an `NSArray` as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param className The name of the `RLMObject` class to create. @param value The value used to populate the object. */ - (RLMObject *)createObject:(NSString *)className withValue:(id)value; /** Deletes an object from a Realm during a migration. It is permitted to call this method from within the block passed to `-[enumerateObjects:block:]`. @param object Object to be deleted from the Realm being migrated. */ - (void)deleteObject:(RLMObject *)object; /** Deletes the data for the class with the given name. All objects of the given class will be deleted. If the `RLMObject` subclass no longer exists in your program, any remaining metadata for the class will be removed from the Realm file. @param name The name of the `RLMObject` class to delete. @return A Boolean value indicating whether there was any data to delete. */ - (BOOL)deleteDataForClassName:(NSString *)name; /** Renames a property of the given class from `oldName` to `newName`. @param className The name of the class whose property should be renamed. This class must be present in both the old and new Realm schemas. @param oldName The old name for the property to be renamed. There must not be a property with this name in the class as defined by the new Realm schema. @param newName The new name for the property to be renamed. There must not be a property with this name in the class as defined by the old Realm schema. */ - (void)renamePropertyForClass:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMObject.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import NS_ASSUME_NONNULL_BEGIN @class RLMNotificationToken; @class RLMObjectSchema; @class RLMPropertyChange; @class RLMPropertyDescriptor; @class RLMRealm; @class RLMResults; /** `RLMObject` is a base class for model objects representing data stored in Realms. Define your model classes by subclassing `RLMObject` and adding properties to be managed. Then instantiate and use your custom subclasses instead of using the `RLMObject` class directly. // Dog.h @interface Dog : RLMObject @property NSString *name; @property BOOL adopted; @end // Dog.m @implementation Dog @end //none needed ### Supported property types - `NSString` - `NSInteger`, `int`, `long`, `float`, and `double` - `BOOL` or `bool` - `NSDate` - `NSData` - `NSNumber`, where `X` is one of `RLMInt`, `RLMFloat`, `RLMDouble` or `RLMBool`, for optional number properties - `RLMObject` subclasses, to model many-to-one relationships. - `RLMArray`, where `X` is an `RLMObject` subclass, to model many-to-many relationships. ### Querying You can initiate queries directly via the class methods: `allObjects`, `objectsWhere:`, and `objectsWithPredicate:`. These methods allow you to easily query a custom subclass for instances of that class in the default Realm. To search in a Realm other than the default Realm, use the `allObjectsInRealm:`, `objectsInRealm:where:`, and `objectsInRealm:withPredicate:` class methods. @see `RLMRealm` ### Relationships See our [Cocoa guide](https://realm.io/docs/objc/latest#relationships) for more details. ### Key-Value Observing All `RLMObject` properties (including properties you create in subclasses) are [Key-Value Observing compliant](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html), except for `realm` and `objectSchema`. Keep the following tips in mind when observing Realm objects: 1. Unlike `NSMutableArray` properties, `RLMArray` properties do not require using the proxy object returned from `-mutableArrayValueForKey:`, or defining KVC mutation methods on the containing class. You can simply call methods on the `RLMArray` directly; any changes will be automatically observed by the containing object. 2. Unmanaged `RLMObject` instances cannot be added to a Realm while they have any observed properties. 3. Modifying managed `RLMObject`s within `-observeValueForKeyPath:ofObject:change:context:` is not recommended. Properties may change even when the Realm is not in a write transaction (for example, when `-[RLMRealm refresh]` is called after changes are made on a different thread), and notifications sent prior to the change being applied (when `NSKeyValueObservingOptionPrior` is used) may be sent at times when you *cannot* begin a write transaction. */ @interface RLMObject : RLMObjectBase #pragma mark - Creating & Initializing Objects /** Creates an unmanaged instance of a Realm object. Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. @see `[RLMRealm addObject:]` */ - (instancetype)init NS_DESIGNATED_INITIALIZER; /** Creates an unmanaged instance of a Realm object. Pass in an `NSArray` or `NSDictionary` instance to set the values of the object's properties. Call `addObject:` on an `RLMRealm` instance to add an unmanaged object into that Realm. @see `[RLMRealm addObject:]` */ - (instancetype)initWithValue:(id)value NS_DESIGNATED_INITIALIZER; /** Returns the class name for a Realm object subclass. @warning Do not override. Realm relies on this method returning the exact class name. @return The class name for the model class. */ + (NSString *)className; /** Creates an instance of a Realm object with a given value, and adds it to the default Realm. If nested objects are included in the argument, `createInDefaultRealmWithValue:` will be recursively called on them. The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param value The value used to populate the object. @see `defaultPropertyValues` */ + (instancetype)createInDefaultRealmWithValue:(id)value; /** Creates an instance of a Realm object with a given value, and adds it to the specified Realm. If nested objects are included in the argument, `createInRealm:withValue:` will be recursively called on them. The `value` argument can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param realm The Realm which should manage the newly-created object. @param value The value used to populate the object. @see `defaultPropertyValues` */ + (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value; /** Creates or updates a Realm object within the default Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the default Realm, its values are updated and the object is returned. Otherwise, this method creates and populates a new instance of the object in the default Realm. If nested objects are included in the argument, `createOrUpdateInDefaultRealmWithValue:` will be recursively called on them if they have primary keys, `createInDefaultRealmWithValue:` if they do not. If the argument is a Realm object already managed by the default Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value; /** Creates or updates an Realm object within a specified Realm. This method may only be called on Realm object types with a primary key defined. If there is already an object with the same primary key value in the given Realm, its values are updated and the object is returned. Otherwise this method creates and populates a new instance of this object in the given Realm. If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be recursively called on them if they have primary keys, `createInRealm:withValue:` if they do not. If the argument is a Realm object already managed by the given Realm, the argument's type is the same as the receiver, and the objects have identical values for their managed properties, this method does nothing. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @param realm The Realm which should own the object. @param value The value used to populate the object. @see `defaultPropertyValues`, `primaryKey` */ + (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value; #pragma mark - Properties /** The Realm which manages the object, or `nil` if the object is unmanaged. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /** The object schema which lists the managed properties for the object. */ @property (nonatomic, readonly) RLMObjectSchema *objectSchema; /** Indicates if the object can no longer be accessed because it is now invalid. An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if `invalidate` is called on that Realm. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Customizing your Objects /** Returns an array of property names for properties which should be indexed. Only string, integer, boolean, and `NSDate` properties are supported. @return An array of property names. */ + (NSArray *)indexedProperties; /** Override this method to specify the default values to be used for each property. @return A dictionary mapping property names to their default values. */ + (nullable NSDictionary *)defaultPropertyValues; /** Override this method to specify the name of a property to be used as the primary key. Only properties of types `RLMPropertyTypeString` and `RLMPropertyTypeInt` can be designated as the primary key. Primary key properties enforce uniqueness for each value whenever the property is set, which incurs minor overhead. Indexes are created automatically for primary key properties. @return The name of the property designated as the primary key. */ + (nullable NSString *)primaryKey; /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. @return An array of property names to ignore. */ + (nullable NSArray *)ignoredProperties; /** Override this method to specify the names of properties that are non-optional (i.e. cannot be assigned a `nil` value). By default, all properties of a type whose values can be set to `nil` are considered optional properties. To require that an object in a Realm always store a non-`nil` value for a property, add the name of the property to the array returned from this method. Properties of `RLMObject` type cannot be non-optional. Array and `NSNumber` properties can be non-optional, but there is no reason to do so: arrays do not support storing nil, and if you want a non-optional number you should instead use the primitive type. @return An array of property names that are required. */ + (NSArray *)requiredProperties; /** Override this method to provide information related to properties containing linking objects. Each property of type `RLMLinkingObjects` must have a key in the dictionary returned by this method consisting of the property name. The corresponding value must be an instance of `RLMPropertyDescriptor` that describes the class and property that the property is linked to. return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Owner.class propertyName:@"dogs"] }; @return A dictionary mapping property names to `RLMPropertyDescriptor` instances. */ + (NSDictionary *)linkingObjectsProperties; #pragma mark - Getting & Querying Objects from the Default Realm /** Returns all objects of this object type from the default Realm. @return An `RLMResults` containing all objects of this type in the default Realm. */ + (RLMResults *)allObjects; /** Returns all objects of this object type matching the given predicate from the default Realm. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. */ + (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: + (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects of this object type matching the given predicate from the default Realm. @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing all objects of this type in the default Realm that match the given predicate. */ + (RLMResults *)objectsWithPredicate:(nullable NSPredicate *)predicate; /** Retrieves the single instance of this object type with the given primary key from the default Realm. Returns the object from the default Realm which has the given primary key, or `nil` if the object does not exist. This is slightly faster than the otherwise equivalent `[[SubclassName objectsWhere:@"primaryKeyPropertyName = %@", key] firstObject]`. This method requires that `primaryKey` be overridden on the receiving subclass. @return An object of this object type, or `nil` if an object with the given primary key does not exist. @see `-primaryKey` */ + (nullable instancetype)objectForPrimaryKey:(nullable id)primaryKey; #pragma mark - Querying Specific Realms /** Returns all objects of this object type from the specified Realm. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm. */ + (RLMResults *)allObjectsInRealm:(RLMRealm *)realm; /** Returns all objects of this object type matching the given predicate from the specified Realm. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. */ + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ...; /// :nodoc: + (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args; /** Returns all objects of this object type matching the given predicate from the specified Realm. @param predicate A predicate to use to filter the elements. @param realm The Realm to query. @return An `RLMResults` containing all objects of this type in the specified Realm that match the given predicate. */ + (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(nullable NSPredicate *)predicate; /** Retrieves the single instance of this object type with the given primary key from the specified Realm. Returns the object from the specified Realm which has the given primary key, or `nil` if the object does not exist. This is slightly faster than the otherwise equivalent `[[SubclassName objectsInRealm:realm where:@"primaryKeyPropertyName = %@", key] firstObject]`. This method requires that `primaryKey` be overridden on the receiving subclass. @return An object of this object type, or `nil` if an object with the given primary key does not exist. @see `-primaryKey` */ + (nullable instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(nullable id)primaryKey; #pragma mark - Notifications /** A callback block for `RLMObject` notifications. If the object is deleted from the managing Realm, the block is called with `deleted` set to `YES` and the other two arguments are `nil`. The block will never be called again after this. If the object is modified, the block will be called with `deleted` set to `NO`, a `nil` error, and an array of `RLMPropertyChange` objects which indicate which properties of the objects were modified. If an error occurs, `deleted` will be `NO`, `changes` will be `nil`, and `error` will include information about the error. The block will never be called again after an error occurs. */ typedef void (^RLMObjectChangeBlock)(BOOL deleted, NSArray *_Nullable changes, NSError *_Nullable error); /** Registers a block to be called each time the object changes. The block will be asynchronously called after each write transaction which deletes the object or modifies any of the managed properties of the object, including self-assignments that set a property to its existing value. For write transactions performed on different threads or in differen processes, the block will be called when the managing Realm is (auto)refreshed to a version including the changes, while for local write transactions it will be called at some point in the future after the write transaction is committed. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. Unlike with `RLMArray` and `RLMResults`, there is no "initial" callback made after you add a new notification block. Only objects which are managed by a Realm can be observed in this way. You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving updates, call `stop` on the token. It is safe to capture a strong reference to the observed object within the callback block. There is no retain cycle due to that the callback is retained by the returned token and not by the object itself. @warning This method cannot be called during a write transaction, when the containing Realm is read-only, or on an unmanaged object. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(RLMObjectChangeBlock)block; #pragma mark - Other Instance Methods /** Returns YES if another Realm object instance points to the same object as the receiver in the Realm managing the receiver. For object types with a primary, key, `isEqual:` is overridden to use this method (along with a corresponding implementation for `hash`). @param object The object to compare the receiver to. @return Whether the object represents the same object as the receiver. */ - (BOOL)isEqualToObject:(RLMObject *)object; #pragma mark - Dynamic Accessors /// :nodoc: - (nullable id)objectForKeyedSubscript:(NSString *)key; /// :nodoc: - (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key; @end /** Information about a specific property which changed in an `RLMObject` change notification. */ @interface RLMPropertyChange : NSObject /** The name of the property which changed. */ @property (nonatomic, readonly, strong) NSString *name; /** The value of the property before the change occurred. This will always be `nil` if the change happened on the same thread as the notification and for `RLMArray` properties. For object properties this will give the object which was previously linked to, but that object will have its new values and not the values it had before the changes. This means that `previousValue` may be a deleted object, and you will need to check `invalidated` before accessing any of its properties. */ @property (nonatomic, readonly, strong, nullable) id previousValue; /** The value of the property after the change occurred. This will always be `nil` for `RLMArray` properties. */ @property (nonatomic, readonly, strong, nullable) id value; @end #pragma mark - RLMArray Property Declaration /** Properties on `RLMObject`s of type `RLMArray` must have an associated type. A type is associated with an `RLMArray` property by defining a protocol for the object type that the array should contain. To define the protocol for an object, you can use the macro RLM_ARRAY_TYPE: RLM_ARRAY_TYPE(ObjectType) ... @property RLMArray *arrayOfObjectTypes; */ #define RLM_ARRAY_TYPE(RLM_OBJECT_SUBCLASS)\ @protocol RLM_OBJECT_SUBCLASS \ @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMObjectBase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @class RLMRealm; @class RLMSchema; @class RLMObjectSchema; /// :nodoc: @interface RLMObjectBase : NSObject @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; - (instancetype)init NS_DESIGNATED_INITIALIZER; + (NSString *)className; // Returns whether the class is included in the default set of classes managed by a Realm. + (BOOL)shouldIncludeInDefaultSchema; + (nullable NSString *)_realmObjectName; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMObjectBase_Dynamic.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMObjectSchema, RLMRealm; NS_ASSUME_NONNULL_BEGIN /** Returns the Realm that manages the object, if one exists. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve the Realm that manages the object via `RLMObject`. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @return The Realm which manages this object. Returns `nil `for unmanaged objects. */ FOUNDATION_EXTERN RLMRealm * _Nullable RLMObjectBaseRealm(RLMObjectBase * _Nullable object); /** Returns an `RLMObjectSchema` which describes the managed properties of the object. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve `objectSchema` via `RLMObject`. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @return The object schema which lists the managed properties for the object. */ FOUNDATION_EXTERN RLMObjectSchema * _Nullable RLMObjectBaseObjectSchema(RLMObjectBase * _Nullable object); /** Returns the object corresponding to a key value. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to retrieve key values via `RLMObject`. @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @param key The name of the property. @return The object for the property requested. */ FOUNDATION_EXTERN id _Nullable RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key); /** Sets a value for a key on the object. @warning This function is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to set key values via `RLMObject`. @warning Will throw an `NSUndefinedKeyException` if `key` is not present on the object. @param object An `RLMObjectBase` obtained via a Swift `Object` or `RLMObject`. @param key The name of the property. @param obj The object to set as the value of the key. */ FOUNDATION_EXTERN void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase * _Nullable object, NSString *key, id _Nullable obj); NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMObjectSchema.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @class RLMProperty; /** This class represents Realm model object schemas. When using Realm, `RLMObjectSchema` instances allow performing migrations and introspecting the database's schema. Object schemas map to tables in the core database. */ @interface RLMObjectSchema : NSObject #pragma mark - Properties /** An array of `RLMProperty` instances representing the managed properties of a class described by the schema. @see `RLMProperty` */ @property (nonatomic, readonly, copy) NSArray *properties; /** The name of the class the schema describes. */ @property (nonatomic, readonly) NSString *className; /** The property which serves as the primary key for the class the schema describes, if any. */ @property (nonatomic, readonly, nullable) RLMProperty *primaryKeyProperty; #pragma mark - Methods /** Retrieves an `RLMProperty` object by the property name. @param propertyName The property's name. @return An `RLMProperty` object, or `nil` if there is no property with the given name. */ - (nullable RLMProperty *)objectForKeyedSubscript:(NSString *)propertyName; /** Returns whether two `RLMObjectSchema` instances are equal. */ - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMPlatform.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #if TARGET_OS_IPHONE #error Attempting to use Realm's OSX framework in an iOS project. #endif ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMProperty.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN /// :nodoc: @protocol RLMInt @end /// :nodoc: @protocol RLMBool @end /// :nodoc: @protocol RLMDouble @end /// :nodoc: @protocol RLMFloat @end /// :nodoc: @interface NSNumber () @end /** `RLMProperty` instances represent properties managed by a Realm in the context of an object schema. Such properties may be persisted to a Realm file or computed from other data from the Realm. When using Realm, `RLMProperty` instances allow performing migrations and introspecting the database's schema. These property instances map to columns in the core database. */ @interface RLMProperty : NSObject #pragma mark - Properties /** The name of the property. */ @property (nonatomic, readonly) NSString *name; /** The type of the property. @see `RLMPropertyType` */ @property (nonatomic, readonly) RLMPropertyType type; /** Indicates whether this property is indexed. @see `RLMObject` */ @property (nonatomic, readonly) BOOL indexed; /** For `RLMObject` and `RLMArray` properties, the name of the class of object stored in the property. */ @property (nonatomic, readonly, copy, nullable) NSString *objectClassName; /** For linking objects properties, the property name of the property the linking objects property is linked to. */ @property (nonatomic, readonly, copy, nullable) NSString *linkOriginPropertyName; /** Indicates whether this property is optional. */ @property (nonatomic, readonly) BOOL optional; #pragma mark - Methods /** Returns whether a given property object is equal to the receiver. */ - (BOOL)isEqualToProperty:(RLMProperty *)property; @end /** An `RLMPropertyDescriptor` instance represents a specific property on a given class. */ @interface RLMPropertyDescriptor : NSObject /** Creates and returns a property descriptor. @param objectClass The class of this property descriptor. @param propertyName The name of this property descriptor. */ + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName; /// The class of the property. @property (nonatomic, readonly) Class objectClass; /// The name of the property. @property (nonatomic, readonly) NSString *propertyName; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMRealm.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMConstants.h" @class RLMRealmConfiguration, RLMObject, RLMSchema, RLMMigration, RLMNotificationToken, RLMThreadSafeReference; NS_ASSUME_NONNULL_BEGIN /** An `RLMRealm` instance (also referred to as "a Realm") represents a Realm database. Realms can either be stored on disk (see `+[RLMRealm realmWithURL:]`) or in memory (see `RLMRealmConfiguration`). `RLMRealm` instances are cached internally, and constructing equivalent `RLMRealm` objects (for example, by using the same path or identifier) multiple times on a single thread within a single iteration of the run loop will normally return the same `RLMRealm` object. If you specifically want to ensure an `RLMRealm` instance is destroyed (for example, if you wish to open a Realm, check some property, and then possibly delete the Realm file and re-open it), place the code which uses the Realm within an `@autoreleasepool {}` and ensure you have no other strong references to it. @warning `RLMRealm` instances are not thread safe and cannot be shared across threads or dispatch queues. Trying to do so will cause an exception to be thrown. You must call this method on each thread you want to interact with the Realm on. For dispatch queues, this means that you must call it in each block which is dispatched, as a queue is not guaranteed to run all of its blocks on the same thread. */ @interface RLMRealm : NSObject #pragma mark - Creating & Initializing a Realm /** Obtains an instance of the default Realm. The default Realm is used by the `RLMObject` class methods which do not take an `RLMRealm` parameter, but is otherwise not special. The default Realm is persisted as *default.realm* under the *Documents* directory of your Application on iOS, and in your application's *Application Support* directory on OS X. The default Realm is created using the default `RLMRealmConfiguration`, which can be changed via `+[RLMRealmConfiguration setDefaultConfiguration:]`. @return The default `RLMRealm` instance for the current thread. */ + (instancetype)defaultRealm; /** Obtains an `RLMRealm` instance with the given configuration. @param configuration A configuration object to use when creating the Realm. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return An `RLMRealm` instance. */ + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; /** Obtains an `RLMRealm` instance persisted at a specified file URL. @param fileURL The local URL of the file the Realm should be saved at. @return An `RLMRealm` instance. */ + (instancetype)realmWithURL:(NSURL *)fileURL; /** The `RLMSchema` used by the Realm. */ @property (nonatomic, readonly) RLMSchema *schema; /** Indicates if the Realm is currently engaged in a write transaction. @warning Do not simply check this property and then start a write transaction whenever an object needs to be created, updated, or removed. Doing so might cause a large number of write transactions to be created, degrading performance. Instead, always prefer performing multiple updates during a single transaction. */ @property (nonatomic, readonly) BOOL inWriteTransaction; /** The `RLMRealmConfiguration` object that was used to create this `RLMRealm` instance. */ @property (nonatomic, readonly) RLMRealmConfiguration *configuration; /** Indicates if this Realm contains any objects. */ @property (nonatomic, readonly) BOOL isEmpty; #pragma mark - Notifications /** The type of a block to run whenever the data within the Realm is modified. @see `-[RLMRealm addNotificationBlock:]` */ typedef void (^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); #pragma mark - Receiving Notification when a Realm Changes /** Adds a notification handler for changes in this Realm, and returns a notification token. Notification handlers are called after each write transaction is committed, either on the current thread or other threads. Handler blocks are called on the same thread that they were added on, and may only be added on threads which are currently within a run loop. Unless you are specifically creating and running a run loop on a background thread, this will normally only be the main thread. The block has the following definition: typedef void(^RLMNotificationBlock)(RLMNotification notification, RLMRealm *realm); It receives the following parameters: - `NSString` \***notification**: The name of the incoming notification. See `RLMRealmNotification` for information on what notifications are sent. - `RLMRealm` \***realm**: The Realm for which this notification occurred. @param block A block which is called to process Realm notifications. @return A token object which must be retained as long as you wish to continue receiving change notifications. */ - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block __attribute__((warn_unused_result)); #pragma mark - Transactions #pragma mark - Writing to a Realm /** Begins a write transaction on the Realm. Only one write transaction can be open at a time for each Realm file. Write transactions cannot be nested, and trying to begin a write transaction on a Realm which is already in a write transaction will throw an exception. Calls to `beginWriteTransaction` from `RLMRealm` instances for the same Realm file in other threads or other processes will block until the current write transaction completes or is cancelled. Before beginning the write transaction, `beginWriteTransaction` updates the `RLMRealm` instance to the latest Realm version, as if `refresh` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. It is rarely a good idea to have write transactions span multiple cycles of the run loop, but if you do wish to do so you will need to ensure that the Realm participating in the write transaction is kept alive until the write transaction is committed. */ - (void)beginWriteTransaction; /** Commits all write operations in the current write transaction, and ends the transaction. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are invoked asynchronously. If you do not want to receive a specific notification for this write tranaction, see `commitWriteTransactionWithoutNotifying:error:`. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. This version of the method throws an exception when errors occur. Use the version with a `NSError` out parameter instead if you wish to handle errors. @warning This method may only be called during a write transaction. */ - (void)commitWriteTransaction NS_SWIFT_UNAVAILABLE(""); /** Commits all write operations in the current write transaction, and ends the transaction. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are invoked asynchronously. If you do not want to receive a specific notification for this write tranaction, see `commitWriteTransactionWithoutNotifying:error:`. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. @warning This method may only be called during a write transaction. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)commitWriteTransaction:(NSError **)error; /** Commits all write operations in the current write transaction, without notifying specific notification blocks of the changes. After saving the changes, all notification blocks registered on this specific `RLMRealm` instance are invoked synchronously. Notification blocks registered on other threads or on collections are scheduled to be invoked asynchronously. You can skip notifiying specific notification blocks about the changes made in this write transaction by passing in their associated notification tokens. This is primarily useful when the write transaction is saving changes already made in the UI and you do not want to have the notification block attempt to re-apply the same changes. The tokens passed to this method must be for notifications for this specific `RLMRealm` instance. Notifications for different threads cannot be skipped using this method. This method can fail if there is insufficient disk space available to save the writes made, or due to unexpected i/o errors. @warning This method may only be called during a write transaction. @param tokens An array of notification tokens which were returned from adding callbacks which you do not want to be notified for the changes made in this write transaction. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray *)tokens error:(NSError **)error; /** Reverts all writes made during the current write transaction and ends the transaction. This rolls back all objects in the Realm to the state they were in at the beginning of the write transaction, and then ends the transaction. This restores the data for deleted objects, but does not revive invalidated object instances. Any `RLMObject`s which were added to the Realm will be invalidated rather than becoming unmanaged. Given the following code: ObjectType *oldObject = [[ObjectType objectsWhere:@"..."] firstObject]; ObjectType *newObject = [[ObjectType alloc] init]; [realm beginWriteTransaction]; [realm addObject:newObject]; [realm deleteObject:oldObject]; [realm cancelWriteTransaction]; Both `oldObject` and `newObject` will return `YES` for `isInvalidated`, but re-running the query which provided `oldObject` will once again return the valid object. KVO observers on any objects which were modified during the transaction will be notified about the change back to their initial values, but no other notifcations are produced by a cancelled write transaction. @warning This method may only be called during a write transaction. */ - (void)cancelWriteTransaction; /** Performs actions contained within the given block inside a write transaction. @see `[RLMRealm transactionWithBlock:error:]` */ - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block NS_SWIFT_UNAVAILABLE(""); /** Performs actions contained within the given block inside a write transaction. Write transactions cannot be nested, and trying to execute a write transaction on a Realm which is already participating in a write transaction will throw an exception. Calls to `transactionWithBlock:` from `RLMRealm` instances in other threads will block until the current write transaction completes. Before beginning the write transaction, `transactionWithBlock:` updates the `RLMRealm` instance to the latest Realm version, as if `refresh` had been called, and generates notifications if applicable. This has no effect if the Realm was already up to date. @param block The block containing actions to perform. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return Whether the transaction succeeded. */ - (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error; /** Updates the Realm and outstanding objects managed by the Realm to point to the most recent data. If the version of the Realm is actually changed, Realm and collection notifications will be sent to reflect the changes. This may take some time, as collection notifications are prepared on a background thread. As a result, calling this method on the main thread is not advisable. @return Whether there were any updates for the Realm. Note that `YES` may be returned even if no data actually changed. */ - (BOOL)refresh; /** Set this property to `YES` to automatically update this Realm when changes happen in other threads. If set to `YES` (the default), changes made on other threads will be reflected in this Realm on the next cycle of the run loop after the changes are committed. If set to `NO`, you must manually call `-refresh` on the Realm to update it to get the latest data. Note that by default, background threads do not have an active run loop and you will need to manually call `-refresh` in order to update to the latest version, even if `autorefresh` is set to `YES`. Even with this property enabled, you can still call `-refresh` at any time to update the Realm before the automatic refresh would occur. Write transactions will still always advance a Realm to the latest version and produce local notifications on commit even if autorefresh is disabled. Disabling `autorefresh` on a Realm without any strong references to it will not have any effect, and `autorefresh` will revert back to `YES` the next time the Realm is created. This is normally irrelevant as it means that there is nothing to refresh (as managed `RLMObject`s, `RLMArray`s, and `RLMResults` have strong references to the Realm that manages them), but it means that setting `RLMRealm.defaultRealm.autorefresh = NO` in `application:didFinishLaunchingWithOptions:` and only later storing Realm objects will not work. Defaults to `YES`. */ @property (nonatomic) BOOL autorefresh; /** Writes a compacted and optionally encrypted copy of the Realm to the given local URL. The destination file cannot already exist. Note that if this method is called from within a write transaction, the *current* data is written, not the data from the point when the previous write transaction was committed. @param fileURL Local URL to save the Realm to. @param key Optional 64-byte encryption key to encrypt the new file with. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return `YES` if the Realm was successfully written to disk, `NO` if an error occurred. */ - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error; /** Invalidates all `RLMObject`s, `RLMResults`, `RLMLinkingObjects`, and `RLMArray`s managed by the Realm. A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need. All `RLMObject`, `RLMResults` and `RLMArray` instances obtained from this `RLMRealm` instance on the current thread are invalidated. `RLMObject`s and `RLMArray`s cannot be used. `RLMResults` will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm. Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. This method may not be called on a read-only Realm. */ - (void)invalidate; #pragma mark - Accessing Objects /** Returns the same object as the one referenced when the `RLMThreadSafeReference` was first created, but resolved for the current Realm for this thread. Returns `nil` if this object was deleted after the reference was created. @param reference The thread-safe reference to the thread-confined object to resolve in this Realm. @warning A `RLMThreadSafeReference` object must be resolved at most once. Failing to resolve a `RLMThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. An exception will be thrown if a reference is resolved more than once. @warning Cannot call within a write transaction. @note Will refresh this Realm if the source Realm was at a later version than this one. @see `+[RLMThreadSafeReference referenceWithThreadConfined:]` */ - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference NS_REFINED_FOR_SWIFT; #pragma mark - Adding and Removing Objects from a Realm /** Adds an object to the Realm. Once added, this object is considered to be managed by the Realm. It can be retrieved using the `objectsWhere:` selectors on `RLMRealm` and on subclasses of `RLMObject`. When added, all child relationships referenced by this object will also be added to the Realm if they are not already in it. If the object or any related objects are already being managed by a different Realm an exception will be thrown. Use `-[RLMObject createInRealm:withObject:]` to insert a copy of a managed object into a different Realm. The object to be added must be valid and cannot have been previously deleted from a Realm (i.e. `isInvalidated` must be `NO`). @warning This method may only be called during a write transaction. @param object The object to be added to this Realm. */ - (void)addObject:(RLMObject *)object; /** Adds all the objects in a collection to the Realm. This is the equivalent of calling `addObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param array An enumerable object such as `NSArray` or `RLMResults` which contains objects to be added to the Realm. @see `addObject:` */ - (void)addObjects:(id)array; /** Adds or updates an existing object into the Realm. The object provided must have a designated primary key. If no objects exist in the Realm with the same primary key value, the object is inserted. Otherwise, the existing object is updated with any changed values. As with `addObject:`, the object cannot already be managed by a different Realm. Use `-[RLMObject createOrUpdateInRealm:withValue:]` to copy values to a different Realm. @warning This method may only be called during a write transaction. @param object The object to be added or updated. */ - (void)addOrUpdateObject:(RLMObject *)object; /** Adds or updates all the objects in a collection into the Realm. This is the equivalent of calling `addOrUpdateObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param array An `NSArray`, `RLMArray`, or `RLMResults` of `RLMObject`s (or subclasses) to be added to the Realm. @see `addOrUpdateObject:` */ - (void)addOrUpdateObjectsFromArray:(id)array; /** Deletes an object from the Realm. Once the object is deleted it is considered invalidated. @warning This method may only be called during a write transaction. @param object The object to be deleted. */ - (void)deleteObject:(RLMObject *)object; /** Deletes one or more objects from the Realm. This is the equivalent of calling `deleteObject:` for every object in a collection. @warning This method may only be called during a write transaction. @param array An `RLMArray`, `NSArray`, or `RLMResults` of `RLMObject`s (or subclasses) to be deleted. @see `deleteObject:` */ - (void)deleteObjects:(id)array; /** Deletes all objects from the Realm. @warning This method may only be called during a write transaction. @see `deleteObject:` */ - (void)deleteAllObjects; #pragma mark - Migrations /** The type of a migration block used to migrate a Realm. @param migration A `RLMMigration` object used to perform the migration. The migration object allows you to enumerate and alter any existing objects which require migration. @param oldSchemaVersion The schema version of the Realm being migrated. */ typedef void (^RLMMigrationBlock)(RLMMigration *migration, uint64_t oldSchemaVersion); /** Returns the schema version for a Realm at a given local URL. @param fileURL Local URL to a Realm file. @param key 64-byte key used to encrypt the file, or `nil` if it is unencrypted. @param error If an error occurs, upon return contains an `NSError` object that describes the problem. If you are not interested in possible errors, pass in `NULL`. @return The version of the Realm at `fileURL`, or `RLMNotVersioned` if the version cannot be read. */ + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(nullable NSData *)key error:(NSError **)error NS_REFINED_FOR_SWIFT; /** Performs the given Realm configuration's migration block on a Realm at the given path. This method is called automatically when opening a Realm for the first time and does not need to be called explicitly. You can choose to call this method to control exactly when and how migrations are performed. @param configuration The Realm configuration used to open and migrate the Realm. @return The error that occurred while applying the migration, if any. @see RLMMigration */ + (nullable NSError *)migrateRealm:(RLMRealmConfiguration *)configuration __deprecated_msg("Use `performMigrationForConfiguration:error:`") NS_REFINED_FOR_SWIFT; /** Performs the given Realm configuration's migration block on a Realm at the given path. This method is called automatically when opening a Realm for the first time and does not need to be called explicitly. You can choose to call this method to control exactly when and how migrations are performed. @param configuration The Realm configuration used to open and migrate the Realm. @return The error that occurred while applying the migration, if any. @see RLMMigration */ + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; @end /** A token which is returned from methods which subscribe to changes to a Realm. Change subscriptions in Realm return an `RLMNotificationToken` instance, which can be used to unsubscribe from the changes. You must store a strong reference to the token for as long as you want to continue to receive notifications. When you wish to stop, call the `-stop` method. Notifications are also stopped if the token is deallocated. */ @interface RLMNotificationToken : NSObject /// Stops notifications for the change subscription that returned this token. - (void)stop; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMRealmConfiguration+Sync.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMSyncUtil.h" @class RLMSyncConfiguration; /// :nodoc: @interface RLMRealmConfiguration (Sync) NS_ASSUME_NONNULL_BEGIN /** A configuration object representing configuration state for Realms intended to sync with a Realm Object Server. This property is mutually exclusive with both `inMemoryIdentifier` and `fileURL`; setting one will nil out the other two. @see `RLMSyncConfiguration` */ @property (nullable, nonatomic) RLMSyncConfiguration *syncConfiguration; NS_ASSUME_NONNULL_END @end ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMRealmConfiguration.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN /** An `RLMRealmConfiguration` instance describes the different options used to create an instance of a Realm. `RLMRealmConfiguration` instances are just plain `NSObject`s. Unlike `RLMRealm`s and `RLMObject`s, they can be freely shared between threads as long as you do not mutate them. Creating configuration objects for class subsets (by setting the `objectClasses` property) can be expensive. Because of this, you will normally want to cache and reuse a single configuration object for each distinct configuration rather than creating a new object each time you open a Realm. */ @interface RLMRealmConfiguration : NSObject #pragma mark - Default Configuration /** Returns the default configuration used to create Realms when no other configuration is explicitly specified (i.e. `+[RLMRealm defaultRealm]`). @return The default Realm configuration. */ + (instancetype)defaultConfiguration; /** Sets the default configuration to the given `RLMRealmConfiguration`. @param configuration The new default Realm configuration. */ + (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration; #pragma mark - Properties /// The local URL of the Realm file. Mutually exclusive with `inMemoryIdentifier`. @property (nonatomic, copy, nullable) NSURL *fileURL; /// A string used to identify a particular in-memory Realm. Mutually exclusive with `fileURL`. @property (nonatomic, copy, nullable) NSString *inMemoryIdentifier; /// A 64-byte key to use to encrypt the data, or `nil` if encryption is not enabled. @property (nonatomic, copy, nullable) NSData *encryptionKey; /// Whether to open the Realm in read-only mode. /// /// This is required to be able to open Realm files which are not writeable or /// are in a directory which is not writeable. This should only be used on files /// which will not be modified by anyone while they are open, and not just to /// get a read-only view of a file which may be written to by another thread or /// process. Opening in read-only mode requires disabling Realm's reader/writer /// coordination, so committing a write transaction from another process will /// result in crashes. @property (nonatomic) BOOL readOnly; /// The current schema version. @property (nonatomic) uint64_t schemaVersion; /// The block which migrates the Realm to the current version. @property (nonatomic, copy, nullable) RLMMigrationBlock migrationBlock; /** Whether to recreate the Realm file with the provided schema if a migration is required. This is the case when the stored schema differs from the provided schema or the stored schema version differs from the version on this configuration. Setting this property to `YES` deletes the file if a migration would otherwise be required or executed. @note Setting this property to `YES` doesn't disable file format migrations. */ @property (nonatomic) BOOL deleteRealmIfMigrationNeeded; /// The classes managed by the Realm. @property (nonatomic, copy, nullable) NSArray *objectClasses; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMRealm_Dynamic.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import @class RLMResults; NS_ASSUME_NONNULL_BEGIN @interface RLMRealm (Dynamic) #pragma mark - Getting Objects from a Realm /** Returns all objects of a given type from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The name of the `RLMObject` subclass to retrieve on (e.g. `MyClass.className`). @return An `RLMResults` containing all objects in the Realm of the given type. @see `+[RLMObject allObjects]` */ - (RLMResults *)allObjects:(NSString *)className; /** Returns all objects matching the given predicate from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The type of objects you are looking for (name of the class). @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` containing results matching the given predicate. @see `+[RLMObject objectsWhere:]` */ - (RLMResults *)objects:(NSString *)className where:(NSString *)predicateFormat, ...; /** Returns all objects matching the given predicate from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get objects of a single class is to use the class methods on `RLMObject`. @param className The type of objects you are looking for (name of the class). @param predicate The predicate with which to filter the objects. @return An `RLMResults` containing results matching the given predicate. @see `+[RLMObject objectsWhere:]` */ - (RLMResults *)objects:(NSString *)className withPredicate:(NSPredicate *)predicate; /** Returns the object of the given type with the given primary key from the Realm. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. The preferred way to get an object of a single class is to use the class methods on `RLMObject`. @param className The class name for the object you are looking for. @param primaryKey The primary key value for the object you are looking for. @return An object, or `nil` if an object with the given primary key does not exist. @see `+[RLMObject objectForPrimaryKey:]` */ - (nullable RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey; /** Creates an `RLMObject` instance of type `className` in the Realm, and populates it using a given object. The `value` argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in `NSJSONSerialization`, or an array containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an array as the `value` argument, all properties must be present, valid and in the same order as the properties defined in the model. @warning This method is useful only in specialized circumstances, for example, when building components that integrate with Realm. If you are simply building an app on Realm, it is recommended to use `[RLMObject createInDefaultRealmWithValue:]`. @param value The value used to populate the object. @return An `RLMObject` instance of type `className`. */ -(RLMObject *)createObject:(NSString *)className withValue:(id)value; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMResults.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN @class RLMObject, RLMRealm, RLMNotificationToken; /** `RLMResults` is an auto-updating container type in Realm returned from object queries. It represents the results of the query in the form of a collection of objects. `RLMResults` can be queried using the same predicates as `RLMObject` and `RLMArray`, and you can chain queries to further filter results. `RLMResults` always reflect the current state of the Realm on the current thread, including during write transactions on the current thread. The one exception to this is when using `for...in` fast enumeration, which will always enumerate over the objects which matched the query when the enumeration is begun, even if some of them are deleted or modified to be excluded by the filter during the enumeration. `RLMResults` are lazily evaluated the first time they are accessed; they only run queries when the result of the query is requested. This means that chaining several temporary `RLMResults` to sort and filter your data does not perform any extra work processing the intermediate state. Once the results have been evaluated or a notification block has been added, the results are eagerly kept up-to-date, with the work done to keep them up-to-date done on a background thread whenever possible. `RLMResults` cannot be directly instantiated. */ @interface RLMResults : NSObject #pragma mark - Properties /** The number of objects in the results collection. */ @property (nonatomic, readonly, assign) NSUInteger count; /** The class name (i.e. type) of the `RLMObject`s contained in the results collection. */ @property (nonatomic, readonly, copy) NSString *objectClassName; /** The Realm which manages this results collection. */ @property (nonatomic, readonly) RLMRealm *realm; /** Indicates if the results collection is no longer valid. The results collection becomes invalid if `invalidate` is called on the containing `realm`. An invalidated results collection can be accessed, but will always be empty. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Accessing Objects from an RLMResults /** Returns the object at the index specified. @param index The index to look up. @return An `RLMObject` of the type contained in the results collection. */ - (RLMObjectType)objectAtIndex:(NSUInteger)index; /** Returns the first object in the results collection. Returns `nil` if called on an empty results collection. @return An `RLMObject` of the type contained in the results collection. */ - (nullable RLMObjectType)firstObject; /** Returns the last object in the results collection. Returns `nil` if called on an empty results collection. @return An `RLMObject` of the type contained in the results collection. */ - (nullable RLMObjectType)lastObject; #pragma mark - Querying Results /** Returns the index of an object in the results collection. Returns `NSNotFound` if the object is not found in the results collection. @param object An object (of the same type as returned from the `objectClassName` selector). */ - (NSUInteger)indexOfObject:(RLMObjectType)object; /** Returns the index of the first object in the results collection matching the predicate. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return The index of the object, or `NSNotFound` if the object is not found in the results collection. */ - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns the index of the first object in the results collection matching the predicate. @param predicate The predicate with which to filter the objects. @return The index of the object, or `NSNotFound` if the object is not found in the results collection. */ - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate; /** Returns all the objects matching the given predicate in the results collection. @param predicateFormat A predicate format string, optionally followed by a variable number of arguments. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...; /// :nodoc: - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args; /** Returns all the objects matching the given predicate in the results collection. @param predicate The predicate with which to filter the objects. @return An `RLMResults` of objects that match the given predicate. */ - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate; /** Returns a sorted `RLMResults` from an existing results collection. @param keyPath The key path to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified key path. */ - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending; /** Returns a sorted `RLMResults` from an existing results collection. @param property The property name to sort by. @param ascending The direction to sort in. @return An `RLMResults` sorted by the specified property. */ - (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending __deprecated_msg("Use `-sortedResultsUsingKeyPath:ascending:`"); /** Returns a sorted `RLMResults` from an existing results collection. @param properties An array of `RLMSortDescriptor`s to sort by. @return An `RLMResults` sorted by the specified properties. */ - (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties; #pragma mark - Notifications /** Registers a block to be called each time the results collection changes. The block will be asynchronously called with the initial results collection, and then called again after each write transaction which changes either any of the objects in the results, or which objects are in the results. The `change` parameter will be `nil` the first time the block is called. For each call after that, it will contain information about which rows in the results collection were added, removed or modified. If a write transaction did not modify any objects in the results collection, the block is not called at all. See the `RLMCollectionChange` documentation for information on how the changes are reported and an example of updating a `UITableView`. If an error occurs the block will be called with `nil` for the results parameter and a non-`nil` error. Currently the only errors that can occur are when opening the Realm on the background worker thread. At the time when the block is called, the `RLMResults` object will be fully evaluated and up-to-date, and as long as you do not perform a write transaction on the same thread or explicitly call `-[RLMRealm refresh]`, accessing it will never perform blocking work. Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a single notification. This can include the notification with the initial results. For example, the following code performs a write transaction immediately after adding the notification block, so there is no opportunity for the initial notification to be delivered first. As a result, the initial notification will reflect the state of the Realm after the write transaction. RLMResults *results = [Dog allObjects]; NSLog(@"dogs.count: %zu", dogs.count); // => 0 self.token = [results addNotificationBlock:^(RLMResults *dogs, RLMCollectionChange *changes, NSError *error) { // Only fired once for the example NSLog(@"dogs.count: %zu", dogs.count); // => 1 }]; [realm transactionWithBlock:^{ Dog *dog = [[Dog alloc] init]; dog.name = @"Rex"; [realm addObject:dog]; }]; // end of run loop execution context You must retain the returned token for as long as you want updates to continue to be sent to the block. To stop receiving updates, call `-stop` on the token. @warning This method cannot be called during a write transaction, or when the containing Realm is read-only. @param block The block to be called whenever a change occurs. @return A token which must be held for as long as you want updates to be delivered. */ - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *__nullable results, RLMCollectionChange *__nullable change, NSError *__nullable error))block __attribute__((warn_unused_result)); #pragma mark - Aggregating Property Values /** Returns the minimum (lowest) value of the given property among all the objects represented by the results collection. NSNumber *min = [results minOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose minimum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The minimum value of the property. */ - (nullable id)minOfProperty:(NSString *)property; /** Returns the maximum (highest) value of the given property among all the objects represented by the results collection. NSNumber *max = [results maxOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose maximum value is desired. Only properties of types `int`, `float`, `double`, and `NSDate` are supported. @return The maximum value of the property. */ - (nullable id)maxOfProperty:(NSString *)property; /** Returns the sum of the values of a given property over all the objects represented by the results collection. NSNumber *sum = [results sumOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose values should be summed. Only properties of types `int`, `float`, and `double` are supported. @return The sum of the given property. */ - (NSNumber *)sumOfProperty:(NSString *)property; /** Returns the average value of a given property over the objects represented by the results collection. NSNumber *average = [results averageOfProperty:@"age"]; @warning You cannot use this method on `RLMObject`, `RLMArray`, and `NSData` properties. @param property The property whose average value should be calculated. Only properties of types `int`, `float`, and `double` are supported. @return The average value of the given property. This will be of type `double` for both `float` and `double` properties. */ - (nullable NSNumber *)averageOfProperty:(NSString *)property; /// :nodoc: - (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index; #pragma mark - Unavailable Methods /** `-[RLMResults init]` is not available because `RLMResults` cannot be created directly. `RLMResults` can be obtained by querying a Realm. */ - (instancetype)init __attribute__((unavailable("RLMResults cannot be created directly"))); /** `+[RLMResults new]` is not available because `RLMResults` cannot be created directly. `RLMResults` can be obtained by querying a Realm. */ + (instancetype)new __attribute__((unavailable("RLMResults cannot be created directly"))); @end /** `RLMLinkingObjects` is an auto-updating container type. It represents a collection of objects that link to its parent object. For more information, please see the "Inverse Relationships" section in the [documentation](https://realm.io/docs/objc/latest/#relationships). */ @interface RLMLinkingObjects : RLMResults @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSchema.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @class RLMObjectSchema; /** `RLMSchema` instances represent collections of model object schemas managed by a Realm. When using Realm, `RLMSchema` instances allow performing migrations and introspecting the database's schema. Schemas map to collections of tables in the core database. */ @interface RLMSchema : NSObject #pragma mark - Properties /** An `NSArray` containing `RLMObjectSchema`s for all object types in the Realm. This property is intended to be used during migrations for dynamic introspection. @see `RLMObjectSchema` */ @property (nonatomic, readonly, copy) NSArray *objectSchema; #pragma mark - Methods /** Returns an `RLMObjectSchema` for the given class name in the schema. @param className The object class name. @return An `RLMObjectSchema` for the given class in the schema. @see `RLMObjectSchema` */ - (nullable RLMObjectSchema *)schemaForClassName:(NSString *)className; /** Looks up and returns an `RLMObjectSchema` for the given class name in the Realm. If there is no object of type `className` in the schema, an exception will be thrown. @param className The object class name. @return An `RLMObjectSchema` for the given class in this Realm. @see `RLMObjectSchema` */ - (RLMObjectSchema *)objectForKeyedSubscript:(NSString *)className; /** Returns whether two `RLMSchema` instances are equivalent. */ - (BOOL)isEqualToSchema:(RLMSchema *)schema; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncConfiguration.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMSyncUser; NS_ASSUME_NONNULL_BEGIN /** A configuration object representing configuration state for a Realm which is intended to sync with a Realm Object Server. */ @interface RLMSyncConfiguration : NSObject /// The user to which the remote Realm belongs. @property (nonatomic, readonly) RLMSyncUser *user; /** The URL of the remote Realm upon the Realm Object Server. @warning The URL cannot end with `.realm`, `.realm.lock` or `.realm.management`. */ @property (nonatomic, readonly) NSURL *realmURL; /** Create a sync configuration instance. @param user A `RLMSyncUser` that owns the Realm at the given URL. @param url The unresolved absolute URL to the Realm on the Realm Object Server, e.g. `realm://example.org/~/path/to/realm`. "Unresolved" means the path should contain the wildcard marker `~`, which will automatically be filled in with the user identity by the Realm Object Server. */ - (instancetype)initWithUser:(RLMSyncUser *)user realmURL:(NSURL *)url; /// :nodoc: - (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); /// :nodoc: + (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncCredentials.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMSyncUtil.h" NS_ASSUME_NONNULL_BEGIN /// A token representing an identity provider's credentials. typedef NSString *RLMSyncCredentialsToken; /// A type representing the unique identifier of a Realm Object Server identity provider. typedef NSString *RLMIdentityProvider RLM_EXTENSIBLE_STRING_ENUM; /// The debug identity provider, which accepts any token string and creates a user associated with that token if one /// does not yet exist. Not enabled for Realm Object Server configured for production. extern RLMIdentityProvider const RLMIdentityProviderDebug; /// The username/password identity provider. User accounts are handled by the Realm Object Server directly without the /// involvement of a third-party identity provider. extern RLMIdentityProvider const RLMIdentityProviderUsernamePassword; /// A Facebook account as an identity provider. extern RLMIdentityProvider const RLMIdentityProviderFacebook; /// A Google account as an identity provider. extern RLMIdentityProvider const RLMIdentityProviderGoogle; /// A CloudKit account as an identity provider. extern RLMIdentityProvider const RLMIdentityProviderCloudKit; /** Opaque credentials representing a specific Realm Object Server user. */ @interface RLMSyncCredentials : NSObject /// An opaque credentials token containing information that uniquely identifies a Realm Object Server user. @property (nonatomic, readonly) RLMSyncCredentialsToken token; /// The name of the identity provider which generated the credentials token. @property (nonatomic, readonly) RLMIdentityProvider provider; /// A dictionary containing additional pertinent information. In most cases this is automatically configured. @property (nonatomic, readonly) NSDictionary *userInfo; /** Construct and return credentials from a Facebook account token. */ + (instancetype)credentialsWithFacebookToken:(RLMSyncCredentialsToken)token; /** Construct and return credentials from a Google account token. */ + (instancetype)credentialsWithGoogleToken:(RLMSyncCredentialsToken)token; /** Construct and return credentials from an CloudKit account token. */ + (instancetype)credentialsWithCloudKitToken:(RLMSyncCredentialsToken)token; /** Construct and return credentials from a Realm Object Server username and password. */ + (instancetype)credentialsWithUsername:(NSString *)username password:(NSString *)password register:(BOOL)shouldRegister; /** Construct and return special credentials representing a token that can be directly used to open a Realm. The identity is used to uniquely identify the user across application launches. */ + (instancetype)credentialsWithAccessToken:(RLMServerToken)accessToken identity:(NSString *)identity; /** Construct and return credentials with a custom token string, identity provider string, and optional user info. In most cases, the convenience initializers should be used instead. */ - (instancetype)initWithCustomToken:(RLMSyncCredentialsToken)token provider:(RLMIdentityProvider)provider userInfo:(nullable NSDictionary *)userInfo NS_DESIGNATED_INITIALIZER; /// :nodoc: - (instancetype)init __attribute__((unavailable("RLMSyncCredentials cannot be created directly"))); /// :nodoc: + (instancetype)new __attribute__((unavailable("RLMSyncCredentials cannot be created directly"))); NS_ASSUME_NONNULL_END @end ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncManager.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMSyncUtil.h" @class RLMSyncSession; /// An enum representing different levels of sync-related logging that can be configured. typedef NS_ENUM(NSUInteger, RLMSyncLogLevel) { /// Nothing will ever be logged. RLMSyncLogLevelOff, /// Only fatal errors will be logged. RLMSyncLogLevelFatal, /// Only errors will be logged. RLMSyncLogLevelError, /// Warnings and errors will be logged. RLMSyncLogLevelWarn, /// Information about sync events will be logged. Fewer events will be logged in order to avoid overhead. RLMSyncLogLevelInfo, /// Information about sync events will be logged. More events will be logged than with `RLMSyncLogLevelInfo`. RLMSyncLogLevelDetail, /// Log information that can aid in debugging. /// /// - warning: Will incur a measurable performance impact. RLMSyncLogLevelDebug, /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelDebug`. /// /// - warning: Will incur a measurable performance impact. RLMSyncLogLevelTrace, /// Log information that can aid in debugging. More events will be logged than with `RLMSyncLogLevelTrace`. /// /// - warning: Will incur a measurable performance impact. RLMSyncLogLevelAll }; NS_ASSUME_NONNULL_BEGIN /// A block type representing a block which can be used to report a sync-related error to the application. If the error /// pertains to a specific session, that session will also be passed into the block. typedef void(^RLMSyncErrorReportingBlock)(NSError *, RLMSyncSession * _Nullable); /** A singleton manager which serves as a central point for sync-related configuration. */ @interface RLMSyncManager : NSObject /** An optional block which can be used to report sync-related errors to your application. Errors reported through this mechanism are always fatal; they represent attempts to open sessions which are invalid (for example, using malformed URLs). */ @property (nullable, nonatomic, copy) RLMSyncErrorReportingBlock errorHandler; /** A reverse-DNS string uniquely identifying this application. In most cases this is automatically set by the SDK, and does not have to be explicitly configured. */ @property (nonatomic, copy) NSString *appID; /** Whether SSL certificate validation should be disabled. SSL certificate validation is ON by default. Setting this property after at least one synced Realm or standalone Session has been opened is a no-op. @warning NEVER disable certificate validation for clients and servers in production. */ @property (nonatomic) BOOL disableSSLValidation; /** The logging threshold which newly opened synced Realms will use. Defaults to `RLMSyncLogLevelInfo`. Set this before any synced Realms are opened. Logging strings are output to ASL. */ @property (nonatomic) RLMSyncLogLevel logLevel; /// The sole instance of the singleton. + (instancetype)sharedManager NS_REFINED_FOR_SWIFT; /// :nodoc: - (instancetype)init __attribute__((unavailable("RLMSyncManager cannot be created directly"))); /// :nodoc: + (instancetype)new __attribute__((unavailable("RLMSyncManager cannot be created directly"))); NS_ASSUME_NONNULL_END @end ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncPermission.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN /** This model is used to reflect permissions. It should be used in conjunction with a `RLMSyncUser`'s Permission Realm. You can only read this Realm. Use the objects in Management Realm to make request for modifications of permissions. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ @interface RLMSyncPermission : RLMObject /// The date this object was last modified. @property (readonly) NSDate *updatedAt; /// The identity of a user affected by this permission. @property (readonly) NSString *userId; /// The path to the realm. @property (readonly) NSString *path; /// Whether the affected user is allowed to read from the Realm. @property (readonly) BOOL mayRead; /// Whether the affected user is allowed to write to the Realm. @property (readonly) BOOL mayWrite; /// Whether the affected user is allowed to manage the access rights for others. @property (readonly) BOOL mayManage; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncPermissionChange.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** This model is used for requesting changes to a Realm's permissions. It should be used in conjunction with an `RLMSyncUser`'s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ @interface RLMSyncPermissionChange : RLMObject /// The globally unique ID string of this permission change object. @property (readonly) NSString *id; /// The date this object was initially created. @property (readonly) NSDate *createdAt; /// The date this object was last modified. @property (readonly) NSDate *updatedAt; /// The status code of the object that was processed by Realm Object Server. @property (nullable, readonly) NSNumber *statusCode; /// An error or informational message, typically written to by the Realm Object Server. @property (nullable, readonly) NSString *statusMessage; /// Sync management object status. @property (readonly) RLMSyncManagementObjectStatus status; /// The remote URL to the realm. @property (readonly) NSString *realmUrl; /// The identity of a user affected by this permission change. @property (readonly) NSString *userId; /// Define read access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. @property (nullable, readonly) NSNumber *mayRead; /// Define write access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. @property (nullable, readonly) NSNumber *mayWrite; /// Define management access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. @property (nullable, readonly) NSNumber *mayManage; /** Construct a permission change object used to change the access permissions for a user on a Realm. @param realmURL The Realm URL whose permissions settings should be changed. Use `*` to change the permissions of all Realms managed by the Management Realm's `RLMSyncUser`. @param userID The user or users who should be granted these permission changes. Use `*` to change the permissions for all users. @param mayRead Define read access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. @param mayWrite Define write access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. @param mayManage Define management access. Set to `YES` or `NO` to update this value. Leave unset to preserve the existing setting. */ + (instancetype)permissionChangeWithRealmURL:(NSString *)realmURL userID:(NSString *)userID read:(nullable NSNumber *)mayRead write:(nullable NSNumber *)mayWrite manage:(nullable NSNumber *)mayManage; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncPermissionOffer.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** This model is used for offering permission changes to other users. It should be used in conjunction with an `RLMSyncUser`'s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ @interface RLMSyncPermissionOffer : RLMObject /// The globally unique ID string of this permission offer object. @property (readonly) NSString *id; /// The date this object was initially created. @property (readonly) NSDate *createdAt; /// The date this object was last modified. @property (readonly) NSDate *updatedAt; /// The status code of the object that was processed by Realm Object Server. @property (nullable, readonly) NSNumber *statusCode; /// An error or informational message, typically written to by the Realm Object Server. @property (nullable, readonly) NSString *statusMessage; /// Sync management object status. @property (readonly) RLMSyncManagementObjectStatus status; /// A token which uniquely identifies this offer. Generated by the server. @property (nullable, readonly) NSString *token; /// The remote URL to the realm. @property (readonly) NSString *realmUrl; /// Whether this offer allows the receiver to read from the Realm. @property (readonly) BOOL mayRead; /// Whether this offer allows the receiver to write to the Realm. @property (readonly) BOOL mayWrite; /// Whether this offer allows the receiver to manage the access rights for others. @property (readonly) BOOL mayManage; /// When this token will expire and become invalid. @property (nullable, readonly) NSDate *expiresAt; /** Construct a permission offer object used to offer permission changes to other users. @param realmURL The URL to the Realm on which to apply these permission changes to, once the offer is accepted. @param expiresAt When this token will expire and become invalid. Pass `nil` if this offer should not expire. @param mayRead Grant or revoke read access. @param mayWrite Grant or revoked read-write access. @param mayManage Grant or revoke administrative access. */ + (instancetype)permissionOfferWithRealmURL:(NSString *)realmURL expiresAt:(nullable NSDate *)expiresAt read:(BOOL)mayRead write:(BOOL)mayWrite manage:(BOOL)mayManage; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncPermissionOfferResponse.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN /** This model is used to apply permission changes defined in the permission offer object represented by the specified token, which was created by another user's `RLMSyncPermissionOffer` object. It should be used in conjunction with an `RLMSyncUser`'s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ @interface RLMSyncPermissionOfferResponse : RLMObject /// The globally unique ID string of this permission offer response object. @property (readonly) NSString *id; /// The date this object was initially created. @property (readonly) NSDate *createdAt; /// The date this object was last modified. @property (readonly) NSDate *updatedAt; /// The status code of the object that was processed by Realm Object Server. @property (nullable, readonly) NSNumber *statusCode; /// An error or informational message, typically written to by the Realm Object Server. @property (nullable, readonly) NSString *statusMessage; /// Sync management object status. @property (readonly) RLMSyncManagementObjectStatus status; /// The received token which uniquely identifies another user's `RLMSyncPermissionOffer`. @property (readonly) NSString *token; /// The remote URL to the realm on which these permission changes were applied. /// Generated by the server. @property (nullable, readonly) NSString *realmUrl; /** Construct a permission offer response object used to apply permission changes defined in the permission offer object represented by the specified token, which was created by another user's `RLMSyncPermissionOffer` object. @param token The received token which uniquely identifies another user's `RLMSyncPermissionOffer`. */ + (instancetype)permissionOfferResponseWithToken:(NSString *)token; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncSession.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import "RLMRealm.h" /** The current state of the session represented by an `RLMSyncSession` object. */ typedef NS_ENUM(NSUInteger, RLMSyncSessionState) { /// The sync session is bound to the Realm Object Server and communicating with it. RLMSyncSessionStateActive, /// The sync session is not currently communicating with the Realm Object Server. RLMSyncSessionStateInactive, /// The sync session encountered a fatal error and is permanently invalid; it should be discarded. RLMSyncSessionStateInvalid }; /** The transfer direction (upload or download) tracked by a given progress notification block. Progress notification blocks can be registered on sessions if your app wishes to be informed how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. */ typedef NS_ENUM(NSUInteger, RLMSyncProgressDirection) { /// For monitoring upload progress. RLMSyncProgressDirectionUpload, /// For monitoring download progress. RLMSyncProgressDirectionDownload, }; /** The desired behavior of a progress notification block. Progress notification blocks can be registered on sessions if your app wishes to be informed how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. */ typedef NS_ENUM(NSUInteger, RLMSyncProgress) { /** The block will be called forever, or until it is unregistered by calling `-[RLMProgressNotificationToken stop]`. Notifications will always report the latest number of transferred bytes, and the most up-to-date number of total transferrable bytes. */ RLMSyncProgressReportIndefinitely, /** The block will, upon registration, store the total number of bytes to be transferred. When invoked, it will always report the most up-to-date number of transferrable bytes out of that original number of transferrable bytes. When the number of transferred bytes reaches or exceeds the number of transferrable bytes, the block will be unregistered. */ RLMSyncProgressForCurrentlyOutstandingWork, }; @class RLMSyncUser, RLMSyncConfiguration; /** The type of a progress notification block intended for reporting a session's network activity to the user. `transferredBytes` refers to the number of bytes that have been uploaded or downloaded. `transferrableBytes` refers to the total number of bytes transferred, and pending transfer. */ typedef void(^RLMProgressNotificationBlock)(NSUInteger transferredBytes, NSUInteger transferrableBytes); NS_ASSUME_NONNULL_BEGIN /** A token object corresponding to a progress notification block on an `RLMSyncSession`. To stop notifications manually, call `-stop` on it. Notifications should be stopped before the token goes out of scope or is destroyed. */ @interface RLMProgressNotificationToken : RLMNotificationToken @end /** An object encapsulating a Realm Object Server "session". Sessions represent the communication between the client (and a local Realm file on disk), and the server (and a remote Realm at a given URL stored on a Realm Object Server). Sessions are always created by the SDK and vended out through various APIs. The lifespans of sessions associated with Realms are managed automatically. */ @interface RLMSyncSession : NSObject /// The session's current state. @property (nonatomic, readonly) RLMSyncSessionState state; /// The Realm Object Server URL of the remote Realm this session corresponds to. @property (nullable, nonatomic, readonly) NSURL *realmURL; /// The user that owns this session. - (nullable RLMSyncUser *)parentUser; /** If the session is valid, return a sync configuration that can be used to open the Realm associated with this session. */ - (nullable RLMSyncConfiguration *)configuration; /** Register a progress notification block. Multiple blocks can be registered with the same session at once. Each block will be invoked on a side queue devoted to progress notifications. If the session has already received progress information from the synchronization subsystem, the block will be called immediately. Otherwise, it will be called as soon as progress information becomes available. The token returned by this method must be retained as long as progress notifications are desired, and the `-stop` method should be called on it when notifications are no longer needed and before the token is destroyed. If no token is returned, the notification block will never be called again. There are a number of reasons this might be true. If the session has previously experienced a fatal error it will not accept progress notification blocks. If the block was configured in the `RLMSyncProgressForCurrentlyOutstandingWork` mode but there is no additional progress to report (for example, the number of transferrable bytes and transferred bytes are equal), the block will not be called again. @param direction The transfer direction (upload or download) to track in this progress notification block. @param mode The desired behavior of this progress notification block. @param block The block to invoke when notifications are available. @return A token which must be held for as long as you want notifications to be delivered. @see `RLMSyncProgressDirection`, `RLMSyncProgress`, `RLMProgressNotificationBlock`, `RLMProgressNotificationToken` */ - (nullable RLMProgressNotificationToken *)addProgressNotificationForDirection:(RLMSyncProgressDirection)direction mode:(RLMSyncProgress)mode block:(RLMProgressNotificationBlock)block NS_REFINED_FOR_SWIFT; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncUser.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMSyncUser, RLMSyncCredentials, RLMSyncSession, RLMRealm; /** The state of the user object. */ typedef NS_ENUM(NSUInteger, RLMSyncUserState) { /// The user is logged out. Call `logInWithCredentials:...` with valid credentials to log the user back in. RLMSyncUserStateLoggedOut, /// The user is logged in, and any Realms associated with it are syncing with the Realm Object Server. RLMSyncUserStateActive, /// The user has encountered a fatal error state, and cannot be used. RLMSyncUserStateError, }; /// A block type used for APIs which asynchronously vend an `RLMSyncUser`. typedef void(^RLMUserCompletionBlock)(RLMSyncUser * _Nullable, NSError * _Nullable); NS_ASSUME_NONNULL_BEGIN /** A `RLMSyncUser` instance represents a single Realm Object Server user account (or just user). A user may have one or more credentials associated with it. These credentials uniquely identify the user to a third-party auth provider, and are used to sign into a Realm Object Server user account. Note that users are only vended out via SDK APIs, and only one user instance ever exists for a given user account. */ @interface RLMSyncUser : NSObject /** A dictionary of all valid, logged-in user identities corresponding to their `RLMSyncUser` objects. */ + (NSDictionary *)allUsers NS_REFINED_FOR_SWIFT; /** The logged-in user. `nil` if none exists. @warning Throws an exception if more than one logged-in user exists. */ + (nullable RLMSyncUser *)currentUser NS_REFINED_FOR_SWIFT; /** The unique Realm Object Server user ID string identifying this user. */ @property (nullable, nonatomic, readonly) NSString *identity; /** The URL of the authentication server this user will communicate with. */ @property (nullable, nonatomic, readonly) NSURL *authenticationServer; /** The current state of the user. */ @property (nonatomic, readonly) RLMSyncUserState state; /** Create, log in, and asynchronously return a new user object, specifying a custom timeout for the network request. Credentials identifying the user must be passed in. The user becomes available in the completion block, at which point it is ready for use. */ + (void)logInWithCredentials:(RLMSyncCredentials *)credentials authServerURL:(NSURL *)authServerURL timeout:(NSTimeInterval)timeout onCompletion:(RLMUserCompletionBlock)completion NS_REFINED_FOR_SWIFT; /** Create, log in, and asynchronously return a new user object. Credentials identifying the user must be passed in. The user becomes available in the completion block, at which point it is ready for use. */ + (void)logInWithCredentials:(RLMSyncCredentials *)credentials authServerURL:(NSURL *)authServerURL onCompletion:(RLMUserCompletionBlock)completion NS_SWIFT_UNAVAILABLE("Use the full version of this API."); /** Log a user out, destroying their server state, deregistering them from the SDK, and removing any synced Realms associated with them from on-disk storage. If the user is already logged out or in an error state, this is a no-op. This method should be called whenever the application is committed to not using a user again unless they are recreated. Failing to call this method may result in unused files and metadata needlessly taking up space. */ - (void)logOut; /** Retrieve a valid session object belonging to this user for a given URL, or `nil` if no such object exists. */ - (nullable RLMSyncSession *)sessionForURL:(NSURL *)url; /** Retrieve all the valid sessions belonging to this user. */ - (NSArray *)allSessions; /** Returns an instance of the Management Realm owned by the user. This Realm can be used to control access permissions for Realms managed by the user. This includes granting other users access to Realms. */ - (RLMRealm *)managementRealmWithError:(NSError **)error NS_REFINED_FOR_SWIFT; /** Returns an instance of the Permission Realm owned by the user. This read-only Realm contains `RLMSyncPermission` objects reflecting the synchronized Realms and permission details this user has access to. */ - (RLMRealm *)permissionRealmWithError:(NSError **)error NS_REFINED_FOR_SWIFT; /// :nodoc: - (instancetype)init __attribute__((unavailable("RLMSyncUser cannot be created directly"))); /// :nodoc: + (instancetype)new __attribute__((unavailable("RLMSyncUser cannot be created directly"))); NS_ASSUME_NONNULL_END @end ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncUtil.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import /// A token originating from the Realm Object Server. typedef NSString* RLMServerToken; NS_ASSUME_NONNULL_BEGIN /// A user info key for use with `RLMSyncErrorClientResetError`. extern NSString *const kRLMSyncPathOfRealmBackupCopyKey; /// A user info key for use with `RLMSyncErrorClientResetError`. extern NSString *const kRLMSyncInitiateClientResetBlockKey; /// The error domain string for all SDK errors related to synchronization functionality. extern NSString *const RLMSyncErrorDomain; /// An error which is related to authentication to a Realm Object Server. typedef RLM_ERROR_ENUM(NSInteger, RLMSyncAuthError, RLMSyncErrorDomain) { /// An error that indicates that the provided credentials are invalid. RLMSyncAuthErrorInvalidCredential = 611, /// An error that indicates that the user with provided credentials does not exist. RLMSyncAuthErrorUserDoesNotExist = 612, /// An error that indicates that the user cannot be registered as it exists already. RLMSyncAuthErrorUserAlreadyExists = 613, }; /// An error which is related to synchronization with a Realm Object Server. typedef RLM_ERROR_ENUM(NSInteger, RLMSyncError, RLMSyncErrorDomain) { /// An error that indicates that the response received from the authentication server was malformed. RLMSyncErrorBadResponse = 1, /// An error that indicates that the supplied Realm path was invalid, or could not be resolved by the authentication /// server. RLMSyncErrorBadRemoteRealmPath = 2, /// An error that indicates that the response received from the authentication server was an HTTP error code. The /// `userInfo` dictionary contains the actual error code value. RLMSyncErrorHTTPStatusCodeError = 3, /// An error that indicates a problem with the session (a specific Realm opened for sync). RLMSyncErrorClientSessionError = 4, /// An error that indicates a problem with a specific user. RLMSyncErrorClientUserError = 5, /// An error that indicates an internal, unrecoverable error with the underlying synchronization engine. RLMSyncErrorClientInternalError = 6, /** An error that indicates the Realm needs to be reset. A synced Realm may need to be reset because the Realm Object Server encountered an error and had to be restored from a backup. If the backup copy of the remote Realm is of an earlier version than the local copy of the Realm, the server will ask the client to reset the Realm. The reset process is as follows: the local copy of the Realm is copied into a recovery directory for safekeeping, and then deleted from the original location. The next time the Realm for that URL is opened, the Realm will automatically be re-downloaded from the Realm Object Server, and can be used as normal. Data written to the Realm after the local copy of the Realm diverged from the backup remote copy will be present in the local recovery copy of the Realm file. The re-downloaded Realm will initially contain only the data present at the time the Realm was backed up on the server. The client reset process can be initiated in one of two ways. The block provided in the `userInfo` dictionary under `kRLMSyncInitiateClientResetBlockKey` can be called to initiate the reset process. This block can be called any time after the error is received, but should only be called if and when your app closes and invalidates every instance of the offending Realm on all threads (note that autorelease pools may make this difficult to guarantee). If the block is not called, the client reset process will be automatically carried out the next time the app is launched and the `RLMSyncManager` singleton is accessed. The value for the `kRLMSyncPathOfRealmBackupCopyKey` key in the `userInfo` dictionary describes the path of the recovered copy of the Realm. This copy will not actually be created until the client reset process is initiated. @see: `-[NSError rlmSync_clientResetBlock]`, `-[NSError rlmSync_clientResetBackedUpRealmPath]` */ RLMSyncErrorClientResetError = 7, }; /// An enum representing the different states a sync management object can take. typedef NS_ENUM(NSUInteger, RLMSyncManagementObjectStatus) { /// The management object has not yet been processed by the object server. RLMSyncManagementObjectStatusNotProcessed, /// The operations encoded in the management object have been successfully /// performed by the object server. RLMSyncManagementObjectStatusSuccess, /** The operations encoded in the management object were not successfully performed by the object server. Refer to the `statusCode` and `statusMessage` properties for more details about the error. */ RLMSyncManagementObjectStatusError, }; NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMThreadSafeReference.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMRealm; NS_ASSUME_NONNULL_BEGIN /** Objects of types which conform to `RLMThreadConfined` can be managed by a Realm, which will make them bound to a thread-specific `RLMRealm` instance. Managed objects must be explicitly exported and imported to be passed between threads. Managed instances of objects conforming to this protocol can be converted to a thread-safe reference for transport between threads by passing to the `+[RLMThreadSafeReference referenceWithThreadConfined:]` constructor. Note that only types defined by Realm can meaningfully conform to this protocol, and defining new classes which attempt to conform to it will not make them work with `RLMThreadSafeReference`. */ @protocol RLMThreadConfined // Conformance to the `RLMThreadConfined_Private` protocol will be enforced at runtime. /** The Realm which manages the object, or `nil` if the object is unmanaged. Unmanaged objects are not confined to a thread and cannot be passed to methods expecting a `RLMThreadConfined` object. */ @property (nonatomic, readonly, nullable) RLMRealm *realm; /// Indicates if the object can no longer be accessed because it is now invalid. @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; @end /** An object intended to be passed between threads containing a thread-safe reference to its thread-confined object. To resolve a thread-safe reference on a target Realm on a different thread, pass to `-[RLMRealm resolveThreadSafeReference:]`. @warning A `RLMThreadSafeReference` object must be resolved at most once. Failing to resolve a `RLMThreadSafeReference` will result in the source version of the Realm being pinned until the reference is deallocated. @note Prefer short-lived `RLMThreadSafeReference`s as the data for the version of the source Realm will be retained until all references have been resolved or deallocated. @see `RLMThreadConfined` @see `-[RLMRealm resolveThreadSafeReference:]` */ @interface RLMThreadSafeReference<__covariant Confined : id> : NSObject /** Create a thread-safe reference to the thread-confined object. @param threadConfined The thread-confined object to create a thread-safe reference to. @note You may continue to use and access the thread-confined object after passing it to this constructor. */ + (instancetype)referenceWithThreadConfined:(Confined)threadConfined; /** Indicates if the reference can no longer be resolved because an attempt to resolve it has already occurred. References can only be resolved once. */ @property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated; #pragma mark - Unavailable Methods /** `-[RLMThreadSafeReference init]` is not available because `RLMThreadSafeReference` cannot be created directly. `RLMThreadSafeReference` instances must be obtained by calling `-[RLMRealm resolveThreadSafeReference:]`. */ - (instancetype)init __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); /** `-[RLMThreadSafeReference new]` is not available because `RLMThreadSafeReference` cannot be created directly. `RLMThreadSafeReference` instances must be obtained by calling `-[RLMRealm resolveThreadSafeReference:]`. */ + (instancetype)new __attribute__((unavailable("RLMThreadSafeReference cannot be created directly"))); @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/Headers/Realm.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import ================================================ FILE: External/Realm/Realm.framework/Versions/A/Modules/module.modulemap ================================================ framework module Realm { umbrella header "Realm.h" export * module * { export * } explicit module Private { header "RLMAccessor.h" header "RLMArray_Private.h" header "RLMListBase.h" header "RLMObjectBase_Dynamic.h" header "RLMObjectSchema_Private.h" header "RLMObjectStore.h" header "RLMObject_Private.h" header "RLMOptionalBase.h" header "RLMProperty_Private.h" header "RLMRealmConfiguration_Private.h" header "RLMRealm_Private.h" header "RLMResults_Private.h" header "RLMSchema_Private.h" header "RLMSyncConfiguration_Private.h" header "RLMSyncUtil_Private.h" } explicit module Dynamic { header "RLMRealm_Dynamic.h" header "RLMObjectBase_Dynamic.h" } } ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMAccessor.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMObjectSchema, RLMProperty, RLMObjectBase, RLMProperty; #ifdef __cplusplus typedef NSUInteger RLMCreationOptions; #else typedef NS_OPTIONS(NSUInteger, RLMCreationOptions); #endif NS_ASSUME_NONNULL_BEGIN // // Accessors Class Creation/Caching // // get accessor classes for an object class - generates classes if not cached Class RLMManagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, const char *name); Class RLMUnmanagedAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema); // // Dynamic getters/setters // FOUNDATION_EXTERN void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id __nullable val); FOUNDATION_EXTERN id __nullable RLMDynamicGet(RLMObjectBase *obj, RLMProperty *prop); FOUNDATION_EXTERN id __nullable RLMDynamicGetByName(RLMObjectBase *obj, NSString *propName, bool asList); // by property/column void RLMDynamicSet(RLMObjectBase *obj, RLMProperty *prop, id val, RLMCreationOptions options); // // Class modification // // Replace className method for the given class void RLMReplaceClassNameMethod(Class accessorClass, NSString *className); // Replace sharedSchema method for the given class void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema * __nullable schema); NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMArray_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @interface RLMArray () - (instancetype)initWithObjectClassName:(NSString *)objectClassName; - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMListBase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMArray; NS_ASSUME_NONNULL_BEGIN // A base class for Swift generic Lists to make it possible to interact with // them from obj-c @interface RLMListBase : NSObject @property (nonatomic, strong) RLMArray *_rlmArray; - (instancetype)initWithArray:(RLMArray *)array; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMMigration_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import namespace realm { class Schema; } NS_ASSUME_NONNULL_BEGIN @interface RLMMigration () @property (nonatomic, strong) RLMRealm *oldRealm; @property (nonatomic, strong) RLMRealm *realm; - (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm schema:(realm::Schema &)schema; - (void)execute:(RLMMigrationBlock)block; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObjectSchema_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN // RLMObjectSchema private @interface RLMObjectSchema () { @public bool _isSwiftClass; } /// The object type name reported to the object store and core. @property (nonatomic, readonly) NSString *objectName; // writable redeclaration @property (nonatomic, readwrite, copy) NSArray *properties; @property (nonatomic, readwrite, assign) bool isSwiftClass; // class used for this object schema @property (nonatomic, readwrite, assign) Class objectClass; @property (nonatomic, readwrite, assign) Class accessorClass; @property (nonatomic, readwrite, assign) Class unmanagedClass; @property (nonatomic, readwrite, nullable) RLMProperty *primaryKeyProperty; @property (nonatomic, copy) NSArray *computedProperties; @property (nonatomic, readonly) NSArray *swiftGenericProperties; // returns a cached or new schema for a given object class + (instancetype)schemaForObjectClass:(Class)objectClass; @end @interface RLMObjectSchema (Dynamic) /** This method is useful only in specialized circumstances, for example, when accessing objects in a Realm produced externally. If you are simply building an app on Realm, it is not recommended to use this method as an [RLMObjectSchema](RLMObjectSchema) is generated automatically for every [RLMObject](RLMObject) subclass. Initialize an RLMObjectSchema with classname, objectClass, and an array of properties @warning This method is useful only in specialized circumstances. @param objectClassName The name of the class used to refer to objects of this type. @param objectClass The Objective-C class used when creating instances of this type. @param properties An array of RLMProperty instances describing the managed properties for this type. @return An initialized instance of RLMObjectSchema. */ - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObjectStore.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #ifdef __cplusplus extern "C" { #endif @class RLMRealm, RLMSchema, RLMObjectBase, RLMResults, RLMProperty; NS_ASSUME_NONNULL_BEGIN // // Accessor Creation // // create or get cached accessors for the given schema void RLMRealmCreateAccessors(RLMSchema *schema); // // Options for object creation // typedef NS_OPTIONS(NSUInteger, RLMCreationOptions) { // Normal object creation RLMCreationOptionsNone = 0, // If the property is a link or array property, upsert the linked objects // if they have a primary key, and insert them otherwise. RLMCreationOptionsCreateOrUpdate = 1 << 0, // Allow unmanaged objects to be promoted to managed objects // if false objects are copied during object creation RLMCreationOptionsPromoteUnmanaged = 1 << 1, // Use the SetDefault instruction. RLMCreationOptionsSetDefault = 1 << 2, }; // // Adding, Removing, Getting Objects // // add an object to the given realm void RLMAddObjectToRealm(RLMObjectBase *object, RLMRealm *realm, bool createOrUpdate); // delete an object from its realm void RLMDeleteObjectFromRealm(RLMObjectBase *object, RLMRealm *realm); // deletes all objects from a realm void RLMDeleteAllObjectsFromRealm(RLMRealm *realm); // get objects of a given class RLMResults *RLMGetObjects(RLMRealm *realm, NSString *objectClassName, NSPredicate * _Nullable predicate) NS_RETURNS_RETAINED; // get an object with the given primary key id _Nullable RLMGetObject(RLMRealm *realm, NSString *objectClassName, id _Nullable key) NS_RETURNS_RETAINED; // create object from array or dictionary RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, id _Nullable value, bool createOrUpdate) NS_RETURNS_RETAINED; // // Accessor Creation // // switch List<> properties from being backed by unmanaged RLMArrays to RLMArrayLinkView void RLMInitializeSwiftAccessorGenerics(RLMObjectBase *object); #ifdef __cplusplus } namespace realm { class Table; template class BasicRowExpr; using RowExpr = BasicRowExpr; } class RLMClassInfo; // Create accessors RLMObjectBase *RLMCreateObjectAccessor(RLMRealm *realm, RLMClassInfo& info, NSUInteger index) NS_RETURNS_RETAINED; RLMObjectBase *RLMCreateObjectAccessor(RLMRealm *realm, RLMClassInfo& info, realm::RowExpr row) NS_RETURNS_RETAINED; #endif NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObject_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN // RLMObject accessor and read/write realm @interface RLMObjectBase () { @public RLMRealm *_realm; __unsafe_unretained RLMObjectSchema *_objectSchema; } // unmanaged initializer - (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema NS_DESIGNATED_INITIALIZER; // live accessor initializer - (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm schema:(RLMObjectSchema *)schema NS_DESIGNATED_INITIALIZER; // shared schema for this class + (nullable RLMObjectSchema *)sharedSchema; // provide injection point for alternative Swift object util class + (Class)objectUtilClass:(BOOL)isSwift; @end @interface RLMObject () // unmanaged initializer - (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema NS_DESIGNATED_INITIALIZER; // live accessor initializer - (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm schema:(RLMObjectSchema *)schema NS_DESIGNATED_INITIALIZER; @end @interface RLMDynamicObject : RLMObject @end // A reference to an object's row that doesn't keep the object accessor alive. // Used by some Swift property types, such as LinkingObjects, to avoid retain cycles // with their containing object. @interface RLMWeakObjectHandle : NSObject - (instancetype)initWithObject:(RLMObjectBase *)object; // Consumes the row, so can only usefully be called once. @property (nonatomic, readonly) RLMObjectBase *object; @end // Calls valueForKey: and re-raises NSUndefinedKeyExceptions FOUNDATION_EXTERN id _Nullable RLMValidatedValueForProperty(id object, NSString *key, NSString *className); // Compare two RLObjectBases FOUNDATION_EXTERN BOOL RLMObjectBaseAreEqual(RLMObjectBase * _Nullable o1, RLMObjectBase * _Nullable o2); typedef void (^RLMObjectNotificationCallback)(NSArray *_Nullable propertyNames, NSArray *_Nullable oldValues, NSArray *_Nullable newValues, NSError *_Nullable error); FOUNDATION_EXTERN RLMNotificationToken *RLMObjectAddNotificationBlock(RLMObjectBase *obj, RLMObjectNotificationCallback block); // Get ObjectUil class for objc or swift FOUNDATION_EXTERN Class RLMObjectUtilClass(BOOL isSwift); FOUNDATION_EXTERN const NSUInteger RLMDescriptionMaxDepth; @class RLMProperty, RLMArray; @interface RLMObjectUtil : NSObject + (nullable NSArray *)ignoredPropertiesForClass:(Class)cls; + (nullable NSArray *)indexedPropertiesForClass:(Class)cls; + (nullable NSDictionary *> *)linkingObjectsPropertiesForClass:(Class)cls; + (nullable NSArray *)getGenericListPropertyNames:(id)obj; + (nullable NSDictionary *)getLinkingObjectsProperties:(id)object; + (nullable NSDictionary *)getOptionalProperties:(id)obj; + (nullable NSArray *)requiredPropertiesForClass:(Class)cls; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMOptionalBase.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import NS_ASSUME_NONNULL_BEGIN @class RLMObjectBase, RLMProperty; @interface RLMOptionalBase : NSProxy - (instancetype)init; @property (nonatomic, weak) RLMObjectBase *object; @property (nonatomic, unsafe_unretained) RLMProperty *property; @property (nonatomic, strong, nullable) id underlyingValue; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMProperty_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import @class RLMObjectBase; NS_ASSUME_NONNULL_BEGIN BOOL RLMPropertyTypeIsNullable(RLMPropertyType propertyType); BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType); FOUNDATION_EXTERN void RLMValidateSwiftPropertyName(NSString *name); // private property interface @interface RLMProperty () { @public RLMPropertyType _type; Ivar _swiftIvar; } - (instancetype)initWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property; - (instancetype)initSwiftPropertyWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(nullable RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property instance:(RLMObjectBase *)objectInstance; - (instancetype)initSwiftListPropertyWithName:(NSString *)name ivar:(Ivar)ivar objectClassName:(nullable NSString *)objectClassName; - (instancetype)initSwiftOptionalPropertyWithName:(NSString *)name indexed:(BOOL)indexed ivar:(Ivar)ivar propertyType:(RLMPropertyType)propertyType; - (instancetype)initSwiftLinkingObjectsPropertyWithName:(NSString *)name ivar:(Ivar)ivar objectClassName:(nullable NSString *)objectClassName linkOriginPropertyName:(nullable NSString *)linkOriginPropertyName; // private setters @property (nonatomic, readwrite) NSString *name; @property (nonatomic, readwrite, assign) RLMPropertyType type; @property (nonatomic, readwrite) BOOL indexed; @property (nonatomic, readwrite) BOOL optional; @property (nonatomic, copy, nullable) NSString *objectClassName; // private properties @property (nonatomic, assign) NSUInteger index; @property (nonatomic, assign) BOOL isPrimary; @property (nonatomic, assign) Ivar swiftIvar; // getter and setter names @property (nonatomic, copy) NSString *getterName; @property (nonatomic, copy) NSString *setterName; @property (nonatomic) SEL getterSel; @property (nonatomic) SEL setterSel; - (RLMProperty *)copyWithNewName:(NSString *)name; @end @interface RLMProperty (Dynamic) /** This method is useful only in specialized circumstances, for example, in conjunction with +[RLMObjectSchema initWithClassName:objectClass:properties:]. If you are simply building an app on Realm, it is not recommened to use this method. Initialize an RLMProperty @warning This method is useful only in specialized circumstances. @param name The property name. @param type The property type. @param objectClassName The object type used for Object and Array types. @param linkOriginPropertyName The property name of the origin of a link. Used for linking objects properties. @return An initialized instance of RLMProperty. */ - (instancetype)initWithName:(NSString *)name type:(RLMPropertyType)type objectClassName:(nullable NSString *)objectClassName linkOriginPropertyName:(nullable NSString *)linkOriginPropertyName indexed:(BOOL)indexed optional:(BOOL)optional; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMRealmConfiguration_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2015 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMSchema; NS_ASSUME_NONNULL_BEGIN @interface RLMRealmConfiguration () @property (nonatomic, readwrite) bool cache; @property (nonatomic, readwrite) bool dynamic; @property (nonatomic, readwrite) bool disableFormatUpgrade; @property (nonatomic, copy, nullable) RLMSchema *customSchema; // Get the default confiugration without copying it + (RLMRealmConfiguration *)rawDefaultConfiguration; + (void)resetRealmConfigurationState; @end // Get a path in the platform-appropriate documents directory with the given filename FOUNDATION_EXTERN NSString *RLMRealmPathForFile(NSString *fileName); FOUNDATION_EXTERN NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *mainBundleIdentifier); NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMRealm_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMFastEnumerator; NS_ASSUME_NONNULL_BEGIN // Disable syncing files to disk. Cannot be re-enabled. Use only for tests. FOUNDATION_EXTERN void RLMDisableSyncToDisk(); FOUNDATION_EXTERN NSData * _Nullable RLMRealmValidatedEncryptionKey(NSData *key); // Translate an in-flight exception resulting from opening a SharedGroup to // an NSError or NSException (if error is nil) void RLMRealmTranslateException(NSError **error); // RLMRealm private members @interface RLMRealm () @property (nonatomic, readonly) BOOL dynamic; @property (nonatomic, readwrite) RLMSchema *schema; + (void)resetRealmState; - (void)registerEnumerator:(RLMFastEnumerator *)enumerator; - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator; - (void)detachAllEnumerators; - (void)sendNotifications:(RLMNotification)notification; - (void)verifyThread; - (void)verifyNotificationsAreSupported; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMResults_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import @class RLMObjectSchema; NS_ASSUME_NONNULL_BEGIN @interface RLMResults () @property (nonatomic, readonly, getter=isAttached) BOOL attached; + (instancetype)emptyDetachedResults; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSchema_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN @class RLMRealm; // // RLMSchema private interface // @interface RLMSchema () /** Returns an `RLMSchema` containing only the given `RLMObject` subclasses. @param classes The classes to be included in the schema. @return An `RLMSchema` containing only the given classes. */ + (instancetype)schemaWithObjectClasses:(NSArray *)classes; @property (nonatomic, readwrite, copy) NSArray *objectSchema; // schema based on runtime objects + (instancetype)sharedSchema; // schema based upon all currently registered object classes + (instancetype)partialSharedSchema; // class for string + (nullable Class)classForString:(NSString *)className; + (nullable RLMObjectSchema *)sharedSchemaForClass:(Class)cls; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncConfiguration_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, RLMSyncStopPolicy) { RLMSyncStopPolicyImmediately, RLMSyncStopPolicyLiveIndefinitely, RLMSyncStopPolicyAfterChangesUploaded, }; @interface RLMSyncConfiguration () @property (nonatomic, readwrite) RLMSyncStopPolicy stopPolicy; // Internal-only APIs @property (nullable, nonatomic) NSURL *customFileURL; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncPermissionChange_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import "RLMSyncPermissionChange.h" NS_ASSUME_NONNULL_BEGIN @interface RLMSyncPermissionChange() @property (readwrite) NSString *id; @property (readwrite) NSDate *createdAt; @property (readwrite) NSDate *updatedAt; @property (nullable, readwrite) NSNumber *statusCode; @property (nullable, readwrite) NSString *statusMessage; @property (readwrite) NSString *realmUrl; @property (readwrite) NSString *userId; @property (nullable, readwrite) NSNumber *mayRead; @property (nullable, readwrite) NSNumber *mayWrite; @property (nullable, readwrite) NSNumber *mayManage; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncPermissionOfferResponse_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import "RLMSyncPermissionOfferResponse.h" NS_ASSUME_NONNULL_BEGIN @interface RLMSyncPermissionOfferResponse() @property (readwrite) NSString *id; @property (readwrite) NSDate *createdAt; @property (readwrite) NSDate *updatedAt; @property (nullable, readwrite) NSNumber *statusCode; @property (nullable, readwrite) NSString *statusMessage; @property (readwrite) NSString *token; @property (nullable, readwrite) NSString *realmUrl; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncPermissionOffer_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import "RLMSyncPermissionOffer.h" NS_ASSUME_NONNULL_BEGIN @interface RLMSyncPermissionOffer() @property (readwrite) NSString *id; @property (readwrite) NSDate *createdAt; @property (readwrite) NSDate *updatedAt; @property (nullable, readwrite) NSNumber *statusCode; @property (nullable, readwrite) NSString *statusMessage; @property (nullable, readwrite) NSString *token; @property (readwrite) NSString *realmUrl; @property (readwrite) BOOL mayRead; @property (readwrite) BOOL mayWrite; @property (readwrite) BOOL mayManage; @property (nullable, readwrite) NSDate *expiresAt; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncPermission_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import "RLMSyncPermission.h" NS_ASSUME_NONNULL_BEGIN @interface RLMSyncPermission() @property (readwrite) NSDate *updatedAt; @property (readwrite) NSString *userId; @property (readwrite) NSString *path; @property (readwrite) BOOL mayRead; @property (readwrite) BOOL mayWrite; @property (readwrite) BOOL mayManage; @end NS_ASSUME_NONNULL_END ================================================ FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncUtil_Private.h ================================================ //////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #import #import #import #import @class RLMSyncUser; typedef void(^RLMSyncCompletionBlock)(NSError * _Nullable, NSDictionary * _Nullable); typedef void(^RLMSyncBasicErrorReportingBlock)(NSError * _Nullable); typedef NSString* RLMServerPath; NS_ASSUME_NONNULL_BEGIN @interface RLMRealmConfiguration (RealmSync) + (instancetype)managementConfigurationForUser:(RLMSyncUser *)user; + (instancetype)permissionConfigurationForUser:(RLMSyncUser *)user; @end extern RLMIdentityProvider const RLMIdentityProviderAccessToken; extern RLMIdentityProvider const RLMIdentityProviderRealm; extern NSString *const kRLMSyncAppIDKey; extern NSString *const kRLMSyncDataKey; extern NSString *const kRLMSyncErrorJSONKey; extern NSString *const kRLMSyncErrorStatusCodeKey; extern NSString *const kRLMSyncIdentityKey; extern NSString *const kRLMSyncPasswordKey; extern NSString *const kRLMSyncPathKey; extern NSString *const kRLMSyncProviderKey; extern NSString *const kRLMSyncRegisterKey; extern NSString *const kRLMSyncUnderlyingErrorKey; /// Convert sync management object status code (nil, 0 and others) to /// RLMSyncManagementObjectStatus enum FOUNDATION_EXTERN RLMSyncManagementObjectStatus RLMMakeSyncManagementObjectStatus(NSNumber * _Nullable statusCode); #define RLM_SYNC_UNINITIALIZABLE \ - (instancetype)init __attribute__((unavailable("This type cannot be created directly"))); \ + (instancetype)new __attribute__((unavailable("This type cannot be created directly"))); NS_ASSUME_NONNULL_END /// A macro to parse a string out of a JSON dictionary, or return nil. #define RLM_SYNC_PARSE_STRING_OR_ABORT(json_macro_val, key_macro_val, prop_macro_val) \ { \ id data = json_macro_val[key_macro_val]; \ if (![data isKindOfClass:[NSString class]]) { return nil; } \ self.prop_macro_val = data; \ } \ #define RLM_SYNC_PARSE_OPTIONAL_STRING(json_macro_val, key_macro_val, prop_macro_val) \ { \ id data = json_macro_val[key_macro_val]; \ if (![data isKindOfClass:[NSString class]]) { data = nil; } \ self.prop_macro_val = data; \ } \ /// A macro to parse a double out of a JSON dictionary, or return nil. #define RLM_SYNC_PARSE_DOUBLE_OR_ABORT(json_macro_val, key_macro_val, prop_macro_val) \ { \ id data = json_macro_val[key_macro_val]; \ if (![data isKindOfClass:[NSNumber class]]) { return nil; } \ self.prop_macro_val = [data doubleValue]; \ } \ /// A macro to build a sub-model out of a JSON dictionary, or return nil. #define RLM_SYNC_PARSE_MODEL_OR_ABORT(json_macro_val, key_macro_val, class_macro_val, prop_macro_val) \ { \ id raw = json_macro_val[key_macro_val]; \ if (![raw isKindOfClass:[NSDictionary class]]) { return nil; } \ id model = [[class_macro_val alloc] initWithDictionary:raw]; \ if (!model) { return nil; } \ self.prop_macro_val = model; \ } \ #define RLM_SYNC_PARSE_OPTIONAL_MODEL(json_macro_val, key_macro_val, class_macro_val, prop_macro_val) \ { \ id model; \ id raw = json_macro_val[key_macro_val]; \ if (![raw isKindOfClass:[NSDictionary class]]) { model = nil; } \ else { model = [[class_macro_val alloc] initWithDictionary:raw]; } \ self.prop_macro_val = model; \ } \ ================================================ FILE: External/Realm/Realm.framework/Versions/A/Resources/CHANGELOG.md ================================================ 2.4.4 Release notes (2017-03-13) ============================================================= ### API Breaking Changes * None. ### Enhancements * Add `(RLM)SyncPermission` class to allow reviewing access permissions for Realms. Requires any edition of the Realm Object Server 1.1.0 or later. * Further reduce the number of files opened per thread-specific Realm on macOS, iOS and watchOS. ### Bugfixes * Fix a crash that could occur if new Realm instances were created while the application was exiting. * Fix a bug that could lead to bad version number errors when delivering change notifications. * Fix a potential use-after-free bug when checking validity of results. * Fix an issue where a sync session might not close properly if it receives an error while being torn down. * Fix some issues where a sync session might not reconnect to the server properly or get into an inconsistent state if revived after invalidation. * Fix an issue where notifications might not fire when the children of an observed object are changed. * Fix an issue where progress notifications on sync sessions might incorrectly report out-of-date values. * Fix an issue where multiple threads accessing encrypted data could result in corrupted data or crashes. * Fix an issue where certain `LIKE` queries could hang. * Fix an issue where `-[RLMRealm writeCopyToURL:encryptionKey:error]` could create a corrupt Realm file. * Fix an issue where incrementing a synced Realm's schema version without actually changing the schema could cause a crash. 2.4.3 Release notes (2017-02-20) ============================================================= ### API Breaking Changes * None. ### Enhancements * Avoid copying copy-on-write data structures, which can grow the file, when the write does not actually change existing values. * Improve performance of deleting all objects in an RLMResults. * Reduce the number of files opened per thread-specific Realm on macOS. * Improve startup performance with large numbers of `RLMObject`/`Object` subclasses. ### Bugfixes * Fix synchronized Realms not downloading remote changes when an access token expires and there are no local changes to upload. * Fix an issue where values set on a Realm object using `setValue(value:, forKey:)` that were not themselves Realm objects were not properly converted into Realm objects or checked for validity. * Fix an issue where `-[RLMSyncUser sessionForURL:]` could erroneously return a non-nil value when passed in an invalid URL. * `SyncSession.Progress.fractionTransferred` now returns 1 if there are no transferrable bytes. * Fix sync progress notifications registered on background threads by always dispatching on a dedicated background queue. * Fix compilation issues with Xcode 8.3 beta 2. * Fix incorrect sync progress notification values for Realms originally created using a version of Realm prior to 2.3.0. * Fix LLDB integration to be able to display summaries of `RLMResults` once more. * Reject Swift properties with names which cause them to fall in to ARC method families rather than crashing when they are accessed. * Fix sorting by key path when the declared property order doesn't match the order of properties in the Realm file, which can happen when properties are added in different schema versions. 2.4.2 Release notes (2017-01-30) ============================================================= ### Bugfixes * Fix an issue where RLMRealm instances could end up in the autorelease pool for other threads. 2.4.1 Release notes (2017-01-27) ============================================================= ### Bugfixes * Fix an issue where authentication tokens were not properly refreshed automatically before expiring. 2.4.0 Release notes (2017-01-26) ============================================================= This release drops support for compiling with Swift 2.x. Swift 3.0.0 is now the minimum Swift version supported. ### API Breaking Changes * None. ### Enhancements * Add change notifications for individual objects with an API similar to that of collection notifications. ### Bugfixes * Fix Realm Objective-C compilation errors with Xcode 8.3 beta 1. * Fix several error handling issues when renewing expired authentication tokens for synchronized Realms. * Fix a race condition leading to bad_version exceptions being thrown in Realm's background worker thread. 2.3.0 Release notes (2017-01-19) ============================================================= ### Sync Breaking Changes * Make `PermissionChange`'s `id` property a primary key. ### API Breaking Changes * None. ### Enhancements * Add `SyncPermissionOffer` and `SyncPermissionOfferResponse` classes to allow creating and accepting permission change events to synchronized Realms between different users. * Support monitoring sync transfer progress by registering notification blocks on `SyncSession`. Specify the transfer direction (`.upload`/`.download`) and mode (`.reportIndefinitely`/`.forCurrentlyOutstandingWork`) to monitor. ### Bugfixes * Fix a call to `commitWrite(withoutNotifying:)` committing a transaction that would not have triggered a notification incorrectly skipping the next notification. * Fix incorrect results and crashes when conflicting object insertions are merged by the synchronization mechanism when there is a collection notification registered for that object type. 2.2.0 Release notes (2017-01-12) ============================================================= ### Sync Breaking Changes (In Beta) * Sync-related error reporting behavior has been changed. Errors not related to a particular user or session are only reported if they are classed as 'fatal' by the underlying sync engine. * Added `RLMSyncErrorClientResetError` to `RLMSyncError` enum. ### API Breaking Changes * The following Objective-C APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:------------------------------------------------------------|:------------------------------------------------------------| | `-[RLMArray sortedResultsUsingProperty:]` | `-[RLMArray sortedResultsUsingKeyPath:]` | | `-[RLMCollection sortedResultsUsingProperty:]` | `-[RLMCollection sortedResultsUsingKeyPath:]` | | `-[RLMResults sortedResultsUsingProperty:]` | `-[RLMResults sortedResultsUsingKeyPath:]` | | `+[RLMSortDescriptor sortDescriptorWithProperty:ascending]` | `+[RLMSortDescriptor sortDescriptorWithKeyPath:ascending:]` | | `RLMSortDescriptor.property` | `RLMSortDescriptor.keyPath` | * The following Swift APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:------------------------------------------------------|:-------------------------------------------------| | `LinkingObjects.sorted(byProperty:ascending:)` | `LinkingObjects.sorted(byKeyPath:ascending:)` | | `List.sorted(byProperty:ascending:)` | `List.sorted(byKeyPath:ascending:)` | | `RealmCollection.sorted(byProperty:ascending:)` | `RealmCollection.sorted(byKeyPath:ascending:)` | | `Results.sorted(byProperty:ascending:)` | `Results.sorted(byKeyPath:ascending:)` | | `SortDescriptor(property:ascending:)` | `SortDescriptor(keyPath:ascending:)` | | `SortDescriptor.property` | `SortDescriptor.keyPath` | ### Enhancements * Introduce APIs for safely passing objects between threads. Create a thread-safe reference to a thread-confined object by passing it to the `+[RLMThreadSafeReference referenceWithThreadConfined:]`/`ThreadSafeReference(to:)` constructor, which you can then safely pass to another thread to resolve in the new Realm with `-[RLMRealm resolveThreadSafeReference:]`/`Realm.resolve(_:)`. * Realm collections can now be sorted by properties over to-one relationships. * Optimized `CONTAINS` queries to use Boyer-Moore algorithm (around 10x speedup on large datasets). ### Bugfixes * Setting `deleteRealmIfMigrationNeeded` now also deletes the Realm if a file format migration is required, such as when moving from a file last accessed with Realm 0.x to 1.x, or 1.x to 2.x. * Fix queries containing nested `SUBQUERY` expressions. * Fix spurious incorrect thread exceptions when a thread id happens to be reused while an RLMRealm instance from the old thread still exists. * Fixed various bugs in aggregate methods (max, min, avg, sum). 2.1.2 Release notes (2016--12-19) ============================================================= This release adds binary versions of Swift 3.0.2 frameworks built with Xcode 8.2. ### Sync Breaking Changes (In Beta) * Rename occurences of "iCloud" with "CloudKit" in APIs and comments to match naming in the Realm Object Server. ### API Breaking Changes * None. ### Enhancements * Add support for 'LIKE' queries (wildcard matching). ### Bugfixes * Fix authenticating with CloudKit. * Fix linker warning about "Direct access to global weak symbol". 2.1.1 Release notes (2016-12-02) ============================================================= ### Enhancements * Add `RealmSwift.ObjectiveCSupport.convert(object:)` methods to help write code that interoperates between Realm Objective-C and Realm Swift APIs. * Throw exceptions when opening a Realm with an incorrect configuration, like: * `readOnly` set with a sync configuration. * `readOnly` set with a migration block. * migration block set with a sync configuration. * Greatly improve performance of write transactions which make a large number of changes to indexed properties, including the automatic migration when opening files written by Realm 1.x. ### Bugfixes * Reset sync metadata Realm in case of decryption error. * Fix issue preventing using synchronized Realms in Xcode Playgrounds. * Fix assertion failure when migrating a model property from object type to `RLMLinkingObjects` type. * Fix a `LogicError: Bad version number` exception when using `RLMResults` with no notification blocks and explicitly called `-[RLMRealm refresh]` from that thread. * Logged-out users are no longer returned from `+[RLMSyncUser currentUser]` or `+[RLMSyncUser allUsers]`. * Fix several issues which could occur when the 1001st object of a given type was created or added to an RLMArray/List, including crashes when rerunning existing queries and possibly data corruption. * Fix a potential crash when the application exits due to a race condition in the destruction of global static variables. * Fix race conditions when waiting for sync uploads or downloads to complete which could result in crashes or the callback being called too early. 2.1.0 Release notes (2016-11-18) ============================================================= ### Sync Breaking Changes (In Beta) * None. ### API breaking changes * None. ### Enhancements * Add the ability to skip calling specific notification blocks when committing a write transaction. ### Bugfixes * Deliver collection notifications when beginning a write transaction which advances the read version of a Realm (previously only Realm-level notifications were sent). * Fix some scenarios which would lead to inconsistent states when using collection notifications. * Fix several race conditions in the notification functionality. * Don't send Realm change notifications when canceling a write transaction. 2.0.4 Release notes (2016-11-14) ============================================================= ### Sync Breaking Changes (In Beta) * Remove `RLMAuthenticationActions` and replace `+[RLMSyncCredential credentialWithUsername:password:actions:]` with `+[RLMSyncCredential credentialsWithUsername:password:register:]`. * Rename `+[RLMSyncUser authenticateWithCredential:]` to `+[RLMSyncUser logInWithCredentials:]`. * Rename "credential"-related types and methods to `RLMSyncCredentials`/`SyncCredentials` and consistently refer to credentials in the plural form. * Change `+[RLMSyncUser all]` to return a dictionary of identifiers to users and rename to: * `+[RLMSyncUser allUsers]` in Objective-C. * `SyncUser.allUsers()` in Swift 2. * `SyncUser.all` in Swift 3. * Rename `SyncManager.sharedManager()` to `SyncManager.shared` in Swift 3. * Change `Realm.Configuration.syncConfiguration` to take a `SyncConfiguration` struct rather than a named tuple. * `+[RLMSyncUser logInWithCredentials:]` now invokes its callback block on a background queue. ### API breaking changes * None. ### Enhancements * Add `+[RLMSyncUser currentUser]`. * Add the ability to change read, write and management permissions for synchronized Realms using the management Realm obtained via the `-[RLMSyncUser managementRealmWithError:]` API and the `RLMSyncPermissionChange` class. ### Bugfixes * None. 2.0.3 Release notes (2016-10-27) ============================================================= This release adds binary versions of Swift 3.0.1 frameworks built with Xcode 8.1 GM seed. ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a `BadVersion` exception caused by a race condition when delivering collection change notifications. * Fix an assertion failure when additional model classes are added and `deleteRealmIfMigrationNeeded` is enabled. * Fix a `BadTransactLog` exception when deleting an `RLMResults` in a synced Realm. * Fix an assertion failure when a write transaction is in progress at the point of process termination. * Fix a crash that could occur when working with a `RLMLinkingObject` property of an unmanaged object. 2.0.2 Release notes (2016-10-05) ============================================================= This release is not protocol-compatible with previous version of the Realm Mobile Platform. ### API breaking changes * Rename Realm Swift's `User` to `SyncUser` to make clear that it relates to the Realm Mobile Platform, and to avoid potential conflicts with other `User` types. ### Bugfixes * Fix Realm headers to be compatible with pre-C++11 dialects of Objective-C++. * Fix incorrect merging of RLMArray/List changes when objects with the same primary key are created on multiple devices. * Fix bad transaction log errors after deleting objects on a different device. * Fix a BadVersion error when a background worker finishes running while older results from that worker are being delivered to a different thread. 2.0.1 Release notes (2016-09-29) ============================================================= ### Bugfixes * Fix an assertion failure when opening a Realm file written by a 1.x version of Realm which has an indexed nullable int or bool property. 2.0.0 Release notes (2016-09-27) ============================================================= This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. ### API breaking changes * By popular demand, `RealmSwift.Error` has been moved from the top-level namespace into a `Realm` extension and is now `Realm.Error`, so that it no longer conflicts with `Swift.Error`. * Files written by Realm 2.0 cannot be read by 1.x or earlier versions. Old files can still be opened. ### Enhancements * The .log, .log_a and .log_b files no longer exist and the state tracked in them has been moved to the main Realm file. This reduces the number of open files needed by Realm, improves performance of both opening and writing to Realms, and eliminates a small window where committing write transactions would prevent other processes from opening the file. ### Bugfixes * Fix an assertion failure when sorting by zero properties. * Fix a mid-commit crash in one process also crashing all other processes with the same Realm open. * Properly initialize new nullable float and double properties added to existing objects to null rather than 0. * Fix a stack overflow when objects with indexed string properties had very long common prefixes. * Fix a race condition which could lead to crashes when using async queries or collection notifications. * Fix a bug which could lead to incorrect state when an object which links to itself is deleted from the Realm. 1.1.0 Release notes (2016-09-16) ============================================================= This release brings official support for Xcode 8, Swift 2.3 and Swift 3.0. Prebuilt frameworks are now built with Xcode 7.3.1 and Xcode 8.0. ### API breaking changes * Deprecate `migrateRealm:` in favor of new `performMigrationForConfiguration:error:` method that follows Cocoa's NSError conventions. * Fix issue where `RLMResults` used `id `instead of its generic type as the return type of subscript. ### Enhancements * Improve error message when using NSNumber incorrectly in Swift models. * Further reduce the download size of the prebuilt static libraries. * Improve sort performance, especially on non-nullable columns. * Allow partial initialization of object by `initWithValue:`, deferring required property checks until object is added to Realm. ### Bugfixes * Fix incorrect truncation of the constant value for queries of the form `column < value` for `float` and `double` columns. * Fix crash when an aggregate is accessed as an `Int8`, `Int16`, `Int32`, or `Int64`. * Fix a race condition that could lead to a crash if an RLMArray or List was deallocated on a different thread than it was created on. * Fix a crash when the last reference to an observed object is released from within the observation. * Fix a crash when `initWithValue:` is used to create a nested object for a class with an uninitialized schema. * Enforce uniqueness for `RealmOptional` primary keys when using the `value` setter. 1.0.2 Release notes (2016-07-13) ============================================================= ### API breaking changes * Attempting to add an object with no properties to a Realm now throws rather than silently doing nothing. ### Enhancements * Swift: A `write` block may now `throw`, reverting any changes already made in the transaction. * Reduce address space used when committing write transactions. * Significantly reduce the download size of prebuilt binaries and slightly reduce the final size contribution of Realm to applications. * Improve performance of accessing RLMArray properties and creating objects with List properties. ### Bugfixes * Fix a crash when reading the shared schema from an observed Swift object. * Fix crashes or incorrect results when passing an array of values to `createOrUpdate` after reordering the class's properties. * Ensure that the initial call of a Results notification block is always passed .Initial even if there is a write transaction between when the notification is added and when the first notification is delivered. * Fix a crash when deleting all objects in a Realm while fast-enumerating query results from that Realm. * Handle EINTR from flock() rather than crashing. * Fix incorrect behavior following a call to `[RLMRealm compact]`. * Fix live updating and notifications for Results created from a predicate involving an inverse relationship to be triggered when an object at the other end of the relationship is modified. 1.0.1 Release notes (2016-06-12) ============================================================= ### API breaking changes * None. ### Enhancements * Significantly improve performance of opening Realm files, and slightly improve performance of committing write transactions. ### Bugfixes * Swift: Fix an error thrown when trying to create or update `Object` instances via `add(:_update:)` with a primary key property of type `RealmOptional`. * Xcode playground in Swift release zip now runs successfully. * The `key` parameter of `Realm.objectForPrimaryKey(_:key:)`/ `Realm.dynamicObjectForPrimaryKey(_:key:)` is now marked as optional. * Fix a potential memory leak when closing Realms after a Realm file has been opened on multiple threads which are running in active run loops. * Fix notifications breaking on tvOS after a very large number of write transactions have been committed. * Fix a "Destruction of mutex in use" assertion failure after an error while opening a file. * Realm now throws an exception if an `Object` subclass is defined with a managed Swift `lazy` property. Objects with ignored `lazy` properties should now work correctly. * Update the LLDB script to work with recent changes to the implementation of `RLMResults`. * Fix an assertion failure when a Realm file is deleted while it is still open, and then a new Realm is opened at the same path. Note that this is still not a supported scenario, and may break in other ways. 1.0.0 Release notes (2016-05-25) ============================================================= No changes since 0.103.2. 0.103.2 Release notes (2016-05-24) ============================================================= ### API breaking changes * None. ### Enhancements * Improve the error messages when an I/O error occurs in `writeCopyToURL`. ### Bugfixes * Fix an assertion failure which could occur when opening a Realm after opening that Realm failed previously in some specific ways in the same run of the application. * Reading optional integers, floats, and doubles from within a migration block now correctly returns `nil` rather than 0 when the stored value is `nil`. 0.103.1 Release notes (2016-05-19) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a bug that sometimes resulted in a single object's NSData properties changing from `nil` to a zero-length non-`nil` NSData when a different object of the same type was deleted. 0.103.0 Release notes (2016-05-18) ============================================================= ### API breaking changes * All functionality deprecated in previous releases has been removed entirely. * Support for Xcode 6.x & Swift prior to 2.2 has been completely removed. * `RLMResults`/`Results` now become empty when a `RLMArray`/`List` or object they depend on is deleted, rather than throwing an exception when accessed. * Migrations are no longer run when `deleteRealmIfMigrationNeeded` is set, recreating the file instead. ### Enhancements * Added `invalidated` properties to `RLMResults`/`Results`, `RLMLinkingObjects`/`LinkingObjects`, `RealmCollectionType` and `AnyRealmCollection`. These properties report whether the Realm the object is associated with has been invalidated. * Some `NSError`s created by Realm now have more descriptive user info payloads. ### Bugfixes * None. 0.102.1 Release notes (2016-05-13) ============================================================= ### API breaking changes * None. ### Enhancements * Return `RLMErrorSchemaMismatch` error rather than the more generic `RLMErrorFail` when a migration is required. * Improve the performance of allocating instances of `Object` subclasses that have `LinkingObjects` properties. ### Bugfixes * `RLMLinkingObjects` properties declared in Swift subclasses of `RLMObject` now work correctly. * Fix an assertion failure when deleting all objects of a type, inserting more objects, and then deleting some of the newly inserted objects within a single write transaction when there is an active notification block for a different object type which links to the objects being deleted. * Fix crashes and/or incorrect results when querying over multiple levels of `LinkingObjects` properties. * Fix opening read-only Realms on multiple threads at once. * Fix a `BadTransactLog` exception when storing dates before the unix epoch (1970-01-01). 0.102.0 Release notes (2016-05-09) ============================================================= ### API breaking changes * None. ### Enhancements * Add a method to rename properties during migrations: * Swift: `Migration.renamePropertyForClass(_:oldName:newName:)` * Objective-C: `-[RLMMigration renamePropertyForClass:oldName:newName:]` * Add `deleteRealmIfMigrationNeeded` to `RLMRealmConfiguration`/`Realm.Configuration`. When this is set to `true`, the Realm file will be automatically deleted and recreated when there is a schema mismatch rather than migrated to the new schema. ### Bugfixes * Fix `BETWEEN` queries that traverse `RLMArray`/`List` properties to ensure that a single related object satisfies the `BETWEEN` criteria, rather than allowing different objects in the array to satisfy the lower and upper bounds. * Fix a race condition when a Realm is opened on one thread while it is in the middle of being closed on another thread which could result in crashes. * Fix a bug which could result in changes made on one thread being applied incorrectly on other threads when those threads are refreshed. * Fix crash when migrating to the new date format introduced in 0.101.0. * Fix crash when querying inverse relationships when objects are deleted. 0.101.0 Release notes (2016-05-04) ============================================================= ### API breaking changes * Files written by this version of Realm cannot be read by older versions of Realm. Existing files will automatically be upgraded when they are opened. ### Enhancements * Greatly improve performance of collection change calculation for complex object graphs, especially for ones with cycles. * NSDate properties now support nanoseconds precision. * Opening a single Realm file on multiple threads now shares a single memory mapping of the file for all threads, significantly reducing the memory required to work with large files. * Crashing while in the middle of a write transaction no longer blocks other processes from performing write transactions on the same file. * Improve the performance of refreshing a Realm (including via autorefresh) when there are live Results/RLMResults objects for that Realm. ### Bugfixes * Fix an assertion failure of "!more_before || index >= std::prev(it)->second)" in `IndexSet::do_add()`. * Fix a crash when an `RLMArray` or `List` object is destroyed from the wrong thread. 0.100.0 Release notes (2016-04-29) ============================================================= ### API breaking changes * `-[RLMObject linkingObjectsOfClass:forProperty]` and `Object.linkingObjects(_:forProperty:)` are deprecated in favor of properties of type `RLMLinkingObjects` / `LinkingObjects`. ### Enhancements * The automatically-maintained inverse direction of relationships can now be exposed as properties of type `RLMLinkingObjects` / `LinkingObjects`. These properties automatically update to reflect the objects that link to the target object, can be used in queries, and can be filtered like other Realm collection types. * Queries that compare objects for equality now support multi-level key paths. ### Bugfixes * Fix an assertion failure when a second write transaction is committed after a write transaction deleted the object containing an RLMArray/List which had an active notification block. * Queries that compare `RLMArray` / `List` properties using != now give the correct results. 0.99.1 Release notes (2016-04-26) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a scenario that could lead to the assertion failure "m_advancer_sg->get_version_of_current_transaction() == new_notifiers.front()->version()". 0.99.0 Release notes (2016-04-22) ============================================================= ### API breaking changes * Deprecate properties of type `id`/`AnyObject`. This type was rarely used, rarely useful and unsupported in every other Realm binding. * The block for `-[RLMArray addNotificationBlock:]` and `-[RLMResults addNotificationBlock:]` now takes another parameter. * The following Objective-C APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:-------------------------------------------------------|:------------------------------------------------------| | `-[RLMRealm removeNotification:]` | `-[RLMNotificationToken stop]` | | `RLMRealmConfiguration.path` | `RLMRealmConfiguration.fileURL` | | `RLMRealm.path` | `RLMRealmConfiguration.fileURL` | | `RLMRealm.readOnly` | `RLMRealmConfiguration.readOnly` | | `+[RLMRealm realmWithPath:]` | `+[RLMRealm realmWithURL:]` | | `+[RLMRealm writeCopyToPath:error:]` | `+[RLMRealm writeCopyToURL:encryptionKey:error:]` | | `+[RLMRealm writeCopyToPath:encryptionKey:error:]` | `+[RLMRealm writeCopyToURL:encryptionKey:error:]` | | `+[RLMRealm schemaVersionAtPath:error:]` | `+[RLMRealm schemaVersionAtURL:encryptionKey:error:]` | | `+[RLMRealm schemaVersionAtPath:encryptionKey:error:]` | `+[RLMRealm schemaVersionAtURL:encryptionKey:error:]` | * The following Swift APIs have been deprecated in favor of newer or preferred versions: | Deprecated API | New API | |:----------------------------------------------|:-----------------------------------------| | `Realm.removeNotification(_:)` | `NotificationToken.stop()` | | `Realm.Configuration.path` | `Realm.Configuration.fileURL` | | `Realm.path` | `Realm.Configuration.fileURL` | | `Realm.readOnly` | `Realm.Configuration.readOnly` | | `Realm.writeCopyToPath(_:encryptionKey:)` | `Realm.writeCopyToURL(_:encryptionKey:)` | | `schemaVersionAtPath(_:encryptionKey:error:)` | `schemaVersionAtURL(_:encryptionKey:)` | ### Enhancements * Add information about what rows were added, removed, or modified to the notifications sent to the Realm collections. * Improve error when illegally appending to an `RLMArray` / `List` property from a default value or the standalone initializer (`init()`) before the schema is ready. ### Bugfixes * Fix a use-after-free when an associated object's dealloc method is used to remove observers from an RLMObject. * Fix a small memory leak each time a Realm file is opened. * Return a recoverable `RLMErrorAddressSpaceExhausted` error rather than crash when there is insufficient available address space on Realm initialization or write commit. 0.98.8 Release notes (2016-04-15) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fixed a bug that caused some encrypted files created using `-[RLMRealm writeCopyToPath:encryptionKey:error:]` to fail to open. 0.98.7 Release notes (2016-04-13) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Mark further initializers in Objective-C as NS_DESIGNATED_INITIALIZER to prevent that these aren't correctly defined in Swift Object subclasses, which don't qualify for auto-inheriting the required initializers. * `-[RLMResults indexOfObjectWithPredicate:]` now returns correct results for `RLMResults` instances that were created by filtering an `RLMArray`. * Adjust how RLMObjects are destroyed in order to support using an associated object on an RLMObject to remove KVO observers from that RLMObject. * `-[RLMResults indexOfObjectWithPredicate:]` now returns the index of the first matching object for a sorted `RLMResults`, matching its documented behavior. * Fix a crash when canceling a transaction that set a relationship. * Fix a crash when a query referenced a deleted object. 0.98.6 Release notes (2016-03-25) ============================================================= Prebuilt frameworks are now built with Xcode 7.3. ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix running unit tests on iOS simulators and devices with Xcode 7.3. 0.98.5 Release notes (2016-03-14) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix a crash when opening a Realm on 32-bit iOS devices. 0.98.4 Release notes (2016-03-10) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Properly report changes made by adding an object to a Realm with addOrUpdate:/createOrUpdate: to KVO observers for existing objects with that primary key. * Fix crashes and assorted issues when a migration which added object link properties is rolled back due to an error in the migration block. * Fix assertion failures when deleting objects within a migration block of a type which had an object link property added in that migration. * Fix an assertion failure in `Query::apply_patch` when updating certain kinds of queries after a write transaction is committed. 0.98.3 Release notes (2016-02-26) ============================================================= ### Enhancements * Initializing the shared schema is 3x faster. ### Bugfixes * Using Realm Objective-C from Swift while having Realm Swift linked no longer causes that the declared `ignoredProperties` are not taken into account. * Fix assertion failures when rolling back a migration which added Object link properties to a class. * Fix potential errors when cancelling a write transaction which modified multiple `RLMArray`/`List` properties. * Report the correct value for inWriteTransaction after attempting to commit a write transaction fails. * Support CocoaPods 1.0 beginning from prerelease 1.0.0.beta.4 while retaining backwards compatibility with 0.39. 0.98.2 Release notes (2016-02-18) ============================================================= ### API breaking changes * None. ### Enhancements * Aggregate operations (`ANY`, `NONE`, `@count`, `SUBQUERY`, etc.) are now supported for key paths that begin with an object relationship so long as there is a `RLMArray`/`List` property at some point in a key path. * Predicates of the form `%@ IN arrayProperty` are now supported. ### Bugfixes * Use of KVC collection operators on Swift collection types no longer throws an exception. * Fix reporting of inWriteTransaction in notifications triggered by `beginWriteTransaction`. * The contents of `List` and `Optional` properties are now correctly preserved when copying a Swift object from one Realm to another, and performing other operations that result in a Swift object graph being recursively traversed from Objective-C. * Fix a deadlock when queries are performed within a Realm notification block. * The `ANY` / `SOME` / `NONE` qualifiers are now required in comparisons involving a key path that traverse a `RLMArray`/`List` property. Previously they were only required if the first key in the key path was an `RLMArray`/`List` property. * Fix several scenarios where the default schema would be initialized incorrectly if the first Realm opened used a restricted class subset (via `objectClasses`/`objectTypes`). 0.98.1 Release notes (2016-02-10) ============================================================= ### Bugfixes * Fix crashes when deleting an object containing an `RLMArray`/`List` which had previously been queried. * Fix a crash when deleting an object containing an `RLMArray`/`List` with active notification blocks. * Fix duplicate file warnings when building via CocoaPods. * Fix crash or incorrect results when calling `indexOfObject:` on an `RLMResults` derived from an `RLMArray`. 0.98.0 Release notes (2016-02-04) ============================================================= ### API breaking changes * `+[RLMRealm realmWithPath:]`/`Realm.init(path:)` now inherits from the default configuration. * Swift 1.2 is no longer supported. ### Enhancements * Add `addNotificationBlock` to `RLMResults`, `Results`, `RLMArray`, and `List`, which calls the given block whenever the collection changes. * Do a lot of the work for keeping `RLMResults`/`Results` up-to-date after write transactions on a background thread to help avoid blocking the main thread. * `NSPredicate`'s `SUBQUERY` operator is now supported. It has the following limitations: * `@count` is the only operator that may be applied to the `SUBQUERY` expression. * The `SUBQUERY(…).@count` expression must be compared with a constant. * Correlated subqueries are not yet supported. ### Bugfixes * None. 0.97.1 Release notes (2016-01-29) ============================================================= ### API breaking changes * None. ### Enhancements * Swift: Added `Error` enum allowing to catch errors e.g. thrown on initializing `RLMRealm`/`Realm` instances. * Fail with `RLMErrorFileNotFound` instead of the more generic `RLMErrorFileAccess`, if no file was found when a realm was opened as read-only or if the directory part of the specified path was not found when a copy should be written. * Greatly improve performance when deleting objects with one or more indexed properties. * Indexing `BOOL`/`Bool` and `NSDate` properties are now supported. * Swift: Add support for indexing optional properties. ### Bugfixes * Fix incorrect results or crashes when using `-[RLMResults setValue:forKey:]` on an RLMResults which was filtered on the key being set. * Fix crashes when an RLMRealm is deallocated from the wrong thread. * Fix incorrect results from aggregate methods on `Results`/`RLMResults` after objects which were previously in the results are deleted. * Fix a crash when adding a new property to an existing class with over a million objects in the Realm. * Fix errors when opening encrypted Realm files created with writeCopyToPath. * Fix crashes or incorrect results for queries that use relationship equality in cases where the `RLMResults` is kept alive and instances of the target class of the relationship are deleted. 0.97.0 Release notes (2015-12-17) ============================================================= ### API breaking changes * All functionality deprecated in previous releases has been removed entirely. * Add generic type annotations to NSArrays and NSDictionaries in public APIs. * Adding a Realm notification block on a thread not currently running from within a run loop throws an exception rather than silently never calling the notification block. ### Enhancements * Support for tvOS. * Support for building Realm Swift from source when using Carthage. * The block parameter of `-[RLMRealm transactionWithBlock:]`/`Realm.write(_:)` is now marked as `__attribute__((noescape))`/`@noescape`. * Many forms of queries with key paths on both sides of the comparison operator are now supported. * Add support for KVC collection operators in `RLMResults` and `RLMArray`. * Fail instead of deadlocking in `+[RLMRealm sharedSchema]`, if a Swift property is initialized to a computed value, which attempts to open a Realm on its own. ### Bugfixes * Fix poor performance when calling `-[RLMRealm deleteObjects:]` on an `RLMResults` which filtered the objects when there are other classes linking to the type of the deleted objects. * An exception is now thrown when defining `Object` properties of an unsupported type. 0.96.3 Release notes (2015-12-04) ============================================================= ### Enhancements * Queries are no longer limited to 16 levels of grouping. * Rework the implementation of encrypted Realms to no longer interfere with debuggers. ### Bugfixes * Fix crash when trying to retrieve object instances via `dynamicObjects`. * Throw an exception when querying on a link providing objects, which are from a different Realm. * Return empty results when querying on a link providing an unattached object. * Fix crashes or incorrect results when calling `-[RLMRealm refresh]` during fast enumeration. * Add `Int8` support for `RealmOptional`, `MinMaxType` and `AddableType`. * Set the default value for newly added non-optional NSData properties to a zero-byte NSData rather than nil. * Fix a potential crash when deleting all objects of a class. * Fix performance problems when creating large numbers of objects with `RLMArray`/`List` properties. * Fix memory leak when using Object(value:) for subclasses with `List` or `RealmOptional` properties. * Fix a crash when computing the average of an optional integer property. * Fix incorrect search results for some queries on integer properties. * Add error-checking for nil realm parameters in many methods such as `+[RLMObject allObjectsInRealm:]`. * Fix a race condition between commits and opening Realm files on new threads that could lead to a crash. * Fix several crashes when opening Realm files. * `-[RLMObject createInRealm:withValue:]`, `-[RLMObject createOrUpdateInRealm:withValue:]`, and their variants for the default Realm now always match the contents of an `NSArray` against properties in the same order as they are defined in the model. 0.96.2 Release notes (2015-10-26) ============================================================= Prebuilt frameworks are now built with Xcode 7.1. ### Bugfixes * Fix ignoring optional properties in Swift. * Fix CocoaPods installation on case-sensitive file systems. 0.96.1 Release notes (2015-10-20) ============================================================= ### Bugfixes * Support assigning `Results` to `List` properties via KVC. * Honor the schema version set in the configuration in `+[RLMRealm migrateRealm:]`. * Fix crash when using optional Int16/Int32/Int64 properties in Swift. 0.96.0 Release notes (2015-10-14) ============================================================= * No functional changes since beta2. 0.96.0-beta2 Release notes (2015-10-08) ============================================================= ### Bugfixes * Add RLMOptionalBase.h to the podspec. 0.96.0-beta Release notes (2015-10-07) ============================================================= ### API breaking changes * CocoaPods v0.38 or greater is now required to install Realm and RealmSwift as pods. ### Enhancements * Functionality common to both `List` and `Results` is now declared in a `RealmCollectionType` protocol that both types conform to. * `Results.realm` now returns an `Optional` in order to conform to `RealmCollectionType`, but will always return `.Some()` since a `Results` cannot exist independently from a `Realm`. * Aggregate operations are now available on `List`: `min`, `max`, `sum`, `average`. * Committing write transactions (via `commitWrite` / `commitWriteTransaction` and `write` / `transactionWithBlock`) now optionally allow for handling errors when the disk is out of space. * Added `isEmpty` property on `RLMRealm`/`Realm` to indicate if it contains any objects. * The `@count`, `@min`, `@max`, `@sum` and `@avg` collection operators are now supported in queries. ### Bugfixes * Fix assertion failure when inserting NSData between 8MB and 16MB in size. * Fix assertion failure when rolling back a migration which removed an object link or `RLMArray`/`List` property. * Add the path of the file being opened to file open errors. * Fix a crash that could be triggered by rapidly opening and closing a Realm many times on multiple threads at once. * Fix several places where exception messages included the name of the wrong function which failed. 0.95.3 Release notes (2015-10-05) ============================================================= ### Bugfixes * Compile iOS Simulator framework architectures with `-fembed-bitcode-marker`. * Fix crashes when the first Realm opened uses a class subset and later Realms opened do not. * Fix inconsistent errors when `Object(value: ...)` is used to initialize the default value of a property of an `Object` subclass. * Throw an exception when a class subset has objects with array or object properties of a type that are not part of the class subset. 0.95.2 Release notes (2015-09-24) ============================================================= * Enable bitcode for iOS and watchOS frameworks. * Build libraries with Xcode 7 final rather than the GM. 0.95.1 Release notes (2015-09-23) ============================================================= ### Enhancements * Add missing KVO handling for moving and exchanging objects in `RLMArray` and `List`. ### Bugfixes * Setting the primary key property on persisted `RLMObject`s / `Object`s via subscripting or key-value coding will cause an exception to be thrown. * Fix crash due to race condition in `RLMRealmConfiguration` where the default configuration was in the process of being copied in one thread, while released in another. * Fix crash when a migration which removed an object or array property is rolled back due to an error. 0.95.0 Release notes (2015-08-25) ============================================================= ### API breaking changes * The following APIs have been deprecated in favor of the new `RLMRealmConfiguration` class in Realm Objective-C: | Deprecated API | New API | |:------------------------------------------------------------------|:---------------------------------------------------------------------------------| | `+[RLMRealm realmWithPath:readOnly:error:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm realmWithPath:encryptionKey:readOnly:error:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm setEncryptionKey:forRealmsAtPath:]` | `-[RLMRealmConfiguration setEncryptionKey:]` | | `+[RLMRealm inMemoryRealmWithIdentifier:]` | `+[RLMRealm realmWithConfiguration:error:]` | | `+[RLMRealm defaultRealmPath]` | `+[RLMRealmConfiguration defaultConfiguration]` | | `+[RLMRealm setDefaultRealmPath:]` | `+[RLMRealmConfiguration setDefaultConfiguration:]` | | `+[RLMRealm setDefaultRealmSchemaVersion:withMigrationBlock]` | `RLMRealmConfiguration.schemaVersion` and `RLMRealmConfiguration.migrationBlock` | | `+[RLMRealm setSchemaVersion:forRealmAtPath:withMigrationBlock:]` | `RLMRealmConfiguration.schemaVersion` and `RLMRealmConfiguration.migrationBlock` | | `+[RLMRealm migrateRealmAtPath:]` | `+[RLMRealm migrateRealm:]` | | `+[RLMRealm migrateRealmAtPath:encryptionKey:]` | `+[RLMRealm migrateRealm:]` | * The following APIs have been deprecated in favor of the new `Realm.Configuration` struct in Realm Swift for Swift 1.2: | Deprecated API | New API | |:--------------------------------------------------------------|:-----------------------------------------------------------------------------| | `Realm.defaultPath` | `Realm.Configuration.defaultConfiguration` | | `Realm(path:readOnly:encryptionKey:error:)` | `Realm(configuration:error:)` | | `Realm(inMemoryIdentifier:)` | `Realm(configuration:error:)` | | `Realm.setEncryptionKey(:forPath:)` | `Realm(configuration:error:)` | | `setDefaultRealmSchemaVersion(schemaVersion:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `setSchemaVersion(schemaVersion:realmPath:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `migrateRealm(path:encryptionKey:)` | `migrateRealm(configuration:)` | * The following APIs have been deprecated in favor of the new `Realm.Configuration` struct in Realm Swift for Swift 2.0: | Deprecated API | New API | |:--------------------------------------------------------------|:-----------------------------------------------------------------------------| | `Realm.defaultPath` | `Realm.Configuration.defaultConfiguration` | | `Realm(path:readOnly:encryptionKey:) throws` | `Realm(configuration:) throws` | | `Realm(inMemoryIdentifier:)` | `Realm(configuration:) throws` | | `Realm.setEncryptionKey(:forPath:)` | `Realm(configuration:) throws` | | `setDefaultRealmSchemaVersion(schemaVersion:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `setSchemaVersion(schemaVersion:realmPath:migrationBlock:)` | `Realm.Configuration.schemaVersion` and `Realm.Configuration.migrationBlock` | | `migrateRealm(path:encryptionKey:)` | `migrateRealm(configuration:)` | * `List.extend` in Realm Swift for Swift 2.0 has been replaced with `List.appendContentsOf`, mirroring changes to `RangeReplaceableCollectionType`. * Object properties on `Object` subclasses in Realm Swift must be marked as optional, otherwise a runtime exception will be thrown. ### Enhancements * Persisted properties of `RLMObject`/`Object` subclasses are now Key-Value Observing compliant. * The different options used to create Realm instances have been consolidated into a single `RLMRealmConfiguration`/`Realm.Configuration` object. * Enumerating Realm collections (`RLMArray`, `RLMResults`, `List<>`, `Results<>`) now enumerates over a copy of the collection, making it no longer an error to modify a collection during enumeration (either directly, or indirectly by modifying objects to make them no longer match a query). * Improve performance of object insertion in Swift to bring it roughly in line with Objective-C. * Allow specifying a specific list of `RLMObject` / `Object` subclasses to include in a given Realm via `RLMRealmConfiguration.objectClasses` / `Realm.Configuration.objectTypes`. ### Bugfixes * Subscripting on `RLMObject` is now marked as nullable. 0.94.1 Release notes (2015-08-10) ============================================================= ### API breaking changes * Building for watchOS requires Xcode 7 beta 5. ### Enhancements * `Object.className` is now marked as `final`. ### Bugfixes * Fix crash when adding a property to a model without updating the schema version. * Fix unnecessary redownloading of the core library when building from source. * Fix crash when sorting by an integer or floating-point property on iOS 7. 0.94.0 Release notes (2015-07-29) ============================================================= ### API breaking changes * None. ### Enhancements * Reduce the amount of memory used by RLMRealm notification listener threads. * Avoid evaluating results eagerly when filtering and sorting. * Add nullability annotations to the Objective-C API to provide enhanced compiler warnings and bridging to Swift. * Make `RLMResult`s and `RLMArray`s support Objective-C generics. * Add support for building watchOS and bitcode-compatible apps. * Make the exceptions thrown in getters and setters more informative. * Add `-[RLMArray exchangeObjectAtIndex:withObjectAtIndex]` and `List.swap(_:_:)` to allow exchanging the location of two objects in the given `RLMArray` / `List`. * Added anonymous analytics on simulator/debugger runs. * Add `-[RLMArray moveObjectAtIndex:toIndex:]` and `List.move(from:to:)` to allow moving objects in the given `RLMArray` / `List`. ### Bugfixes * Processes crashing due to an uncaught exception inside a write transaction will no longer cause other processes using the same Realm to hang indefinitely. * Fix incorrect results when querying for < or <= on ints that require 64 bits to represent with a CPU that supports SSE 4.2. * An exception will no longer be thrown when attempting to reset the schema version or encryption key on an open Realm to the current value. * Date properties on 32 bit devices will retain 64 bit second precision. * Wrap calls to the block passed to `enumerate` in an autoreleasepool to reduce memory growth when migrating a large amount of objects. * In-memory realms no longer write to the Documents directory on iOS or Application Support on OS X. 0.93.2 Release notes (2015-06-12) ============================================================= ### Bugfixes * Fixed an issue where the packaged OS X Realm.framework was built with `GCC_GENERATE_TEST_COVERAGE_FILES` and `GCC_INSTRUMENT_PROGRAM_FLOW_ARCS` enabled. * Fix a memory leak when constructing standalone Swift objects with NSDate properties. * Throw an exception rather than asserting when an invalidated object is added to an RLMArray. * Fix a case where data loss would occur if a device was hard-powered-off shortly after a write transaction was committed which had to expand the Realm file. 0.93.1 Release notes (2015-05-29) ============================================================= ### Bugfixes * Objects are no longer copied into standalone objects during object creation. This fixes an issue where nested objects with a primary key are sometimes duplicated rather than updated. * Comparison predicates with a constant on the left of the operator and key path on the right now give correct results. An exception is now thrown for predicates that do not yet support this ordering. * Fix some crashes in `index_string.cpp` with int primary keys or indexed int properties. 0.93.0 Release notes (2015-05-27) ============================================================= ### API breaking changes * Schema versions are now represented as `uint64_t` (Objective-C) and `UInt64` (Swift) so that they have the same representation on all architectures. ### Enhancements * Swift: `Results` now conforms to `CVarArgType` so it can now be passed as an argument to `Results.filter(_:...)` and `List.filter(_:...)`. * Swift: Made `SortDescriptor` conform to the `Equatable` and `StringLiteralConvertible` protocols. * Int primary keys are once again automatically indexed. * Improve error reporting when attempting to mark a property of a type that cannot be indexed as indexed. ### Bugfixes * Swift: `RealmSwift.framework` no longer embeds `Realm.framework`, which now allows apps using it to pass iTunes Connect validation. 0.92.4 Release notes (2015-05-22) ============================================================= ### API breaking changes * None. ### Enhancements * Swift: Made `Object.init()` a required initializer. * `RLMObject`, `RLMResults`, `Object` and `Results` can now be safely deallocated (but still not used) from any thread. * Improve performance of `-[RLMArray indexOfObjectWhere:]` and `-[RLMArray indexOfObjectWithPredicate:]`, and implement them for standalone RLMArrays. * Improved performance of most simple queries. ### Bugfixes * The interprocess notification mechanism no longer uses dispatch worker threads, preventing it from starving other GCD clients of the opportunity to execute blocks when dozens of Realms are open at once. 0.92.3 Release notes (2015-05-13) ============================================================= ### API breaking changes * Swift: `Results.average(_:)` now returns an optional, which is `nil` if and only if the results set is empty. ### Enhancements * Swift: Added `List.invalidated`, which returns if the given `List` is no longer safe to be accessed, and is analogous to `-[RLMArray isInvalidated]`. * Assertion messages are automatically logged to Crashlytics if it's loaded into the current process to make it easier to diagnose crashes. ### Bugfixes * Swift: Enumerating through a standalone `List` whose objects themselves have list properties won't crash. * Swift: Using a subclass of `RealmSwift.Object` in an aggregate operator of a predicate no longer throws a spurious type error. * Fix incorrect results for when using OR in a query on a `RLMArray`/`List<>`. * Fix incorrect values from `[RLMResults count]`/`Results.count` when using `!=` on an int property with no other query conditions. * Lower the maximum doubling threshold for Realm file sizes from 128MB to 16MB to reduce the amount of wasted space. 0.92.2 Release notes (2015-05-08) ============================================================= ### API breaking changes * None. ### Enhancements * Exceptions raised when incorrect object types are used with predicates now contain more detailed information. * Added `-[RLMMigration deleteDataForClassName:]` and `Migration.deleteData(_:)` to enable cleaning up after removing object subclasses ### Bugfixes * Prevent debugging of an application using an encrypted Realm to work around frequent LLDB hangs. Until the underlying issue is addressed you may set REALM_DISABLE_ENCRYPTION=YES in your application's environment variables to have requests to open an encrypted Realm treated as a request for an unencrypted Realm. * Linked objects are properly updated in `createOrUpdateInRealm:withValue:`. * List properties on Objects are now properly initialized during fast enumeration. 0.92.1 Release notes (2015-05-06) ============================================================= ### API breaking changes * None. ### Enhancements * `-[RLMRealm inWriteTransaction]` is now public. * Realm Swift is now available on CoocaPods. ### Bugfixes * Force code re-signing after stripping architectures in `strip-frameworks.sh`. 0.92.0 Release notes (2015-05-05) ============================================================= ### API breaking changes * Migration blocks are no longer called when a Realm file is first created. * The following APIs have been deprecated in favor of newer method names: | Deprecated API | New API | |:-------------------------------------------------------|:------------------------------------------------------| | `-[RLMMigration createObject:withObject:]` | `-[RLMMigration createObject:withValue:]` | | `-[RLMObject initWithObject:]` | `-[RLMObject initWithValue:]` | | `+[RLMObject createInDefaultRealmWithObject:]` | `+[RLMObject createInDefaultRealmWithValue:]` | | `+[RLMObject createInRealm:withObject:]` | `+[RLMObject createInRealm:withValue:]` | | `+[RLMObject createOrUpdateInDefaultRealmWithObject:]` | `+[RLMObject createOrUpdateInDefaultRealmWithValue:]` | | `+[RLMObject createOrUpdateInRealm:withObject:]` | `+[RLMObject createOrUpdateInRealm:withValue:]` | ### Enhancements * `Int8` properties defined in Swift are now treated as integers, rather than booleans. * NSPredicates created using `+predicateWithValue:` are now supported. ### Bugfixes * Compound AND predicates with no subpredicates now correctly match all objects. 0.91.5 Release notes (2015-04-28) ============================================================= ### Bugfixes * Fix issues with removing search indexes and re-enable it. 0.91.4 Release notes (2015-04-27) ============================================================= ### Bugfixes * Temporarily disable removing indexes from existing columns due to bugs. 0.91.3 Release notes (2015-04-17) ============================================================= ### Bugfixes * Fix `Extra argument 'objectClassName' in call` errors when building via CocoaPods. 0.91.2 Release notes (2015-04-16) ============================================================= * Migration blocks are no longer called when a Realm file is first created. ### Enhancements * `RLMCollection` supports collection KVC operations. * Sorting `RLMResults` is 2-5x faster (typically closer to 2x). * Refreshing `RLMRealm` after a write transaction which inserts or modifies strings or `NSData` is committed on another thread is significantly faster. * Indexes are now added and removed from existing properties when a Realm file is opened, rather than only when properties are first added. ### Bugfixes * `+[RLMSchema dynamicSchemaForRealm:]` now respects search indexes. * `+[RLMProperty isEqualToProperty:]` now checks for equal `indexed` properties. 0.91.1 Release notes (2015-03-12) ============================================================= ### Enhancements * The browser will automatically refresh when the Realm has been modified from another process. * Allow using Realm in an embedded framework by setting `APPLICATION_EXTENSION_API_ONLY` to YES. ### Bugfixes * Fix a crash in CFRunLoopSourceInvalidate. 0.91.0 Release notes (2015-03-10) ============================================================= ### API breaking changes * `attributesForProperty:` has been removed from `RLMObject`. You now specify indexed properties by implementing the `indexedProperties` method. * An exception will be thrown when calling `setEncryptionKey:forRealmsAtPath:`, `setSchemaVersion:forRealmAtPath:withMigrationBlock:`, and `migrateRealmAtPath:` when a Realm at the given path is already open. * Object and array properties of type `RLMObject` will no longer be allowed. ### Enhancements * Add support for sharing Realm files between processes. * The browser will no longer show objects that have no persisted properties. * `RLMSchema`, `RLMObjectSchema`, and `RLMProperty` now have more useful descriptions. * Opening an encrypted Realm while a debugger is attached to the process no longer throws an exception. * `RLMArray` now exposes an `isInvalidated` property to indicate if it can no longer be accessed. ### Bugfixes * An exception will now be thrown when calling `-beginWriteTransaction` from within a notification triggered by calling `-beginWriteTransaction` elsewhere. * When calling `delete:` we now verify that the object being deleted is persisted in the target Realm. * Fix crash when calling `createOrUpdate:inRealm` with nested linked objects. * Use the key from `+[RLMRealm setEncryptionKey:forRealmsAtPath:]` in `-writeCopyToPath:error:` and `+migrateRealmAtPath:`. * Comparing an RLMObject to a non-RLMObject using `-[RLMObject isEqual:]` or `-isEqualToObject:` now returns NO instead of crashing. * Improved error message when an `RLMObject` subclass is defined nested within another Swift declaration. * Fix crash when the process is terminated by the OS on iOS while encrypted realms are open. * Fix crash after large commits to encrypted realms. 0.90.6 Release notes (2015-02-20) ============================================================= ### Enhancements * Improve compatiblity of encrypted Realms with third-party crash reporters. ### Bugfixes * Fix incorrect results when using aggregate functions on sorted RLMResults. * Fix data corruption when using writeCopyToPath:encryptionKey:. * Maybe fix some assertion failures. 0.90.5 Release notes (2015-02-04) ============================================================= ### Bugfixes * Fix for crashes when encryption is enabled on 64-bit iOS devices. 0.90.4 Release notes (2015-01-29) ============================================================= ### Bugfixes * Fix bug that resulted in columns being dropped and recreated during migrations. 0.90.3 Release notes (2015-01-27) ============================================================= ### Enhancements * Calling `createInDefaultRealmWithObject:`, `createInRealm:withObject:`, `createOrUpdateInDefaultRealmWithObject:` or `createOrUpdateInRealm:withObject:` is a no-op if the argument is an RLMObject of the same type as the receiver and is already backed by the target realm. ### Bugfixes * Fix incorrect column type assertions when the first Realm file opened is a read-only file that is missing tables. * Throw an exception when adding an invalidated or deleted object as a link. * Throw an exception when calling `createOrUpdateInRealm:withObject:` when the receiver has no primary key defined. 0.90.1 Release notes (2015-01-22) ============================================================= ### Bugfixes * Fix for RLMObject being treated as a model object class and showing up in the browser. * Fix compilation from the podspec. * Fix for crash when calling `objectsWhere:` with grouping in the query on `allObjects`. 0.90.0 Release notes (2015-01-21) ============================================================= ### API breaking changes * Rename `-[RLMRealm encryptedRealmWithPath:key:readOnly:error:]` to `-[RLMRealm realmWithPath:encryptionKey:readOnly:error:]`. * `-[RLMRealm setSchemaVersion:withMigrationBlock]` is no longer global and must be called for each individual Realm path used. You can now call `-[RLMRealm setDefaultRealmSchemaVersion:withMigrationBlock]` for the default Realm and `-[RLMRealm setSchemaVersion:forRealmAtPath:withMigrationBlock:]` for all others; ### Enhancements * Add `-[RLMRealm writeCopyToPath:encryptionKey:error:]`. * Add support for comparing string columns to other string columns in queries. ### Bugfixes * Roll back changes made when an exception is thrown during a migration. * Throw an exception if the number of items in a RLMResults or RLMArray changes while it's being fast-enumerated. * Also encrypt the temporary files used when encryption is enabled for a Realm. * Fixed crash in JSONImport example on OS X with non-en_US locale. * Fixed infinite loop when opening a Realm file in the Browser at the same time as it is open in a 32-bit simulator. * Fixed a crash when adding primary keys to older realm files with no primary keys on any objects. * Fixed a crash when removing a primary key in a migration. * Fixed a crash when multiple write transactions with no changes followed by a write transaction with changes were committed without the main thread RLMRealm getting a chance to refresh. * Fixed incomplete results when querying for non-null relationships. * Improve the error message when a Realm file is opened in multiple processes at once. 0.89.2 Release notes (2015-01-02) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix an assertion failure when invalidating a Realm which is in a write transaction, has already been invalidated, or has never been used. * Fix an assertion failure when sorting an empty RLMArray property. * Fix a bug resulting in the browser never becoming visible on 10.9. * Write UTF-8 when generating class files from a realm file in the Browser. 0.89.1 Release notes (2014-12-22) ============================================================= ### API breaking changes * None. ### Enhancements * Improve the error message when a Realm can't be opened due to lacking write permissions. ### Bugfixes * Fix an assertion failure when inserting rows after calling `deleteAllObjects` on a Realm. * Separate dynamic frameworks are now built for the simulator and devices to work around App Store submission errors due to the simulator version not being automatically stripped from dynamic libraries. 0.89.0 Release notes (2014-12-18) ============================================================= ### API breaking changes * None. ### Enhancements * Add support for encrypting Realm files on disk. * Support using KVC-compliant objects without getters or with custom getter names to initialize RLMObjects with `createObjectInRealm` and friends. ### Bugfixes * Merge native Swift default property values with defaultPropertyValues(). * Don't leave the database schema partially updated when opening a realm fails due to a migration being needed. * Fixed issue where objects with custom getter names couldn't be used to initialize other objects. * Fix a major performance regression on queries on string properties. * Fix a memory leak when circularly linked objects are added to a Realm. 0.88.0 Release notes (2014-12-02) ============================================================= ### API breaking changes * Deallocating an RLMRealm instance in a write transaction lacking an explicit commit/cancel will now be automatically cancelled instead of committed. * `-[RLMObject isDeletedFromRealm]` has been renamed to `-[RLMObject isInvalidated]`. ### Enhancements * Add `-[RLMRealm writeCopyToPath:]` to write a compacted copy of the Realm another file. * Add support for case insensitive, BEGINSWITH, ENDSWITH and CONTAINS string queries on array properties. * Make fast enumeration of `RLMArray` and `RLMResults` ~30% faster and `objectAtIndex:` ~55% faster. * Added a lldb visualizer script for displaying the contents of persisted RLMObjects when debugging. * Added method `-setDefaultRealmPath:` to change the default Realm path. * Add `-[RLMRealm invalidate]` to release data locked by the current thread. ### Bugfixes * Fix for crash when running many simultaneous write transactions on background threads. * Fix for crashes caused by opening Realms at multiple paths simultaneously which have had properties re-ordered during migration. * Don't run the query twice when `firstObject` or `lastObject` are called on an `RLMResults` which has not had its results accessed already. * Fix for bug where schema version is 0 for new Realm created at the latest version. * Fix for error message where no migration block is specified when required. 0.87.4 Release notes (2014-11-07) ============================================================= ### API breaking changes * None. ### Enhancements * None. ### Bugfixes * Fix browser location in release zip. 0.87.3 Release notes (2014-11-06) ============================================================= ### API breaking changes * None. ### Enhancements * Added method `-linkingObjectsOfClass:forProperty:` to RLMObject to expose inverse relationships/backlinks. ### Bugfixes * Fix for crash due to missing search index when migrating an object with a string primary key in a database created using an older versions (0.86.3 and earlier). * Throw an exception when passing an array containing a non-RLMObject to -[RLMRealm addObjects:]. * Fix for crash when deleting an object from multiple threads. 0.87.0 Release notes (2014-10-21) ============================================================= ### API breaking changes * RLMArray has been split into two classes, `RLMArray` and `RLMResults`. RLMArray is used for object properties as in previous releases. Moving forward all methods used to enumerate, query, and sort objects return an instance of a new class `RLMResults`. This change was made to support diverging apis and the future addition of change notifications for queries. * The api for migrations has changed. You now call `setSchemaVersion:withMigrationBlock:` to register a global migration block and associated version. This block is applied to Realms as needed when opened for Realms at a previous version. The block can be applied manually if desired by calling `migrateRealmAtPath:`. * `arraySortedByProperty:ascending:` was renamed to `sortedResultsUsingProperty:ascending` * `addObjectsFromArray:` on both `RLMRealm` and `RLMArray` has been renamed to `addObjects:` and now accepts any container class which implements `NSFastEnumeration` * Building with Swift support now requires Xcode 6.1 ### Enhancements * Add support for sorting `RLMArray`s by multiple columns with `sortedResultsUsingDescriptors:` * Added method `deleteAllObjects` on `RLMRealm` to clear a Realm. * Added method `createObject:withObject:` on `RLMMigration` which allows object creation during migrations. * Added method `deleteObject:` on `RLMMigration` which allows object deletion during migrations. * Updating to core library version 0.85.0. * Implement `objectsWhere:` and `objectsWithPredicate:` for array properties. * Add `cancelWriteTransaction` to revert all changes made in a write transaction and end the transaction. * Make creating `RLMRealm` instances on background threads when an instance exists on another thread take a fifth of the time. * Support for partial updates when calling `createOrUpdateWithObject:` and `addOrUpdateObject:` * Re-enable Swift support on OS X ### Bugfixes * Fix exceptions when trying to set `RLMObject` properties after rearranging the properties in a `RLMObject` subclass. * Fix crash on IN query with several thousand items. * Fix crash when querying indexed `NSString` properties. * Fixed an issue which prevented in-memory Realms from being used accross multiple threads. * Preserve the sort order when querying a sorted `RLMResults`. * Fixed an issue with migrations where if a Realm file is deleted after a Realm is initialized, the newly created Realm can be initialized with an incorrect schema version. * Fix crash in `RLMSuperSet` when assigning to a `RLMArray` property on a standalone object. * Add an error message when the protocol for an `RLMArray` property is not a valid object type. * Add an error message when an `RLMObject` subclass is defined nested within another Swift class. 0.86.3 Release notes (2014-10-09) ============================================================= ### Enhancements * Add support for != in queries on object relationships. ### Bugfixes * Re-adding an object to its Realm no longer throws an exception and is now a no-op (as it was previously). * Fix another bug which would sometimes result in subclassing RLMObject subclasses not working. 0.86.2 Release notes (2014-10-06) ============================================================= ### Bugfixes * Fixed issues with packaging "Realm Browser.app" for release. 0.86.1 Release notes (2014-10-03) ============================================================= ### Bugfixes * Fix a bug which would sometimes result in subclassing RLMObject subclasses not working. 0.86.0 Release notes (2014-10-03) ============================================================= ### API breaking changes * Xcode 6 is now supported from the main Xcode project `Realm.xcodeproj`. Xcode 5 is no longer supported. ### Enhancements * Support subclassing RLMObject models. Although you can now persist subclasses, polymorphic behavior is not supported (i.e. setting a property to an instance of its subclass). * Add support for sorting RLMArray properties. * Speed up inserting objects with `addObject:` by ~20%. * `readonly` properties are automatically ignored rather than having to be added to `ignoredProperties`. * Updating to core library version 0.83.1. * Return "[deleted object]" rather than throwing an exception when `-description` is called on a deleted RLMObject. * Significantly improve performance of very large queries. * Allow passing any enumerable to IN clauses rather than just NSArray. * Add `objectForPrimaryKey:` and `objectInRealm:forPrimaryKey:` convenience methods to fetch an object by primary key. ### Bugfixes * Fix error about not being able to persist property 'hash' with incompatible type when building for devices with Xcode 6. * Fix spurious notifications of new versions of Realm. * Fix for updating nested objects where some types do not have primary keys. * Fix for inserting objects from JSON with NSNull values when default values should be used. * Trying to add a persisted RLMObject to a different Realm now throws an exception rather than creating an uninitialized object. * Fix validation errors when using IN on array properties. * Fix errors when an IN clause has zero items. * Fix for chained queries ignoring all but the last query's conditions. 0.85.0 Release notes (2014-09-15) ============================================================= ### API breaking changes * Notifications for a refresh being needed (when autorefresh is off) now send the notification type RLMRealmRefreshRequiredNotification rather than RLMRealmDidChangeNotification. ### Enhancements * Updating to core library version 0.83.0. * Support for primary key properties (for int and string columns). Declaring a property to be the primary key ensures uniqueness for that property for all objects of a given type. At the moment indexes on primary keys are not yet supported but this will be added in a future release. * Added methods to update or insert (upsert) for objects with primary keys defined. * `[RLMObject initWithObject:]` and `[RLMObject createInRealmWithObject:]` now support any object type with kvc properties. * The Swift support has been reworked to work around Swift not being supported in Frameworks on iOS 7. * Improve performance when getting the count of items matching a query but not reading any of the objects in the results. * Add a return value to `-[RLMRealm refresh]` that indicates whether or not there was anything to refresh. * Add the class name to the error message when an RLMObject is missing a value for a property without a default. * Add support for opening Realms in read-only mode. * Add an automatic check for updates when using Realm in a simulator (the checker code is not compiled into device builds). This can be disabled by setting the REALM_DISABLE_UPDATE_CHECKER environment variable to any value. * Add support for Int16 and Int64 properties in Swift classes. ### Bugfixes * Realm change notifications when beginning a write transaction are now sent after updating rather than before, to match refresh. * `-isEqual:` now uses the default `NSObject` implementation unless a primary key is specified for an RLMObject. When a primary key is specified, `-isEqual:` calls `-isEqualToObject:` and a corresponding implementation for `-hash` is also implemented. 0.84.0 Release notes (2014-08-28) ============================================================= ### API breaking changes * The timer used to trigger notifications has been removed. Notifications are now only triggered by commits made in other threads, and can not currently be triggered by changes made by other processes. Interprocess notifications will be re-added in a future commit with an improved design. ### Enhancements * Updating to core library version 0.82.2. * Add property `deletedFromRealm` to RLMObject to indicate objects which have been deleted. * Add support for the IN operator in predicates. * Add support for the BETWEEN operator in link queries. * Add support for multi-level link queries in predicates (e.g. `foo.bar.baz = 5`). * Switch to building the SDK from source when using CocoaPods and add a Realm.Headers subspec for use in targets that should not link a copy of Realm (such as test targets). * Allow unregistering from change notifications in the change notification handler block. * Significant performance improvements when holding onto large numbers of RLMObjects. * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta6. * Improved performance during RLMArray iteration, especially when mutating contained objects. ### Bugfixes * Fix crashes and assorted bugs when sorting or querying a RLMArray returned from a query. * Notifications are no longer sent when initializing new RLMRealm instances on background threads. * Handle object cycles in -[RLMObject description] and -[RLMArray description]. * Lowered the deployment target for the Xcode 6 projects and Swift examples to iOS 7.0, as they didn't actually require 8.0. * Support setting model properties starting with the letter 'z' * Fixed crashes that could result from switching between Debug and Relase builds of Realm. 0.83.0 Release notes (2014-08-13) ============================================================= ### API breaking changes * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta5. * Properties to be persisted in Swift classes must be explicitly declared as `dynamic`. * Subclasses of RLMObject subclasses now throw an exception on startup, rather than when added to a Realm. ### Enhancements * Add support for querying for nil object properties. * Improve error message when specifying invalid literals when creating or initializing RLMObjects. * Throw an exception when an RLMObject is used from the incorrect thread rather than crashing in confusing ways. * Speed up RLMRealm instantiation and array property iteration. * Allow array and objection relation properties to be missing or null when creating a RLMObject from a NSDictionary. ### Bugfixes * Fixed a memory leak when querying for objects. * Fixed initializing array properties on standalone Swift RLMObject subclasses. * Fix for queries on 64bit integers. 0.82.0 Release notes (2014-08-05) ============================================================= ### API breaking changes * Realm-Xcode6.xcodeproj now only builds using Xcode6-Beta4. ### Enhancements * Updating to core library version 0.80.5. * Now support disabling the `autorefresh` property on RLMRealm instances. * Building Realm-Xcode6 for iOS now builds a universal framework for Simulator & Device. * Using NSNumber properties (unsupported) now throws a more informative exception. * Added `[RLMRealm defaultRealmPath]` * Proper implementation for [RLMArray indexOfObjectWhere:] * The default Realm path on OS X is now ~/Library/Application Support/[bundle identifier]/default.realm rather than ~/Documents * We now check that the correct framework (ios or osx) is used at compile time. ### Bugfixes * Fixed rapid growth of the realm file size. * Fixed a bug which could cause a crash during RLMArray destruction after a query. * Fixed bug related to querying on float properties: `floatProperty = 1.7` now works. * Fixed potential bug related to the handling of array properties (RLMArray). * Fixed bug where array properties accessed the wrong property. * Fixed bug that prevented objects with custom getters to be added to a Realm. * Fixed a bug where initializing a standalone object with an array literal would trigger an exception. * Clarified exception messages when using unsupported NSPredicate operators. * Clarified exception messages when using unsupported property types on RLMObject subclasses. * Fixed a memory leak when breaking out of a for-in loop on RLMArray. * Fixed a memory leak when removing objects from a RLMArray property. * Fixed a memory leak when querying for objects. 0.81.0 Release notes (2014-07-22) ============================================================= ### API breaking changes * None. ### Enhancements * Updating to core library version 0.80.3. * Added support for basic querying of RLMObject and RLMArray properties (one-to-one and one-to-many relationships). e.g. `[Person objectsWhere:@"dog.name == 'Alfonso'"]` or `[Person objectsWhere:@"ANY dogs.name == 'Alfonso'"]` Supports all normal operators for numeric and date types. Does not support NSData properties or `BEGINSWITH`, `ENDSWITH`, `CONTAINS` and other options for string properties. * Added support for querying for object equality in RLMObject and RLMArray properties (one-to-one and one-to-many relationships). e.g. `[Person objectsWhere:@"dog == %@", myDog]` `[Person objectsWhere:@"ANY dogs == %@", myDog]` `[Person objectsWhere:@"ANY friends.dog == %@", dog]` Only supports comparing objects for equality (i.e. ==) * Added a helper method to RLMRealm to perform a block inside a transaction. * OSX framework now supported in CocoaPods. ### Bugfixes * Fixed Unicode support in property names and string contents (Chinese, Russian, etc.). Closing #612 and #604. * Fixed bugs related to migration when properties are removed. * Fixed keyed subscripting for standalone RLMObjects. * Fixed bug related to double clicking on a .realm file to launch the Realm Browser (thanks to Dean Moore). 0.80.0 Release notes (2014-07-15) ============================================================= ### API breaking changes * Rename migration methods to -migrateDefaultRealmWithBlock: and -migrateRealmAtPath:withBlock: * Moved Realm specific query methods from RLMRealm to class methods on RLMObject (-allObjects: to +allObjectsInRealm: ect.) ### Enhancements * Added +createInDefaultRealmWithObject: method to RLMObject. * Added support for array and object literals when calling -createWithObject: and -initWithObject: variants. * Added method -deleteObjects: to batch delete objects from a Realm * Support for defining RLMObject models entirely in Swift (experimental, see known issues). * RLMArrays in Swift support Sequence-style enumeration (for obj in array). * Implemented -indexOfObject: for RLMArray ### Known Issues for Swift-defined models * Properties other than String, NSData and NSDate require a default value in the model. This can be an empty (but typed) array for array properties. * The previous caveat also implies that not all models defined in Objective-C can be used for object properties. Only Objective-C models with only implicit (i.e. primitives) or explicit default values can be used. However, any Objective-C model object can be used in a Swift array property. * Array property accessors don't work until its parent object has been added to a realm. * Realm-Bridging-Header.h is temporarily exposed as a public header. This is temporary and will be private again once rdar://17633863 is fixed. * Does not leverage Swift generics and still uses RLM-prefix everywhere. This is coming in #549. 0.22.0 Release notes ============================================================= ### API breaking changes * Rename schemaForObject: to schemaForClassName: on RLMSchema * Removed -objects:where: and -objects:orderedBy:where: from RLMRealm * Removed -indexOfObjectWhere:, -objectsWhere: and -objectsOrderedBy:where: from RLMArray * Removed +objectsWhere: and +objectsOrderedBy:where: from RLMObject ### Enhancements * New Xcode 6 project for experimental swift support. * New Realm Editor app for reading and editing Realm db files. * Added support for migrations. * Added support for RLMArray properties on objects. * Added support for creating in-memory default Realm. * Added -objectsWithClassName:predicateFormat: and -objectsWithClassName:predicate: to RLMRealm * Added -indexOfObjectWithPredicateFormat:, -indexOfObjectWithPredicate:, -objectsWithPredicateFormat:, -objectsWithPredi * Added +objectsWithPredicateFormat: and +objectsWithPredicate: to RLMObject * Now allows predicates comparing two object properties of the same type. 0.20.0 Release notes (2014-05-28) ============================================================= Completely rewritten to be much more object oriented. ### API breaking changes * Everything ### Enhancements * None. ### Bugfixes * None. 0.11.0 Release notes (not released) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * `RLMTable` objects can only be created with an `RLMRealm` object. * Renamed `RLMContext` to `RLMTransactionManager` * Renamed `RLMContextDidChangeNotification` to `RLMRealmDidChangeNotification` * Renamed `contextWithDefaultPersistence` to `managerForDefaultRealm` * Renamed `contextPersistedAtPath:` to `managerForRealmWithPath:` * Renamed `realmWithDefaultPersistence` to `defaultRealm` * Renamed `realmWithDefaultPersistenceAndInitBlock` to `defaultRealmWithInitBlock` * Renamed `find:` to `firstWhere:` * Renamed `where:` to `allWhere:` * Renamed `where:orderBy:` to `allWhere:orderBy:` ### Enhancements * Added `countWhere:` on `RLMTable` * Added `sumOfColumn:where:` on `RLMTable` * Added `averageOfColumn:where:` on `RLMTable` * Added `minOfProperty:where:` on `RLMTable` * Added `maxOfProperty:where:` on `RLMTable` * Added `toJSONString` on `RLMRealm`, `RLMTable` and `RLMView` * Added support for `NOT` operator in predicates * Added support for default values * Added validation support in `createInRealm:withObject:` ### Bugfixes * None. 0.10.0 Release notes (2014-04-23) ============================================================= TightDB is now Realm! The Objective-C API has been updated and your code will break! ### API breaking changes * All references to TightDB have been changed to Realm. * All prefixes changed from `TDB` to `RLM`. * `TDBTransaction` and `TDBSmartContext` have merged into `RLMRealm`. * Write transactions now take an optional rollback parameter (rather than needing to return a boolean). * `addColumnWithName:` and variant methods now return the index of the newly created column if successful, `NSNotFound` otherwise. ### Enhancements * `createTableWithName:columns:` has been added to `RLMRealm`. * Added keyed subscripting for RLMTable's first column if column is of type RLMPropertyTypeString. * `setRow:atIndex:` has been added to `RLMTable`. * `RLMRealm` constructors now have variants that take an writable initialization block * New object interface - tables created/retrieved using `tableWithName:objectClass:` return custom objects ### Bugfixes * None. 0.6.0 Release notes (2014-04-11) ============================================================= ### API breaking changes * `contextWithPersistenceToFile:error:` renamed to `contextPersistedAtPath:error:` in `TDBContext` * `readWithBlock:` renamed to `readUsingBlock:` in `TDBContext` * `writeWithBlock:error:` renamed to `writeUsingBlock:error:` in `TDBContext` * `readTable:withBlock:` renamed to `readTable:usingBlock:` in `TDBContext` * `writeTable:withBlock:error:` renamed to `writeTable:usingBlock:error:` in `TDBContext` * `findFirstRow` renamed to `indexOfFirstMatchingRow` on `TDBQuery`. * `findFirstRowFromIndex:` renamed to `indexOfFirstMatchingRowFromIndex:` on `TDBQuery`. * Return `NSNotFound` instead of -1 when appropriate. * Renamed `castClass` to `castToTytpedTableClass` on `TDBTable`. * `removeAllRows`, `removeRowAtIndex`, `removeLastRow`, `addRow` and `insertRow` methods on table now return void instead of BOOL. ### Enhancements * A `TDBTable` can now be queried using `where:` and `where:orderBy:` taking `NSPredicate` and `NSSortDescriptor` as arguments. * Added `find:` method on `TDBTable` to find first row matching predicate. * `contextWithDefaultPersistence` class method added to `TDBContext`. Will create a context persisted to a file in app/documents folder. * `renameColumnWithIndex:to:` has been added to `TDBTable`. * `distinctValuesInColumnWithIndex` has been added to `TDBTable`. * `dateIsBetween::`, `doubleIsBetween::`, `floatIsBetween::` and `intIsBetween::` have been added to `TDBQuery`. * Column names in Typed Tables can begin with non-capital letters too. The generated `addX` selector can look odd. For example, a table with one column with name `age`, appending a new row will look like `[table addage:7]`. * Mixed typed values are better validated when rows are added, inserted, or modified as object literals. * `addRow`, `insertRow`, and row updates can be done using objects derived from `NSObject`. * `where` has been added to `TDBView`and `TDBViewProtocol`. * Adding support for "smart" contexts (`TDBSmartContext`). ### Bugfixes * Modifications of a `TDBView` and `TDBQuery` now throw an exception in a readtransaction. 0.5.0 Release notes (2014-04-02) ============================================================= The Objective-C API has been updated and your code will break! Of notable changes a fast interface has been added. This interface includes specific methods to get and set values into Tightdb. To use these methods import ``. ### API breaking changes * `getTableWithName:` renamed to `tableWithName:` in `TDBTransaction`. * `addColumnWithName:andType:` renamed to `addColumnWithName:type:` in `TDBTable`. * `columnTypeOfColumn:` renamed to `columnTypeOfColumnWithIndex` in `TDBTable`. * `columnNameOfColumn:` renamed to `nameOfColumnWithIndex:` in `TDBTable`. * `addColumnWithName:andType:` renamed to `addColumnWithName:type:` in `TDBDescriptor`. * Fast getters and setters moved from `TDBRow.h` to `TDBRowFast.h`. ### Enhancements * Added `minDateInColumnWithIndex` and `maxDateInColumnWithIndex` to `TDBQuery`. * Transactions can now be started directly on named tables. * You can create dynamic tables with initial schema. * `TDBTable` and `TDBView` now have a shared protocol so they can easier be used interchangeably. ### Bugfixes * Fixed bug in 64 bit iOS when inserting BOOL as NSNumber. 0.4.0 Release notes (2014-03-26) ============================================================= ### API breaking changes * Typed interface Cursor has now been renamed to Row. * TDBGroup has been renamed to TDBTransaction. * Header files are renamed so names match class names. * Underscore (_) removed from generated typed table classes. * TDBBinary has been removed; use NSData instead. * Underscope (_) removed from generated typed table classes. * Constructor for TDBContext has been renamed to contextWithPersistenceToFile: * Table findFirstRow and min/max/sum/avg operations has been hidden. * Table.appendRow has been renamed to addRow. * getOrCreateTable on Transaction has been removed. * set*:inColumnWithIndex:atRowIndex: methods have been prefixed with TDB * *:inColumnWithIndex:atRowIndex: methods have been prefixed with TDB * addEmptyRow on table has been removed. Use [table addRow:nil] instead. * TDBMixed removed. Use id and NSObject instead. * insertEmptyRow has been removed from table. Use insertRow:nil atIndex:index instead. #### Enhancements * Added firstRow, lastRow selectors on view. * firstRow and lastRow on table now return nil if table is empty. * getTableWithName selector added on group. * getting and creating table methods on group no longer take error argument. * [TDBQuery parent] and [TDBQuery subtable:] selectors now return self. * createTable method added on Transaction. Throws exception if table with same name already exists. * Experimental support for pinning transactions on Context. * TDBView now has support for object subscripting. ### Bugfixes * None. 0.3.0 Release notes (2014-03-14) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * Most selectors have been renamed in the binding! * Prepend TDB-prefix on all classes and types. ### Enhancements * Return types and parameters changed from size_t to NSUInteger. * Adding setObject to TightdbTable (t[2] = @[@1, @"Hello"] is possible). * Adding insertRow to TightdbTable. * Extending appendRow to accept NSDictionary. ### Bugfixes * None. 0.2.0 Release notes (2014-03-07) ============================================================= The Objective-C API has been updated and your code will break! ### API breaking changes * addRow renamed to addEmptyRow ### Enhancements * Adding a simple class for version numbering. * Adding get-version and set-version targets to build.sh. * tableview now supports sort on column with column type bool, date and int * tableview has method for checking the column type of a specified column * tableview has method for getting the number of columns * Adding methods getVersion, getCoreVersion and isAtLeast. * Adding appendRow to TightdbTable. * Adding object subscripting. * Adding method removeColumn on table. ### Bugfixes * None. ================================================ FILE: External/Realm/Realm.framework/Versions/A/Resources/Info.plist ================================================ BuildMachineOSBuild 16D32 CFBundleDevelopmentRegion English CFBundleExecutable Realm CFBundleIdentifier io.Realm.Realm CFBundleInfoDictionaryVersion 6.0 CFBundleName Realm CFBundlePackageType FMWK CFBundleShortVersionString 2.4.4 CFBundleSignature ???? CFBundleSupportedPlatforms MacOSX CFBundleVersion 2.4.4 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 8C38 DTPlatformVersion GM DTSDKBuild 16C58 DTSDKName macosx10.12 DTXcode 0820 DTXcodeBuild 8C38 NSHumanReadableCopyright Copyright © 2014 Realm. All rights reserved. UIDeviceFamily 3 2 1 4 ================================================ FILE: External/Realm/Realm.framework/Versions/A/Resources/LICENSE ================================================ TABLE OF CONTENTS 1. Apache License version 2.0 2. Realm Components 3. Export Compliance ------------------------------------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. REALM COMPONENTS This software contains components with separate copyright and license terms. Your use of these components is subject to the terms and conditions of the following licenses. For the Realm Core component Realm Core Binary License Copyright (c) 2011-2016 Realm Inc All rights reserved Redistribution and use in binary form, with or without modification, is permitted provided that the following conditions are met: 1. You agree not to attempt to decompile, disassemble, reverse engineer or otherwise discover the source code from which the binary code was derived. You may, however, access and obtain a separate license for most of the source code from which this Software was created, at http://realm.io/pricing/. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. EXPORT COMPLIANCE You understand that the Software may contain cryptographic functions that may be subject to export restrictions, and you represent and warrant that you are not (i) located in a jurisdiction that is subject to United States economic sanctions (“Prohibited Jurisdiction”), including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, (ii) a person listed on any U.S. government blacklist (to include the List of Specially Designated Nationals and Blocked Persons or the Consolidated Sanctions List administered by the U.S. Department of the Treasury’s Office of Foreign Assets Control, or the Denied Persons List or Entity List administered by the U.S. Department of Commerce) (“Sanctioned Person”), or (iii) controlled or 50% or more owned by a Sanctioned Person. You agree to comply with all export, re-export and import restrictions and regulations of the U.S. Department of Commerce or other agency or authority of the United States or other applicable countries. You also agree not to transfer, or authorize the transfer of, directly or indirectly, of the Software to any Prohibited Jurisdiction, or otherwise in violation of any such restrictions or regulations. ================================================ FILE: External/Realm/Realm.framework/Versions/A/Resources/strip-frameworks.sh ================================================ ################################################################################ # # Copyright 2015 Realm Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ # This script strips all non-valid architectures from dynamic libraries in # the application's `Frameworks` directory. # # The following environment variables are required: # # BUILT_PRODUCTS_DIR # FRAMEWORKS_FOLDER_PATH # VALID_ARCHS # EXPANDED_CODE_SIGN_IDENTITY # Signs a framework with the provided identity code_sign() { # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements $1" /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements "$1" } # Set working directory to product’s embedded frameworks cd "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ "$ACTION" = "install" ]; then echo "Copy .bcsymbolmap files to .xcarchive" find . -name '*.bcsymbolmap' -type f -exec mv {} "${CONFIGURATION_BUILD_DIR}" \; else # Delete *.bcsymbolmap files from framework bundle unless archiving find . -name '*.bcsymbolmap' -type f -exec rm -rf "{}" +\; fi echo "Stripping frameworks" for file in $(find . -type f -perm +111); do # Skip non-dynamic libraries if ! [[ "$(file "$file")" == *"dynamically linked shared library"* ]]; then continue fi # Get architectures for current file archs="$(lipo -info "${file}" | rev | cut -d ':' -f1 | rev)" stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$file" "$file" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" != "" ]]; then echo "Stripped $file of architectures:$stripped" if [ "${CODE_SIGNING_REQUIRED}" == "YES" ]; then code_sign "${file}" fi fi done ================================================ FILE: External/Realm/RealmRepository.swift ================================================ // // CoreDataRepository.swift // Jirassic // // Created by Cristian Baluta on 15/04/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RealmSwift class RealmRepository { fileprivate let databaseName = "Jirassic" var realm: Realm! init() { var config = Realm.Configuration() config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(databaseName).realm") Realm.Configuration.defaultConfiguration = config realm = try! Realm() } convenience init (documentsDirectory: String) { self.init() var config = Realm.Configuration() config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(databaseName).realm") Realm.Configuration.defaultConfiguration = config realm = try! Realm() } } extension RealmRepository { fileprivate func queryWithPredicate (_ predicate: NSPredicate?, sortingKeyPath: String?) -> [T] { var results = [T]() var resultsObjs = realm.objects(T.self) if let pred = predicate { resultsObjs = resultsObjs.filter(pred) } if let key = sortingKeyPath { resultsObjs = resultsObjs.sorted(byKeyPath: key) } for result in resultsObjs { results.append(result) } return results } } extension RealmRepository: RepositoryUser { func currentUser() -> User { // let userPredicate = NSPredicate(format: "isLoggedIn == YES") // let cusers: [RUser] = queryWithPredicate(userPredicate, sortDescriptors: nil) // if let cuser = cusers.last { // return User(isLoggedIn: true, email: cuser.email, userId: cuser.userId, lastSyncDate: cuser.lastSyncDate) // } return User(isLoggedIn: false, email: nil, userId: nil, lastSyncDate: nil) } func loginWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to CoreDataRepository") } func registerWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to CoreDataRepository") } func logout() { } } extension RealmRepository: RepositoryTasks { func queryTasks (_ page: Int, completion: ([Task], NSError?) -> Void) { let results: [RTask] = queryWithPredicate(nil, sortingKeyPath: "endDate") let tasks = tasksFromRTasks(results) completion(tasks, nil) } func queryTasksInDay (_ day: Date) -> [Task] { let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [ NSPredicate(format: "endDate >= %@ AND endDate <= %@", day.startOfDay() as CVarArg, day.endOfDay() as CVarArg) ]) // let sortDescriptors = [NSSortDescriptor(key: "endDate", ascending: true)] let results: [RTask] = queryWithPredicate(compoundPredicate, sortingKeyPath: "endDate") let tasks = tasksFromRTasks(results) return tasks } func queryUnsyncedTasks() -> [Task] { let predicate = NSPredicate(format: "lastModifiedDate == nil") let results: [RTask] = queryWithPredicate(predicate, sortingKeyPath: nil) let tasks = tasksFromRTasks(results) return tasks } func deleteTask (_ task: Task, completion: ((_ success: Bool) -> Void)) { realm.beginWrite() let rtask = rtaskFromTask(task) realm.delete(rtask) try! realm.commitWrite() completion(true) } func saveTask (_ task: Task, completion: (_ success: Bool) -> Void) -> Task { realm.beginWrite() let rtask = rtaskFromTask(task) try! realm.commitWrite() return taskFromRTask(rtask) } fileprivate func taskFromRTask (_ rtask: RTask) -> Task { return Task(startDate: rtask.startDate, endDate: rtask.endDate!, notes: rtask.notes, taskNumber: rtask.taskNumber, taskType: TaskType(rawValue: rtask.taskType)!, objectId: rtask.objectId! ) } fileprivate func tasksFromRTasks (_ rtasks: [RTask]) -> [Task] { var tasks = [Task]() for rtask in rtasks { tasks.append(self.taskFromRTask(rtask)) } return tasks } fileprivate func rtaskFromTask (_ task: Task) -> RTask { let taskPredicate = NSPredicate(format: "objectId == %@", task.objectId) let tasks: [RTask] = queryWithPredicate(taskPredicate, sortingKeyPath: nil) var rtask: RTask? = tasks.first if rtask == nil { // rtask = RTask() rtask = realm.create(RTask.self) } if rtask?.objectId == nil { rtask?.objectId = task.objectId } return updatedRTask(rtask!, withTask: task) } // Update only updatable properties. objectId can't be updated fileprivate func updatedRTask (_ rtask: RTask, withTask task: Task) -> RTask { rtask.taskNumber = task.taskNumber rtask.taskType = task.taskType.rawValue rtask.notes = task.notes rtask.startDate = task.startDate rtask.endDate = task.endDate return rtask } } extension RealmRepository: RepositorySettings { func settings() -> Settings { let results: [RSettings] = queryWithPredicate(nil, sortingKeyPath: nil) var rsettings: RSettings? = results.first if rsettings == nil { realm.beginWrite() rsettings = RSettings() rsettings?.startOfDayEnabled = true rsettings?.lunchEnabled = true rsettings?.scrumEnabled = true rsettings?.meetingEnabled = true rsettings?.autoTrackEnabled = true rsettings?.trackingMode = 1 rsettings?.startOfDayTime = Date(hour: 9, minute: 0) rsettings?.endOfDayTime = Date(hour: 17, minute: 0) rsettings?.lunchTime = Date(hour: 13, minute: 0) rsettings?.scrumTime = Date(hour: 10, minute: 30) rsettings?.minSleepDuration = Date(hour: 0, minute: 13) try! realm.commitWrite() } return settingsFromRSettings(rsettings!) } func saveSettings (_ settings: Settings) { realm.beginWrite() let _ = rsettingsFromSettings(settings) try! realm.commitWrite() } fileprivate func settingsFromRSettings (_ rsettings: RSettings) -> Settings { return Settings(startOfDayEnabled: rsettings.startOfDayEnabled, lunchEnabled: rsettings.lunchEnabled, scrumEnabled: rsettings.scrumEnabled, meetingEnabled: rsettings.meetingEnabled, autoTrackEnabled: rsettings.autoTrackEnabled, trackingMode: TaskTrackingMode(rawValue: rsettings.trackingMode)!, startOfDayTime: rsettings.startOfDayTime!, endOfDayTime: rsettings.endOfDayTime!, lunchTime: rsettings.lunchTime!, scrumTime: rsettings.scrumTime!, minSleepDuration: rsettings.minSleepDuration! ) } fileprivate func rsettingsFromSettings (_ settings: Settings) -> RSettings { let results: [RSettings] = queryWithPredicate(nil, sortingKeyPath: nil) var rsettings: RSettings? = results.first if rsettings == nil { rsettings = RSettings() } rsettings?.startOfDayEnabled = settings.startOfDayEnabled rsettings?.lunchEnabled = settings.lunchEnabled rsettings?.scrumEnabled = settings.scrumEnabled rsettings?.meetingEnabled = settings.meetingEnabled rsettings?.autoTrackEnabled = settings.autoTrackEnabled rsettings?.trackingMode = settings.trackingMode.rawValue rsettings?.startOfDayTime = settings.startOfDayTime rsettings?.endOfDayTime = settings.endOfDayTime rsettings?.lunchTime = settings.lunchTime rsettings?.scrumTime = settings.scrumTime rsettings?.minSleepDuration = settings.minSleepDuration return rsettings! } } ================================================ FILE: External/Realm/RealmSwift.framework/Versions/A/Headers/RealmSwift-Swift.h ================================================ // Generated by Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1) #pragma clang diagnostic push #if defined(__has_include) && __has_include() # include #endif #pragma clang diagnostic ignored "-Wauto-import" #include #include #include #include #if !defined(SWIFT_TYPEDEFS) # define SWIFT_TYPEDEFS 1 # if defined(__has_include) && __has_include() # include # elif !defined(__cplusplus) || __cplusplus < 201103L typedef uint_least16_t char16_t; typedef uint_least32_t char32_t; # endif typedef float swift_float2 __attribute__((__ext_vector_type__(2))); typedef float swift_float3 __attribute__((__ext_vector_type__(3))); typedef float swift_float4 __attribute__((__ext_vector_type__(4))); typedef double swift_double2 __attribute__((__ext_vector_type__(2))); typedef double swift_double3 __attribute__((__ext_vector_type__(3))); typedef double swift_double4 __attribute__((__ext_vector_type__(4))); typedef int swift_int2 __attribute__((__ext_vector_type__(2))); typedef int swift_int3 __attribute__((__ext_vector_type__(3))); typedef int swift_int4 __attribute__((__ext_vector_type__(4))); typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); #endif #if !defined(SWIFT_PASTE) # define SWIFT_PASTE_HELPER(x, y) x##y # define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) #endif #if !defined(SWIFT_METATYPE) # define SWIFT_METATYPE(X) Class #endif #if !defined(SWIFT_CLASS_PROPERTY) # if __has_feature(objc_class_property) # define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ # else # define SWIFT_CLASS_PROPERTY(...) # endif #endif #if defined(__has_attribute) && __has_attribute(objc_runtime_name) # define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) #else # define SWIFT_RUNTIME_NAME(X) #endif #if defined(__has_attribute) && __has_attribute(swift_name) # define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) #else # define SWIFT_COMPILE_NAME(X) #endif #if defined(__has_attribute) && __has_attribute(objc_method_family) # define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) #else # define SWIFT_METHOD_FAMILY(X) #endif #if defined(__has_attribute) && __has_attribute(noescape) # define SWIFT_NOESCAPE __attribute__((noescape)) #else # define SWIFT_NOESCAPE #endif #if !defined(SWIFT_CLASS_EXTRA) # define SWIFT_CLASS_EXTRA #endif #if !defined(SWIFT_PROTOCOL_EXTRA) # define SWIFT_PROTOCOL_EXTRA #endif #if !defined(SWIFT_ENUM_EXTRA) # define SWIFT_ENUM_EXTRA #endif #if !defined(SWIFT_CLASS) # if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) # define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA # define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA # else # define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA # define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA # endif #endif #if !defined(SWIFT_PROTOCOL) # define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA # define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA #endif #if !defined(SWIFT_EXTENSION) # define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) #endif #if !defined(OBJC_DESIGNATED_INITIALIZER) # if defined(__has_attribute) && __has_attribute(objc_designated_initializer) # define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) # else # define OBJC_DESIGNATED_INITIALIZER # endif #endif #if !defined(SWIFT_ENUM) # define SWIFT_ENUM(_type, _name) enum _name : _type _name; enum SWIFT_ENUM_EXTRA _name : _type # if defined(__has_feature) && __has_feature(generalized_swift_name) # define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_EXTRA _name : _type # else # define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) SWIFT_ENUM(_type, _name) # endif #endif #if !defined(SWIFT_UNAVAILABLE) # define SWIFT_UNAVAILABLE __attribute__((unavailable)) #endif #if defined(__has_feature) && __has_feature(modules) @import Realm; @import ObjectiveC; @import Foundation; @import Realm.Private; #endif #pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" #pragma clang diagnostic ignored "-Wduplicate-method-arg" @class RLMRealm; @class RLMObjectSchema; @class RLMSchema; /** \code Object \endcode is a class used to define Realm model objects. In Realm you define your model classes by subclassing \code Object \endcode and adding properties to be managed. You then instantiate and use your custom subclasses instead of using the \code Object \endcode class directly. \code class Dog: Object { dynamic var name: String = "" dynamic var adopted: Bool = false let siblings = List() } \endcode

Supported property types

  • \code String \endcode, \code NSString \endcode
  • \code Int \endcode
  • \code Int8 \endcode, \code Int16 \endcode, \code Int32 \endcode, \code Int64 \endcode
  • \code Float \endcode
  • \code Double \endcode
  • \code Bool \endcode
  • \code Date \endcode, \code NSDate \endcode
  • \code Data \endcode, \code NSData \endcode
  • \code RealmOptional \endcode for optional numeric properties
  • \code Object \endcode subclasses, to model many-to-one relationships
  • \code List \endcode, to model many-to-many relationships
\code String \endcode, \code NSString \endcode, \code Date \endcode, \code NSDate \endcode, \code Data \endcode, \code NSData \endcode and \code Object \endcode subclass properties can be declared as optional. \code Int \endcode, \code Int8 \endcode, \code Int16 \endcode, \code Int32 \endcode, \code Int64 \endcode, \code Float \endcode, \code Double \endcode, \code Bool \endcode, and \code List \endcode properties cannot. To store an optional number, use \code RealmOptional \endcode, \code RealmOptional \endcode, \code RealmOptional \endcode, or \code RealmOptional \endcode instead, which wraps an optional numeric value. All property types except for \code List \endcode and \code RealmOptional \endcode must be declared as \code dynamic var \endcode. \code List \endcode and \code RealmOptional \endcode properties must be declared as non-dynamic \code let \endcode properties. Swift \code lazy \endcode properties are not allowed. Note that none of the restrictions listed above apply to properties that are configured to be ignored by Realm.

Querying

You can retrieve all objects of a given type from a Realm by calling the \code objects(_:) \endcode instance method.

Relationships

See our Cocoa guide for more details. */ SWIFT_CLASS_NAMED("Object") @interface RealmSwiftObject : RLMObjectBase /** Creates an unmanaged instance of a Realm object. Call \code add(_:) \endcode on a \code Realm \endcode instance to add an unmanaged object into that Realm.
  • see: \code Realm().add(_:) \endcode
*/ - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; /** Creates an unmanaged instance of a Realm object. The \code value \endcode argument is used to populate the object. It can be a key-value coding compliant object, an array or dictionary returned from the methods in \code NSJSONSerialization \endcode, or an \code Array \endcode containing one element for each managed property. An exception will be thrown if any required properties are not present and those properties were not defined with default values. When passing in an \code Array \endcode as the \code value \endcode argument, all properties must be present, valid and in the same order as the properties defined in the model. Call \code add(_:) \endcode on a \code Realm \endcode instance to add an unmanaged object into that Realm. \param value The value used to populate the object. */ - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; /** Indicates if the object can no longer be accessed because it is now invalid. An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if \code invalidate() \endcode is called on that Realm. */ @property (nonatomic, readonly) BOOL isInvalidated; /** A human-readable description of the object. */ @property (nonatomic, readonly, copy) NSString * _Nonnull description; /** Helper to return the class name for an Object subclass. */ @property (nonatomic, readonly, copy) NSString * _Nonnull className; /** WARNING: This is an internal helper method not intended for public use. :nodoc: */ + (Class _Nonnull)objectUtilClass:(BOOL)isSwift; /** Override this method to specify the name of a property to be used as the primary key. Only properties of types \code String \endcode and \code Int \endcode can be designated as the primary key. Primary key properties enforce uniqueness for each value whenever the property is set, which incurs minor overhead. Indexes are created automatically for primary key properties. returns: The name of the property designated as the primary key, or \code nil \endcode if the model has no primary key. */ + (NSString * _Nullable)primaryKey; /** Override this method to specify the names of properties to ignore. These properties will not be managed by the Realm that manages the object. returns: An array of property names to ignore. */ + (NSArray * _Nonnull)ignoredProperties; /** Returns an array of property names for properties which should be indexed. Only string, integer, boolean, \code Date \endcode, and \code NSDate \endcode properties are supported. returns: An array of property names. */ + (NSArray * _Nonnull)indexedProperties; - (id _Nullable)objectForKeyedSubscript:(NSString * _Nonnull)key; - (void)setObject:(id _Nullable)value forKeyedSubscript:(NSString * _Nonnull)key; /** Returns whether two Realm objects are equal. Objects are considered equal if and only if they are both managed by the same Realm and point to the same underlying object in the database. \param object The object to compare the receiver to. */ - (BOOL)isEqual:(id _Nullable)object; /** WARNING: This is an internal initializer not intended for public use. :nodoc: */ - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; /** WARNING: This is an internal initializer not intended for public use. :nodoc: */ - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end /** Object interface which allows untyped getters and setters for Objects. :nodoc: */ SWIFT_CLASS("_TtC10RealmSwift13DynamicObject") @interface DynamicObject : RealmSwiftObject - (id _Nullable)objectForKeyedSubscript:(NSString * _Nonnull)key; - (void)setObject:(id _Nullable)value forKeyedSubscript:(NSString * _Nonnull)key; /** :nodoc: */ - (id _Nullable)valueForUndefinedKey:(NSString * _Nonnull)key; /** :nodoc: */ - (void)setValue:(id _Nullable)value forUndefinedKey:(NSString * _Nonnull)key; /** :nodoc: */ + (BOOL)shouldIncludeInDefaultSchema; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end /** :nodoc: Internal class. Do not use directly. Used for reflection and initialization */ SWIFT_CLASS("_TtC10RealmSwift18LinkingObjectsBase") @interface LinkingObjectsBase : NSObject @property (nonatomic, readonly, copy) NSString * _Nonnull objectClassName; @property (nonatomic, readonly, copy) NSString * _Nonnull propertyName; @property (nonatomic, readonly, strong) RLMResults * _Nonnull rlmResults; - (nonnull instancetype)initFromClassName:(NSString * _Nonnull)objectClassName property:(NSString * _Nonnull)propertyName OBJC_DESIGNATED_INITIALIZER; - (NSInteger)countByEnumeratingWithState:(NSFastEnumerationState * _Nonnull)state objects:(id _Nullable * _Null_unspecified)buffer count:(NSInteger)len; - (nonnull instancetype)init SWIFT_UNAVAILABLE; @end /** :nodoc: Internal class. Do not use directly. */ SWIFT_CLASS("_TtC10RealmSwift8ListBase") @interface ListBase : RLMListBase /** Returns a human-readable description of the objects contained in the List. */ @property (nonatomic, readonly, copy) NSString * _Nonnull description; /** Returns the number of objects in this List. */ @property (nonatomic, readonly) NSInteger count; - (nonnull instancetype)initWithArray:(RLMArray * _Nonnull)array OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; @end @interface NSDate (SWIFT_EXTENSION(RealmSwift)) @end @interface NSNumber (SWIFT_EXTENSION(RealmSwift)) @end @interface NSNumber (SWIFT_EXTENSION(RealmSwift)) @end @interface RealmSwiftObject (SWIFT_EXTENSION(RealmSwift)) - (RLMObject * _Nonnull)unsafeCastToRLMObject; @end @interface RealmSwiftObject (SWIFT_EXTENSION(RealmSwift)) + (nonnull instancetype)bridgingFrom:(id _Nonnull)objectiveCValue with:(id _Nullable)metadata; @end /** :nodoc: Internal class. Do not use directly. */ SWIFT_CLASS_NAMED("ObjectUtil") @interface RealmSwiftObjectUtil : NSObject - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; @end @interface RLMSyncCredentials (SWIFT_EXTENSION(RealmSwift)) @end @interface RLMSyncManager (SWIFT_EXTENSION(RealmSwift)) /** The sole instance of the singleton. */ SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RLMSyncManager * _Nonnull shared;) + (RLMSyncManager * _Nonnull)shared; @end @interface RLMSyncSession (SWIFT_EXTENSION(RealmSwift)) @end @interface RLMSyncUser (SWIFT_EXTENSION(RealmSwift)) /** A dictionary of all valid, logged-in user identities corresponding to their \code SyncUser \endcode objects. */ SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSDictionary * _Nonnull all;) + (NSDictionary * _Nonnull)all; /** The logged-in user. \code nil \endcode if none exists. Only use this property if your application expects no more than one logged-in user at any given time. warning: Throws an Objective-C exception if more than one logged-in user exists. */ SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RLMSyncUser * _Nullable current;) + (RLMSyncUser * _Nullable)current; @end /** This model is used to reflect permissions. It should be used in conjunction with a \code SyncUser \endcode’s Permission Realm. You can only read this Realm. Use the objects in Management Realm to make request for modifications of permissions. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ SWIFT_CLASS("_TtC10RealmSwift14SyncPermission") @interface SyncPermission : RealmSwiftObject /** The date this object was last modified. */ @property (nonatomic, copy) NSDate * _Nonnull updatedAt; /** The ID of the affected user by the permission. */ @property (nonatomic, copy) NSString * _Nonnull userId; /** The path to the realm. */ @property (nonatomic, copy) NSString * _Nonnull path; /** Whether the affected user is allowed to read from the Realm. */ @property (nonatomic) BOOL mayRead; /** Whether the affected user is allowed to write to the Realm. */ @property (nonatomic) BOOL mayWrite; /** Whether the affected user is allowed to manage the access rights for others. */ @property (nonatomic) BOOL mayManage; /** :nodoc: */ + (BOOL)shouldIncludeInDefaultSchema; /** :nodoc: */ + (NSString * _Nullable)_realmObjectName; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end /** This model is used for requesting changes to a Realm’s permissions. It should be used in conjunction with a \code SyncUser \endcode’s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ SWIFT_CLASS("_TtC10RealmSwift20SyncPermissionChange") @interface SyncPermissionChange : RealmSwiftObject /** The globally unique ID string of this permission change object. */ @property (nonatomic, copy) NSString * _Nonnull id; /** The date this object was initially created. */ @property (nonatomic, copy) NSDate * _Nonnull createdAt; /** The date this object was last modified. */ @property (nonatomic, copy) NSDate * _Nonnull updatedAt; /** An error or informational message, typically written to by the Realm Object Server. */ @property (nonatomic, copy) NSString * _Nullable statusMessage; /** Sync management object status. */ @property (nonatomic, readonly) RLMSyncManagementObjectStatus status; /** The remote URL to the realm. */ @property (nonatomic, copy) NSString * _Nonnull realmUrl; /** The identity of a user affected by this permission change. */ @property (nonatomic, copy) NSString * _Nonnull userId; /** :nodoc: */ + (NSString * _Nullable)primaryKey; /** :nodoc: */ + (BOOL)shouldIncludeInDefaultSchema; /** :nodoc: */ + (NSString * _Nullable)_realmObjectName; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end /** This model is used for offering permission changes to other users. It should be used in conjunction with a \code SyncUser \endcode’s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ SWIFT_CLASS("_TtC10RealmSwift19SyncPermissionOffer") @interface SyncPermissionOffer : RealmSwiftObject /** The globally unique ID string of this permission offer object. */ @property (nonatomic, copy) NSString * _Nonnull id; /** The date this object was initially created. */ @property (nonatomic, copy) NSDate * _Nonnull createdAt; /** The date this object was last modified. */ @property (nonatomic, copy) NSDate * _Nonnull updatedAt; /** An error or informational message, typically written to by the Realm Object Server. */ @property (nonatomic, copy) NSString * _Nullable statusMessage; /** Sync management object status. */ @property (nonatomic, readonly) RLMSyncManagementObjectStatus status; /** A token which uniquely identifies this offer. Generated by the server. */ @property (nonatomic, copy) NSString * _Nullable token; /** The remote URL to the realm. */ @property (nonatomic, copy) NSString * _Nonnull realmUrl; /** Whether this offer allows the receiver to read from the Realm. */ @property (nonatomic) BOOL mayRead; /** Whether this offer allows the receiver to write to the Realm. */ @property (nonatomic) BOOL mayWrite; /** Whether this offer allows the receiver to manage the access rights for others. */ @property (nonatomic) BOOL mayManage; /** When this token will expire and become invalid. */ @property (nonatomic, copy) NSDate * _Nullable expiresAt; /** Construct a permission offer object used to offer permission changes to other users. \param realmURL The URL to the Realm on which to apply these permission changes to, once the offer is accepted. \param expiresAt When this token will expire and become invalid. Pass \code nil \endcode if this offer should not expire. \param mayRead Grant or revoke read access. \param mayWrite Grant or revoked read-write access. \param mayManage Grant or revoke administrative access. */ - (nonnull instancetype)initWithRealmURL:(NSString * _Nonnull)realmURL expiresAt:(NSDate * _Nullable)expiresAt mayRead:(BOOL)mayRead mayWrite:(BOOL)mayWrite mayManage:(BOOL)mayManage; /** :nodoc: */ + (NSArray * _Nonnull)indexedProperties; /** :nodoc: */ + (NSString * _Nullable)primaryKey; /** :nodoc: */ + (BOOL)shouldIncludeInDefaultSchema; /** :nodoc: */ + (NSString * _Nullable)_realmObjectName; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end /** This model is used to apply permission changes defined in the permission offer object represented by the specified token, which was created by another user’s \code SyncPermissionOffer \endcode object. It should be used in conjunction with a \code SyncUser \endcode’s Management Realm. See https://realm.io/docs/realm-object-server/#permissions for general documentation. */ SWIFT_CLASS("_TtC10RealmSwift27SyncPermissionOfferResponse") @interface SyncPermissionOfferResponse : RealmSwiftObject /** The globally unique ID string of this permission offer response object. */ @property (nonatomic, copy) NSString * _Nonnull id; /** The date this object was initially created. */ @property (nonatomic, copy) NSDate * _Nonnull createdAt; /** The date this object was last modified. */ @property (nonatomic, copy) NSDate * _Nonnull updatedAt; /** An error or informational message, typically written to by the Realm Object Server. */ @property (nonatomic, copy) NSString * _Nullable statusMessage; /** Sync management object status. */ @property (nonatomic, readonly) RLMSyncManagementObjectStatus status; /** The received token which uniquely identifies another user’s \code SyncPermissionOffer \endcode. */ @property (nonatomic, copy) NSString * _Nonnull token; /** The remote URL to the realm on which these permission changes were applied. */ @property (nonatomic, copy) NSString * _Nullable realmUrl; /** Construct a permission offer response object used to apply permission changes defined in the permission offer object represented by the specified token, which was created by another user’s \code SyncPermissionOffer \endcode object. \param token The received token which uniquely identifies another user’s \code SyncPermissionOffer \endcode. */ - (nonnull instancetype)initWithToken:(NSString * _Nonnull)token; /** :nodoc: */ + (NSString * _Nullable)primaryKey; /** :nodoc: */ + (BOOL)shouldIncludeInDefaultSchema; /** :nodoc: */ + (NSString * _Nullable)_realmObjectName; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithRealm:(RLMRealm * _Nonnull)realm schema:(RLMObjectSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; - (nonnull instancetype)initWithValue:(id _Nonnull)value schema:(RLMSchema * _Nonnull)schema OBJC_DESIGNATED_INITIALIZER; @end #pragma clang diagnostic pop ================================================ FILE: External/Realm/RealmSwift.framework/Versions/A/Modules/module.modulemap ================================================ framework module RealmSwift { header "RealmSwift-Swift.h" } ================================================ FILE: External/Realm/RealmSwift.framework/Versions/A/Resources/Info.plist ================================================ BuildMachineOSBuild 16D32 CFBundleDevelopmentRegion English CFBundleExecutable RealmSwift CFBundleIdentifier io.realm.RealmSwit CFBundleInfoDictionaryVersion 6.0 CFBundleName RealmSwift CFBundlePackageType FMWK CFBundleShortVersionString 2.4.4 CFBundleSignature ???? CFBundleSupportedPlatforms MacOSX CFBundleVersion 2.4.4 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 8C38 DTPlatformVersion GM DTSDKBuild 16C58 DTSDKName macosx10.12 DTXcode 0820 DTXcodeBuild 8C38 NSHumanReadableCopyright Copyright © 2014 Realm. All rights reserved. ================================================ FILE: External/Repository.swift ================================================ // // Repository.swift // Jirassic // // Created by Baluta Cristian on 01/05/15. // Copyright (c) 2015 Cristian Baluta. All rights reserved. // import Foundation protocol RepositoryUser { func getUser (_ completion: @escaping ((_ user: User?) -> Void)) func loginWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) func registerWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) func logout() } 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 queryDeletedTasks (_ completion: @escaping ([Task]) -> Void) func queryUpdates (_ 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)) // Save a task and returns the same task with a taskId generated if it didn't had. Return nil if task was not saved func saveTask (_ task: Task, completion: @escaping ((_ task: Task?) -> Void)) } protocol RepositorySettings { func settings() -> Settings func saveSettings (_ settings: Settings) } typealias Repository = RepositoryUser & RepositoryTasks & RepositorySettings ================================================ FILE: External/RepositoryInteractor.swift ================================================ // // RepositoryInteractor.swift // Jirassic // // Created by Cristian Baluta on 01/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation // Base class for any Interactor that wishes to use the repository class RepositoryInteractor { var repository: Repository! var remoteRepository: Repository? init (repository: Repository, remoteRepository: Repository?) { self.repository = repository self.remoteRepository = remoteRepository } } ================================================ FILE: External/sqlite/SQLTable.swift ================================================ // // SQLTable.swift // SQLiteDB-iOS // // Created by Fahim Farook on 6/11/15. // Copyright © 2015 RookSoft Pte. Ltd. All rights reserved. // import Foundation @objcMembers class SQLTable: NSObject { /// Internal reference to the SQLite table name as determined based on the name of the `SQLTable` sub-class name. The sub-class name should be in the singular - for example, Task for a tasks table. internal var table = "" /// Internal dictionary to keep track of whether a specific table was verfied to be in existence in the database. This dictionary is used to automatically create the table if it does not exist in the DB. private static var verified = [String: Bool]() private var db = SQLiteDB.shared /// Static variable indicating the table name - used in class methods since the instance variable `table` is not accessible in class methods. private static var table: String { let cls = "\(classForCoder())".lowercased() let ndx = cls.index(before: cls.endIndex) let tnm = cls.hasSuffix("y") ? cls[.. String { return "id" } func ignoredKeys() -> [String] { return [] } // MARK:- Class Methods class func rows (filter: String = "", order: String = "", limit: Int = 0) -> [SQLTable] { var sql = "SELECT * FROM \(table)" if !filter.isEmpty { sql += " WHERE \(filter)" } if !order.isEmpty { sql += " ORDER BY \(order)" } if limit > 0 { sql += " LIMIT 0, \(limit)" } return self.rowsFor(sql: sql) } class func rowsFor (sql: String = "") -> [SQLTable] { var res = [SQLTable]() let tmp = self.init() let data = tmp.values() let db = SQLiteDB.shared! let fsql = sql.isEmpty ? "SELECT * FROM \(table)" : sql let arr = db.query(sql: fsql) for row in arr { let t = self.init() for (key, _) in data { if let val = row[key] { t.setValue(val, forKey: key) } } res.append(t) } return res } class func rowBy (id: Any) -> SQLTable? { let row = self.init() let data = row.values() let db = SQLiteDB.shared! var val = "\(id)" if id is String { val = "'\(id)'" } let sql = "SELECT * FROM \(table) WHERE \(row.primaryKey())=\(val)" let arr = db.query(sql: sql) if arr.count == 0 { return nil } for (key, _) in data { if let val = arr[0][key] { row.setValue(val, forKey: key) } } return row } class func count (filter: String = "") -> Int { let db = SQLiteDB.shared! var sql = "SELECT COUNT(*) AS count FROM \(table)" if !filter.isEmpty { sql += " WHERE \(filter)" } let arr = db.query(sql:sql) if arr.count == 0 { return 0 } if let val = arr[0]["count"] as? Int { return val } return 0 } class func row (number: Int, filter: String = "", order: String = "") -> SQLTable? { let row = self.init() let data = row.values() let db = SQLiteDB.shared! var sql = "SELECT * FROM \(table)" if !filter.isEmpty { sql += " WHERE \(filter)" } if !order.isEmpty { sql += " ORDER BY \(order)" } // Limit to specified row sql += " LIMIT 1 OFFSET \(number-1)" let arr = db.query(sql:sql) if arr.count == 0 { return nil } for (key, _) in data { if let val = arr[0][key] { row.setValue(val, forKey:key) } } return row } class func remove (filter: String = "") -> Bool { let db = SQLiteDB.shared! let sql: String if filter.isEmpty { // Delete all records sql = "DELETE FROM \(table)" } else { // Use filter to delete sql = "DELETE FROM \(table) WHERE \(filter)" } let rc = db.execute(sql: sql) return (rc != 0) } class func zap() { let db = SQLiteDB.shared! let sql = "DELETE FROM \(table)" _ = db.execute(sql:sql) } // MARK:- Public Methods func save() -> Int { let db = SQLiteDB.shared! let key = primaryKey() let data = values() var insert = true if let rid = data[key] { var val = "\(rid)" if let _rid = rid as? String { val = "'\(_rid)'" } let sql = "SELECT COUNT(*) AS count FROM \(table) WHERE \(primaryKey())=\(val)" let arr = db.query(sql: sql) if arr.count == 1 { if let cnt = arr[0]["count"] as? Int { insert = (cnt == 0) } } } // Insert or update let (sql, params) = getSQL(data: data, forInsert: insert) let rc = db.execute(sql: sql, parameters: params) if rc == 0 { if SQLiteDB.sql_logs_enabled { print("Error saving record!") } return 0 } // Update primary key let pid = data[key] if insert { if pid is Int64 { setValue(rc, forKey: key) } else if pid is Int { setValue(Int(rc), forKey: key) } } return rc } func delete() -> Bool { let db = SQLiteDB.shared! let key = primaryKey() let data = values() if let rid = data[key] { let v: String if let s = rid as? String { v = "'\(s)'" } else { v = "\(rid)" } let sql = "DELETE FROM \(table) WHERE \(primaryKey())=\(v)" let rc = db.execute(sql: sql) return (rc != 0) } return false } func refresh() { let db = SQLiteDB.shared! let key = primaryKey() let data = values() if let rid = data[key] { let sql = "SELECT * FROM \(table) WHERE \(primaryKey())=\(rid)" let arr = db.query(sql:sql) for (key, _) in data { if let val = arr[0][key] { setValue(val, forKey: key) } } } } // MARK:- Private Methods // private func properties() -> [String] { // var res = [String]() // for c in Mirror(reflecting:self).children { // if let name = c.label{ // res.append(name) // } // } // return res // } internal func values() -> [String: Any] { var res = [String: Any]() let obj = Mirror(reflecting: self) for (_, attr) in obj.children.enumerated() { if let name = attr.label { // Ignore special properties and lazy vars if ignoredKeys().contains(name) || name.hasSuffix(".storage") { continue } res[name] = attr.value } } return res } /// Returns a valid SQL statement and matching list of bound parameters needed to insert a new row into the database or to update an existing row of data. /// /// - Parameters: /// - data: A dictionary of property names and their corresponding values that need to be persisted to the underlying table. /// - forInsert: A boolean value indicating whether this is an insert or update action. /// - Returns: A tuple containing a valid SQL command to persist data to the underlying table and the bound parameters for the SQL command, if any. private func getSQL (data: [String: Any], forInsert: Bool = true) -> (String, [Any]?) { var sql = "" var params: [Any]? = nil if forInsert { // INSERT INTO tasks(task, categoryID) VALUES ('\(txtTask.text)', 1) sql = "INSERT INTO \(table) (" } else { // UPDATE tasks SET task = ? WHERE categoryID = ? sql = "UPDATE \(table) SET " } let pkey = primaryKey() var wsql = "" var rid: Any? var first = true for (key, val) in data { // Primary key handling if pkey == key { if forInsert { if val is Int && (val as! Int) == -1 { // Do not add this since this is (could be?) an auto-increment value continue } } else { // Update - set up WHERE clause wsql += " WHERE " + key + " = ?" rid = val continue } } // Set up parameter array - if we get here, then there are parameters if first && params == nil { params = [AnyObject]() } if forInsert { sql += first ? "\(key)" : ", \(key)" wsql += first ? " VALUES (?" : ", ?" params!.append(val) } else { sql += first ? "\(key) = ?" : ", \(key) = ?" params!.append(val) } first = false } // Finalize SQL if forInsert { sql += ")" + wsql + ")" } else if params != nil && !wsql.isEmpty { sql += wsql params!.append(rid!) } if SQLiteDB.sql_logs_enabled { print("Final SQL: \(sql) with parameters: \(String(describing: params))") } return (sql, params) } /// Returns a valid SQL fragment for creating the columns, with the correct data type, for the underlying table. /// /// - Parameter columns: A dictionary of property names and their corresponding values for the `SQLTable` sub-class /// - Returns: A string containing an SQL fragment for delcaring the columns for the underlying table with the correct data type private func getColumnSQL (columns: [String: Any]) -> String { var sql = "" for key in columns.keys { let val = columns[key]! var col = "'\(key)' " if val is Int { // Integers col += "INTEGER" if key == primaryKey() { col += " PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE" } } else { // Other values if val is Float || val is Double { col += "REAL" } else if val is Bool { col += "BOOLEAN" } else if val is Date { col += "DATE" } else if val is NSData { col += "BLOB" } else { // Default to text col += "TEXT" } if key == primaryKey() { col += " PRIMARY KEY NOT NULL UNIQUE" } } if sql.isEmpty { sql = col } else { sql += ", " + col } } return sql } } ================================================ FILE: External/sqlite/SQLiteDB.swift ================================================ // // SQLiteDB.swift // TasksGalore // // Created by Fahim Farook on 12/6/14. // Copyright (c) 2014 RookSoft Pte. Ltd. All rights reserved. // import Foundation let SQLITE_DATE = SQLITE_NULL + 1 fileprivate let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self) fileprivate let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) @objcMembers class SQLiteDB: NSObject { static var sql_logs_enabled = false let QUEUE_LABEL = "SQLiteDB" fileprivate var db: OpaquePointer? = nil fileprivate var queue: DispatchQueue! fileprivate let fmt = DateFormatter() fileprivate var path: String! static var shared: SQLiteDB! convenience init (url: URL) { self.init() openDB(path: url.path) SQLiteDB.shared = self } deinit { closeDB() } override var description: String { return "SQLiteDB: \(String(describing: path))" } func dbDate (dt: Date) -> String { return fmt.string(from: dt) } // Execute SQL with parameters and return result code func execute (sql: String, parameters: [Any]? = nil) -> Int { if SQLiteDB.sql_logs_enabled { print(sql) } var result = 0 queue.sync { if let stmt = self.prepare(sql: sql, params: parameters) { result = self.execute(stmt: stmt, sql: sql) } } return result } // Run SQL query with parameters func query (sql: String, parameters: [Any]? = nil) -> [[String: Any]] { if SQLiteDB.sql_logs_enabled { print(sql) } var rows = [[String: Any]]() queue.sync { if let stmt = self.prepare(sql: sql, params: parameters) { rows = self.query(stmt: stmt, sql: sql) } } return rows } var version: Int { get { var version = 0 let arr = query(sql: "PRAGMA user_version") if arr.count == 1 { version = arr[0]["user_version"] as! Int } return version } set { _ = execute(sql: "PRAGMA user_version=\(newValue)") } } } extension SQLiteDB { fileprivate func openDB (path: String) { // Set up essentials queue = DispatchQueue(label: QUEUE_LABEL, attributes: []) // You need to set the locale in order for the 24-hour date format to work correctly on devices where 24-hour format is turned off fmt.locale = Locale(identifier: "en_US_POSIX") fmt.timeZone = TimeZone(secondsFromGMT: 0) fmt.dateFormat = "yyyy-MM-dd HH:mm:ss" // Open the DB let cpath = path.cString(using: String.Encoding.utf8) let error = sqlite3_open(cpath!, &db) if error != SQLITE_OK { // Open failed, close DB and fail if SQLiteDB.sql_logs_enabled { print("SQLiteDB - failed to open DB!") } sqlite3_close(db) return } if SQLiteDB.sql_logs_enabled { print("SQLiteDB opened!") } } fileprivate func closeDB() { guard db != nil else { return } // Get launch count value let ud = UserDefaults.standard var launchCount = ud.integer(forKey: "LaunchCount") launchCount -= 1 if SQLiteDB.sql_logs_enabled { print("SQLiteDB - Launch count \(launchCount)") } var clean = false if launchCount < 0 { clean = true launchCount = 500 } ud.set(launchCount, forKey: "LaunchCount") ud.synchronize() // Do we clean DB? if !clean { sqlite3_close(db) return } // Clean DB if SQLiteDB.sql_logs_enabled { print("SQLiteDB - Optimize DB") } let sql = "VACUUM; ANALYZE" if CInt(execute(sql: sql)) != SQLITE_OK { if SQLiteDB.sql_logs_enabled { print("SQLiteDB - Error cleaning DB") } } sqlite3_close(db) } // fileprivate method which prepares the SQL fileprivate func prepare (sql: String, params: [Any]?) -> OpaquePointer? { var stmt: OpaquePointer? = nil let cSql = sql.cString(using: String.Encoding.utf8) // Prepare let result = sqlite3_prepare_v2(self.db, cSql!, -1, &stmt, nil) if result != SQLITE_OK { sqlite3_finalize(stmt) if let error = String(validatingUTF8:sqlite3_errmsg(self.db)) { let msg = "SQLiteDB - failed to prepare SQL: \(sql), Error: \(error)" NSLog(msg) } return nil } // Bind parameters, if any if params != nil { // Validate parameters let cntParams = sqlite3_bind_parameter_count(stmt) let cnt = CInt(params!.count) if cntParams != cnt { let msg = "SQLiteDB - failed to bind parameters, counts did not match. SQL: \(sql), Parameters: \(String(describing: params))" NSLog(msg) return nil } var flag: CInt = 0 // Text & BLOB values passed to a C-API do not work correctly if they are not marked as transient. for ndx in 1...cnt { // NSLog("Binding: \(params![ndx-1]) at Index: \(ndx)") let i: Int = Int(ndx) - 1 // Check for data types if let txt = params![i] as? String { flag = sqlite3_bind_text(stmt, ndx, txt, -1, SQLITE_TRANSIENT) } else if let data = params![i] as? NSData { flag = sqlite3_bind_blob(stmt, ndx, data.bytes, CInt(data.length), SQLITE_TRANSIENT) } else if let date = params![i] as? Date { let txt = fmt.string(from: date) flag = sqlite3_bind_text(stmt, ndx, txt, -1, SQLITE_TRANSIENT) } else if let val = params![i] as? Bool { let num = val ? 1 : 0 flag = sqlite3_bind_int(stmt, ndx, CInt(num)) } else if let val = params![i] as? Double { flag = sqlite3_bind_double(stmt, ndx, CDouble(val)) } else if let val = params![i] as? Int { flag = sqlite3_bind_int(stmt, ndx, CInt(val)) } else { flag = sqlite3_bind_null(stmt, ndx) } // Check for errors if flag != SQLITE_OK { sqlite3_finalize(stmt) if let error = String(validatingUTF8:sqlite3_errmsg(self.db)) { let msg = "SQLiteDB - failed to bind for SQL: \(sql), Parameters: \(String(describing: params)), Index: \(ndx) Error: \(error)" if SQLiteDB.sql_logs_enabled { print(msg) } } return nil } } } return stmt } // fileprivate method which handles the actual execution of an SQL statement fileprivate func execute (stmt: OpaquePointer, sql: String) -> Int { // Step let res = sqlite3_step(stmt) if res != SQLITE_OK && res != SQLITE_DONE { sqlite3_finalize(stmt) if let error = String(validatingUTF8: sqlite3_errmsg(self.db)) { let msg = "SQLiteDB - failed to execute SQL: \(sql), Error: \(error)" if SQLiteDB.sql_logs_enabled { print(msg) } } return 0 } // Is this an insert let upp = sql.uppercased() var result = 0 if upp.hasPrefix("INSERT ") { // Known limitations: http://www.sqlite.org/c3ref/last_insert_rowid.html let rid = sqlite3_last_insert_rowid(self.db) result = Int(rid) } else if upp.hasPrefix("DELETE") || upp.hasPrefix("UPDATE") { var cnt = sqlite3_changes(self.db) if cnt == 0 { cnt += 1 } result = Int(cnt) } else { result = 1 } // Finalize sqlite3_finalize(stmt) return result } // fileprivate method which handles the actual execution of an SQL query fileprivate func query (stmt: OpaquePointer, sql: String) -> [[String: Any]] { var rows = [[String:Any]]() var fetchColumnInfo = true var columnCount:CInt = 0 var columnNames = [String]() var columnTypes = [CInt]() var result = sqlite3_step(stmt) while result == SQLITE_ROW { // Should we get column info? if fetchColumnInfo { columnCount = sqlite3_column_count(stmt) for index in 0.. CInt { var type:CInt = 0 // Column types - http://www.sqlite.org/datatype3.html (section 2.2 table column 1) let blobTypes = ["BINARY", "BLOB", "VARBINARY"] let charTypes = ["CHAR", "CHARACTER", "CLOB", "NATIONAL VARYING CHARACTER", "NATIVE CHARACTER", "NCHAR", "NVARCHAR", "TEXT", "VARCHAR", "VARIANT", "VARYING CHARACTER"] let dateTypes = ["DATE", "DATETIME", "TIME", "TIMESTAMP"] let intTypes = ["BIGINT", "BIT", "BOOL", "BOOLEAN", "INT", "INT2", "INT8", "INTEGER", "MEDIUMINT", "SMALLINT", "TINYINT"] let nullTypes = ["NULL"] let realTypes = ["DECIMAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "NUMERIC", "REAL"] // Determine type of column - http://www.sqlite.org/c3ref/c_blob.html let buf = sqlite3_column_decltype(stmt, index) // NSLog("SQLiteDB - Got column type: \(buf)") if buf != nil { var tmp = String(validatingUTF8:buf!)!.uppercased() // Remove bracketed section if let pos = tmp.range(of:"(") { tmp = String(tmp[.. Any? { // Integer if type == SQLITE_INTEGER { let val = sqlite3_column_int(stmt, index) return Int(val) } // Float if type == SQLITE_FLOAT { let val = sqlite3_column_double(stmt, index) return Double(val) } // Text - handled by default handler at end // Blob if type == SQLITE_BLOB { let data = sqlite3_column_blob(stmt, index) let size = sqlite3_column_bytes(stmt, index) let val = NSData(bytes: data, length: Int(size)) return val } // Null if type == SQLITE_NULL { return nil } // Date if type == SQLITE_DATE { // Is this a text date if let ptr = UnsafeRawPointer.init(sqlite3_column_text(stmt, index)) { let uptr = ptr.bindMemory(to: CChar.self, capacity: 0) let txt = String(validatingUTF8: uptr)! let set = CharacterSet(charactersIn: "-:") if txt.rangeOfCharacter(from: set) != nil { let dateFormatter = DateFormatter() dateFormatter.timeZone = TimeZone(abbreviation: "GMT") dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" return dateFormatter.date(from: txt)! } } // If not a text date, then it's a time interval let val = sqlite3_column_double(stmt, index) if val == 0.0 { return nil } let dt = Date(timeIntervalSince1970: val) return dt } // If nothing works, return a string representation if let ptr = UnsafeRawPointer.init(sqlite3_column_text(stmt, index)) { let uptr = ptr.bindMemory(to: CChar.self, capacity: 0) let txt = String(validatingUTF8: uptr) return txt } return nil } } ================================================ FILE: External/sqlite/SQLiteSchema.swift ================================================ // // 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 } } ================================================ FILE: External/sqlite/SSettings.swift ================================================ // // SSettings.swift // Jirassic // // Created by Cristian Baluta on 17/09/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation class SSettings: SQLTable { var autotrack: Bool = false var autotrackingMode: Int = 0 var trackLunch: Bool = false var trackScrum: Bool = false var trackMeetings: Bool = false var trackCodeReviews: Bool = false var trackWastedTime: Bool = false var trackStartOfDay: Bool = false var enableBackup: Bool = false var startOfDayTime: Date? = nil var endOfDayTime: Date? = nil var lunchTime: Date? = nil var scrumTime: Date? = nil var minSleepDuration: Int = 0 var minCodeRevDuration: Int = 0 var codeRevLink: String? = nil var minWasteDuration: Int = 0 var wasteLinks: String? = nil var i: Int = 0 override func primaryKey() -> String { return "i" } } ================================================ FILE: External/sqlite/STask.swift ================================================ // // CTask.swift // Jirassic // // Created by Cristian Baluta on 01/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation class STask: SQLTable { var lastModifiedDate: Date? var markedForDeletion = false var startDate: Date? var endDate: Date? var notes: String? var taskNumber: String? var taskTitle: String? var taskType: Int = 0 var objectId: String? override func primaryKey() -> String { return "objectId" } override var description: String { return "" } } ================================================ FILE: External/sqlite/SUser.swift ================================================ // // SUser.swift // Jirassic // // Created by Cristian Baluta on 04/05/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation class SUser: SQLTable { var userId: String? var email: String? var lastSyncDate: Date? var isLoggedIn: Bool = false override func primaryKey() -> String { return "userId" } } ================================================ FILE: External/sqlite/SqliteRepository+Settings.swift ================================================ // // SqliteRepository+Settings.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation extension SqliteRepository: RepositorySettings { func settings() -> Settings { let results: [SSettings] = queryWithPredicate(nil, sortingKeyPath: nil) var ssettings: SSettings? = results.first if ssettings == nil { // Default values ssettings = SSettings() ssettings?.autotrack = true ssettings?.autotrackingMode = 1 ssettings?.trackLunch = true ssettings?.trackScrum = true ssettings?.trackMeetings = true ssettings?.trackCodeReviews = true ssettings?.trackWastedTime = true ssettings?.trackStartOfDay = true ssettings?.enableBackup = true ssettings?.startOfDayTime = Date(hour: 9, minute: 0) ssettings?.endOfDayTime = Date(hour: 17, minute: 0) ssettings?.lunchTime = Date(hour: 13, minute: 0) ssettings?.scrumTime = Date(hour: 10, minute: 30) ssettings?.minSleepDuration = 13 ssettings?.minCodeRevDuration = 2 ssettings?.minWasteDuration = 5 ssettings?.codeRevLink = "(http|https)://(.+)/projects/(.+)/repos/(.+)/pull-requests" ssettings?.wasteLinks = "facebook.com,youtube.com,twitter.com" } return settingsFromSSettings(ssettings!) } func saveSettings (_ settings: Settings) { let ssettings = ssettingsFromSettings(settings) _ = ssettings.save() } fileprivate func settingsFromSSettings (_ ssettings: SSettings) -> Settings { return Settings(enableBackup: ssettings.enableBackup, settingsTracking: SettingsTracking( autotrack: ssettings.autotrack, autotrackingMode: TrackingMode(rawValue: ssettings.autotrackingMode)!, trackLunch: ssettings.trackLunch, trackScrum: ssettings.trackScrum, trackMeetings: ssettings.trackMeetings, trackStartOfDay: ssettings.trackStartOfDay, startOfDayTime: ssettings.startOfDayTime!, endOfDayTime: ssettings.endOfDayTime!, lunchTime: ssettings.lunchTime!, scrumTime: ssettings.scrumTime!, minSleepDuration: ssettings.minSleepDuration ), settingsBrowser: SettingsBrowser( trackCodeReviews: ssettings.trackCodeReviews, trackWastedTime: ssettings.trackWastedTime, minCodeRevDuration: ssettings.minCodeRevDuration, codeRevLink: ssettings.codeRevLink!, minWasteDuration: ssettings.minWasteDuration, wasteLinks: ssettings.wasteLinks!.toArray() ) ) } fileprivate func ssettingsFromSettings (_ settings: Settings) -> SSettings { let results: [SSettings] = queryWithPredicate(nil, sortingKeyPath: nil) var ssettings: SSettings? = results.first if ssettings == nil { ssettings = SSettings() } ssettings?.autotrack = settings.settingsTracking.autotrack ssettings?.autotrackingMode = settings.settingsTracking.autotrackingMode.rawValue ssettings?.trackLunch = settings.settingsTracking.trackLunch ssettings?.trackScrum = settings.settingsTracking.trackScrum ssettings?.trackMeetings = settings.settingsTracking.trackMeetings ssettings?.trackCodeReviews = settings.settingsBrowser.trackCodeReviews ssettings?.trackWastedTime = settings.settingsBrowser.trackWastedTime ssettings?.trackStartOfDay = settings.settingsTracking.trackStartOfDay ssettings?.enableBackup = settings.enableBackup ssettings?.startOfDayTime = settings.settingsTracking.startOfDayTime ssettings?.endOfDayTime = settings.settingsTracking.endOfDayTime ssettings?.lunchTime = settings.settingsTracking.lunchTime ssettings?.scrumTime = settings.settingsTracking.scrumTime ssettings?.minSleepDuration = settings.settingsTracking.minSleepDuration ssettings?.minCodeRevDuration = settings.settingsBrowser.minCodeRevDuration ssettings?.codeRevLink = settings.settingsBrowser.codeRevLink ssettings?.minWasteDuration = settings.settingsBrowser.minWasteDuration ssettings?.wasteLinks = settings.settingsBrowser.wasteLinks.toString() return ssettings! } } ================================================ FILE: External/sqlite/SqliteRepository+Tasks.swift ================================================ // // SqliteRepository+Tasks.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation import RCLog extension SqliteRepository: RepositoryTasks { func queryTask (withId objectId: String) -> Task? { let taskPredicate = "objectId == '\(objectId)'" let tasks: [STask] = queryWithPredicate(taskPredicate, sortingKeyPath: nil) if let stask = tasks.first { return taskFromSTask(stask) } return nil } func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil) -> [Task] { let tasks = self.tasksBetween (startDate: startDate, endDate: endDate, predicate: predicate) return tasks } func queryTasks (startDate: Date, endDate: Date, predicate: NSPredicate? = nil, completion: @escaping ([Task], NSError?) -> Void) { queue.async { let tasks = self.queryTasks (startDate: startDate, endDate: endDate, predicate: predicate) completion(tasks, nil) } } func queryUnsyncedTasks() -> [Task] { #if !CMD RCLog("Query tasks since last sync date: \(String(describing: UserDefaults.standard.lastSyncDateWithRemote))") #endif var sinceDatePredicate = "" if let lastSyncDateWithRemote = UserDefaults.standard.lastSyncDateWithRemote { sinceDatePredicate = " OR datetime(lastModifiedDate) > datetime('\(lastSyncDateWithRemote.YYYYMMddHHmmssGMT())')" } let predicate = "(lastModifiedDate is NULL\(sinceDatePredicate)) AND markedForDeletion == 0" let results: [STask] = queryWithPredicate(predicate, sortingKeyPath: nil) let tasks = tasksFromSTasks(results) return tasks } func queryDeletedTasks (_ completion: @escaping ([Task]) -> Void) { let predicate = "markedForDeletion == 1" let results: [STask] = queryWithPredicate(predicate, sortingKeyPath: nil) let tasks = tasksFromSTasks(results) completion(tasks) } func queryUpdates (_ completion: @escaping ([Task], [String], NSError?) -> Void) { queryDeletedTasks { (deletedTasks) in let unsyncedTasks = self.queryUnsyncedTasks() let deletedTasksIds = deletedTasks.map{ $0.objectId! } completion(unsyncedTasks, deletedTasksIds, nil) } } func deleteTask (_ task: Task, permanently: Bool, completion: @escaping ((_ success: Bool) -> Void)) { let stask = staskFromTask(task) if permanently { completion( stask.delete() ) } else { stask.markedForDeletion = true completion( stask.save() == 1 ) } } func deleteTask (objectId: String, completion: @escaping ((_ success: Bool) -> Void)) { let taskPredicate = "objectId == '\(objectId)'" let tasks: [STask] = queryWithPredicate(taskPredicate, sortingKeyPath: nil) if let stask = tasks.first { completion( stask.delete() ) } else { completion( false ) } } func saveTask (_ task: Task, completion: @escaping ((_ task: Task?) -> Void)) { let stask = staskFromTask(task) let saved = stask.save() #if !CMD RCLog("Saved to sqlite \(saved) \(task)") #endif if saved == 1 { completion( taskFromSTask(stask)) } else { completion(nil) } } } extension SqliteRepository { private func tasksBetween (startDate: Date, endDate: Date, predicate: NSPredicate? = nil) -> [Task] { let startDateString = startDate.YYYYMMddHHmmssGMT() let endDateString = endDate.YYYYMMddHHmmssGMT() var predicateComponents = ["datetime(endDate) BETWEEN datetime('\(startDateString)') AND datetime('\(endDateString)')", "markedForDeletion == 0"] if let p = predicate { predicateComponents.append("(\(p.predicateFormat))") } let sqlPredicate = predicateComponents.joined(separator: " AND ") let results: [STask] = queryWithPredicate(sqlPredicate, sortingKeyPath: "endDate") let tasks = tasksFromSTasks(results) 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 staskFromTask (_ task: Task) -> STask { let taskPredicate = "objectId == '\(task.objectId!)'" let tasks: [STask] = queryWithPredicate(taskPredicate, sortingKeyPath: nil) var stask: STask? = tasks.first if stask == nil { stask = STask() stask!.objectId = task.objectId } 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 } } ================================================ FILE: External/sqlite/SqliteRepository+User.swift ================================================ // // SqliteRepository+User.swift // Jirassic // // Created by Cristian Baluta on 02/04/2017. // Copyright © 2017 Imagin soft. All rights reserved. // import Foundation extension SqliteRepository: RepositoryUser { func getUser(_ completion: @escaping ((_ user: User?) -> Void)) { fatalError("This method is not applicable to local Repository") } func loginWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to local Repository") } func registerWithCredentials (_ credentials: UserCredentials, completion: (NSError?) -> Void) { fatalError("This method is not applicable to local Repository") } func logout() { fatalError("This method is not applicable to local Repository") } } ================================================ FILE: External/sqlite/SqliteRepository.swift ================================================ // // CoreDataRepository.swift // Jirassic // // Created by Cristian Baluta on 15/04/16. // Copyright © 2016 Cristian Baluta. All rights reserved. // import Foundation import RCLog class SqliteRepository { private let appName = "Jirassic" private let databaseName = "Jirassic" private var db: SQLiteDB! internal let queue = DispatchQueue(label: "fetch_tasks", attributes: []) init() { let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) let baseUrl = urls.last! let url = baseUrl.appendingPathComponent(appName) open(atUrl: url) } required init (documentsDirectory: URL) { open(atUrl: documentsDirectory) } private func open (atUrl url: URL) { if !FileManager.default.fileExists(atPath: url.path) { try! FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil) } let dbUrl = url.appendingPathComponent("\(databaseName).sqlite") #if !CMD RCLog(dbUrl) #endif db = SQLiteDB(url: dbUrl) _ = SQLiteSchema(db: db) } internal func queryWithPredicate (_ predicate: String?, sortingKeyPath: String?) -> [T] { var results = [T]() let resultsObjs = T.rows(filter: predicate ?? "", order: sortingKeyPath != nil ? "\(sortingKeyPath!) ASC" : "") as! [T] // RCLog(resultsObjs) for result in resultsObjs { results.append(result) } return results } } ================================================ FILE: External/sqlite/UserDefaults+uploadToken.swift ================================================ // // 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") } } } ================================================ FILE: Jirassic macOS.entitlements ================================================ com.apple.security.personal-information.calendars ================================================ FILE: Jirassic.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 2801388F205F86460051B532 /* TimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2801388E205F86460051B532 /* TimeBox.swift */; }; 28013890205F86460051B532 /* TimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2801388E205F86460051B532 /* TimeBox.swift */; }; 280300D61EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */; }; 280300D71EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */; }; 280300D81EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */; }; 280300D91EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */; }; 2803A0CA1EC184FF005F9389 /* BrowserNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2803A0C91EC184FF005F9389 /* BrowserNotification.swift */; }; 280C1D431ED74EF900C126A1 /* BrowserSupport.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 280C1D411ED74EF900C126A1 /* BrowserSupport.scpt */; }; 280C1D441ED74EF900C126A1 /* ShellSupport.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 280C1D421ED74EF900C126A1 /* ShellSupport.scpt */; }; 280D70B11ECC09D9005D2689 /* AppTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280D70B01ECC09D9005D2689 /* AppTheme.swift */; }; 280D70C81ECFE5AC005D2689 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3421DD3B44200B73201 /* AppDelegate.swift */; }; 280D70C91ECFE5B1005D2689 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3431DD3B44200B73201 /* LaunchScreen.xib */; }; 280D70CA1ECFE5BD005D2689 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3451DD3B44200B73201 /* Main.storyboard */; }; 280D70CB1ECFE5D0005D2689 /* DaysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3471DD3B44200B73201 /* DaysViewController.swift */; }; 280D70CD1ECFE5DC005D2689 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3491DD3B44200B73201 /* Images.xcassets */; }; 280D70CE1ECFE5E3005D2689 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D34B1DD3B44200B73201 /* LoginViewController.swift */; }; 280D70CF1ECFE5E7005D2689 /* NonTaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D34D1DD3B44200B73201 /* NonTaskCell.swift */; }; 280D70D01ECFE5EA005D2689 /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D34E1DD3B44200B73201 /* TaskCell.swift */; }; 280D70D11ECFE5EE005D2689 /* TasksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D34F1DD3B44200B73201 /* TasksViewController.swift */; }; 280D70D21ECFE7ED005D2689 /* Day.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3181DD3B44200B73201 /* Day.swift */; }; 280D70D31ECFE7FB005D2689 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3191DD3B44200B73201 /* Report.swift */; }; 280D70D41ECFE7FF005D2689 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31B1DD3B44200B73201 /* Task.swift */; }; 280D70D51ECFE803005D2689 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31D1DD3B44200B73201 /* User.swift */; }; 280D70D61ECFE807005D2689 /* Week.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31E1DD3B44200B73201 /* Week.swift */; }; 280D70D71ECFE817005D2689 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3201DD3B44200B73201 /* DateExtension.swift */; }; 280D70D81ECFE82E005D2689 /* ViewAutolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3231DD3B44200B73201 /* ViewAutolayout.swift */; }; 280D70D91ECFE835005D2689 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3261DD3B44200B73201 /* Conversions.swift */; }; 280D70DA1ECFE83F005D2689 /* CreateReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32B1DD3B44200B73201 /* CreateReport.swift */; }; 280D70DB1ECFE847005D2689 /* ReadDaysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32E1DD3B44200B73201 /* ReadDaysInteractor.swift */; }; 280D70DC1ECFE84C005D2689 /* ReadTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */; }; 280D70DE1ECFE870005D2689 /* UserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33D1DD3B44200B73201 /* UserInteractor.swift */; }; 280D70DF1ECFE888005D2689 /* CoreDataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38C1DD3B44200B73201 /* CoreDataRepository.swift */; }; 280D70E01ECFE88F005D2689 /* CoreDataRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928891E910F4D0022AB55 /* CoreDataRepository+Tasks.swift */; }; 280D70E11ECFE88F005D2689 /* CoreDataRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288C1E910F970022AB55 /* CoreDataRepository+Settings.swift */; }; 280D70E21ECFE88F005D2689 /* CoreDataRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288F1E910FF60022AB55 /* CoreDataRepository+User.swift */; }; 280D70E31ECFE88F005D2689 /* CSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38D1DD3B44200B73201 /* CSettings.swift */; }; 280D70E41ECFE88F005D2689 /* CTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38E1DD3B44200B73201 /* CTask.swift */; }; 280D70E51ECFE88F005D2689 /* CUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38F1DD3B44200B73201 /* CUser.swift */; }; 280D70E71ECFE8B9005D2689 /* CloudKitRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38A1DD3B44200B73201 /* CloudKitRepository.swift */; }; 280D70E81ECFE8B9005D2689 /* CloudKitRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928921E91104B0022AB55 /* CloudKitRepository+Tasks.swift */; }; 280D70E91ECFE8B9005D2689 /* CloudKitRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928961E9110BF0022AB55 /* CloudKitRepository+Settings.swift */; }; 280D70EA1ECFE8B9005D2689 /* CloudKitRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928941E9110980022AB55 /* CloudKitRepository+User.swift */; }; 280D70EB1ECFE8B9005D2689 /* UserDefaults+token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AFE7301E9A594500BAAD8C /* UserDefaults+token.swift */; }; 280D70F01ECFE8CC005D2689 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3941DD3B44200B73201 /* Repository.swift */; }; 280D70F11ECFE8D0005D2689 /* RepositoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */; }; 280D70F41ECFE96F005D2689 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31A1DD3B44200B73201 /* Settings.swift */; }; 280D70F51ECFE98C005D2689 /* UserDefaults+uploadToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */; }; 280D70F61ECFE9B3005D2689 /* RegisterUserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33C1DD3B44200B73201 /* RegisterUserInteractor.swift */; }; 280D70F71ED0E6EC005D2689 /* StringIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */; }; 280D70FA1ED0E7B0005D2689 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 280D70F91ED0E7B0005D2689 /* CloudKit.framework */; }; 280D71021ED35A25005D2689 /* UserDefaults+token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AFE7301E9A594500BAAD8C /* UserDefaults+token.swift */; }; 280D71031ED35A3E005D2689 /* StringArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280F507C1EC868B0007416AB /* StringArray.swift */; }; 280D712C1ED6043D005D2689 /* Day.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3181DD3B44200B73201 /* Day.swift */; }; 280D712D1ED6043D005D2689 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3191DD3B44200B73201 /* Report.swift */; }; 280D712E1ED6043D005D2689 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31A1DD3B44200B73201 /* Settings.swift */; }; 280D712F1ED6043D005D2689 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31B1DD3B44200B73201 /* Task.swift */; }; 280D71311ED6043D005D2689 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31D1DD3B44200B73201 /* User.swift */; }; 280D71321ED6043D005D2689 /* Week.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31E1DD3B44200B73201 /* Week.swift */; }; 280D71331ED6043D005D2689 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3201DD3B44200B73201 /* DateExtension.swift */; }; 280D71351ED6043D005D2689 /* StringIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */; }; 280D71361ED6043D005D2689 /* StringArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280F507C1EC868B0007416AB /* StringArray.swift */; }; 280D71371ED6043D005D2689 /* ViewAutolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3231DD3B44200B73201 /* ViewAutolayout.swift */; }; 280D71381ED6043D005D2689 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3241DD3B44200B73201 /* ViewController.swift */; }; 280D71391ED6043D005D2689 /* ViewControllerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3251DD3B44200B73201 /* ViewControllerStoryboard.swift */; }; 280D713A1ED6043D005D2689 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3261DD3B44200B73201 /* Conversions.swift */; }; 280D713B1ED6043D005D2689 /* ComputerWakeUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3291DD3B44200B73201 /* ComputerWakeUpInteractor.swift */; }; 280D713D1ED6043D005D2689 /* CreateReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32B1DD3B44200B73201 /* CreateReport.swift */; }; 280D713F1ED6043D005D2689 /* ReadDaysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32E1DD3B44200B73201 /* ReadDaysInteractor.swift */; }; 280D71411ED6043D005D2689 /* ReadTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */; }; 280D71431ED6043D005D2689 /* TaskFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3301DD3B44200B73201 /* TaskFinder.swift */; }; 280D71451ED6043D005D2689 /* TaskInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3321DD3B44200B73201 /* TaskInteractor.swift */; }; 280D71471ED6043D005D2689 /* TaskTypeEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3341DD3B44200B73201 /* TaskTypeEstimator.swift */; }; 280D71491ED6043D005D2689 /* TaskTypeSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3361DD3B44200B73201 /* TaskTypeSelection.swift */; }; 280D714A1ED6043D005D2689 /* PredictiveTimeTyping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3391DD3B44200B73201 /* PredictiveTimeTyping.swift */; }; 280D714C1ED6043D005D2689 /* RegisterUserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33C1DD3B44200B73201 /* RegisterUserInteractor.swift */; }; 280D714D1ED6043D005D2689 /* UserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33D1DD3B44200B73201 /* UserInteractor.swift */; }; 280D71591ED6043D005D2689 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3541DD3B44200B73201 /* AppDelegate.swift */; }; 280D715A1ED6043D005D2689 /* AppWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3551DD3B44200B73201 /* AppWireframe.swift */; }; 280D715B1ED6043D005D2689 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405B15651DEF75660009871C /* AppViewController.swift */; }; 280D715D1ED6043D005D2689 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4051D5F81E0EA48A002042BB /* AppLauncher.swift */; }; 280D715E1ED6043D005D2689 /* Versioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2823C9341E4F69970055D036 /* Versioning.swift */; }; 280D715F1ED6043D005D2689 /* AppTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280D70B01ECC09D9005D2689 /* AppTheme.swift */; }; 280D71601ED6043D005D2689 /* MenuBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3651DD3B44200B73201 /* MenuBarController.swift */; }; 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 */; }; 280D71901ED6043D005D2689 /* BrowserNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2803A0C91EC184FF005F9389 /* BrowserNotification.swift */; }; 280D719D1ED6043D005D2689 /* SqliteRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA71E8ADC53002B07FD /* SqliteRepository.swift */; }; 280D719E1ED6043D005D2689 /* SqliteRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928801E910DA40022AB55 /* SqliteRepository+Tasks.swift */; }; 280D719F1ED6043D005D2689 /* SqliteRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928831E910E5E0022AB55 /* SqliteRepository+User.swift */; }; 280D71A01ED6043D005D2689 /* SqliteRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928861E910EA70022AB55 /* SqliteRepository+Settings.swift */; }; 280D71A11ED6043D005D2689 /* SSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA81E8ADC53002B07FD /* SSettings.swift */; }; 280D71A21ED6043D005D2689 /* STask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA91E8ADC53002B07FD /* STask.swift */; }; 280D71A31ED6043D005D2689 /* SUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EAA1E8ADC53002B07FD /* SUser.swift */; }; 280D71A41ED6043D005D2689 /* SQLiteDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB31E8AF08F002B07FD /* SQLiteDB.swift */; }; 280D71A51ED6043D005D2689 /* SQLTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB41E8AF08F002B07FD /* SQLTable.swift */; }; 280D71A61ED6043D005D2689 /* SQLiteSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928771E8F78580022AB55 /* SQLiteSchema.swift */; }; 280D71A71ED6043D005D2689 /* UserDefaults+uploadToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */; }; 280D71B61ED6043D005D2689 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3941DD3B44200B73201 /* Repository.swift */; }; 280D71B71ED6043D005D2689 /* RepositoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */; }; 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 */; }; 2812F61E2145031B008EE81E /* IAPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2812F61C2145031A008EE81E /* IAPHelper.swift */; }; 2812F61F2145031B008EE81E /* IAPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2812F61C2145031A008EE81E /* IAPHelper.swift */; }; 2812F6202145031B008EE81E /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2812F61D2145031A008EE81E /* Store.swift */; }; 2812F6212145031B008EE81E /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2812F61D2145031A008EE81E /* Store.swift */; }; 2818848121A4A2F800B33B9C /* Jirassic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2818847F21A4A2F800B33B9C /* Jirassic.xcdatamodeld */; }; 2818848221A4A2F800B33B9C /* Jirassic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2818847F21A4A2F800B33B9C /* Jirassic.xcdatamodeld */; }; 2823C9351E4F69970055D036 /* Versioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2823C9341E4F69970055D036 /* Versioning.swift */; }; 28279AD121BBF08C00376304 /* InMemoryCoreDataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3931DD3B44200B73201 /* InMemoryCoreDataRepository.swift */; }; 28279AD221BBF08E00376304 /* InMemoryCoreDataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3931DD3B44200B73201 /* InMemoryCoreDataRepository.swift */; }; 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 */; }; 284192DB2018855B00E64A9A /* JiraRepository+Projects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192D92018855B00E64A9A /* JiraRepository+Projects.swift */; }; 284192DD2018A8B200E64A9A /* ModuleJiraTempo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192DC2018A8B200E64A9A /* ModuleJiraTempo.swift */; }; 284192DE2018A8B200E64A9A /* ModuleJiraTempo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284192DC2018A8B200E64A9A /* ModuleJiraTempo.swift */; }; 2845B1352066C6E6006EFB3B /* AppleScriptProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B1342066C6E6006EFB3B /* AppleScriptProtocol.swift */; }; 2845B1362066C6E6006EFB3B /* AppleScriptProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B1342066C6E6006EFB3B /* AppleScriptProtocol.swift */; }; 2845B139206703C5006EFB3B /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845B138206703C5006EFB3B /* Keychain.swift */; }; 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 */; }; 28577EAE1E8ADC53002B07FD /* SSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA81E8ADC53002B07FD /* SSettings.swift */; }; 28577EAF1E8ADC53002B07FD /* STask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA91E8ADC53002B07FD /* STask.swift */; }; 28577EB01E8ADC53002B07FD /* STask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EA91E8ADC53002B07FD /* STask.swift */; }; 28577EB11E8ADC53002B07FD /* SUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EAA1E8ADC53002B07FD /* SUser.swift */; }; 28577EB21E8ADC53002B07FD /* SUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EAA1E8ADC53002B07FD /* SUser.swift */; }; 28577EB51E8AF08F002B07FD /* SQLiteDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB31E8AF08F002B07FD /* SQLiteDB.swift */; }; 28577EB61E8AF08F002B07FD /* SQLiteDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB31E8AF08F002B07FD /* SQLiteDB.swift */; }; 28577EB71E8AF08F002B07FD /* SQLTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB41E8AF08F002B07FD /* SQLTable.swift */; }; 28577EB81E8AF08F002B07FD /* SQLTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28577EB41E8AF08F002B07FD /* SQLTable.swift */; }; 28577EBA1E8AF379002B07FD /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 28577EB91E8AF379002B07FD /* libsqlite3.tbd */; }; 28667B5D1FCB7017007B98E3 /* ModuleHookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28667B5C1FCB7017007B98E3 /* ModuleHookup.swift */; }; 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 */; }; 287195302025C35C001C237E /* CoreDataRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288F1E910FF60022AB55 /* CoreDataRepository+User.swift */; }; 287195312025C360001C237E /* CSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38D1DD3B44200B73201 /* CSettings.swift */; }; 287195322025C364001C237E /* CTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38E1DD3B44200B73201 /* CTask.swift */; }; 287195332025C367001C237E /* CUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38F1DD3B44200B73201 /* CUser.swift */; }; 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 */; }; 2892B2991F094A170085BAC2 /* JiraRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B2971F094A170085BAC2 /* JiraRepository.swift */; }; 2892B29B1F094A760085BAC2 /* JiraRepository+Reports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B29A1F094A760085BAC2 /* JiraRepository+Reports.swift */; }; 2892B29C1F094A760085BAC2 /* JiraRepository+Reports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B29A1F094A760085BAC2 /* JiraRepository+Reports.swift */; }; 2892B29E1F094B0D0085BAC2 /* JReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2892B29D1F094B0D0085BAC2 /* JReport.swift */; }; 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 */; }; 2898D3AF2184ED3000CF5AD4 /* Components.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2898D3AD2184ED3000CF5AD4 /* Components.storyboard */; }; 2898D3B12185959300CF5AD4 /* EditableTimeBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2898D3B02185959300CF5AD4 /* EditableTimeBox.swift */; }; 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 */; }; 28A9287B1E8FC9B90022AB55 /* CreateReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32B1DD3B44200B73201 /* CreateReport.swift */; }; 28A9287D1E9030BA0022AB55 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28A9287C1E9030BA0022AB55 /* CloudKit.framework */; }; 28A928811E910DA40022AB55 /* SqliteRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928801E910DA40022AB55 /* SqliteRepository+Tasks.swift */; }; 28A928821E910DA40022AB55 /* SqliteRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928801E910DA40022AB55 /* SqliteRepository+Tasks.swift */; }; 28A928841E910E5E0022AB55 /* SqliteRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928831E910E5E0022AB55 /* SqliteRepository+User.swift */; }; 28A928851E910E5E0022AB55 /* SqliteRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928831E910E5E0022AB55 /* SqliteRepository+User.swift */; }; 28A928871E910EA70022AB55 /* SqliteRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928861E910EA70022AB55 /* SqliteRepository+Settings.swift */; }; 28A928881E910EA70022AB55 /* SqliteRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928861E910EA70022AB55 /* SqliteRepository+Settings.swift */; }; 28A928931E91104B0022AB55 /* CloudKitRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928921E91104B0022AB55 /* CloudKitRepository+Tasks.swift */; }; 28A928951E9110980022AB55 /* CloudKitRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928941E9110980022AB55 /* CloudKitRepository+User.swift */; }; 28A928971E9110BF0022AB55 /* CloudKitRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928961E9110BF0022AB55 /* CloudKitRepository+Settings.swift */; }; 28AA50081EDCD51300AAF03D /* CoreDataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38C1DD3B44200B73201 /* CoreDataRepository.swift */; }; 28AA50091EDCD51300AAF03D /* CoreDataRepository+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A928891E910F4D0022AB55 /* CoreDataRepository+Tasks.swift */; }; 28AA500A1EDCD51300AAF03D /* CoreDataRepository+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288C1E910F970022AB55 /* CoreDataRepository+Settings.swift */; }; 28AA500B1EDCD51300AAF03D /* CoreDataRepository+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A9288F1E910FF60022AB55 /* CoreDataRepository+User.swift */; }; 28AA500C1EDCD51300AAF03D /* CSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D38D1DD3B44200B73201 /* CSettings.swift */; }; 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 */; }; 28C9C6231EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */; }; 28CBB596204554A2006F9D3A /* ParseGitBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CBB595204554A2006F9D3A /* ParseGitBranch.swift */; }; 28CBB597204554A2006F9D3A /* ParseGitBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CBB595204554A2006F9D3A /* ParseGitBranch.swift */; }; 28CBB598204554A2006F9D3A /* ParseGitBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CBB595204554A2006F9D3A /* ParseGitBranch.swift */; }; 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 */; }; 4065D39F1DD3B44200B73201 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31B1DD3B44200B73201 /* Task.swift */; }; 4065D3A11DD3B44200B73201 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31D1DD3B44200B73201 /* User.swift */; }; 4065D3A21DD3B44200B73201 /* Week.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31E1DD3B44200B73201 /* Week.swift */; }; 4065D3A31DD3B44200B73201 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3201DD3B44200B73201 /* DateExtension.swift */; }; 4065D3A51DD3B44200B73201 /* StringIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */; }; 4065D3A61DD3B44200B73201 /* ViewAutolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3231DD3B44200B73201 /* ViewAutolayout.swift */; }; 4065D3A71DD3B44200B73201 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3241DD3B44200B73201 /* ViewController.swift */; }; 4065D3A81DD3B44200B73201 /* ViewControllerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3251DD3B44200B73201 /* ViewControllerStoryboard.swift */; }; 4065D3A91DD3B44200B73201 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3261DD3B44200B73201 /* Conversions.swift */; }; 4065D3AB1DD3B44200B73201 /* ComputerWakeUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3291DD3B44200B73201 /* ComputerWakeUpInteractor.swift */; }; 4065D3AC1DD3B44200B73201 /* CreateReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32B1DD3B44200B73201 /* CreateReport.swift */; }; 4065D3AE1DD3B44200B73201 /* ReadDaysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32E1DD3B44200B73201 /* ReadDaysInteractor.swift */; }; 4065D3AF1DD3B44200B73201 /* ReadTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */; }; 4065D3B01DD3B44200B73201 /* TaskFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3301DD3B44200B73201 /* TaskFinder.swift */; }; 4065D3B21DD3B44200B73201 /* TaskInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3321DD3B44200B73201 /* TaskInteractor.swift */; }; 4065D3B41DD3B44200B73201 /* TaskTypeEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3341DD3B44200B73201 /* TaskTypeEstimator.swift */; }; 4065D3B61DD3B44200B73201 /* TaskTypeSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3361DD3B44200B73201 /* TaskTypeSelection.swift */; }; 4065D3B81DD3B44200B73201 /* PredictiveTimeTyping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3391DD3B44200B73201 /* PredictiveTimeTyping.swift */; }; 4065D3BA1DD3B44200B73201 /* RegisterUserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33C1DD3B44200B73201 /* RegisterUserInteractor.swift */; }; 4065D3BB1DD3B44200B73201 /* UserInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33D1DD3B44200B73201 /* UserInteractor.swift */; }; 4065D3C81DD3B44200B73201 /* FlipAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3521DD3B44200B73201 /* FlipAnimation.swift */; }; 4065D3C91DD3B44200B73201 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3541DD3B44200B73201 /* AppDelegate.swift */; }; 4065D3CA1DD3B44200B73201 /* AppWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3551DD3B44200B73201 /* AppWireframe.swift */; }; 4065D3CE1DD3B44200B73201 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3591DD3B44200B73201 /* Main.storyboard */; }; 4065D3CF1DD3B44200B73201 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4065D35B1DD3B44200B73201 /* Images.xcassets */; }; 4065D3D31DD3B44200B73201 /* jirassic.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 4065D3611DD3B44200B73201 /* jirassic.sdef */; }; 4065D3D61DD3B44200B73201 /* MenuBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3651DD3B44200B73201 /* MenuBarController.swift */; }; 4065D3D71DD3B44200B73201 /* MenuBarIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3661DD3B44200B73201 /* MenuBarIconView.swift */; }; 4065D3D81DD3B44200B73201 /* InternalNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3681DD3B44200B73201 /* InternalNotifications.swift */; }; 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 */; }; 4065D4171DD4536200B73201 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3871DD3B44200B73201 /* main.swift */; }; 4065D4181DD454C500B73201 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3941DD3B44200B73201 /* Repository.swift */; }; 4065D4191DD454CB00B73201 /* RepositoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */; }; 4065D4201DD4561D00B73201 /* UserInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33E1DD3B44200B73201 /* UserInteractorTests.swift */; }; 4065D4211DD4562100B73201 /* PredictiveTimeTypingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D33A1DD3B44200B73201 /* PredictiveTimeTypingTests.swift */; }; 4065D4231DD4562900B73201 /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3211DD3B44200B73201 /* DateExtensionTests.swift */; }; 4065D4241DD4562900B73201 /* CreateReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32C1DD3B44200B73201 /* CreateReportTests.swift */; }; 4065D4251DD4562900B73201 /* TaskFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3311DD3B44200B73201 /* TaskFinderTests.swift */; }; 4065D4261DD4562900B73201 /* TaskInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3331DD3B44200B73201 /* TaskInteractorTests.swift */; }; 4065D4271DD4562900B73201 /* TaskTypeEstimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3351DD3B44200B73201 /* TaskTypeEstimatorTests.swift */; }; 4065D4281DD4574D00B73201 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31D1DD3B44200B73201 /* User.swift */; }; 4065D4291DD4575B00B73201 /* Week.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31E1DD3B44200B73201 /* Week.swift */; }; 4065D42A1DD4576000B73201 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31B1DD3B44200B73201 /* Task.swift */; }; 4065D42B1DD4576500B73201 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D31A1DD3B44200B73201 /* Settings.swift */; }; 4065D42C1DD4576800B73201 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3191DD3B44200B73201 /* Report.swift */; }; 4065D42D1DD4576C00B73201 /* Day.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3181DD3B44200B73201 /* Day.swift */; }; 4065D42E1DD4577D00B73201 /* StringIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */; }; 4065D42F1DD4579400B73201 /* TaskInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D3321DD3B44200B73201 /* TaskInteractor.swift */; }; 4065D4301DD4579F00B73201 /* ReadTasksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */; }; 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 */; }; 6D3702662BA83EC7002260D0 /* RCHttp in Frameworks */ = {isa = PBXBuildFile; productRef = 6D3702652BA83EC7002260D0 /* RCHttp */; }; 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 4065D4071DD4532100B73201 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4065D2FC1DD3A1AA00B73201 /* Project object */; proxyType = 1; remoteGlobalIDString = 4065D3031DD3A1AA00B73201; remoteInfo = Jirassic; }; 566C09141F2B985C0058D90A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4065D2FC1DD3A1AA00B73201 /* Project object */; proxyType = 1; remoteGlobalIDString = 280D711C1ED5EE7F005D2689; remoteInfo = "Jirassic macOS"; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 28577E941E89972F002B07FD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 28577E9B1E89A969002B07FD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 4055B1351E0D81E600279430 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( 4055B1361E0D821500279430 /* JirassicLauncher.app in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D40E1DD4534C00B73201 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 2801388E205F86460051B532 /* TimeBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBox.swift; sourceTree = ""; }; 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StatisticsInteractor.swift; path = Statistics/StatisticsInteractor.swift; sourceTree = ""; }; 2803A0C91EC184FF005F9389 /* BrowserNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserNotification.swift; sourceTree = ""; }; 280C1D411ED74EF900C126A1 /* BrowserSupport.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = BrowserSupport.scpt; sourceTree = ""; }; 280C1D421ED74EF900C126A1 /* ShellSupport.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = ShellSupport.scpt; sourceTree = ""; }; 280D70B01ECC09D9005D2689 /* AppTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppTheme.swift; sourceTree = ""; }; 280D70B61ECFE06A005D2689 /* Jirassic iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Jirassic iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 280D70F91ED0E7B0005D2689 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 280D70FD1ED20CBE005D2689 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 280D70FF1ED21989005D2689 /* Jirassic Scrum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Jirassic Scrum.entitlements"; sourceTree = ""; }; 280D71171ED4CFCB005D2689 /* ReadDaysInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadDaysInteractorTests.swift; sourceTree = ""; }; 280D711D1ED5EE7F005D2689 /* Jirassic no cloud.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Jirassic no cloud.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 280D71BA1ED60677005D2689 /* libsqlite3.0.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; 280F507A1EC8541D007416AB /* ExtensionsInstallerInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionsInstallerInteractor.swift; sourceTree = ""; }; 280F507C1EC868B0007416AB /* StringArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringArray.swift; sourceTree = ""; }; 2812F61C2145031A008EE81E /* IAPHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IAPHelper.swift; sourceTree = ""; }; 2812F61D2145031A008EE81E /* Store.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; 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 = ""; }; 28577EAA1E8ADC53002B07FD /* SUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SUser.swift; sourceTree = ""; }; 28577EB31E8AF08F002B07FD /* SQLiteDB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDB.swift; sourceTree = ""; }; 28577EB41E8AF08F002B07FD /* SQLTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLTable.swift; sourceTree = ""; }; 28577EB91E8AF379002B07FD /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 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 = ""; }; 28A928831E910E5E0022AB55 /* SqliteRepository+User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SqliteRepository+User.swift"; sourceTree = ""; }; 28A928861E910EA70022AB55 /* SqliteRepository+Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SqliteRepository+Settings.swift"; sourceTree = ""; }; 28A928891E910F4D0022AB55 /* CoreDataRepository+Tasks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataRepository+Tasks.swift"; sourceTree = ""; }; 28A9288C1E910F970022AB55 /* CoreDataRepository+Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataRepository+Settings.swift"; sourceTree = ""; }; 28A9288F1E910FF60022AB55 /* CoreDataRepository+User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataRepository+User.swift"; sourceTree = ""; }; 28A928921E91104B0022AB55 /* CloudKitRepository+Tasks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CloudKitRepository+Tasks.swift"; sourceTree = ""; }; 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 = ""; }; 4065D31A1DD3B44200B73201 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 4065D31B1DD3B44200B73201 /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; 4065D31D1DD3B44200B73201 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 4065D31E1DD3B44200B73201 /* Week.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Week.swift; sourceTree = ""; }; 4065D3201DD3B44200B73201 /* DateExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 4065D3211DD3B44200B73201 /* DateExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringIdGenerator.swift; sourceTree = ""; }; 4065D3231DD3B44200B73201 /* ViewAutolayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewAutolayout.swift; sourceTree = ""; }; 4065D3241DD3B44200B73201 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 4065D3251DD3B44200B73201 /* ViewControllerStoryboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerStoryboard.swift; sourceTree = ""; }; 4065D3261DD3B44200B73201 /* Conversions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Conversions.swift; sourceTree = ""; }; 4065D3291DD3B44200B73201 /* ComputerWakeUpInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComputerWakeUpInteractor.swift; sourceTree = ""; }; 4065D32B1DD3B44200B73201 /* CreateReport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateReport.swift; sourceTree = ""; }; 4065D32C1DD3B44200B73201 /* CreateReportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateReportTests.swift; sourceTree = ""; }; 4065D32E1DD3B44200B73201 /* ReadDaysInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadDaysInteractor.swift; sourceTree = ""; }; 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadTasksInteractor.swift; sourceTree = ""; }; 4065D3301DD3B44200B73201 /* TaskFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskFinder.swift; sourceTree = ""; }; 4065D3311DD3B44200B73201 /* TaskFinderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskFinderTests.swift; sourceTree = ""; }; 4065D3321DD3B44200B73201 /* TaskInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskInteractor.swift; sourceTree = ""; }; 4065D3331DD3B44200B73201 /* TaskInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskInteractorTests.swift; sourceTree = ""; }; 4065D3341DD3B44200B73201 /* TaskTypeEstimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskTypeEstimator.swift; sourceTree = ""; }; 4065D3351DD3B44200B73201 /* TaskTypeEstimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskTypeEstimatorTests.swift; sourceTree = ""; }; 4065D3361DD3B44200B73201 /* TaskTypeSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskTypeSelection.swift; sourceTree = ""; }; 4065D3391DD3B44200B73201 /* PredictiveTimeTyping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictiveTimeTyping.swift; sourceTree = ""; }; 4065D33A1DD3B44200B73201 /* PredictiveTimeTypingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictiveTimeTypingTests.swift; sourceTree = ""; }; 4065D33C1DD3B44200B73201 /* RegisterUserInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegisterUserInteractor.swift; sourceTree = ""; }; 4065D33D1DD3B44200B73201 /* UserInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInteractor.swift; sourceTree = ""; }; 4065D33E1DD3B44200B73201 /* UserInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInteractorTests.swift; sourceTree = ""; }; 4065D3421DD3B44200B73201 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4065D3441DD3B44200B73201 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 4065D3461DD3B44200B73201 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 4065D3471DD3B44200B73201 /* DaysViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DaysViewController.swift; sourceTree = ""; }; 4065D3491DD3B44200B73201 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 4065D34B1DD3B44200B73201 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 4065D34D1DD3B44200B73201 /* NonTaskCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonTaskCell.swift; sourceTree = ""; }; 4065D34E1DD3B44200B73201 /* TaskCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; 4065D34F1DD3B44200B73201 /* TasksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasksViewController.swift; sourceTree = ""; }; 4065D3521DD3B44200B73201 /* FlipAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlipAnimation.swift; sourceTree = ""; }; 4065D3541DD3B44200B73201 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4065D3551DD3B44200B73201 /* AppWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppWireframe.swift; sourceTree = ""; }; 4065D35A1DD3B44200B73201 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 4065D35B1DD3B44200B73201 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 4065D35C1DD3B44200B73201 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4065D3611DD3B44200B73201 /* jirassic.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = jirassic.sdef; sourceTree = ""; }; 4065D3651DD3B44200B73201 /* MenuBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBarController.swift; sourceTree = ""; }; 4065D3661DD3B44200B73201 /* MenuBarIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBarIconView.swift; sourceTree = ""; }; 4065D3681DD3B44200B73201 /* InternalNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalNotifications.swift; sourceTree = ""; }; 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 = ""; }; 4065D38D1DD3B44200B73201 /* CSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSettings.swift; sourceTree = ""; }; 4065D38E1DD3B44200B73201 /* CTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CTask.swift; sourceTree = ""; }; 4065D38F1DD3B44200B73201 /* CUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CUser.swift; sourceTree = ""; }; 4065D3931DD3B44200B73201 /* InMemoryCoreDataRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryCoreDataRepository.swift; sourceTree = ""; }; 4065D3941DD3B44200B73201 /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryInteractor.swift; sourceTree = ""; }; 4065D4021DD4532100B73201 /* JirassicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JirassicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 280D70B31ECFE06A005D2689 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 280D70FA1ED0E7B0005D2689 /* CloudKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 280D711A1ED5EE7F005D2689 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 280D71BE1ED6069A005D2689 /* ServiceManagement.framework in Frameworks */, 280D71BC1ED60683005D2689 /* libsqlite3.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 4055B1201E0D802300279430 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 4065D3011DD3A1AA00B73201 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 28A9287D1E9030BA0022AB55 /* CloudKit.framework in Frameworks */, 6D3702622BA83EB9002260D0 /* RCLog in Frameworks */, 28577EBA1E8AF379002B07FD /* libsqlite3.tbd in Frameworks */, 6D3702602BA83E9A002260D0 /* SwiftKeychainWrapper in Frameworks */, 6D3702662BA83EC7002260D0 /* RCHttp in Frameworks */, 6D3702642BA83EBF002260D0 /* RCPreferences in Frameworks */, 4055B1391E0D82A900279430 /* ServiceManagement.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D3FF1DD4532100B73201 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 4065D40D1DD4534C00B73201 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 28A9287A1E8FA8E90022AB55 /* libsqlite3.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2801388D205F86250051B532 /* Components */ = { isa = PBXGroup; children = ( 2801388E205F86460051B532 /* TimeBox.swift */, 2898D3B02185959300CF5AD4 /* EditableTimeBox.swift */, 2898D3AA2184D1DB00CF5AD4 /* TimeBoxViewController.swift */, 4065D3711DD3B44200B73201 /* NewTaskViewController.swift */, 28279AD421C6245700376304 /* GitUsersViewController.swift */, 2898D3AD2184ED3000CF5AD4 /* Components.storyboard */, ); path = Components; sourceTree = ""; }; 280300D41EDB5E6D000A763E /* Statistics */ = { isa = PBXGroup; children = ( 280300D51EDB5ECA000A763E /* StatisticsInteractor.swift */, ); name = Statistics; sourceTree = ""; }; 2812F61B2145031A008EE81E /* IAP */ = { isa = PBXGroup; children = ( 2812F61C2145031A008EE81E /* IAPHelper.swift */, 2812F61D2145031A008EE81E /* Store.swift */, ); path = IAP; sourceTree = ""; }; 28279E9A1E8300E200EAF9FC /* Placeholder */ = { isa = PBXGroup; children = ( 28279E9B1E8300E200EAF9FC /* PlaceholderViewController.swift */, 28E896621E830D6700722032 /* Placeholder.storyboard */, ); path = Placeholder; sourceTree = ""; }; 2845B137206703A8006EFB3B /* Keychain */ = { isa = PBXGroup; children = ( 2845B138206703C5006EFB3B /* Keychain.swift */, ); 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 = ( 28577EA71E8ADC53002B07FD /* SqliteRepository.swift */, 28A928801E910DA40022AB55 /* SqliteRepository+Tasks.swift */, 28A928831E910E5E0022AB55 /* SqliteRepository+User.swift */, 28A928861E910EA70022AB55 /* SqliteRepository+Settings.swift */, 28577EA81E8ADC53002B07FD /* SSettings.swift */, 28577EA91E8ADC53002B07FD /* STask.swift */, 28577EAA1E8ADC53002B07FD /* SUser.swift */, 28577EB31E8AF08F002B07FD /* SQLiteDB.swift */, 28577EB41E8AF08F002B07FD /* SQLTable.swift */, 28A928771E8F78580022AB55 /* SQLiteSchema.swift */, 28C9C6211EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift */, ); path = sqlite; sourceTree = ""; }; 28667B591FCADF4E007B98E3 /* Modules */ = { isa = PBXGroup; children = ( 5683DC3C20ECDEA30000A138 /* CalendarEvents */, 28A28392203786F600DDCB63 /* GitLogs */, 28667B5A1FCADF68007B98E3 /* JiraTempo */, 28667B5B1FCADF73007B98E3 /* Hookup */, ); path = Modules; sourceTree = ""; }; 28667B5A1FCADF68007B98E3 /* JiraTempo */ = { isa = PBXGroup; children = ( 284192DC2018A8B200E64A9A /* ModuleJiraTempo.swift */, ); path = JiraTempo; sourceTree = ""; }; 28667B5B1FCADF73007B98E3 /* Hookup */ = { isa = PBXGroup; children = ( 28667B5C1FCB7017007B98E3 /* ModuleHookup.swift */, ); 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 = ( 2892B2971F094A170085BAC2 /* JiraRepository.swift */, 2892B29A1F094A760085BAC2 /* JiraRepository+Reports.swift */, 284192D92018855B00E64A9A /* JiraRepository+Projects.swift */, 2892B29D1F094B0D0085BAC2 /* JReport.swift */, 2892B2A61F094C800085BAC2 /* JWorkAttribute.swift */, 284192D62018841700E64A9A /* JProject.swift */, 28DCA4542018B6E700DFAE29 /* JProjectIssue.swift */, ); path = Jira; sourceTree = ""; }; 2898D3A62181905600CF5AD4 /* MonthReports */ = { isa = PBXGroup; children = ( 2898D3A72181907700CF5AD4 /* MonthReportsHeaderView.swift */, 28B116BA21AE6179004ACE01 /* MonthReportsHeaderView.xib */, ); path = MonthReports; sourceTree = ""; }; 28A28392203786F600DDCB63 /* GitLogs */ = { isa = PBXGroup; children = ( 28A283932037874600DDCB63 /* ModuleGitLogs.swift */, 28A2839E20385BE000DDCB63 /* GitCommitsParser.swift */, 28A283A120385F8C00DDCB63 /* GitCommitsParserTests.swift */, 56ADBF6C21C3F94D008350A6 /* GitUserParser.swift */, 28A283AA203A93AB00DDCB63 /* GitBranchParser.swift */, 28A283AD203C069F00DDCB63 /* GitBranchParserTests.swift */, ); path = GitLogs; sourceTree = ""; }; 28A283A32038AF9600DDCB63 /* JirassicCmd */ = { isa = PBXGroup; children = ( 28A283A42038AFB100DDCB63 /* JirassicCell.swift */, 28A283A72038AFC500DDCB63 /* JirassicCell.xib */, ); path = JirassicCmd; sourceTree = ""; }; 28CBB59420455467006F9D3A /* Parsing */ = { isa = PBXGroup; children = ( 28CBB595204554A2006F9D3A /* ParseGitBranch.swift */, 28CBB59920474755006F9D3A /* ParseGitBranchTests.swift */, ); 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 = ( 28FE1886207A520B00DF796E /* NewTaskCommand.swift */, ); 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 = ( 280D71BA1ED60677005D2689 /* libsqlite3.0.tbd */, 280D70F91ED0E7B0005D2689 /* CloudKit.framework */, 28A9287C1E9030BA0022AB55 /* CloudKit.framework */, 28577EB91E8AF379002B07FD /* libsqlite3.tbd */, 4055B1381E0D82A900279430 /* ServiceManagement.framework */, ); name = Frameworks; sourceTree = ""; }; 4055B13A1E0E663800279430 /* macOS-launcher */ = { isa = PBXGroup; children = ( 28E8966E1E83732200722032 /* AppDelegate.h */, 28E8966F1E83732200722032 /* AppDelegate.m */, 28E896701E83732200722032 /* main.m */, 4051D5F01E0EA0EA002042BB /* Info.plist */, 28E896731E83770D00722032 /* MainMenu.xib */, 4051D5EF1E0EA0EA002042BB /* JirassicLauncher.entitlements */, ); 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 = ( 56CD22BE1E72F89700F9CDB8 /* BuildScript.sh */, 4065D35B1DD3B44200B73201 /* Images.xcassets */, 4065D35C1DD3B44200B73201 /* Info.plist */, 28577EBB1E8AFB1B002B07FD /* Bridging-Header.h */, 4065D3611DD3B44200B73201 /* jirassic.sdef */, 4051D5F71E0EA320002042BB /* Jirassic.entitlements */, 280C1D411ED74EF900C126A1 /* BrowserSupport.scpt */, 280C1D421ED74EF900C126A1 /* ShellSupport.scpt */, ); name = Resources; sourceTree = ""; }; 4065D2FB1DD3A1AA00B73201 = { isa = PBXGroup; children = ( 6D3702672BA84233002260D0 /* Jirassic macOS.entitlements */, 4065D3161DD3B44200B73201 /* App */, 4065D3401DD3B44200B73201 /* Delivery */, 4065D3881DD3B44200B73201 /* External */, 4065D3991DD3B44200B73201 /* Vendor */, 4065D4031DD4532100B73201 /* JirassicTests */, 4065D3051DD3A1AA00B73201 /* Products */, 4055B1371E0D82A800279430 /* Frameworks */, ); sourceTree = ""; }; 4065D3051DD3A1AA00B73201 /* Products */ = { isa = PBXGroup; children = ( 4065D3041DD3A1AA00B73201 /* Jirassic.app */, 4065D4021DD4532100B73201 /* JirassicTests.xctest */, 4065D4101DD4534C00B73201 /* jirassic */, 4055B1231E0D802300279430 /* JirassicLauncher.app */, 280D70B61ECFE06A005D2689 /* Jirassic iOS.app */, 280D711D1ED5EE7F005D2689 /* Jirassic no cloud.app */, ); name = Products; sourceTree = ""; }; 4065D3161DD3B44200B73201 /* App */ = { isa = PBXGroup; children = ( 4065D3171DD3B44200B73201 /* Entities */, 4065D31F1DD3B44200B73201 /* Extensions */, 4065D3271DD3B44200B73201 /* Notifications */, 4065D32A1DD3B44200B73201 /* Reports */, 4065D32D1DD3B44200B73201 /* Tasks */, 280300D41EDB5E6D000A763E /* Statistics */, 4065D3371DD3B44200B73201 /* Time */, 4065D33B1DD3B44200B73201 /* User */, 28CBB59420455467006F9D3A /* Parsing */, ); path = App; sourceTree = ""; }; 4065D3171DD3B44200B73201 /* Entities */ = { isa = PBXGroup; children = ( 4065D3181DD3B44200B73201 /* Day.swift */, 4065D3191DD3B44200B73201 /* Report.swift */, 4065D31A1DD3B44200B73201 /* Settings.swift */, 4065D31B1DD3B44200B73201 /* Task.swift */, 4065D31D1DD3B44200B73201 /* User.swift */, 4065D31E1DD3B44200B73201 /* Week.swift */, 28A2839920382BBB00DDCB63 /* GitCommit.swift */, 56ADBF6921C3F625008350A6 /* GitUser.swift */, ); path = Entities; sourceTree = ""; }; 4065D31F1DD3B44200B73201 /* Extensions */ = { isa = PBXGroup; children = ( 4065D3201DD3B44200B73201 /* DateExtension.swift */, 4065D3211DD3B44200B73201 /* DateExtensionTests.swift */, 4065D3221DD3B44200B73201 /* StringIdGenerator.swift */, 280F507C1EC868B0007416AB /* StringArray.swift */, 4065D3231DD3B44200B73201 /* ViewAutolayout.swift */, 4065D3241DD3B44200B73201 /* ViewController.swift */, 4065D3251DD3B44200B73201 /* ViewControllerStoryboard.swift */, 288BB6862087169F00CF720A /* ViewXib.swift */, 2845B13E2068351F006EFB3B /* TableViewCell.swift */, 4065D3261DD3B44200B73201 /* Conversions.swift */, ); path = Extensions; sourceTree = ""; }; 4065D3271DD3B44200B73201 /* Notifications */ = { isa = PBXGroup; children = ( 4065D3291DD3B44200B73201 /* ComputerWakeUpInteractor.swift */, 4073184E1DE9B1B40046F409 /* ComputerWakeUpInteractorTests.swift */, ); path = Notifications; sourceTree = ""; }; 4065D32A1DD3B44200B73201 /* Reports */ = { isa = PBXGroup; children = ( 4065D32B1DD3B44200B73201 /* CreateReport.swift */, 4065D32C1DD3B44200B73201 /* CreateReportTests.swift */, 6D5BC4C62C1B5B70002DA29B /* CreateDayReport.swift */, 285465A1216C84E10052CB6A /* CreateMonthReport.swift */, 56D069E0216CABCB000D051D /* CreateMonthReportTests.swift */, ); path = Reports; sourceTree = ""; }; 4065D32D1DD3B44200B73201 /* Tasks */ = { isa = PBXGroup; children = ( 4065D32E1DD3B44200B73201 /* ReadDaysInteractor.swift */, 280D71171ED4CFCB005D2689 /* ReadDaysInteractorTests.swift */, 286BC00321909F85004D4CDD /* CloseDay.swift */, 4065D32F1DD3B44200B73201 /* ReadTasksInteractor.swift */, 4065D3301DD3B44200B73201 /* TaskFinder.swift */, 4065D3311DD3B44200B73201 /* TaskFinderTests.swift */, 4065D3321DD3B44200B73201 /* TaskInteractor.swift */, 4065D3331DD3B44200B73201 /* TaskInteractorTests.swift */, 4065D3341DD3B44200B73201 /* TaskTypeEstimator.swift */, 4065D3351DD3B44200B73201 /* TaskTypeEstimatorTests.swift */, 4065D3361DD3B44200B73201 /* TaskTypeSelection.swift */, 28A283AF203CB1A100DDCB63 /* MergeTasksInteractor.swift */, 28A283B2203CB1B900DDCB63 /* MergeTasksInteractorTests.swift */, 28C6A38321D359E60036DB29 /* RemoveDuplicate.swift */, ); path = Tasks; sourceTree = ""; }; 4065D3371DD3B44200B73201 /* Time */ = { isa = PBXGroup; children = ( 4065D3391DD3B44200B73201 /* PredictiveTimeTyping.swift */, 4065D33A1DD3B44200B73201 /* PredictiveTimeTypingTests.swift */, 287195352027CAD9001C237E /* TimeInteractor.swift */, ); path = Time; sourceTree = ""; }; 4065D33B1DD3B44200B73201 /* User */ = { isa = PBXGroup; children = ( 4065D33C1DD3B44200B73201 /* RegisterUserInteractor.swift */, 4065D33D1DD3B44200B73201 /* UserInteractor.swift */, 4065D33E1DD3B44200B73201 /* UserInteractorTests.swift */, ); path = User; sourceTree = ""; }; 4065D3401DD3B44200B73201 /* Delivery */ = { isa = PBXGroup; children = ( 4065D3411DD3B44200B73201 /* iOS */, 4065D3501DD3B44200B73201 /* macOS */, 4055B13A1E0E663800279430 /* macOS-launcher */, 4065D3861DD3B44200B73201 /* macOS-cmd */, ); path = Delivery; sourceTree = ""; }; 4065D3411DD3B44200B73201 /* iOS */ = { isa = PBXGroup; children = ( 4065D3421DD3B44200B73201 /* AppDelegate.swift */, 4065D34B1DD3B44200B73201 /* LoginViewController.swift */, 4065D3471DD3B44200B73201 /* DaysViewController.swift */, 4065D34F1DD3B44200B73201 /* TasksViewController.swift */, 4065D34E1DD3B44200B73201 /* TaskCell.swift */, 4065D34D1DD3B44200B73201 /* NonTaskCell.swift */, 4065D3491DD3B44200B73201 /* Images.xcassets */, 280D70FD1ED20CBE005D2689 /* Info.plist */, 280D70FF1ED21989005D2689 /* Jirassic Scrum.entitlements */, 4065D3451DD3B44200B73201 /* Main.storyboard */, 4065D3431DD3B44200B73201 /* LaunchScreen.xib */, ); path = iOS; sourceTree = ""; }; 4065D3501DD3B44200B73201 /* macOS */ = { isa = PBXGroup; children = ( 2812F61B2145031A008EE81E /* IAP */, 4065D3531DD3B44200B73201 /* App */, 4065D3641DD3B44200B73201 /* Menu */, 4065D3511DD3B44200B73201 /* Animations */, 4065D36B1DD3B44200B73201 /* Screens */, 2801388D205F86250051B532 /* Components */, 40FCE4211DF7646F00D4FD45 /* External */, 28667B591FCADF4E007B98E3 /* Modules */, 4065D3671DD3B44200B73201 /* Notifications */, 405B15641DEF66EE0009871C /* Resources */, ); path = macOS; sourceTree = ""; }; 4065D3511DD3B44200B73201 /* Animations */ = { isa = PBXGroup; children = ( 4065D3521DD3B44200B73201 /* FlipAnimation.swift */, 40FCE42F1DFD7C8D00D4FD45 /* Animatable.swift */, ); path = Animations; sourceTree = ""; }; 4065D3531DD3B44200B73201 /* App */ = { isa = PBXGroup; children = ( 4065D3591DD3B44200B73201 /* Main.storyboard */, 4065D3541DD3B44200B73201 /* AppDelegate.swift */, 4065D3551DD3B44200B73201 /* AppWireframe.swift */, 405B15651DEF75660009871C /* AppViewController.swift */, 4051D5F81E0EA48A002042BB /* AppLauncher.swift */, 2823C9341E4F69970055D036 /* Versioning.swift */, 280D70B01ECC09D9005D2689 /* AppTheme.swift */, 2892E810208E6275004E5298 /* LocalPreferences.swift */, ); path = App; sourceTree = ""; }; 4065D3641DD3B44200B73201 /* Menu */ = { isa = PBXGroup; children = ( 4065D3651DD3B44200B73201 /* MenuBarController.swift */, 4065D3661DD3B44200B73201 /* MenuBarIconView.swift */, ); path = Menu; sourceTree = ""; }; 4065D3671DD3B44200B73201 /* Notifications */ = { isa = PBXGroup; children = ( 4065D3681DD3B44200B73201 /* InternalNotifications.swift */, 4065D3691DD3B44200B73201 /* UserNotifications.swift */, 4065D36A1DD3B44200B73201 /* SleepNotifications.swift */, 2803A0C91EC184FF005F9389 /* BrowserNotification.swift */, ); path = Notifications; sourceTree = ""; }; 4065D36B1DD3B44200B73201 /* Screens */ = { isa = PBXGroup; children = ( 28279E9A1E8300E200EAF9FC /* Placeholder */, 287B358520FBAD160022F43E /* Onboarding */, 405B155F1DEF3CE20009871C /* TaskSuggestion */, 5685C7621DE8721100CA545E /* Account */, 4065D3721DD3B44200B73201 /* Settings */, 564E55F12028839F00CE4C76 /* Worklogs */, 2845B142206AE342006EFB3B /* Calendar */, 4065D3761DD3B44200B73201 /* Tasks */, ); 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 = ( 4065D3871DD3B44200B73201 /* main.swift */, ); path = "macOS-cmd"; sourceTree = ""; }; 4065D3881DD3B44200B73201 /* External */ = { isa = PBXGroup; children = ( 28FE1885207A516300DF796E /* AppleScriptCommands */, 2845B137206703A8006EFB3B /* Keychain */, 2892B2961F0949D70085BAC2 /* Jira */, 28577EA01E8ADBBD002B07FD /* sqlite */, 4065D38B1DD3B44200B73201 /* CoreData */, 4065D3921DD3B44200B73201 /* InMemoryStorage */, 4065D3891DD3B44200B73201 /* CloudKit */, 4065D3941DD3B44200B73201 /* Repository.swift */, 4065D3951DD3B44200B73201 /* RepositoryInteractor.swift */, 565E19EF20A5E336003A5E2A /* RCSync.swift */, ); path = External; sourceTree = ""; }; 4065D3891DD3B44200B73201 /* CloudKit */ = { isa = PBXGroup; children = ( 4065D38A1DD3B44200B73201 /* CloudKitRepository.swift */, 28A928921E91104B0022AB55 /* CloudKitRepository+Tasks.swift */, 28A928961E9110BF0022AB55 /* CloudKitRepository+Settings.swift */, 28A928941E9110980022AB55 /* CloudKitRepository+User.swift */, 28AFE7301E9A594500BAAD8C /* UserDefaults+token.swift */, ); path = CloudKit; sourceTree = ""; }; 4065D38B1DD3B44200B73201 /* CoreData */ = { isa = PBXGroup; children = ( 4065D38C1DD3B44200B73201 /* CoreDataRepository.swift */, 28A928891E910F4D0022AB55 /* CoreDataRepository+Tasks.swift */, 28A9288C1E910F970022AB55 /* CoreDataRepository+Settings.swift */, 28A9288F1E910FF60022AB55 /* CoreDataRepository+User.swift */, 4065D38D1DD3B44200B73201 /* CSettings.swift */, 4065D38E1DD3B44200B73201 /* CTask.swift */, 4065D38F1DD3B44200B73201 /* CUser.swift */, 2818847F21A4A2F800B33B9C /* Jirassic.xcdatamodeld */, ); path = CoreData; sourceTree = ""; }; 4065D3921DD3B44200B73201 /* InMemoryStorage */ = { isa = PBXGroup; children = ( 4065D3931DD3B44200B73201 /* InMemoryCoreDataRepository.swift */, ); path = InMemoryStorage; sourceTree = ""; }; 4065D3991DD3B44200B73201 /* Vendor */ = { isa = PBXGroup; children = ( ); path = Vendor; sourceTree = ""; }; 4065D4031DD4532100B73201 /* JirassicTests */ = { isa = PBXGroup; children = ( 4065D4061DD4532100B73201 /* Info.plist */, ); path = JirassicTests; sourceTree = ""; }; 40FCE4211DF7646F00D4FD45 /* External */ = { isa = PBXGroup; children = ( 40FCE4231DF7646F00D4FD45 /* ExtensionsInteractor.swift */, 280F507A1EC8541D007416AB /* ExtensionsInstallerInteractor.swift */, 2845B1342066C6E6006EFB3B /* AppleScriptProtocol.swift */, 40FCE4221DF7646F00D4FD45 /* AppleScript.swift */, 40FCE4241DF7646F00D4FD45 /* SandboxedAppleScript.swift */, ); path = External; sourceTree = ""; }; 564E55F12028839F00CE4C76 /* Worklogs */ = { isa = PBXGroup; children = ( 564E55F2202883DB00CE4C76 /* WorklogsViewController.swift */, 564E55F5202884DE00CE4C76 /* WorklogsPresenter.swift */, 564E55F82028857300CE4C76 /* Worklogs.storyboard */, ); path = Worklogs; sourceTree = ""; }; 5683DC3C20ECDEA30000A138 /* CalendarEvents */ = { isa = PBXGroup; children = ( 5683DC3D20ECDED30000A138 /* ModuleCalendar.swift */, ); path = CalendarEvents; sourceTree = ""; }; 5685C7621DE8721100CA545E /* Account */ = { isa = PBXGroup; children = ( 5685C76B1DE8724400CA545E /* AccountViewController.swift */, 5685C7631DE8721100CA545E /* CloudKitLoginViewController.swift */, 5685C7641DE8721100CA545E /* Login.storyboard */, 5685C7651DE8721100CA545E /* LoginPresenter.swift */, 5685C7661DE8721100CA545E /* LoginViewController.swift */, ); path = Account; sourceTree = ""; }; 569C4C53202318F50049FBF1 /* Shell */ = { isa = PBXGroup; children = ( 569C4C572023193B0049FBF1 /* ShellCell.swift */, 569C4C6F202319DE0049FBF1 /* ShellCell.xib */, ); path = Shell; sourceTree = ""; }; 569C4C54202319010049FBF1 /* Browser */ = { isa = PBXGroup; children = ( 569C4C69202319A20049FBF1 /* BrowserCell.swift */, 569C4C6C202319CA0049FBF1 /* BrowserPresenter.swift */, 569C4C7820231A0B0049FBF1 /* BrowserCell.xib */, ); path = Browser; sourceTree = ""; }; 569C4C552023190A0049FBF1 /* Git */ = { isa = PBXGroup; children = ( 569C4C63202319870049FBF1 /* GitCell.swift */, 569C4C66202319930049FBF1 /* GitPresenter.swift */, 569C4C75202319FC0049FBF1 /* GitCell.xib */, ); path = Git; sourceTree = ""; }; 569C4C56202319120049FBF1 /* Jit */ = { isa = PBXGroup; children = ( 569C4C5D202319630049FBF1 /* JitCell.swift */, 569C4C72202319EE0049FBF1 /* JitCell.xib */, ); path = Jit; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 280D70B51ECFE06A005D2689 /* Jirassic iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 280D70C51ECFE06A005D2689 /* Build configuration list for PBXNativeTarget "Jirassic iOS" */; buildPhases = ( 280D70B21ECFE06A005D2689 /* Sources */, 280D70B31ECFE06A005D2689 /* Frameworks */, 280D70B41ECFE06A005D2689 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Jirassic iOS"; productName = "Jirassic iOS"; productReference = 280D70B61ECFE06A005D2689 /* Jirassic iOS.app */; productType = "com.apple.product-type.application"; }; 280D711C1ED5EE7F005D2689 /* Jirassic macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 280D71291ED5EE7F005D2689 /* Build configuration list for PBXNativeTarget "Jirassic macOS" */; buildPhases = ( 280D71191ED5EE7F005D2689 /* Sources */, 280D711A1ED5EE7F005D2689 /* Frameworks */, 280D711B1ED5EE7F005D2689 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Jirassic macOS"; packageProductDependencies = ( ); productName = "Jirassic no cloud"; productReference = 280D711D1ED5EE7F005D2689 /* Jirassic no cloud.app */; productType = "com.apple.product-type.application"; }; 4055B1221E0D802300279430 /* JirassicLauncher */ = { isa = PBXNativeTarget; buildConfigurationList = 4055B1311E0D802300279430 /* Build configuration list for PBXNativeTarget "JirassicLauncher" */; buildPhases = ( 4055B11F1E0D802300279430 /* Sources */, 4055B1201E0D802300279430 /* Frameworks */, 4055B1211E0D802300279430 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = JirassicLauncher; productName = JirassicLauncher; productReference = 4055B1231E0D802300279430 /* JirassicLauncher.app */; productType = "com.apple.product-type.application"; }; 4065D3031DD3A1AA00B73201 /* Jirassic AppStore */ = { isa = PBXNativeTarget; buildConfigurationList = 4065D3131DD3A1AA00B73201 /* Build configuration list for PBXNativeTarget "Jirassic AppStore" */; buildPhases = ( 4065D3001DD3A1AA00B73201 /* Sources */, 4065D3011DD3A1AA00B73201 /* Frameworks */, 4065D3021DD3A1AA00B73201 /* Resources */, 4055B1351E0D81E600279430 /* CopyFiles */, 28577E941E89972F002B07FD /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = "Jirassic AppStore"; packageProductDependencies = ( 6D37025F2BA83E9A002260D0 /* SwiftKeychainWrapper */, 6D3702612BA83EB9002260D0 /* RCLog */, 6D3702632BA83EBF002260D0 /* RCPreferences */, 6D3702652BA83EC7002260D0 /* RCHttp */, ); productName = Jirassic; productReference = 4065D3041DD3A1AA00B73201 /* Jirassic.app */; productType = "com.apple.product-type.application"; }; 4065D4011DD4532100B73201 /* JirassicTests */ = { isa = PBXNativeTarget; buildConfigurationList = 4065D4091DD4532100B73201 /* Build configuration list for PBXNativeTarget "JirassicTests" */; buildPhases = ( 4065D3FE1DD4532100B73201 /* Sources */, 4065D3FF1DD4532100B73201 /* Frameworks */, 4065D4001DD4532100B73201 /* Resources */, ); buildRules = ( ); dependencies = ( 4065D4081DD4532100B73201 /* PBXTargetDependency */, 566C09151F2B985C0058D90A /* PBXTargetDependency */, ); name = JirassicTests; productName = JirassicTests; productReference = 4065D4021DD4532100B73201 /* JirassicTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 4065D40F1DD4534C00B73201 /* jirassic-cmd */ = { isa = PBXNativeTarget; buildConfigurationList = 4065D4141DD4534C00B73201 /* Build configuration list for PBXNativeTarget "jirassic-cmd" */; buildPhases = ( 4065D40C1DD4534C00B73201 /* Sources */, 4065D40D1DD4534C00B73201 /* Frameworks */, 4065D40E1DD4534C00B73201 /* CopyFiles */, 28577E9B1E89A969002B07FD /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = "jirassic-cmd"; productName = "jirassic-cmd"; productReference = 4065D4101DD4534C00B73201 /* jirassic */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4065D2FC1DD3A1AA00B73201 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0830; LastUpgradeCheck = 1530; ORGANIZATIONNAME = "Imagin soft"; TargetAttributes = { 280D70B51ECFE06A005D2689 = { CreatedOnToolsVersion = 8.3.2; DevelopmentTeam = 5NHDC5EV44; LastSwiftMigration = 0920; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Push = { enabled = 0; }; com.apple.iCloud = { enabled = 1; }; }; }; 280D711C1ED5EE7F005D2689 = { CreatedOnToolsVersion = 8.3.2; DevelopmentTeam = 5NHDC5EV44; LastSwiftMigration = ""; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Sandbox = { enabled = 0; }; }; }; 4055B1221E0D802300279430 = { CreatedOnToolsVersion = 8.2.1; LastSwiftMigration = 0820; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 4065D3031DD3A1AA00B73201 = { CreatedOnToolsVersion = 8.1; LastSwiftMigration = ""; SystemCapabilities = { com.apple.Push = { enabled = 0; }; com.apple.Sandbox = { enabled = 1; }; com.apple.iCloud = { enabled = 1; }; }; }; 4065D4011DD4532100B73201 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 5NHDC5EV44; LastSwiftMigration = ""; ProvisioningStyle = Manual; TestTargetID = 280D711C1ED5EE7F005D2689; }; 4065D40F1DD4534C00B73201 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 5NHDC5EV44; LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 4065D2FF1DD3A1AA00B73201 /* Build configuration list for PBXProject "Jirassic" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, ); mainGroup = 4065D2FB1DD3A1AA00B73201; packageReferences = ( 6D37025B2BA83DB0002260D0 /* XCRemoteSwiftPackageReference "RCLog" */, 6D37025C2BA83DDB002260D0 /* XCRemoteSwiftPackageReference "RCpreferences" */, 6D37025D2BA83DFE002260D0 /* XCRemoteSwiftPackageReference "RChttp" */, 6D37025E2BA83E9A002260D0 /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */, ); productRefGroup = 4065D3051DD3A1AA00B73201 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 280D711C1ED5EE7F005D2689 /* Jirassic macOS */, 4065D3031DD3A1AA00B73201 /* Jirassic AppStore */, 4065D4011DD4532100B73201 /* JirassicTests */, 4055B1221E0D802300279430 /* JirassicLauncher */, 280D70B51ECFE06A005D2689 /* Jirassic iOS */, 4065D40F1DD4534C00B73201 /* jirassic-cmd */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 280D70B41ECFE06A005D2689 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 280D70C91ECFE5B1005D2689 /* LaunchScreen.xib in Resources */, 280D70CD1ECFE5DC005D2689 /* Images.xcassets in Resources */, 280D70CA1ECFE5BD005D2689 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 280D711B1ED5EE7F005D2689 /* Resources */ = { 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 */, ); runOnlyForDeploymentPostprocessing = 0; }; 4055B1211E0D802300279430 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 28E896751E83770D00722032 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D3021DD3A1AA00B73201 /* Resources */ = { isa = PBXResourcesBuildPhase; 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 */, 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; }; 4065D4001DD4532100B73201 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 280D70B21ECFE06A005D2689 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 280D70E51ECFE88F005D2689 /* CUser.swift in Sources */, 280D70F51ECFE98C005D2689 /* UserDefaults+uploadToken.swift in Sources */, 280D70E91ECFE8B9005D2689 /* CloudKitRepository+Settings.swift in Sources */, 280D70D21ECFE7ED005D2689 /* Day.swift in Sources */, 280D70DA1ECFE83F005D2689 /* CreateReport.swift in Sources */, 280D70D71ECFE817005D2689 /* DateExtension.swift in Sources */, 280D70F01ECFE8CC005D2689 /* Repository.swift in Sources */, 280D70F61ECFE9B3005D2689 /* RegisterUserInteractor.swift in Sources */, 280D70DE1ECFE870005D2689 /* UserInteractor.swift in Sources */, 280D70E11ECFE88F005D2689 /* CoreDataRepository+Settings.swift in Sources */, 280D70C81ECFE5AC005D2689 /* AppDelegate.swift in Sources */, 280D70E21ECFE88F005D2689 /* CoreDataRepository+User.swift in Sources */, 280D70EA1ECFE8B9005D2689 /* CloudKitRepository+User.swift in Sources */, 280D70CB1ECFE5D0005D2689 /* DaysViewController.swift in Sources */, 280D70D31ECFE7FB005D2689 /* Report.swift in Sources */, 280D70CF1ECFE5E7005D2689 /* NonTaskCell.swift in Sources */, 280D70CE1ECFE5E3005D2689 /* LoginViewController.swift in Sources */, 2818848221A4A2F800B33B9C /* Jirassic.xcdatamodeld in Sources */, 280D70DF1ECFE888005D2689 /* CoreDataRepository.swift in Sources */, 280D70F41ECFE96F005D2689 /* Settings.swift in Sources */, 280D70E81ECFE8B9005D2689 /* CloudKitRepository+Tasks.swift in Sources */, 280D70E71ECFE8B9005D2689 /* CloudKitRepository.swift in Sources */, 280D70E01ECFE88F005D2689 /* CoreDataRepository+Tasks.swift in Sources */, 280D70E31ECFE88F005D2689 /* CSettings.swift in Sources */, 280D70DC1ECFE84C005D2689 /* ReadTasksInteractor.swift in Sources */, 280D70EB1ECFE8B9005D2689 /* UserDefaults+token.swift in Sources */, 280D70F11ECFE8D0005D2689 /* RepositoryInteractor.swift in Sources */, 280D70D01ECFE5EA005D2689 /* TaskCell.swift in Sources */, 280D70D81ECFE82E005D2689 /* ViewAutolayout.swift in Sources */, 280300D81EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */, 280D70D91ECFE835005D2689 /* Conversions.swift in Sources */, 565E19F220A5E336003A5E2A /* RCSync.swift in Sources */, 280D70DB1ECFE847005D2689 /* ReadDaysInteractor.swift in Sources */, 28CBB598204554A2006F9D3A /* ParseGitBranch.swift in Sources */, 280D70E41ECFE88F005D2689 /* CTask.swift in Sources */, 280D70D41ECFE7FF005D2689 /* Task.swift in Sources */, 280D70D51ECFE803005D2689 /* User.swift in Sources */, 280D70D11ECFE5EE005D2689 /* TasksViewController.swift in Sources */, 280D70F71ED0E6EC005D2689 /* StringIdGenerator.swift in Sources */, 280D70D61ECFE807005D2689 /* Week.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 280D71191ED5EE7F005D2689 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 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 */, 287195312025C360001C237E /* CSettings.swift in Sources */, 56ADBF6A21C3F625008350A6 /* GitUser.swift in Sources */, 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 */, 2812F61E2145031B008EE81E /* IAPHelper.swift in Sources */, 280D713A1ED6043D005D2689 /* Conversions.swift in Sources */, 280D713B1ED6043D005D2689 /* ComputerWakeUpInteractor.swift in Sources */, 280D713D1ED6043D005D2689 /* CreateReport.swift in Sources */, 280D713F1ED6043D005D2689 /* ReadDaysInteractor.swift in Sources */, 280D71411ED6043D005D2689 /* ReadTasksInteractor.swift in Sources */, 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 */, 28279AD521C6245700376304 /* GitUsersViewController.swift in Sources */, 287195322025C364001C237E /* CTask.swift in Sources */, 5683DC3E20ECDED30000A138 /* ModuleCalendar.swift in Sources */, 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 */, 2892B2981F094A170085BAC2 /* JiraRepository.swift in Sources */, 280D71901ED6043D005D2689 /* BrowserNotification.swift in Sources */, 280D719D1ED6043D005D2689 /* SqliteRepository.swift in Sources */, 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 */, 280D71A51ED6043D005D2689 /* SQLTable.swift in Sources */, 2845B139206703C5006EFB3B /* Keychain.swift in Sources */, 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 */, ); runOnlyForDeploymentPostprocessing = 0; }; 4055B11F1E0D802300279430 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 28E896721E83732200722032 /* main.m in Sources */, 28E896711E83732200722032 /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D3001DD3A1AA00B73201 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; 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 */, 28279AD321BBF09900376304 /* Jirassic.xcdatamodeld in Sources */, 56ADBF6E21C3F94D008350A6 /* GitUserParser.swift in Sources */, 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 */, 28013890205F86460051B532 /* TimeBox.swift in Sources */, 2812F6212145031B008EE81E /* Store.swift in Sources */, 4065D3F91DD3B44200B73201 /* Repository.swift in Sources */, 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 */, 2892B29C1F094A760085BAC2 /* JiraRepository+Reports.swift in Sources */, 2845B1402068351F006EFB3B /* TableViewCell.swift in Sources */, 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 */, 4065D3AE1DD3B44200B73201 /* ReadDaysInteractor.swift in Sources */, 285465A3216C84E10052CB6A /* CreateMonthReport.swift in Sources */, 4065D3D71DD3B44200B73201 /* MenuBarIconView.swift in Sources */, 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 */, 4065D3D61DD3B44200B73201 /* MenuBarController.swift in Sources */, 28A5F27E1E5789FC002BE564 /* TasksDataSource.swift in Sources */, 4065D3B61DD3B44200B73201 /* TaskTypeSelection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D3FE1DD4532100B73201 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 4065D4231DD4562900B73201 /* DateExtensionTests.swift in Sources */, 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 */, 4065D4261DD4562900B73201 /* TaskInteractorTests.swift in Sources */, 28A283B3203CB1B900DDCB63 /* MergeTasksInteractorTests.swift in Sources */, 4065D4201DD4561D00B73201 /* UserInteractorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4065D40C1DD4534C00B73201 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 28C9C6231EAD37D0007EB3E6 /* UserDefaults+uploadToken.swift in Sources */, 28577EB81E8AF08F002B07FD /* SQLTable.swift in Sources */, 4065D42D1DD4576C00B73201 /* Day.swift in Sources */, 28577EAC1E8ADC53002B07FD /* SqliteRepository.swift in Sources */, 28577EAE1E8ADC53002B07FD /* SSettings.swift in Sources */, 28A9287B1E8FC9B90022AB55 /* CreateReport.swift in Sources */, 285465A4216C84E10052CB6A /* CreateMonthReport.swift in Sources */, 4065D42E1DD4577D00B73201 /* StringIdGenerator.swift in Sources */, 28A928881E910EA70022AB55 /* SqliteRepository+Settings.swift in Sources */, 4065D4321DD457B500B73201 /* Conversions.swift in Sources */, 28A928821E910DA40022AB55 /* SqliteRepository+Tasks.swift in Sources */, 4065D4191DD454CB00B73201 /* RepositoryInteractor.swift in Sources */, 4065D42A1DD4576000B73201 /* Task.swift in Sources */, 28A928851E910E5E0022AB55 /* SqliteRepository+User.swift in Sources */, 4065D42C1DD4576800B73201 /* Report.swift in Sources */, 280300D91EDB5ECA000A763E /* StatisticsInteractor.swift in Sources */, 4065D4301DD4579F00B73201 /* ReadTasksInteractor.swift in Sources */, 6D5BC4CB2C1B5BF7002DA29B /* CreateDayReport.swift in Sources */, 4065D4171DD4536200B73201 /* main.swift in Sources */, 4065D42B1DD4576500B73201 /* Settings.swift in Sources */, 280D71021ED35A25005D2689 /* UserDefaults+token.swift in Sources */, 4065D4281DD4574D00B73201 /* User.swift in Sources */, 4065D4311DD457B000B73201 /* DateExtension.swift in Sources */, 28577EB01E8ADC53002B07FD /* STask.swift in Sources */, 4065D4291DD4575B00B73201 /* Week.swift in Sources */, 280D71031ED35A3E005D2689 /* StringArray.swift in Sources */, 28577EB21E8ADC53002B07FD /* SUser.swift in Sources */, 4065D42F1DD4579400B73201 /* TaskInteractor.swift in Sources */, 28577EB61E8AF08F002B07FD /* SQLiteDB.swift in Sources */, 28A928791E8F78580022AB55 /* SQLiteSchema.swift in Sources */, 4065D4181DD454C500B73201 /* Repository.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 4065D4081DD4532100B73201 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4065D3031DD3A1AA00B73201 /* Jirassic AppStore */; targetProxy = 4065D4071DD4532100B73201 /* PBXContainerItemProxy */; }; 566C09151F2B985C0058D90A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 280D711C1ED5EE7F005D2689 /* Jirassic macOS */; targetProxy = 566C09141F2B985C0058D90A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 28E896731E83770D00722032 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 28E896741E83770D00722032 /* Base */, ); name = MainMenu.xib; sourceTree = ""; }; 4065D3431DD3B44200B73201 /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( 4065D3441DD3B44200B73201 /* Base */, ); name = LaunchScreen.xib; sourceTree = ""; }; 4065D3451DD3B44200B73201 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 4065D3461DD3B44200B73201 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 4065D3591DD3B44200B73201 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 4065D35A1DD3B44200B73201 /* Base */, ); name = Main.storyboard; path = ..; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 280D70C61ECFE06A005D2689 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/iOS/Jirassic Scrum.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = "$(SRCROOT)/Delivery/iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 280D70C71ECFE06A005D2689 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/iOS/Jirassic Scrum.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = "$(SRCROOT)/Delivery/iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; 280D712A1ED5EE7F005D2689 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Jirassic macOS.entitlements"; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos; PRODUCT_NAME = "Jirassic no cloud"; PROVISIONING_PROFILE_SPECIFIER = "jirassic dev"; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; }; name = Debug; }; 280D712B1ED5EE7F005D2689 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Jirassic macOS.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos; PRODUCT_NAME = "Jirassic no cloud"; PROVISIONING_PROFILE_SPECIFIER = "jirassic distribution"; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; }; name = Release; }; 4055B12F1E0D802300279430 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/macOS-launcher/JirassicLauncher.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS-launcher/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos.launcher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "3509678e-d1ba-4d07-bf3c-684b4c4fa932"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; }; 4055B1301E0D802300279430 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/macOS-launcher/JirassicLauncher.entitlements"; CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS-launcher/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos.launcher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "19d0790c-3a77-4e31-8bbc-4f0b931c9e98"; PROVISIONING_PROFILE_SPECIFIER = "jirassic launcher appstore"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; name = Release; }; 4065D3111DD3A1AA00B73201 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 4065D3121DD3A1AA00B73201 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 4065D3141DD3A1AA00B73201 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppStoreIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/macOS/Jirassic.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", "APPSTORE=1", ); INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS/Info.plist"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_SWIFT_FLAGS = "-D APPSTORE -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos; PRODUCT_NAME = Jirassic; PROVISIONING_PROFILE = "f679af74-4f51-4414-a117-26fcd2203fad"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; }; name = Debug; }; 4065D3151DD3A1AA00B73201 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppStoreIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "$(SRCROOT)/Delivery/macOS/Jirassic.entitlements"; CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = "APPSTORE=1"; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS/Info.plist"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_SWIFT_FLAGS = "-D APPSTORE"; PRODUCT_BUNDLE_IDENTIFIER = com.jirassic.macos; PRODUCT_NAME = Jirassic; PROVISIONING_PROFILE = "ec430240-7fda-4d18-9ddb-146d41480c9b"; PROVISIONING_PROFILE_SPECIFIER = "jirassic appstore"; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; }; name = Release; }; 4065D40A1DD4532100B73201 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = JirassicTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.ralcr.JirassicTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Jirassic no cloud.app/Contents/MacOS/Jirassic no cloud"; }; name = Debug; }; 4065D40B1DD4532100B73201 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = JirassicTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.ralcr.JirassicTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Jirassic no cloud.app/Contents/MacOS/Jirassic no cloud"; }; name = Release; }; 4065D4151DD4534C00B73201 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/External/Realm", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", "CMD=1", ); INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS-cmd/Info.plist"; MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_SWIFT_FLAGS = "-D CMD"; PRODUCT_NAME = jirassic; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.2; }; name = Debug; }; 4065D4161DD4534C00B73201 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NHDC5EV44; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/External/Realm", ); GCC_PREPROCESSOR_DEFINITIONS = "CMD=1"; INFOPLIST_FILE = "$(SRCROOT)/Delivery/macOS-cmd/Info.plist"; MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; OTHER_SWIFT_FLAGS = "-D CMD"; PRODUCT_NAME = jirassic; SWIFT_OBJC_BRIDGING_HEADER = "Delivery/macOS/Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.2; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 280D70C51ECFE06A005D2689 /* Build configuration list for PBXNativeTarget "Jirassic iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 280D70C61ECFE06A005D2689 /* Debug */, 280D70C71ECFE06A005D2689 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 280D71291ED5EE7F005D2689 /* Build configuration list for PBXNativeTarget "Jirassic macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 280D712A1ED5EE7F005D2689 /* Debug */, 280D712B1ED5EE7F005D2689 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4055B1311E0D802300279430 /* Build configuration list for PBXNativeTarget "JirassicLauncher" */ = { isa = XCConfigurationList; buildConfigurations = ( 4055B12F1E0D802300279430 /* Debug */, 4055B1301E0D802300279430 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4065D2FF1DD3A1AA00B73201 /* Build configuration list for PBXProject "Jirassic" */ = { isa = XCConfigurationList; buildConfigurations = ( 4065D3111DD3A1AA00B73201 /* Debug */, 4065D3121DD3A1AA00B73201 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4065D3131DD3A1AA00B73201 /* Build configuration list for PBXNativeTarget "Jirassic AppStore" */ = { isa = XCConfigurationList; buildConfigurations = ( 4065D3141DD3A1AA00B73201 /* Debug */, 4065D3151DD3A1AA00B73201 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4065D4091DD4532100B73201 /* Build configuration list for PBXNativeTarget "JirassicTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 4065D40A1DD4532100B73201 /* Debug */, 4065D40B1DD4532100B73201 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4065D4141DD4534C00B73201 /* Build configuration list for PBXNativeTarget "jirassic-cmd" */ = { isa = XCConfigurationList; buildConfigurations = ( 4065D4151DD4534C00B73201 /* Debug */, 4065D4161DD4534C00B73201 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 6D37025B2BA83DB0002260D0 /* XCRemoteSwiftPackageReference "RCLog" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cristibaluta/RCLog"; requirement = { branch = master; kind = branch; }; }; 6D37025C2BA83DDB002260D0 /* XCRemoteSwiftPackageReference "RCpreferences" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cristibaluta/RCpreferences"; requirement = { branch = master; kind = branch; }; }; 6D37025D2BA83DFE002260D0 /* XCRemoteSwiftPackageReference "RChttp" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cristibaluta/RChttp"; requirement = { branch = master; kind = branch; }; }; 6D37025E2BA83E9A002260D0 /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/jrendel/SwiftKeychainWrapper"; requirement = { kind = upToNextMajorVersion; minimumVersion = 4.0.1; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 6D37025F2BA83E9A002260D0 /* SwiftKeychainWrapper */ = { isa = XCSwiftPackageProductDependency; package = 6D37025E2BA83E9A002260D0 /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */; productName = SwiftKeychainWrapper; }; 6D3702612BA83EB9002260D0 /* RCLog */ = { isa = XCSwiftPackageProductDependency; package = 6D37025B2BA83DB0002260D0 /* XCRemoteSwiftPackageReference "RCLog" */; productName = RCLog; }; 6D3702632BA83EBF002260D0 /* RCPreferences */ = { isa = XCSwiftPackageProductDependency; package = 6D37025C2BA83DDB002260D0 /* XCRemoteSwiftPackageReference "RCpreferences" */; productName = RCPreferences; }; 6D3702652BA83EC7002260D0 /* RCHttp */ = { isa = XCSwiftPackageProductDependency; package = 6D37025D2BA83DFE002260D0 /* XCRemoteSwiftPackageReference "RChttp" */; productName = RCHttp; }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ 2818847F21A4A2F800B33B9C /* Jirassic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( 2818848021A4A2F800B33B9C /* Jirassic.xcdatamodel */, ); currentVersion = 2818848021A4A2F800B33B9C /* Jirassic.xcdatamodel */; path = Jirassic.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; /* End XCVersionGroup section */ }; rootObject = 4065D2FC1DD3A1AA00B73201 /* Project object */; } ================================================ FILE: Jirassic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Jirassic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ ================================================ FILE: Jirassic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ================================================ { "originHash" : "63bc3e1363d6b6d18dc356b60ac504be39a3587819f597ee22b44a717292b934", "pins" : [ { "identity" : "rchttp", "kind" : "remoteSourceControl", "location" : "https://github.com/cristibaluta/RChttp", "state" : { "branch" : "master", "revision" : "eb5fbb823105f356eb38b10d70202b532831cd0e" } }, { "identity" : "rclog", "kind" : "remoteSourceControl", "location" : "https://github.com/cristibaluta/RCLog", "state" : { "branch" : "master", "revision" : "f3166aa52b66e2278523ce8eef40bf31a37ddaad" } }, { "identity" : "rcpreferences", "kind" : "remoteSourceControl", "location" : "https://github.com/cristibaluta/RCpreferences", "state" : { "branch" : "master", "revision" : "d8a2750659ebff18429489d43a933c5560e8367a" } }, { "identity" : "swiftkeychainwrapper", "kind" : "remoteSourceControl", "location" : "https://github.com/jrendel/SwiftKeychainWrapper", "state" : { "revision" : "185a3165346a03767101c4f62e9a545a0fe0530f", "version" : "4.0.1" } } ], "version" : 3 } ================================================ FILE: Jirassic.xcodeproj/xcshareddata/xcschemes/Jirassic AppStore.xcscheme ================================================ ================================================ FILE: Jirassic.xcodeproj/xcshareddata/xcschemes/Jirassic iOS.xcscheme ================================================ ================================================ FILE: Jirassic.xcodeproj/xcshareddata/xcschemes/Jirassic macOS.xcscheme ================================================ ================================================ FILE: Jirassic.xcodeproj/xcshareddata/xcschemes/JirassicLauncher.xcscheme ================================================ ================================================ FILE: Jirassic.xcodeproj/xcshareddata/xcschemes/jirassic-cmd.xcscheme ================================================ ================================================ FILE: JirassicTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: MyPlayground.playground/Contents.swift ================================================ //: Playground - noun: a place where people can play import Cocoa let ymdhmsUnitFlags: Set = [.year, .month, .weekday, .day, .hour, .minute, .second] let gregorian = Calendar(identifier: Calendar.Identifier.gregorian) extension Date { init (year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int=0) { var comps = gregorian.dateComponents(ymdhmsUnitFlags, from: Date()) comps.year = year comps.month = month comps.day = day comps.hour = hour comps.minute = minute comps.second = second self.init(timeInterval: 0, since: gregorian.date(from: comps)!) } } print(Date()) Date(year: 2017, month: 5, day: 5, hour: 0, minute:0) NSCalendar.current.isDate(Date(year: 2017, month: 5, day: 5, hour: 0, minute:0), inSameDayAs: Date(year: 2017, month: 5, day: 5, hour: 23, minute:59)) print ( gregorian.startOfDay(for: Date()) ) let taskIdEreg = "([A-Z]+-[0-9])\\w+" let title = "Pull Request #2411: IOS-1690 Push Notifications Handle" let regex = try! NSRegularExpression(pattern: taskIdEreg, options: []) let match = regex.firstMatch(in: title, options: [], range: NSRange(location: 0, length: title.characters.count)) (title as NSString).substring(with: match!.range) ================================================ FILE: MyPlayground.playground/contents.xcplayground ================================================ ================================================ FILE: README.md ================================================ # Jirassic - Jira Time Tracker for employees Jirassic is a Mac app that runs in background and tracks automatically the work you do at your workplace. At the end of the day creates a worklog which you can save to Jira Tempo. Its purpose is to replace primitive tracking methods or relying on memory and do everything automatically. # Features - Track automatically the time you’ve spent on tasks based on git commits - Track lunch break - Track daily scrum meetings - Track code reviews - Track time on social media - Save worklogs to Jira Tempo ![Screenshot](https://image.ibb.co/eAniz7/tasks.png) ![Screenshot](https://image.ibb.co/bBhoXS/settings.png) # How it works - When you open your computer in the morning, Jirassic will ask you to start the day - Tasks are logged automatically or manually when you finish them, not when you start. The premise is that you're always doing something from start to finish, which you do, isn't? - At the end of the day/week/month you can save the worklogs to Jira Tempo or go to reports tab and see them in more detail. # Shell support - jit-CLI Is a wrapper over git which easies the work with jira and branches. Commits made with jit will log tasks to Jirassic Read more at https://github.com/ralcr/Jit - jirassic-CLI Use Jirassic from the command line, you can manipulate the Jirassic database directly from the command line and the app does not have to be open. ### Install jirassic CLI You can install it by running this commands in Terminal: sudo curl -o /usr/local/bin/jirassic https://raw.githubusercontent.com/ralcr/Jirassic/master/build/jirassic sudo chmod +x /usr/local/bin/jirassic (Note that this precompiled version works with the AppStore app, it won't see the database of the 'Jirassic macOS' target) ![Screenshot](https://s1.postimg.org/tq0jtk65b/Screen_Shot_2017-04-01_at_17.45.21.png) # Compile Xcode9 and swift4 is needed. Use the target 'Jirassic macOS' because it is configured to run without signing, that means backup to iCloud will not be available. If you need iCloud you can use the Jirassic AppStore target after you create your own iCloud container and provisioning. # Licence & contribution Jirassic is free in the Appstore but with some IAP for some features, for this reason you are not allowed to resell or distribute this software. If you wish to contribute with code note that you will not be paid, we still hope you contribute with creating issues to make it a better software. ================================================ FILE: build/jirassic ================================================ [File too large to display: 10.6 MB] ================================================ FILE: licence.txt ================================================ Copyright (C) 2017 jirassic.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use or modify the app for personal purpose. Re-selling or distributing this app is not allowed. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: scripts/add-key.sh ================================================ #!/bin/sh # Create a custom keychain security create-keychain -p travis macos-build.keychain # Make the custom keychain default, so xcodebuild will use it for signing security default-keychain -s macos-build.keychain # Unlock the keychain security unlock-keychain -p travis macos-build.keychain # Set keychain timeout to 1 hour for long builds # see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/ security set-keychain-settings -t 3600 -l ~/Library/Keychains/macos-build.keychain # Add certificates to keychain and allow codesign to access them security import ./scripts/certs/MacDeveloper.p12 -k ~/Library/Keychains/macos-build.keychain -P $PASSWORD -T /usr/bin/codesign # Put the provisioning profile in place mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp "./scripts/profile/jirassic_dev.provisionprofile" ~/Library/MobileDevice/Provisioning\ Profiles/ cp "./scripts/profile/jirassic_launcher_dev.provisionprofile" ~/Library/MobileDevice/Provisioning\ Profiles/