Full Code of ralcr/Jirassic for AI

master a5f43dadd57a cached
343 files
12.3 MB
425.6k tokens
28 symbols
1 requests
Download .txt
Showing preview only (1,813K chars total). Download the full file or copy to clipboard to get everything.
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 "<Day dateStart: \(dateStart), dateEnd: \(String(describing: dateEnd))>"
    }
}


================================================
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<Calendar.Component> = [.year, .month, .day]
let ymdhmsUnitFlags: Set<Calendar.Component> = [.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<Date> {
        
        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..<length {
            let randomValue = arc4random_uniform(UInt32(chars.count))
            randomString += "\(chars[chars.index(chars.startIndex, offsetBy: Int(randomValue))])"
        }
        
        return randomString
    }
}


================================================
FILE: App/Extensions/TableViewCell.swift
================================================
//
//  TableView.swift
//  Jirassic
//
//  Created by Cristian Baluta on 25/03/2018.
//  Copyright © 2018 Imagin soft. All rights reserved.
//

#if os(iOS)
    import UIKit
    typealias TableViewCell = UITableViewCell
    typealias TableView = UITableView
#else
    import Cocoa
    typealias TableViewCell = NSTableRowView
    typealias TableView = NSTableView
#endif

extension TableViewCell {
    
    class func register (in tableView: TableView) {
        return register(in: tableView, type: self)
    }
    
    private class func register<T> (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<T> (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<T> (_ 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<T> (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 = "<p>\(note)</p>"
                note += "<ul>"
                for n in report.notes {
                    note += "<li>\(n)</li>"
                }
                note += "</ul>"
            }
            notes += "<tr><td style=\"text-align: left; padding-left: 10px;\">\(note)</td><td>\(report.duration.secToHoursAndMin)</td></tr>\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..<reports.byTasks.count {
            totalDuration += reports.byTasks[i1].duration
            for i2 in 0..<reports.byTasks.count {
                if i1 != i2 {
                    XCTAssertFalse(reports.byTasks[i1].taskNumber == reports.byTasks[i2].taskNumber, "Duplicate taskNumber found, they should be unique")
                }
            }
        }
        XCTAssert(totalDuration == targetHoursInDay*3, "Duration should be 8*3 hours")
    }
}


================================================
FILE: App/Reports/CreateReport.swift
================================================
//
//  CreateReport.swift
//  Jirassic
//
//  Created by Baluta Cristian on 28/03/15.
//  Copyright (c) 2015 Cristian Baluta. All rights reserved.
//

import Foundation

class CreateReport {
    
    func reports (fromTasks tasks: [Task], targetHoursInDay: Double?) -> [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..<tasks.count {
            
            task = tasks[i]
            
            if let startDate = task.startDate {
                // Tasks with begining and ending defined are inlined tasks.
                // Extract them in front of the overlapped task. This will take from the time of the actual task
                let duration = task.endDate.timeIntervalSince(startDate)
                task.startDate = nil
                task.endDate = previousEndDate.addingTimeInterval(duration)
                arr.append(task)
                previousEndDate = task.endDate
//                print("inlined \(startDate)")
            } else {
                arr.append(task)
                previousEndDate = task.endDate
            }
        }
        return arr
    }
    
    private func removeUntrackableTasks (_ tasks: [Task]) -> [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..<tasks.count-1 {
            
            task = tasks[i]
            
            if targetHoursInDay != nil && isAdjustable(taskType: task.taskType) {
                extraTimeToAdd += extraTimePerTask
            }
            
            task.endDate = targetHoursInDay == nil
                ? task.endDate.addingTimeInterval(extraTimeToAdd)
                : task.endDate.addingTimeInterval(extraTimeToAdd).round()
            task.startDate = previousDate
            previousDate = task.endDate
            
            roundedTasks.append(task)
        }
        
        // Handle the last task separately, add the remaining time till targetHoursInDay
        task = tasks.last!
        task.endDate = roundedTasks.first!.endDate.addingTimeInterval(requiredHours)
        task.startDate = previousDate
        roundedTasks.append(task)
        
        return roundedTasks
    }
    
    private func groupByTaskNumber (_ tasks: [Task]) -> (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..<reports.count {
            for i2 in 0..<reports.count {
                if i1 != i2 {
                    XCTAssertFalse(reports[i1].taskNumber == reports[i2].taskNumber, "Duplicate taskNumber found, they should be unique")
                }
            }
        }
    }
    
    func testRoundLessThan8HoursOfWork() {
		
		let reports = report.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay)
        
        var duration = 0.0
        for report in reports {
            duration += report.duration
        }
		
		XCTAssert(duration == targetHoursInDay)
    }
    
	func testRoundMoreThan8HoursOfWork() {
		
		var tasks = self.tasks
        tasks.removeLast()
        tasks += buildTasks("|19.30||||0")
        
        let reports = report.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay)
		
        var duration = 0.0
        for report in reports {
            duration += report.duration
        }
        
        XCTAssert(duration == targetHoursInDay)
    }
    
    func testDoNotRoundMeetings() {
        
        var tasks = self.tasks
        tasks.removeLast()
        tasks += buildTasks("|18.20|Learning time|learning||7")
        
        let reports = report.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay)
        
        XCTAssert(reports.count == 6)
        
        XCTAssert(reports[1].taskNumber == "scrum")
        XCTAssert(reports[1].duration == 18.minToSec)
        
        XCTAssert(reports[5].taskNumber == "learning")
        XCTAssert(reports[5].duration == 18.minToSec)
    }
    
    func testRealSituationWhereDurationCanBeMessedUp() {
        
        let str = "|8.59||||1;" +
            "10.4|11.10|Meeting 1|meeting||4;" +
            "11.39|12.22||||3;" +
            "|13.4|Note 1|APP-3730||0;" +
            "|13.19|Note 2|APP-3730||0;" +
            "13.20|13.29|coderev 1|coderev||8;" +
            "14.13|14.21|coderev 2|coderev||8;" +
            "16.4|16.7|coderev 3|coderev||8;" +
            "16.56|16.59|coderev 4|coderev||8;" +
            "|17.30|Note 3|APP-3730||0"
        tasks = buildTasks(str)
        
        let reports = report.reports(fromTasks: tasks, targetHoursInDay: targetHoursInDay)
        XCTAssert(reports.count == 3, "There should be only 3 unique task numbers. Lunch is ignored")
        XCTAssert(reports[0].duration > 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<C : Collection>(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<Task>(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..<tasks.count {
                taskInteractor.deleteTask(tasks[i])
            }
        })
    }
}


================================================
FILE: App/Tasks/TaskFinder.swift
================================================
//
//  TaskTypeFinder.swift
//  Jirassic
//
//  Created by Baluta Cristian on 09/11/15.
//  Copyright © 2015 Cristian Baluta. All rights reserved.
//

import Foundation

class TaskFinder {
    
