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
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
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.