	func scrumExists (_ tasks: [Task]) -> 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<Task>(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<Task>(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..<to.index(to.endIndex, offsetBy: -charsToDelete)
            return String(to[rangeToKeep])
        }
        guard Int(string) != nil else {
            // New digit is not numeric, return previous string
            return to
        }
		
        // Separate hours from minutes
		let timeComps = to.components(separatedBy: ":")
		let hr: String = timeComps.first!
		
		// If it contains minutes
        if timeComps.count == 2 {
			var m = 0
			let min = timeComps.last!
			var minToAdd = ""
			
			if min.count == 2 {
				let rangeToKeep = min.startIndex..<min.index(min.endIndex, offsetBy: -1)
				return "\(hr):\(min[rangeToKeep])\(string)"
			} else {
				m = decimalValueOf(min, newDigit: string)
			}
			
			if (m >= 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <customFonts key="customFonts">
        <array key="Avenir.ttc">
            <string>Avenir-Medium</string>
        </array>
    </customFonts>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="iN0-l3-epB">
            <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Time tracking done right" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
                    <rect key="frame" x="106.5" y="370" width="268.5" height="33"/>
                    <fontDescription key="fontDescription" name="Avenir-Medium" family="Avenir" pointSize="24"/>
                    <color key="textColor" red="0.82619418379999998" green="0.18153228830000001" blue="0.1534976841" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                    <nil key="highlightedColor"/>
                </label>
                <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SplashIcon" translatesAutoresizingMaskIntoConstraints="NO" id="8Co-43-sBV">
                    <rect key="frame" x="140" y="140" width="200" height="200"/>
                    <constraints>
                        <constraint firstAttribute="width" constant="200" id="48U-DK-pCe"/>
                        <constraint firstAttribute="height" constant="200" id="azM-hT-9yV"/>
                    </constraints>
                </imageView>
            </subviews>
            <color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
            <constraints>
                <constraint firstAttribute="centerY" secondItem="8Co-43-sBV" secondAttribute="centerY" id="3Sg-PJ-Yzv"/>
                <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
                <constraint firstItem="kId-c2-rCX" firstAttribute="top" secondItem="8Co-43-sBV" secondAttribute="bottom" constant="30" id="aoI-OR-eUc"/>
                <constraint firstAttribute="centerX" secondItem="8Co-43-sBV" secondAttribute="centerX" id="oBk-RF-Fqh"/>
            </constraints>
            <nil key="simulatedStatusBarMetrics"/>
            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
            <point key="canvasLocation" x="548" y="455"/>
        </view>
    </objects>
    <resources>
        <image name="SplashIcon" width="200" height="200"/>
    </resources>
</document>


================================================
FILE: Delivery/iOS/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="rS3-R9-Ivy">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Master-->
        <scene sceneID="cUi-kZ-frf">
            <objects>
                <navigationController title="Master" id="rS3-R9-Ivy" sceneMemberID="viewController">
                    <navigationBar key="navigationBar" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="yXu-0R-QUA">
                        <rect key="frame" x="0.0" y="20" width="375" height="44"/>
                        <autoresizingMask key="autoresizingMask"/>
                    </navigationBar>
                    <connections>
                        <segue destination="pGT-tt-fL9" kind="relationship" relationship="rootViewController" id="rmv-pN-cet"/>
                    </connections>
                </navigationController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="eq9-QA-ai8" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-951" y="61"/>
        </scene>
        <!--Login View Controller-->
        <scene sceneID="EsZ-fK-YUx">
            <objects>
                <viewController id="pGT-tt-fL9" customClass="LoginViewController" customModule="Jirassic_iOS" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Qls-sY-lCN"/>
                        <viewControllerLayoutGuide type="bottom" id="H8a-Vv-fAg"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="D52-14-Kez">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b4t-lD-bTw">
                                <rect key="frame" x="142" y="307" width="91" height="53"/>
                                <fontDescription key="fontDescription" name="Avenir-BlackOblique" family="Avenir" pointSize="30"/>
                                <state key="normal" title="iCloud">
                                    <color key="titleColor" red="0.94117647059999998" green="0.15686274510000001" blue="0.15686274510000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                    <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                </state>
                            </button>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You must be logged in to continue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fW5-5D-GpD">
                                <rect key="frame" x="57" y="368" width="261" height="21"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F4c-hm-ycU">
                                <rect key="frame" x="156.5" y="397" width="62" height="30"/>
                                <state key="normal" title="Continue"/>
                                <connections>
                                    <action selector="handleLoginButton:" destination="pGT-tt-fL9" eventType="touchUpInside" id="Ldj-i1-ioz"/>
                                </connections>
                            </button>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="b4t-lD-bTw" firstAttribute="centerX" secondItem="D52-14-Kez" secondAttribute="centerX" id="A2N-Dx-zHx"/>
                            <constraint firstItem="F4c-hm-ycU" firstAttribute="top" secondItem="fW5-5D-GpD" secondAttribute="bottom" constant="8" id="EMz-Es-LIe"/>
                            <constraint firstItem="fW5-5D-GpD" firstAttribute="centerX" secondItem="D52-14-Kez" secondAttribute="centerX" id="OGS-Ja-8J1"/>
                            <constraint firstItem="b4t-lD-bTw" firstAttribute="centerY" secondItem="D52-14-Kez" secondAttribute="centerY" id="TS4-Nn-TVO"/>
                            <constraint firstItem="fW5-5D-GpD" firstAttribute="top" secondItem="b4t-lD-bTw" secondAttribute="bottom" constant="8" id="cdG-xq-Ouk"/>
                            <constraint firstItem="F4c-hm-ycU" firstAttribute="centerX" secondItem="D52-14-Kez" secondAttribute="centerX" id="jcL-IC-LEj"/>
                        </constraints>
                    </view>
                    <navigationItem key="navigationItem" id="Q5D-WZ-kV0"/>
                    <connections>
                        <outlet property="butLogin" destination="F4c-hm-ycU" id="I5O-P7-dec"/>
                        <outlet property="infoTextField" destination="fW5-5D-GpD" id="BVV-vy-291"/>
                        <segue destination="pGg-6v-bdr" kind="show" identifier="ShowDaysSegue" id="SRI-lJ-09U"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="kwp-mQ-Xd2" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-176" y="61"/>
        </scene>
        <!--Master-->
        <scene sceneID="VgW-fR-Quf">
            <objects>
                <tableViewController title="Master" id="pGg-6v-bdr" customClass="DaysViewController" customModule="Jirassic_iOS" customModuleProvider="target" sceneMemberID="viewController">
                    <tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="63" sectionHeaderHeight="18" sectionFooterHeight="18" id="mLL-gJ-YKr">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
                        <prototypes>
                            <tableViewCell contentMode="scaleToFill" selectionStyle="blue" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="DayCell" textLabel="2pz-XF-uhl" rowHeight="63" style="IBUITableViewCellStyleDefault" id="m0d-ak-lc9">
                                <rect key="frame" x="0.0" y="55.5" width="375" height="63"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="m0d-ak-lc9" id="d3P-M7-ByW">
                                    <rect key="frame" x="0.0" y="0.0" width="341" height="62.5"/>
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2pz-XF-uhl">
                                            <rect key="frame" x="16" y="0.0" width="324" height="62.5"/>
                                            <autoresizingMask key="autoresizingMask"/>
                                            <fontDescription key="fontDescription" name="Avenir-Book" family="Avenir" pointSize="20"/>
                                            <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        </label>
                                    </subviews>
                                </tableViewCellContentView>
                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <connections>
                                    <segue destination="Ne8-5p-NMX" kind="show" identifier="ShowTasksSegue" id="3D0-nZ-6Kj"/>
                                </connections>
                            </tableViewCell>
                        </prototypes>
                        <sections/>
                        <connections>
                            <outlet property="dataSource" destination="pGg-6v-bdr" id="P41-gY-KXY"/>
                            <outlet property="delegate" destination="pGg-6v-bdr" id="Y6K-Cp-Qkv"/>
                        </connections>
                    </tableView>
                    <navigationItem key="navigationItem" title="Days" id="tQt-TN-PWz"/>
                </tableViewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="6Cn-md-YlS" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="607" y="61"/>
        </scene>
        <!--Tasks View Controller-->
        <scene sceneID="iy0-h1-6m5">
            <objects>
                <tableViewController id="Ne8-5p-NMX" customClass="TasksViewController" customModule="Jirassic_iOS" customModuleProvider="target" sceneMemberID="viewController">
                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="22" sectionFooterHeight="22" id="EHJ-KF-bI6">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <prototypes>
                            <tableViewCell userInteractionEnabled="NO" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="TaskCell" rowHeight="128" id="lba-rR-GTL" customClass="TaskCell" customModule="Jirassic_iOS" customModuleProvider="target">
                                <rect key="frame" x="0.0" y="22" width="375" height="128"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="lba-rR-GTL" id="2tP-Kw-4Mv">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="128"/>
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Task nr" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4pY-nC-xVt">
                                            <rect key="frame" x="28" y="20" width="224" height="21"/>
                                            <constraints>
                                                <constraint firstAttribute="height" constant="21" id="RqQ-HS-0PD"/>
                                            </constraints>
                                            <fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="17"/>
                                            <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Task title" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R8k-VZ-rhw">
                                            <rect key="frame" x="28" y="41" width="331" height="23.5"/>
                                            <fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="17"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HgI-hl-IWE">
                                            <rect key="frame" x="10" y="0.0" width="5" height="128"/>
                                            <color key="backgroundColor" red="0.33333333333333331" green="0.33333333333333331" blue="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <constraints>
                                                <constraint firstAttribute="width" constant="5" id="AkL-7W-BoN"/>
                                                <constraint firstAttribute="width" constant="5" id="GW0-Pk-1KP"/>
                                            </constraints>
                                            <variation key="default">
                                                <mask key="constraints">
                                                    <exclude reference="AkL-7W-BoN"/>
                                                </mask>
                                            </variation>
                                        </view>
                                        <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hkz-za-4XF">
                                            <rect key="frame" x="6" y="20" width="14" height="14"/>
                                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                            <subviews>
                                                <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8Ze-KE-FbQ">
                                                    <rect key="frame" x="2" y="2" width="10" height="10"/>
                                                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                                    <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                </view>
                                            </subviews>
                                            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        </view>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Time" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f6t-Rd-90h">
                                            <rect key="frame" x="321" y="20" width="38" height="21"/>
                                            <constraints>
                                                <constraint firstAttribute="height" constant="21" id="brM-iV-eYx"/>
                                            </constraints>
                                            <fontDescription key="fontDescription" name="Avenir-BookOblique" family="Avenir" pointSize="17"/>
                                            <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" ambiguous="YES" text="Task description" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MiG-DV-mZh">
                                            <rect key="frame" x="28" y="72.5" width="331" height="23.5"/>
                                            <fontDescription key="fontDescription" name="Avenir-Book" family="Avenir" pointSize="17"/>
                                            <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                    </subviews>
                                    <constraints>
                                        <constraint firstItem="4pY-nC-xVt" firstAttribute="leading" secondItem="2tP-Kw-4Mv" secondAttribute="leadingMargin" constant="12" id="2Po-v0-Fh6"/>
                                        <constraint firstItem="R8k-VZ-rhw" firstAttribute="trailing" secondItem="2tP-Kw-4Mv" secondAttribute="trailingMargin" id="2vk-6e-spm"/>
                                        <constraint firstItem="MiG-DV-mZh" firstAttribute="top" secondItem="R8k-VZ-rhw" secondAttribute="bottom" constant="8" id="5Qw-FZ-tcX"/>
                                        <constraint firstItem="MiG-DV-mZh" firstAttribute="leading" secondItem="2tP-Kw-4Mv" secondAttribute="leadingMargin" constant="12" id="DqQ-o3-VO3"/>
                                        <constraint firstItem="HgI-hl-IWE" firstAttribute="top" secondItem="2tP-Kw-4Mv" secondAttribute="topMargin" id="FOX-Lf-PgO"/>
                                        <constraint firstItem="4pY-nC-xVt" firstAttribute="top" secondItem="2tP-Kw-4Mv" secondAttribute="topMargin" constant="9" id="FwX-mA-tff"/>
                                        <constraint firstItem="MiG-DV-mZh" firstAttribute="trailing" secondItem="2tP-Kw-4Mv" secondAttribute="trailingMargin" id="M6Q-R1-LUZ"/>
                                        <constraint firstAttribute="bottom" secondItem="HgI-hl-IWE" secondAttribute="bottom" id="RP0-zG-ueq"/>
                                        <constraint firstItem="R8k-VZ-rhw" firstAttribute="top" secondItem="4pY-nC-xVt" secondAttribute="bottom" id="Tt7-ah-yBT"/>
                                        <constraint firstItem="HgI-hl-IWE" firstAttribute="leading" secondItem="2tP-Kw-4Mv" secondAttribute="leadingMargin" constant="2" id="VY5-pN-ARW"/>
                                        <constraint firstItem="R8k-VZ-rhw" firstAttribute="leading" secondItem="2tP-Kw-4Mv" secondAttribute="leadingMargin" constant="12" id="Xom-cB-hNq"/>
                                        <constraint firstAttribute="bottom" secondItem="MiG-DV-mZh" secondAttribute="bottom" constant="30" id="Zjo-lo-F0L"/>
                                        <constraint firstItem="HgI-hl-IWE" firstAttribute="leading" secondItem="2tP-Kw-4Mv" secondAttribute="leading" constant="10" id="cOC-Id-gSF"/>
                                        <constraint firstItem="HgI-hl-IWE" firstAttribute="top" secondItem="2tP-Kw-4Mv" secondAttribute="top" id="nQb-uV-h9O"/>
                                        <constraint firstAttribute="bottomMargin" secondItem="HgI-hl-IWE" secondAttribute="bottom" id="sDg-UD-tKC"/>
                                        <constraint firstAttribute="trailingMargin" secondItem="4pY-nC-xVt" secondAttribute="trailing" constant="107" id="vyD-r3-pJr"/>
                                        <constraint firstItem="f6t-Rd-90h" firstAttribute="top" secondItem="2tP-Kw-4Mv" secondAttribute="topMargin" constant="9" id="xjP-Qv-Gya"/>
                                        <constraint firstItem="f6t-Rd-90h" firstAttribute="trailing" secondItem="2tP-Kw-4Mv" secondAttribute="trailingMargin" id="yJC-yy-baZ"/>
                                    </constraints>
                                    <variation key="default">
                                        <mask key="constraints">
                                            <exclude reference="FOX-Lf-PgO"/>
                                            <exclude reference="VY5-pN-ARW"/>
                                            <exclude reference="sDg-UD-tKC"/>
                                        </mask>
                                    </variation>
                                </tableViewCellContentView>
                                <connections>
                                    <outlet property="circleDark" destination="8Ze-KE-FbQ" id="T4b-76-EFK"/>
                                    <outlet property="circleWhite" destination="hkz-za-4XF" id="YnE-57-aLr"/>
                                    <outlet property="dateLabel" destination="f6t-Rd-90h" id="Z06-h5-ygM"/>
                                    <outlet property="notesLabel" destination="MiG-DV-mZh" id="BhJ-rl-2WR"/>
                                    <outlet property="taskNrLabel" destination="4pY-nC-xVt" id="LQm-0a-5LM"/>
                                    <outlet property="titleLabel" destination="R8k-VZ-rhw" id="T8J-MO-ndH"/>
                                </connections>
                            </tableViewCell>
                            <tableViewCell userInteractionEnabled="NO" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="NonTaskCell" id="4X7-kF-Wim" customClass="NonTaskCell" customModule="Jirassic_iOS" customModuleProvider="target">
                                <rect key="frame" x="0.0" y="150" width="375" height="44"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4X7-kF-Wim" id="yb0-IS-u2w">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T7x-ia-d8x">
                                            <rect key="frame" x="10" y="0.0" width="5" height="44"/>
                                            <color key="backgroundColor" red="0.33333333333333331" green="0.33333333333333331" blue="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <constraints>
                                                <constraint firstAttribute="width" constant="5" id="42U-gd-MEa"/>
                                                <constraint firstAttribute="width" constant="5" id="mGd-T1-rUr"/>
                                            </constraints>
                                            <variation key="default">
                                                <mask key="constraints">
                                                    <exclude reference="mGd-T1-rUr"/>
                                                </mask>
                                            </variation>
                                        </view>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rL1-iN-yxg">
                                            <rect key="frame" x="28" y="6" width="339" height="32"/>
                                            <fontDescription key="fontDescription" name="Avenir-Roman" family="Avenir" pointSize="17"/>
                                            <color key="textColor" white="0.59517299107142863" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                    </subviews>
                                    <constraints>
                                        <constraint firstItem="rL1-iN-yxg" firstAttribute="leading" secondItem="yb0-IS-u2w" secondAttribute="leading" constant="28" id="Keh-j8-meh"/>
                                        <constraint firstAttribute="bottom" secondItem="T7x-ia-d8x" secondAttribute="bottom" id="WsO-XL-2E1"/>
                                        <constraint firstItem="T7x-ia-d8x" firstAttribute="top" secondItem="yb0-IS-u2w" secondAttribute="top" id="Y1Y-zs-jGZ"/>
                                        <constraint firstItem="T7x-ia-d8x" firstAttribute="leading" secondItem="yb0-IS-u2w" secondAttribute="leading" constant="10" id="kVQ-9N-NO2"/>
                                        <constraint firstAttribute="trailing" secondItem="rL1-iN-yxg" secondAttribute="trailing" constant="8" id="l4Z-xx-FR5"/>
                                        <constraint firstItem="rL1-iN-yxg" firstAttribute="top" secondItem="yb0-IS-u2w" secondAttribute="top" constant="6" id="rCo-Rj-Cu3"/>
                                        <constraint firstAttribute="bottom" secondItem="rL1-iN-yxg" secondAttribute="bottom" constant="6" id="xkJ-cE-CcN"/>
                                    </constraints>
                                </tableViewCellContentView>
                                <connections>
                                    <outlet property="notesLabel" destination="rL1-iN-yxg" id="Cg4-DS-U4t"/>
                                </connections>
                            </tableViewCell>
                        </prototypes>
                        <connections>
                            <outlet property="dataSource" destination="Ne8-5p-NMX" id="BHb-07-2Ud"/>
                            <outlet property="delegate" destination="Ne8-5p-NMX" id="UbZ-cF-jpY"/>
                        </connections>
                    </tableView>
                </tableViewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="hpt-xA-0zd" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="1364" y="60.719640179910051"/>
        </scene>
    </scenes>
</document>


================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
	<string>Jirassic</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1.0</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
</dict>
</plist>


================================================
FILE: Delivery/iOS/Jirassic Scrum.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.developer.icloud-container-identifiers</key>
	<array>
		<string>iCloud.com.jirassic.macos</string>
	</array>
	<key>com.apple.developer.icloud-services</key>
	<array>
		<string>CloudKit</string>
	</array>
	<key>com.apple.developer.ubiquity-kvstore-identifier</key>
	<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
</dict>
</plist>


================================================
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<LocalPreferences>()
    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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Application-->
        <scene sceneID="JPo-4y-FX3">
            <objects>
                <application id="hnw-xV-0zn" sceneMemberID="viewController">
                    <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
                        <items>
                            <menuItem title="Jirassic" id="1Xt-HY-uBw">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Jirassic" systemMenu="apple" id="uQy-DD-JDr">
                                    <items>
                                        <menuItem title="About Jira Logger" id="5kV-Vb-QxS">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
                                        <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
                                        <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
                                        <menuItem title="Services" id="NMo-om-nkz">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
                                        <menuItem title="Hide Jira Logger" keyEquivalent="h" id="Olw-nP-bQN">
                                            <connections>
                                                <action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                            <connections>
                                                <action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Show All" id="Kd2-mp-pUS">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
                                        <menuItem title="Quit Jira Logger" keyEquivalent="q" id="4sb-4s-VLi">
                                            <connections>
                                                <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="File" id="dMs-cI-mzQ">
                                <modifierMask key="keyEquiv
Download .txt
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
Download .txt
SYMBOL INDEX (28 symbols across 14 files)

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMConstants.h
  type NSString (line 158) | typedef NSString * RLMNotification

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncCredentials.h
  type NSString (line 29) | typedef NSString *RLMIdentityProvider

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncManager.h
  type RLMSyncLogLevelOff (line 26) | typedef NS_ENUM(NSUInteger, RLMSyncLogLevel) {

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncSession.h
  type RLMSyncSessionStateActive (line 26) | typedef NS_ENUM(NSUInteger, RLMSyncSessionState) {
  type RLMSyncProgressDirectionUpload (line 41) | typedef NS_ENUM(NSUInteger, RLMSyncProgressDirection) {

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncUser.h
  type RLMSyncUserStateLoggedOut (line 26) | typedef NS_ENUM(NSUInteger, RLMSyncUserState) {

FILE: External/Realm/Realm.framework/Versions/A/Headers/RLMSyncUtil.h
  type NSString (line 22) | typedef NSString* RLMServerToken;

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMAccessor.h
  type NSUInteger (line 25) | typedef NSUInteger RLMCreationOptions;

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMMigration_Private.h
  function namespace (line 23) | namespace realm {

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObjectSchema_Private.h
  function NS_ASSUME_NONNULL_BEGIN (line 23) | NS_ASSUME_NONNULL_BEGIN

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObjectStore.h
  function namespace (line 90) | namespace realm {

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMObject_Private.h
  function NS_ASSUME_NONNULL_BEGIN (line 21) | NS_ASSUME_NONNULL_BEGIN

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMProperty_Private.h
  function interface (line 32) | interface RLMProperty () {

FILE: External/Realm/Realm.framework/Versions/A/PrivateHeaders/RLMSyncUtil_Private.h
  type NSString (line 30) | typedef NSString* RLMServerPath;

FILE: External/Realm/RealmSwift.framework/Versions/A/Headers/RealmSwift-Swift.h
  type uint_least16_t (line 19) | typedef uint_least16_t char16_t;
  type uint_least32_t (line 20) | typedef uint_least32_t char32_t;
  type swift_float2 (line 22) | typedef float swift_float2  __attribute__((__ext_vector_type__(2)));
  type swift_float3 (line 23) | typedef float swift_float3  __attribute__((__ext_vector_type__(3)));
  type swift_float4 (line 24) | typedef float swift_float4  __attribute__((__ext_vector_type__(4)));
  type swift_double2 (line 25) | typedef double swift_double2  __attribute__((__ext_vector_type__(2)));
  type swift_double3 (line 26) | typedef double swift_double3  __attribute__((__ext_vector_type__(3)));
  type swift_double4 (line 27) | typedef double swift_double4  __attribute__((__ext_vector_type__(4)));
  type swift_int2 (line 28) | typedef int swift_int2  __attribute__((__ext_vector_type__(2)));
  type swift_int3 (line 29) | typedef int swift_int3  __attribute__((__ext_vector_type__(3)));
  type swift_int4 (line 30) | typedef int swift_int4  __attribute__((__ext_vector_type__(4)));
  type swift_uint2 (line 31) | typedef unsigned int swift_uint2  __attribute__((__ext_vector_type__(2)));
  type swift_uint3 (line 32) | typedef unsigned int swift_uint3  __attribute__((__ext_vector_type__(3)));
  type swift_uint4 (line 33) | typedef unsigned int swift_uint4  __attribute__((__ext_vector_type__(4)));
Condensed preview — 343 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,853K chars).
[
  {
    "path": ".gitignore",
    "chars": 100,
    "preview": "*.xcuserstate\n\n.DS_Store\n\n*.xcworkspacedata\n\n*.xccheckout\n\nxcuserdata\n\nweb\n\nfastlane\n\ntravis_signing"
  },
  {
    "path": ".gitmodules",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".travis.yml",
    "chars": 823,
    "preview": "language: swift\nos: osx\nosx_image: xcode10.1\nbranches:\n  only:\n    - master\ncache: bundler\nxcode_project: Jirassic.xcode"
  },
  {
    "path": "App/Entities/Day.swift",
    "chars": 625,
    "preview": "//\n//  Day.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 30/12/15.\n//  Copyright © 2015 Cristian Baluta. All r"
  },
  {
    "path": "App/Entities/GitCommit.swift",
    "chars": 361,
    "preview": "//\n//  CommitResult.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2018.\n//  Copyright © 2018 Imagin soft"
  },
  {
    "path": "App/Entities/GitUser.swift",
    "chars": 265,
    "preview": "//\n//  GitUser.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 14/12/2018.\n//  Copyright © 2018 Imagin soft. All"
  },
  {
    "path": "App/Entities/Report.swift",
    "chars": 291,
    "preview": "//\n//  Report.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 06/11/2016.\n//  Copyright © 2016 Cristian Baluta. "
  },
  {
    "path": "App/Entities/Settings.swift",
    "chars": 911,
    "preview": "//\n//  Settings.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/09/16.\n//  Copyright © 2016 Cristian Baluta. "
  },
  {
    "path": "App/Entities/Task.swift",
    "chars": 2960,
    "preview": "//\n//  Task.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 21/11/15.\n//  Copyright © 2015 Cristian Baluta. All "
  },
  {
    "path": "App/Entities/User.swift",
    "chars": 288,
    "preview": "//\n//  User.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 21/11/15.\n//  Copyright © 2015 Cristian Baluta. All "
  },
  {
    "path": "App/Entities/Week.swift",
    "chars": 295,
    "preview": "//\n//  Week.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 30/12/15.\n//  Copyright © 2015 Cristian Baluta. All "
  },
  {
    "path": "App/Extensions/Conversions.swift",
    "chars": 950,
    "preview": "//\n//  Conversions.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 06/11/2016.\n//  Copyright © 2016 Cristian Bal"
  },
  {
    "path": "App/Extensions/DateExtension.swift",
    "chars": 9861,
    "preview": "//\n//  DateExtension.swift\n//  Spoto\n//\n//  Created by Baluta Cristian on 05/12/14.\n//  Copyright (c) 2014 Baluta Cristi"
  },
  {
    "path": "App/Extensions/DateExtensionTests.swift",
    "chars": 5181,
    "preview": "//\n//  DateTests.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 01/05/15.\n//  Copyright (c) 2015 Cristian Balut"
  },
  {
    "path": "App/Extensions/StringArray.swift",
    "chars": 419,
    "preview": "//\n//  StringArray.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 14/05/2017.\n//  Copyright © 2017 Imagin soft."
  },
  {
    "path": "App/Extensions/StringIdGenerator.swift",
    "chars": 643,
    "preview": "//\n//  StringIdGenerator.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 03/05/16.\n//  Copyright © 2016 Cristian"
  },
  {
    "path": "App/Extensions/TableViewCell.swift",
    "chars": 1828,
    "preview": "//\n//  TableView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 25/03/2018.\n//  Copyright © 2018 Imagin soft. A"
  },
  {
    "path": "App/Extensions/ViewAutolayout.swift",
    "chars": 6719,
    "preview": "//\n//  NSViewAutolayout.swift\n//  Spoto\n//\n//  Created by Baluta Cristian on 15/07/15.\n//  Copyright (c) 2015 Baluta Cri"
  },
  {
    "path": "App/Extensions/ViewController.swift",
    "chars": 280,
    "preview": "//\n//  ViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 06/05/16.\n//  Copyright © 2016 Cristian Ba"
  },
  {
    "path": "App/Extensions/ViewControllerStoryboard.swift",
    "chars": 883,
    "preview": "//\n//  UIViewControllerStoryboard.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 02/05/16.\n//  Copyright © 2016"
  },
  {
    "path": "App/Extensions/ViewXib.swift",
    "chars": 1012,
    "preview": "//\n//  ViewXib.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/04/2018.\n//  Copyright © 2018 Imagin soft. All"
  },
  {
    "path": "App/Notifications/ComputerWakeUpInteractor.swift",
    "chars": 2911,
    "preview": "//\n//  ComputerWakeUpInteractor.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 27/12/15.\n//  Copyright © 2015 C"
  },
  {
    "path": "App/Notifications/ComputerWakeUpInteractorTests.swift",
    "chars": 2112,
    "preview": "//\n//  ComputerWakeUpInteractorTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 26/11/2016.\n//  Copyright ©"
  },
  {
    "path": "App/Parsing/ParseGitBranch.swift",
    "chars": 3257,
    "preview": "//\n//  ParseGitBranch.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 27/02/2018.\n//  Copyright © 2018 Imagin so"
  },
  {
    "path": "App/Parsing/ParseGitBranchTests.swift",
    "chars": 2515,
    "preview": "//\n//  ParseGitBranchTests.swift\n//  JirassicTests\n//\n//  Created by Cristian Baluta on 28/02/2018.\n//  Copyright © 2018"
  },
  {
    "path": "App/Reports/CreateDayReport.swift",
    "chars": 1462,
    "preview": "//\n//  CreateDayReport.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 13.06.2024.\n//  Copyright © 2024 Imagin s"
  },
  {
    "path": "App/Reports/CreateMonthReport.swift",
    "chars": 6614,
    "preview": "//\n//  CreateMonthReport.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 09/10/2018.\n//  Copyright © 2018 Imagin"
  },
  {
    "path": "App/Reports/CreateMonthReportTests.swift",
    "chars": 2600,
    "preview": "//\n//  CreateMonthReportTests.swift\n//  JirassicTests\n//\n//  Created by Cristian Baluta on 09/10/2018.\n//  Copyright © 2"
  },
  {
    "path": "App/Reports/CreateReport.swift",
    "chars": 9766,
    "preview": "//\n//  CreateReport.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 28/03/15.\n//  Copyright (c) 2015 Cristian Ba"
  },
  {
    "path": "App/Reports/CreateReportTests.swift",
    "chars": 5591,
    "preview": "//\n//  CreateReportTests.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 01/06/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "App/Statistics/StatisticsInteractor.swift",
    "chars": 637,
    "preview": "//\n//  StatisticsInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 28/05/2017.\n//  Copyright © 2017 Ima"
  },
  {
    "path": "App/Tasks/CloseDay.swift",
    "chars": 1238,
    "preview": "//\n//  CloseDay.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 05/11/2018.\n//  Copyright © 2018 Imagin soft. Al"
  },
  {
    "path": "App/Tasks/MergeTasksInteractor.swift",
    "chars": 2188,
    "preview": "//\n//  MergeTasksInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 20/02/2018.\n//  Copyright © 2018 Ima"
  },
  {
    "path": "App/Tasks/MergeTasksInteractorTests.swift",
    "chars": 3278,
    "preview": "//\n//  MergeTasksInteractorTests.swift\n//  JirassicTests\n//\n//  Created by Cristian Baluta on 20/02/2018.\n//  Copyright "
  },
  {
    "path": "App/Tasks/ReadDaysInteractor.swift",
    "chars": 5767,
    "preview": "//\n//  ReadDaysInteractor.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 21/11/15.\n//  Copyright © 2015 Cristia"
  },
  {
    "path": "App/Tasks/ReadDaysInteractorTests.swift",
    "chars": 1181,
    "preview": "//\n//  ReadDaysInteractorTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 23/05/2017.\n//  Copyright © 2017 "
  },
  {
    "path": "App/Tasks/ReadTasksInteractor.swift",
    "chars": 874,
    "preview": "//\n//  ReadTasks.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 28/03/15.\n//  Copyright (c) 2015 Cristian Balut"
  },
  {
    "path": "App/Tasks/RemoveDuplicate.swift",
    "chars": 1155,
    "preview": "//\n//  RemoveDuplicate.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 26/12/2018.\n//  Copyright © 2018 Imagin s"
  },
  {
    "path": "App/Tasks/TaskFinder.swift",
    "chars": 463,
    "preview": "//\n//  TaskTypeFinder.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 09/11/15.\n//  Copyright © 2015 Cristian Ba"
  },
  {
    "path": "App/Tasks/TaskFinderTests.swift",
    "chars": 1299,
    "preview": "//\n//  TaskFinderTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 11/06/16.\n//  Copyright © 2016 Cristian B"
  },
  {
    "path": "App/Tasks/TaskInteractor.swift",
    "chars": 2187,
    "preview": "//\n//  TaskInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 19/10/15.\n//  Copyright © 2017 Cristian Ba"
  },
  {
    "path": "App/Tasks/TaskInteractorTests.swift",
    "chars": 1296,
    "preview": "//\n//  TaskInteractorTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 07/05/16.\n//  Copyright © 2016 Cristi"
  },
  {
    "path": "App/Tasks/TaskTypeEstimator.swift",
    "chars": 1589,
    "preview": "//\n//  TaskTypeEstimator.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 04/05/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "App/Tasks/TaskTypeEstimatorTests.swift",
    "chars": 3183,
    "preview": "//\n//  TaskTypeEstimatorTests.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 11/06/15.\n//  Copyright (c) 2015 C"
  },
  {
    "path": "App/Tasks/TaskTypeSelection.swift",
    "chars": 664,
    "preview": "//\n//  TaskTypeSelection.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 20/08/16.\n//  Copyright © 2016 Cristian"
  },
  {
    "path": "App/Time/PredictiveTimeTyping.swift",
    "chars": 2940,
    "preview": "//\n//  PredictiveTimeTyping.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 06/05/15.\n//  Copyright (c) 2015 Cri"
  },
  {
    "path": "App/Time/PredictiveTimeTypingTests.swift",
    "chars": 3040,
    "preview": "//\n//  PredictiveTimeTypingTests.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 28/09/15.\n//  Copyright © 2015 "
  },
  {
    "path": "App/Time/TimeInteractor.swift",
    "chars": 685,
    "preview": "//\n//  TimeInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 05/02/2018.\n//  Copyright © 2018 Imagin so"
  },
  {
    "path": "App/User/RegisterUserInteractor.swift",
    "chars": 753,
    "preview": "//\n//  RegisterUserInteractor.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 14/06/15.\n//  Copyright (c) 2015 C"
  },
  {
    "path": "App/User/UserInteractor.swift",
    "chars": 1458,
    "preview": "//\n//  UserInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 03/05/16.\n//  Copyright © 2016 Cristian Ba"
  },
  {
    "path": "App/User/UserInteractorTests.swift",
    "chars": 918,
    "preview": "//\n//  UserInteractorTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 03/05/16.\n//  Copyright © 2016 Cristi"
  },
  {
    "path": "Delivery/iOS/AppDelegate.swift",
    "chars": 2603,
    "preview": "//\n//  AppDelegate.swift\n//  Jirassic-Scrum\n//\n//  Created by Baluta Cristian on 13/05/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "Delivery/iOS/Base.lproj/LaunchScreen.xib",
    "chars": 3648,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVe"
  },
  {
    "path": "Delivery/iOS/Base.lproj/Main.storyboard",
    "chars": 28830,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Delivery/iOS/DaysViewController.swift",
    "chars": 2068,
    "preview": "//\n//  MasterViewController.swift\n//  Scrum\n//\n//  Created by Baluta Cristian on 05/05/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "Delivery/iOS/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1022,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"logo40.png\",\n      \"scale\" "
  },
  {
    "path": "Delivery/iOS/Images.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Delivery/iOS/Images.xcassets/SplashIcon.imageset/Contents.json",
    "chars": 338,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n     "
  },
  {
    "path": "Delivery/iOS/Info.plist",
    "chars": 1102,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Delivery/iOS/Jirassic Scrum.entitlements",
    "chars": 531,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Delivery/iOS/LoginViewController.swift",
    "chars": 710,
    "preview": "//\n//  LoginViewController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 25/05/15.\n//  Copyright (c) 2015 Cris"
  },
  {
    "path": "Delivery/iOS/NonTaskCell.swift",
    "chars": 521,
    "preview": "//\n//  NonTaskCell.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 15/05/15.\n//  Copyright (c) 2015 Cristian Bal"
  },
  {
    "path": "Delivery/iOS/TaskCell.swift",
    "chars": 670,
    "preview": "//\n//  TaskTableViewCell.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 14/05/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "Delivery/iOS/TasksViewController.swift",
    "chars": 3760,
    "preview": "//\n//  DetailViewController.swift\n//  Scrum\n//\n//  Created by Baluta Cristian on 05/05/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "Delivery/macOS/Animations/Animatable.swift",
    "chars": 284,
    "preview": "//\n//  Animatable.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 11/12/2016.\n//  Copyright © 2016 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/Animations/FlipAnimation.swift",
    "chars": 2654,
    "preview": "//\n//  Flip.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 24/05/15.\n//  Copyright (c) 2015 Cristian Baluta. Al"
  },
  {
    "path": "Delivery/macOS/App/AppDelegate.swift",
    "chars": 9863,
    "preview": "//\n//  AppDelegate.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 24/03/15.\n//  Copyright (c) 2015 Cristian Bal"
  },
  {
    "path": "Delivery/macOS/App/AppLauncher.swift",
    "chars": 1018,
    "preview": "//\n//  AppLauncher.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 24/12/2016.\n//  Copyright © 2016 Imagin soft."
  },
  {
    "path": "Delivery/macOS/App/AppTheme.swift",
    "chars": 1568,
    "preview": "//\n//  AppTheme.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/05/2017.\n//  Copyright © 2017 Imagin soft. Al"
  },
  {
    "path": "Delivery/macOS/App/AppViewController.swift",
    "chars": 317,
    "preview": "//\n//  AppViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 30/11/2016.\n//  Copyright © 2016 Imagin"
  },
  {
    "path": "Delivery/macOS/App/AppWireframe.swift",
    "chars": 9701,
    "preview": "//\n//  AppWireframe.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 06/11/15.\n//  Copyright © 2015 Cristian Balu"
  },
  {
    "path": "Delivery/macOS/App/LocalPreferences.swift",
    "chars": 2855,
    "preview": "//\n//  LocalPreferences.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 23/04/2018.\n//  Copyright © 2018 Imagin "
  },
  {
    "path": "Delivery/macOS/App/Versioning.swift",
    "chars": 2719,
    "preview": "//\n//  Versioning.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 11/02/2017.\n//  Copyright © 2017 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/Base.lproj/Main.storyboard",
    "chars": 56461,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Bridging-Header.h",
    "chars": 249,
    "preview": "//\n//  BridgingHeader.h\n//  Jirassic\n//\n//  Created by Cristian Baluta on 28/03/2017.\n//  Copyright © 2017 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/BuildScript.sh",
    "chars": 475,
    "preview": "#!/bin/sh\n\n#if [ ${CONFIGURATION} == \"Release\" ]; then\n\n    COMMIT_COUNT=$(git rev-list HEAD --count);\n    BUNDLE_VERSIO"
  },
  {
    "path": "Delivery/macOS/Components/Components.storyboard",
    "chars": 37913,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Components/EditableTimeBox.swift",
    "chars": 2477,
    "preview": "//\n//  EditableTimeBox.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 28/10/2018.\n//  Copyright © 2018 Imagin s"
  },
  {
    "path": "Delivery/macOS/Components/GitUsersViewController.swift",
    "chars": 2499,
    "preview": "//\n//  GitUsersViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 16/12/2018.\n//  Copyright © 2018 I"
  },
  {
    "path": "Delivery/macOS/Components/NewTaskViewController.swift",
    "chars": 6355,
    "preview": "//\n//  NewTaskViewController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 06/05/15.\n//  Copyright (c) 2015 Cr"
  },
  {
    "path": "Delivery/macOS/Components/TimeBox.swift",
    "chars": 3216,
    "preview": "//\n//  TimeBox.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 19/03/2018.\n//  Copyright © 2018 Imagin soft. All"
  },
  {
    "path": "Delivery/macOS/Components/TimeBoxViewController.swift",
    "chars": 3144,
    "preview": "//\n//  TimeBoxViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 27/10/2018.\n//  Copyright © 2018 Im"
  },
  {
    "path": "Delivery/macOS/External/AppleScript.swift",
    "chars": 10357,
    "preview": "//\n//  AppleScript.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 20/11/2016.\n//  Copyright © 2016 Imagin soft."
  },
  {
    "path": "Delivery/macOS/External/AppleScriptProtocol.swift",
    "chars": 856,
    "preview": "//\n//  AppleScriptProtocol.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 24/03/2018.\n//  Copyright © 2018 Imag"
  },
  {
    "path": "Delivery/macOS/External/ExtensionsInstallerInteractor.swift",
    "chars": 5801,
    "preview": "//\n//  ExtensionsInstallerInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 14/05/2017.\n//  Copyright ©"
  },
  {
    "path": "Delivery/macOS/External/ExtensionsInteractor.swift",
    "chars": 2004,
    "preview": "//\n//  CMDToolsInstaller.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 04/09/16.\n//  Copyright © 2016 Cristian"
  },
  {
    "path": "Delivery/macOS/External/SandboxedAppleScript.swift",
    "chars": 1592,
    "preview": "//\n//  SandboxedAppleScriptt.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 20/11/2016.\n//  Copyright © 2016 Im"
  },
  {
    "path": "Delivery/macOS/IAP/IAPHelper.swift",
    "chars": 13006,
    "preview": "//\n//  IAPHelper.swift\n//  IAPDemo\n//\n//  Created by Jason Zheng on 8/24/16.\n//  Copyright © 2016 Jason Zheng. All right"
  },
  {
    "path": "Delivery/macOS/IAP/Store.swift",
    "chars": 4246,
    "preview": "//\n//  Created by Cristian Baluta on 27/06/2018.\n//  Copyright © 2018 Baluta Cristian. All rights reserved.\n//\n\nimport F"
  },
  {
    "path": "Delivery/macOS/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1340,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"icon_jirassic_16.png\",\n      \""
  },
  {
    "path": "Delivery/macOS/Images.xcassets/AppStoreIcon.appiconset/Contents.json",
    "chars": 1340,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"icon_jirassic_16.png\",\n      \""
  },
  {
    "path": "Delivery/macOS/Images.xcassets/ButClose.imageset/Contents.json",
    "chars": 268,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"ButClose.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n     "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/ButDisabled.imageset/Contents.json",
    "chars": 274,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"ButDisabled.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n  "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/ButMinimize.imageset/Contents.json",
    "chars": 274,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"ButMinimize.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n  "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/CalendarIcon.imageset/Contents.json",
    "chars": 269,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Calendar-2.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n   "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Delivery/macOS/Images.xcassets/GitIcon.imageset/Contents.json",
    "chars": 304,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"GitIcon.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n"
  },
  {
    "path": "Delivery/macOS/Images.xcassets/MenuBarIcon-Normal.imageset/Contents.json",
    "chars": 357,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"MenuBarIcon-Normal.png\",\n      \"scale\" : \"1x\"\n    },\n "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/MenuBarIcon-Selected.imageset/Contents.json",
    "chars": 292,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"MenuBarIcon-Selected.png\",\n      \"scale\" : \"1x\"\n    },"
  },
  {
    "path": "Delivery/macOS/Images.xcassets/Plus.imageset/Contents.json",
    "chars": 232,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"AddMailboxTemplate-2.pdf\"\n    }\n  ],\n  \"info\" : {\n    "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/WarningButton.imageset/Contents.json",
    "chars": 229,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" "
  },
  {
    "path": "Delivery/macOS/Images.xcassets/WarningIcon.imageset/Contents.json",
    "chars": 229,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" "
  },
  {
    "path": "Delivery/macOS/Info.plist",
    "chars": 1504,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Delivery/macOS/Jirassic.entitlements",
    "chars": 837,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Delivery/macOS/Menu/MenuBarController.swift",
    "chars": 1427,
    "preview": "//\n//  MenuBarController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 26/03/15.\n//  Copyright (c) 2015 Cristi"
  },
  {
    "path": "Delivery/macOS/Menu/MenuBarIconView.swift",
    "chars": 1774,
    "preview": "//\n//  IconView.swift\n//  SwiftStatusBarApplication\n//\n//  Created by Tommy Leung on 6/7/14.\n//  Copyright (c) 2014 Tomm"
  },
  {
    "path": "Delivery/macOS/Modules/CalendarEvents/ModuleCalendar.swift",
    "chars": 3335,
    "preview": "//\n//  ModuleCalendar.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 04/07/2018.\n//  Copyright © 2018 Imagin so"
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/GitBranchParser.swift",
    "chars": 873,
    "preview": "//\n//  GitBranchParser.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 19/02/2018.\n//  Copyright © 2018 Imagin s"
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/GitBranchParserTests.swift",
    "chars": 924,
    "preview": "//\n//  GitBranchParserTests.swift\n//  JirassicTests\n//\n//  Created by Cristian Baluta on 20/02/2018.\n//  Copyright © 201"
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/GitCommitsParser.swift",
    "chars": 1496,
    "preview": "//\n//  GitCommitsParser.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2018.\n//  Copyright © 2018 Imagin "
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/GitCommitsParserTests.swift",
    "chars": 1010,
    "preview": "//\n//  GitCommitsParserTests.swift\n//  JirassicTests\n//\n//  Created by Cristian Baluta on 17/02/2018.\n//  Copyright © 20"
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/GitUserParser.swift",
    "chars": 955,
    "preview": "//\n//  GitUserParser.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 14/12/2018.\n//  Copyright © 2018 Imagin sof"
  },
  {
    "path": "Delivery/macOS/Modules/GitLogs/ModuleGitLogs.swift",
    "chars": 10092,
    "preview": "//\n//  ModuleGitLogs.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 16/02/2018.\n//  Copyright © 2018 Imagin sof"
  },
  {
    "path": "Delivery/macOS/Modules/Hookup/ModuleHookup.swift",
    "chars": 4050,
    "preview": "//\n//  Hookup.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 26/11/2017.\n//  Copyright © 2017 Imagin soft. All "
  },
  {
    "path": "Delivery/macOS/Modules/JiraTempo/ModuleJiraTempo.swift",
    "chars": 2626,
    "preview": "//\n//  JiraTempo.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 24/01/2018.\n//  Copyright © 2018 Imagin soft. A"
  },
  {
    "path": "Delivery/macOS/Notifications/BrowserNotification.swift",
    "chars": 7806,
    "preview": "//\n//  BrowserNotifications.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 09/05/2017.\n//  Copyright © 2017 Ima"
  },
  {
    "path": "Delivery/macOS/Notifications/InternalNotifications.swift",
    "chars": 456,
    "preview": "//\n//  InternalNotifications.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 13/12/15.\n//  Copyright © 2015 Cris"
  },
  {
    "path": "Delivery/macOS/Notifications/SleepNotifications.swift",
    "chars": 1676,
    "preview": "//\n//  SleepNotifications.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 10/05/15.\n//  Copyright (c) 2015 Crist"
  },
  {
    "path": "Delivery/macOS/Notifications/UserNotifications.swift",
    "chars": 457,
    "preview": "//\n//  LocalNotifications.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 12/12/15.\n//  Copyright © 2015 Cristia"
  },
  {
    "path": "Delivery/macOS/Screens/Account/AccountViewController.swift",
    "chars": 2804,
    "preview": "//\n//  AccountViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 25/11/2016.\n//  Copyright © 2016 Im"
  },
  {
    "path": "Delivery/macOS/Screens/Account/CloudKitLoginViewController.swift",
    "chars": 312,
    "preview": "//\n//  CloudKitLoginViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 13/06/16.\n//  Copyright © 201"
  },
  {
    "path": "Delivery/macOS/Screens/Account/Login.storyboard",
    "chars": 22686,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Screens/Account/LoginPresenter.swift",
    "chars": 1277,
    "preview": "//\n//  LoginPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/05/16.\n//  Copyright © 2016 Cristian Ba"
  },
  {
    "path": "Delivery/macOS/Screens/Account/LoginViewController.swift",
    "chars": 2208,
    "preview": "//\n//  LoginViewController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 07/05/15.\n//  Copyright (c) 2015 Cris"
  },
  {
    "path": "Delivery/macOS/Screens/Calendar/CalendarScrollView.swift",
    "chars": 4313,
    "preview": "//\n//  DatesScrollView.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 30/12/15.\n//  Copyright © 2015 Cristian B"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/Welcome.storyboard",
    "chars": 42988,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WelcomeViewController.swift",
    "chars": 3525,
    "preview": "//\n//  WelcomeViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 10/12/2016.\n//  Copyright © 2016 Im"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardAppleScriptView.swift",
    "chars": 4547,
    "preview": "//\n//  WizardAppleScriptView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/04/2018.\n//  Copyright © 2018 Im"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardAppleScriptView.xib",
    "chars": 7224,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardCalendarView.swift",
    "chars": 2973,
    "preview": "//\n//  WizardCalendarView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 15/07/2018.\n//  Copyright © 2018 Imagi"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardCalendarView.xib",
    "chars": 6126,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardGitView.swift",
    "chars": 3557,
    "preview": "//\n//  WizardGitView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/04/2018.\n//  Copyright © 2018 Imagin sof"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardGitView.xib",
    "chars": 6442,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardJiraView.swift",
    "chars": 3628,
    "preview": "//\n//  WizardJiraView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/04/2018.\n//  Copyright © 2018 Imagin so"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardJiraView.xib",
    "chars": 14875,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Onboarding/WizardViewController.swift",
    "chars": 7701,
    "preview": "//\n//  WizardViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 16/04/2018.\n//  Copyright © 2018 Ima"
  },
  {
    "path": "Delivery/macOS/Screens/Placeholder/Placeholder.storyboard",
    "chars": 6197,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Screens/Placeholder/PlaceholderViewController.swift",
    "chars": 1448,
    "preview": "//\n//  PlaceholderViewController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 06/05/15.\n//  Copyright (c) 201"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Browser/BrowserCell.swift",
    "chars": 4228,
    "preview": "//\n//  BrowserCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin soft."
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Browser/BrowserCell.xib",
    "chars": 20798,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Browser/BrowserPresenter.swift",
    "chars": 169,
    "preview": "//\n//  BrowserPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin "
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.swift",
    "chars": 3354,
    "preview": "//\n//  CalendarCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 05/07/2018.\n//  Copyright © 2018 Imagin soft"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Calendar/CalendarCell.xib",
    "chars": 12049,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Calendar/CalendarPresenter.swift",
    "chars": 2997,
    "preview": "//\n//  CalendarPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 05/07/2018.\n//  Copyright © 2018 Imagin"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Git/GitCell.swift",
    "chars": 4478,
    "preview": "//\n//  GitCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin soft. All"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Git/GitCell.xib",
    "chars": 17508,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Git/GitPresenter.swift",
    "chars": 6799,
    "preview": "//\n//  GitPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin soft"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/InputsScrollView.swift",
    "chars": 2074,
    "preview": "//\n//  InputsScrollView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin "
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/InputsScrollView.xib",
    "chars": 6436,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/InputsTableViewDataSource.swift",
    "chars": 3029,
    "preview": "//\n//  InputsTableViewDataSource.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 201"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/JirassicCmd/JirassicCell.swift",
    "chars": 1669,
    "preview": "//\n//  JirassicCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2018.\n//  Copyright © 2018 Imagin soft"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/JirassicCmd/JirassicCell.xib",
    "chars": 6330,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Jit/JitCell.swift",
    "chars": 2452,
    "preview": "//\n//  JitCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin soft. All"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Jit/JitCell.xib",
    "chars": 9506,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Shell/ShellCell.swift",
    "chars": 1794,
    "preview": "//\n//  ShellCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin soft. A"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Input/Shell/ShellCell.xib",
    "chars": 6411,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupCell.swift",
    "chars": 2175,
    "preview": "//\n//  CocoaHookupCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 09/04/2018.\n//  Copyright © 2018 Imagin s"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupCell.xib",
    "chars": 10078,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/CocoaHookup/CocoaHookupPresenter.swift",
    "chars": 4005,
    "preview": "//\n//  CocoaHookupPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 09/04/2018.\n//  Copyright © 2018 Ima"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/Hookup/HookupCell.swift",
    "chars": 2591,
    "preview": "//\n//  HookupCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 31/01/2018.\n//  Copyright © 2018 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/Hookup/HookupCell.xib",
    "chars": 12324,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/Hookup/HookupPresenter.swift",
    "chars": 4241,
    "preview": "//\n//  HookupPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin s"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoCell.swift",
    "chars": 4459,
    "preview": "//\n//  JiraTempoCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 31/01/2018.\n//  Copyright © 2018 Imagin sof"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoCell.xib",
    "chars": 21048,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/JiraTempo/JiraTempoPresenter.swift",
    "chars": 4994,
    "preview": "//\n//  JiraTempoPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagi"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/OutputTableViewDataSource.swift",
    "chars": 2159,
    "preview": "//\n//  OutputTableViewDataSource.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 201"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/OutputsScrollView.swift",
    "chars": 1314,
    "preview": "//\n//  OutputsTableView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 01/02/2018.\n//  Copyright © 2018 Imagin "
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Output/OutputsScrollView.xib",
    "chars": 6437,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Saveable.swift",
    "chars": 200,
    "preview": "//\n//  Saveable.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2018.\n//  Copyright © 2018 Imagin soft. Al"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Settings.storyboard",
    "chars": 10797,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/SettingsInteractor.swift",
    "chars": 1213,
    "preview": "//\n//  SettingsInteractor.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 06/09/16.\n//  Copyright © 2016 Cristia"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/SettingsPresenter.swift",
    "chars": 4570,
    "preview": "//\n//  SettingsPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 02/05/16.\n//  Copyright © 2016 Cristian"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/SettingsViewController.swift",
    "chars": 5936,
    "preview": "//\n//  SettingsViewController.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 06/05/15.\n//  Copyright (c) 2015 C"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Store/StoreView.swift",
    "chars": 3189,
    "preview": "//\n//  StoreView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/10/2018.\n//  Copyright © 2018 Imagin soft. A"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Store/StoreView.xib",
    "chars": 10315,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Tracking/TrackingView.swift",
    "chars": 3185,
    "preview": "//\n//  TrackingView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 02/02/2018.\n//  Copyright © 2018 Imagin soft"
  },
  {
    "path": "Delivery/macOS/Screens/Settings/Tracking/TrackingView.xib",
    "chars": 23815,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionPresenter.swift",
    "chars": 4566,
    "preview": "//\n//  TaskSuggestionPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 30/11/2016.\n//  Copyright © 2016 "
  },
  {
    "path": "Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionTests.swift",
    "chars": 2191,
    "preview": "//\n//  TaskSuggestionTests.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 10/12/2016.\n//  Copyright © 2016 Imag"
  },
  {
    "path": "Delivery/macOS/Screens/TaskSuggestion/TaskSuggestionViewController.swift",
    "chars": 1979,
    "preview": "//\n//  TaskSuggestionViewController.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 30/11/2016.\n//  Copyright © "
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/CellProtocol.swift",
    "chars": 773,
    "preview": "//\n//  CellProtocol.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 10/05/15.\n//  Copyright (c) 2015 Cristian Ba"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/HeaderView/TasksHeaderView.swift",
    "chars": 2381,
    "preview": "//\n//  FooterCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 30/01/2018.\n//  Copyright © 2018 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/HeaderView/TasksHeaderView.xib",
    "chars": 7745,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.swift",
    "chars": 7591,
    "preview": "//\n//  NonTaskCell.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 07/05/15.\n//  Copyright (c) 2015 Cristian Bal"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/NonTaskCell/NonTaskCell.xib",
    "chars": 9620,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.swift",
    "chars": 8028,
    "preview": "//\n//  TaskCell.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 28/03/15.\n//  Copyright (c) 2015 Cristian Baluta"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCell.xib",
    "chars": 13580,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellPresenter.swift",
    "chars": 1983,
    "preview": "//\n//  TaskCellPresenter.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 27/11/15.\n//  Copyright © 2015 Cristian"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/TaskCell/TaskCellTests.swift",
    "chars": 1032,
    "preview": "//\n//  TaskCellTests.swift\n//  Jirassic\n//\n//  Created by Baluta Cristian on 17/09/15.\n//  Copyright © 2015 Cristian Bal"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/AllTasks/TasksDataSource.swift",
    "chars": 3866,
    "preview": "//\n//  TasksDataSource.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2017.\n//  Copyright © 2017 Imagin s"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/DataSource.swift",
    "chars": 534,
    "preview": "//\n//  DataSource.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 18/02/2017.\n//  Copyright © 2017 Imagin soft. "
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.swift",
    "chars": 987,
    "preview": "//\n//  MonthReportsHeaderView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 25/10/2018.\n//  Copyright © 2018 I"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/MonthReports/MonthReportsHeaderView.xib",
    "chars": 9298,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.swift",
    "chars": 2312,
    "preview": "//\n//  ReportsHeaderView.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 19/02/2017.\n//  Copyright © 2017 Imagin"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/HeaderView/ReportsHeaderView.xib",
    "chars": 6970,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.swift",
    "chars": 4464,
    "preview": "//\n//  ReportCell.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 04/09/16.\n//  Copyright © 2016 Cristian Baluta"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCell.xib",
    "chars": 7237,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/ReportCell/ReportCellPresenter.swift",
    "chars": 1777,
    "preview": "//\n//  ReportCellPresenter.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 06/11/2016.\n//  Copyright © 2016 Cris"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Reports/ReportsDataSource.swift",
    "chars": 2101,
    "preview": "//\n//  ReportsDataSource.swift\n//  Jirassic\n//\n//  Created by Cristian Baluta on 17/02/2017.\n//  Copyright © 2017 Imagin"
  },
  {
    "path": "Delivery/macOS/Screens/Tasks/Tasks.storyboard",
    "chars": 39671,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  }
]

// ... and 143 more files (download for full content)

About this extraction

This page contains the full source code of the ralcr/Jirassic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 343 files (12.3 MB), approximately 425.6k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!