Browse Source

Merge branch 'master' of https://github.com/n0shake/Clocker

pull/113/head
Abhishek Banthia 3 years ago
parent
commit
e17dc1d239
  1. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-1024.png
  2. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-128.png
  3. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-16.png
  4. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-256.png
  5. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-32.png
  6. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-512.png
  7. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon-64.png
  8. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon.png
  9. 0
      Archive/New Icons/Older Icons/New Icons/NewIcon.xcf
  10. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-1024.png
  11. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-16.png
  12. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-256.png
  13. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-32.png
  14. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-512.png
  15. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon-64.png
  16. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon.png
  17. 0
      Archive/New Icons/Older Icons/Old Icons/ClockerIcon.xcf
  18. 0
      Archive/New Icons/Older Icons/Old Icons/Icon.png
  19. 2
      Clocker/AppDelegate.swift
  20. 4
      Clocker/Clocker.xcodeproj/project.pbxproj
  21. 79
      Clocker/ClockerUnitTests/EventInfoTests.swift
  22. 67
      Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift
  23. 47
      Clocker/Events and Reminders/CalendarHandler.swift
  24. 2
      Clocker/Overall App/AppKit + Additions.swift
  25. 2
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  26. 1
      Clocker/Preferences/Calendar/CalendarViewController.swift
  27. 32
      Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift
  28. 74
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  29. 55
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift
  30. 7
      Clocker/fastlane/Appfile
  31. 73
      Clocker/fastlane/Fastfile
  32. BIN
      Clocker/fastlane/screenshots/Screenshot1 2.png
  33. BIN
      Clocker/fastlane/screenshots/Screenshot1.png
  34. BIN
      Clocker/fastlane/screenshots/Screenshot2.png
  35. BIN
      Clocker/fastlane/screenshots/Screenshot3 2.png
  36. BIN
      Clocker/fastlane/screenshots/Screenshot3.png
  37. 171
      Clocker/fastlane/test_output/report.html
  38. 8
      Clocker/fastlane/test_output/report.junit

0
Clocker/Icons/New Icons/NewIcon-1024.png → Archive/New Icons/Older Icons/New Icons/NewIcon-1024.png

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

0
Clocker/Icons/New Icons/NewIcon-128.png → Archive/New Icons/Older Icons/New Icons/NewIcon-128.png

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

0
Clocker/Icons/New Icons/NewIcon-16.png → Archive/New Icons/Older Icons/New Icons/NewIcon-16.png

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 633 B

0
Clocker/Icons/New Icons/NewIcon-256.png → Archive/New Icons/Older Icons/New Icons/NewIcon-256.png

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

0
Clocker/Icons/New Icons/NewIcon-32.png → Archive/New Icons/Older Icons/New Icons/NewIcon-32.png

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

0
Clocker/Icons/New Icons/NewIcon-512.png → Archive/New Icons/Older Icons/New Icons/NewIcon-512.png

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

0
Clocker/Icons/New Icons/NewIcon-64.png → Archive/New Icons/Older Icons/New Icons/NewIcon-64.png

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

0
Clocker/Icons/New Icons/NewIcon.png → Archive/New Icons/Older Icons/New Icons/NewIcon.png

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

0
Clocker/Icons/New Icons/NewIcon.xcf → Archive/New Icons/Older Icons/New Icons/NewIcon.xcf

0
Clocker/Icons/Old Icons/ClockerIcon-1024.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-1024.png

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 184 KiB

0
Clocker/Icons/Old Icons/ClockerIcon-16.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-16.png

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

0
Clocker/Icons/Old Icons/ClockerIcon-256.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-256.png

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

0
Clocker/Icons/Old Icons/ClockerIcon-32.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-32.png

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

0
Clocker/Icons/Old Icons/ClockerIcon-512.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-512.png

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

0
Clocker/Icons/Old Icons/ClockerIcon-64.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon-64.png

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

0
Clocker/Icons/Old Icons/ClockerIcon.png → Archive/New Icons/Older Icons/Old Icons/ClockerIcon.png

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

0
Clocker/Icons/Old Icons/ClockerIcon.xcf → Archive/New Icons/Older Icons/Old Icons/ClockerIcon.xcf

0
Clocker/Icons/Old Icons/Icon.png → Archive/New Icons/Older Icons/Old Icons/Icon.png

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

2
Clocker/AppDelegate.swift

@ -115,7 +115,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
checkIfAppIsAlreadyOpen() checkIfAppIsAlreadyOpen()
// Install the menubar item! // Install the menubar item!
statusBarHandler = StatusItemHandler() statusBarHandler = StatusItemHandler(with: DataStore.shared())
if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) { if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) {
FirebaseApp.configure() FirebaseApp.configure()

4
Clocker/Clocker.xcodeproj/project.pbxproj

@ -61,6 +61,7 @@
35B2FEC0259A186F005DA84D /* StartupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEBF259A186F005DA84D /* StartupKit */; }; 35B2FEC0259A186F005DA84D /* StartupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEBF259A186F005DA84D /* StartupKit */; };
35B2FEDD259A2291005DA84D /* CoreLoggerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEDC259A2291005DA84D /* CoreLoggerKit */; }; 35B2FEDD259A2291005DA84D /* CoreLoggerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEDC259A2291005DA84D /* CoreLoggerKit */; };
35B2FEF1259A2DB1005DA84D /* CoreModelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEF0259A2DB1005DA84D /* CoreModelKit */; }; 35B2FEF1259A2DB1005DA84D /* CoreModelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEF0259A2DB1005DA84D /* CoreModelKit */; };
35BD9A572807580800077443 /* EventInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BD9A562807580800077443 /* EventInfoTests.swift */; };
35C36EF122595F14002FA5C6 /* OnboardingPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */; }; 35C36EF122595F14002FA5C6 /* OnboardingPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */; };
35C36EF222595F14002FA5C6 /* OnboardingWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */; }; 35C36EF222595F14002FA5C6 /* OnboardingWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */; };
35C36EF322595F14002FA5C6 /* WelcomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEA22595F13002FA5C6 /* WelcomeView.xib */; }; 35C36EF322595F14002FA5C6 /* WelcomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEA22595F13002FA5C6 /* WelcomeView.xib */; };
@ -298,6 +299,7 @@
35B2FEB1259A1649005DA84D /* StartupKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = StartupKit; sourceTree = "<group>"; }; 35B2FEB1259A1649005DA84D /* StartupKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = StartupKit; sourceTree = "<group>"; };
35B2FED4259A2244005DA84D /* CoreLoggerKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreLoggerKit; sourceTree = "<group>"; }; 35B2FED4259A2244005DA84D /* CoreLoggerKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreLoggerKit; sourceTree = "<group>"; };
35B2FEE4259A2C25005DA84D /* CoreModelKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreModelKit; sourceTree = "<group>"; }; 35B2FEE4259A2C25005DA84D /* CoreModelKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreModelKit; sourceTree = "<group>"; };
35BD9A562807580800077443 /* EventInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventInfoTests.swift; sourceTree = "<group>"; };
35C11E2024873A550031F18C /* VersionUpdateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateHandler.swift; sourceTree = "<group>"; }; 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateHandler.swift; sourceTree = "<group>"; };
35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = "<group>"; }; 35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = "<group>"; };
35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingWelcomeViewController.swift; sourceTree = "<group>"; }; 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingWelcomeViewController.swift; sourceTree = "<group>"; };
@ -881,6 +883,7 @@
35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */, 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */,
35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */, 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */,
35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */, 35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */,
35BD9A562807580800077443 /* EventInfoTests.swift */,
); );
path = ClockerUnitTests; path = ClockerUnitTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1266,6 +1269,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
35BD9A572807580800077443 /* EventInfoTests.swift in Sources */,
35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */, 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */,
35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */, 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */,
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */, 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */,

79
Clocker/ClockerUnitTests/EventInfoTests.swift

@ -0,0 +1,79 @@
// Copyright © 2015 Abhishek Banthia
@testable import Clocker
import EventKit
import XCTest
class EventInfoTests: XCTestCase {
private let eventStore = EKEventStore()
func testMetadataForUpcomingEventHappeningInFiveMinutes() throws {
let futureChunk = TimeChunk(seconds: 10, minutes: 5, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(futureChunk)
let mockEventInfo = EventInfo(event: mockEvent,
isAllDay: false,
meetingURL: nil,
attendeStatus: .accepted)
XCTAssert(mockEventInfo.metadataForMeeting() == "in 5m",
"Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation")
}
func testMetadataForUpcomingEventHappeningInTenSeconds() throws {
let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(futureChunk)
let mockEventInfo = EventInfo(event: mockEvent,
isAllDay: false,
meetingURL: nil,
attendeStatus: .accepted)
XCTAssert(mockEventInfo.metadataForMeeting() == "in <1m",
"Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation")
}
func testMetadataForEventPastTwoMinutes() throws {
let pastChunk = TimeChunk(seconds: 10, minutes: 2, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().subtract(pastChunk)
let mockEventInfo = EventInfo(event: mockEvent,
isAllDay: false,
meetingURL: nil,
attendeStatus: .accepted)
XCTAssert(mockEventInfo.metadataForMeeting() == "started +2m.",
"Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation")
}
func testMetadataForEventPastTenMinutes() throws {
let pastChunk = TimeChunk(seconds: 10, minutes: 10, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().subtract(pastChunk)
let mockEventInfo = EventInfo(event: mockEvent,
isAllDay: false,
meetingURL: nil,
attendeStatus: .accepted)
XCTAssert(mockEventInfo.metadataForMeeting() == "Error",
"Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation")
}
func testMetadataForEventHappeningTomorrow() throws {
let pastChunk = TimeChunk(seconds: 10, minutes: 0, hours: 25, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(pastChunk)
let mockEventInfo = EventInfo(event: mockEvent,
isAllDay: false,
meetingURL: nil,
attendeStatus: .accepted)
XCTAssert(mockEventInfo.metadataForMeeting() == "in 25h",
"Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation")
}
}

67
Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift

@ -1,11 +1,14 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
import CoreModelKit import CoreModelKit
import EventKit
import XCTest import XCTest
@testable import Clocker @testable import Clocker
class StandardMenubarHandlerTests: XCTestCase { class StandardMenubarHandlerTests: XCTestCase {
private let eventStore = EKEventStore()
private let mumbai = ["customLabel": "Ghar", private let mumbai = ["customLabel": "Ghar",
"formattedAddress": "Mumbai", "formattedAddress": "Mumbai",
"place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0", "place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0",
@ -113,4 +116,68 @@ class StandardMenubarHandlerTests: XCTestCase {
let menubarHandler = MenubarTitleProvider(with: store) let menubarHandler = MenubarTitleProvider(with: store)
XCTAssertNotNil(menubarHandler.titleForMenubar()) XCTAssertNotNil(menubarHandler.titleForMenubar())
} }
func testFormattedUpcomingEvent() {
let store = makeMockStore()
let futureChunk = TimeChunk(seconds: 10, minutes: 10, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(futureChunk)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title in 10m",
"Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
}
func testUpcomingEventHappeningWithinOneMinute() {
let store = makeMockStore()
let futureChunk = TimeChunk(seconds: 10, minutes: 1, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(futureChunk)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title in 1m",
"Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
}
func testUpcomingEventHappeningWithinSeconds() {
let store = makeMockStore()
let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Mock Title"
mockEvent.startDate = Date().add(futureChunk)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title starts now.",
"Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
}
func testEmptyUpcomingEvent() {
let store = makeMockStore()
let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.startDate = Date().add(futureChunk)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssert(menubarHandler.format(event: mockEvent) == CLEmptyString,
"Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
}
func testLongUpcomingEvent() {
let store = makeMockStore()
let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
let mockEvent = EKEvent(eventStore: eventStore)
mockEvent.title = "Really long calendar event title that longer than the longest name"
mockEvent.startDate = Date().add(futureChunk)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssert(menubarHandler.format(event: mockEvent) == "Really long calendar event tit... starts now.",
"Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
}
} }

47
Clocker/Events and Reminders/CalendarHandler.swift

@ -116,40 +116,6 @@ extension EventCenter {
return (formattedTitle, menubarText) return (formattedTitle, menubarText)
} }
func format(event: EKEvent) -> String {
guard let truncateLength = DataStore.shared().retrieve(key: CLTruncateTextLength) as? NSNumber, let eventTitle = event.title else {
return CLEmptyString
}
let seconds = event.startDate.timeIntervalSinceNow
var menubarText: String = CLEmptyString
if eventTitle.count > truncateLength.intValue {
let truncateIndex = eventTitle.index(eventTitle.startIndex, offsetBy: truncateLength.intValue)
let truncatedTitle = String(eventTitle[..<truncateIndex])
menubarText.append(truncatedTitle)
menubarText.append("...")
} else {
menubarText.append(eventTitle)
}
let minutes = seconds / 60
if minutes > 2 {
let suffix = String(format: " in %0.f mins", minutes)
menubarText.append(suffix)
} else if minutes == 1 {
let suffix = String(format: " in %0.f min", minutes)
menubarText.append(suffix)
} else {
menubarText.append(" starts now.")
}
return menubarText
}
func nextOccuring(_: [EventInfo]) -> EventInfo? { func nextOccuring(_: [EventInfo]) -> EventInfo? {
if calendarAccessDenied() || calendarAccessNotDetermined() { if calendarAccessDenied() || calendarAccessNotDetermined() {
return nil return nil
@ -362,17 +328,11 @@ extension EventCenter {
private func generateEventInfo(for event: EKEvent, _ date: Date, _ nextDate: Date) -> EventInfo { private func generateEventInfo(for event: EKEvent, _ date: Date, _ nextDate: Date) -> EventInfo {
// Make a customized struct // Make a customized struct
let isStartDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.startDate) && (event.endDate.compare(date) == .orderedDescending)
let isEndDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.endDate) && (event.startDate.compare(date) == .orderedAscending)
let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame) let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame)
let isSingleDay = event.isAllDay && (event.startDate.compare(date) == .orderedSame && event.endDate.compare(nextDate) == .orderedSame)
let eventParticipationStatus = attendingStatusForUser(event) let eventParticipationStatus = attendingStatusForUser(event)
let meetingURL = retrieveMeetingURL(event) let meetingURL = retrieveMeetingURL(event)
let eventInfo = EventInfo(event: event, let eventInfo = EventInfo(event: event,
isStartDate: isStartDate,
isEndDate: isEndDate,
isAllDay: isAllDay, isAllDay: isAllDay,
isSingleDay: isSingleDay,
meetingURL: meetingURL, meetingURL: meetingURL,
attendeStatus: eventParticipationStatus) attendeStatus: eventParticipationStatus)
return eventInfo return eventInfo
@ -481,10 +441,7 @@ struct CalendarInfo {
struct EventInfo { struct EventInfo {
let event: EKEvent let event: EKEvent
let isStartDate: Bool
let isEndDate: Bool
let isAllDay: Bool let isAllDay: Bool
let isSingleDay: Bool
let meetingURL: URL? let meetingURL: URL?
let attendeStatus: EKParticipantStatus let attendeStatus: EKParticipantStatus
@ -492,13 +449,13 @@ struct EventInfo {
let timeIntervalSinceNowForMeeting = event.startDate.timeIntervalSinceNow let timeIntervalSinceNowForMeeting = event.startDate.timeIntervalSinceNow
if timeIntervalSinceNowForMeeting < 0, timeIntervalSinceNowForMeeting > -300 { if timeIntervalSinceNowForMeeting < 0, timeIntervalSinceNowForMeeting > -300 {
return "started +\(event.startDate.shortTimeAgoSinceNow)." return "started +\(event.startDate.shortTimeAgoSinceNow)."
} else if event.startDate.isToday { } else if event.startDate.isToday, timeIntervalSinceNowForMeeting > 0 {
let timeSince = Date().timeAgo(since: event.startDate).lowercased() let timeSince = Date().timeAgo(since: event.startDate).lowercased()
let withoutAn = timeSince.replacingOccurrences(of: "an", with: CLEmptyString) let withoutAn = timeSince.replacingOccurrences(of: "an", with: CLEmptyString)
let withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: CLEmptyString) let withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: CLEmptyString)
// If the user has not turned on seconds granularity for one of the timezones, // If the user has not turned on seconds granularity for one of the timezones,
// we return "in 12 seconds" which looks weird. // we return "in 12 seconds" which looks weird.
return withoutAgo.contains("seconds") ? "starts in <1m" : "in \(withoutAgo.lowercased())" return withoutAgo.contains("seconds") ? "in <1m" : "in \(withoutAgo.lowercased())".trimmingCharacters(in: .whitespaces)
} else if event.startDate.isTomorrow { } else if event.startDate.isTomorrow {
let hoursUntil = event.startDate.hoursUntil let hoursUntil = event.startDate.hoursUntil
return "in \(hoursUntil)h" return "in \(hoursUntil)h"

2
Clocker/Overall App/AppKit + Additions.swift

@ -21,7 +21,7 @@ extension NSTextField {
} }
extension NSFont { extension NSFont {
func size(_ string: String, _ width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize { func size(for string: String, width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize {
let size = CGSize(width: width, let size = CGSize(width: width,
height: Double.greatestFiniteMagnitude) height: Double.greatestFiniteMagnitude)

2
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

@ -210,7 +210,7 @@ class AppFeedbackWindowController: NSWindowController {
AppFeedbackConstants.CLClockerVersion: versionInfo, AppFeedbackConstants.CLClockerVersion: versionInfo,
AppFeedbackConstants.CLAppFeedbackDateProperty: todaysDate(), AppFeedbackConstants.CLAppFeedbackDateProperty: todaysDate(),
AppFeedbackConstants.CLAppFeedbackUserPreferences: generateUserPreferences(), AppFeedbackConstants.CLAppFeedbackUserPreferences: generateUserPreferences(),
AppFeedbackConstants.CLFeedbackEntryPoint: appFeedbackWindowDelegate?.appFeedbackWindoEntryPoint() ?? "Error" AppFeedbackConstants.CLFeedbackEntryPoint: appFeedbackWindowDelegate?.appFeedbackWindoEntryPoint() ?? "Error",
] ]
} }

1
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -5,7 +5,6 @@ import CoreLoggerKit
import EventKit import EventKit
class ClockerTextBackgroundView: NSView { class ClockerTextBackgroundView: NSView {
override func awakeFromNib() { override func awakeFromNib() {
wantsLayer = true wantsLayer = true
layer?.cornerRadius = 8.0 layer?.cornerRadius = 8.0

32
Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift

@ -60,11 +60,41 @@ class MenubarTitleProvider: NSObject {
continue continue
} }
return EventCenter.sharedCenter().format(event: event) return format(event: event)
} }
} }
} }
return nil return nil
} }
internal func format(event: EKEvent) -> String {
guard let truncateLength = store.retrieve(key: CLTruncateTextLength) as? NSNumber, let eventTitle = event.title, event.title.isEmpty == false else {
return CLEmptyString
}
let seconds = event.startDate.timeIntervalSinceNow
var menubarText: String = CLEmptyString
if eventTitle.count > truncateLength.intValue {
let truncateIndex = eventTitle.index(eventTitle.startIndex, offsetBy: truncateLength.intValue)
let truncatedTitle = String(eventTitle[..<truncateIndex])
menubarText.append(truncatedTitle)
menubarText.append("...")
} else {
menubarText.append(eventTitle)
}
let minutes = seconds / 60
if minutes >= 1 {
let suffix = String(format: " in %0.fm", minutes)
menubarText.append(suffix)
} else {
menubarText.append(" starts now.")
}
return menubarText
}
} }

74
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -4,33 +4,11 @@ import Cocoa
import CoreLoggerKit import CoreLoggerKit
import CoreModelKit import CoreModelKit
func bufferCalculatedWidth() -> Int { func compactWidth(for timezone: TimezoneData, with store: DataStore) -> Int {
var totalWidth = 55 var totalWidth = 55
let timeFormat = timezone.timezoneFormat(store.timezoneFormat())
if DataStore.shared().shouldShowDayInMenubar() { if store.shouldShowDayInMenubar() {
totalWidth += 12
}
if DataStore.shared().isBufferRequiredForTwelveHourFormats() {
totalWidth += 20
}
if DataStore.shared().shouldShowDateInMenubar() {
totalWidth += 20
}
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
totalWidth += 100
}
return totalWidth
}
func compactWidth(for timezone: TimezoneData) -> Int {
var totalWidth = 55
let timeFormat = timezone.timezoneFormat(DataStore.shared().timezoneFormat())
if DataStore.shared().shouldShowDayInMenubar() {
totalWidth += 12 totalWidth += 12
} }
@ -46,12 +24,12 @@ func compactWidth(for timezone: TimezoneData) -> Int {
totalWidth += 0 totalWidth += 0
} }
if timezone.shouldShowSeconds(DataStore.shared().timezoneFormat()) { if timezone.shouldShowSeconds(store.timezoneFormat()) {
// Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM // Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM
totalWidth += 15 totalWidth += 15
} }
if DataStore.shared().shouldShowDateInMenubar() { if store.shouldShowDateInMenubar() {
totalWidth += 20 totalWidth += 20
} }
@ -71,6 +49,7 @@ protocol StatusItemViewConforming {
class StatusContainerView: NSView { class StatusContainerView: NSView {
private var previousX: Int = 0 private var previousX: Int = 0
private let store: DataStore
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
@ -78,7 +57,13 @@ class StatusContainerView: NSView {
layer?.backgroundColor = NSColor.clear.cgColor layer?.backgroundColor = NSColor.clear.cgColor
} }
init(with timezones: [Data], showUpcomingEventView: Bool) { init(with timezones: [Data],
store: DataStore,
showUpcomingEventView: Bool,
bufferContainerWidth: Int)
{
self.store = store
func addSubviews() { func addSubviews() {
if showUpcomingEventView, if showUpcomingEventView,
let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())], let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())],
@ -110,16 +95,20 @@ class StatusContainerView: NSView {
var compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in var compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in
if let timezoneObject = TimezoneData.customObject(from: timezone) { if let timezoneObject = TimezoneData.customObject(from: timezone) {
let precalculatedWidth = Double(compactWidth(for: timezoneObject)) let precalculatedWidth = Double(compactWidth(for: timezoneObject, with: store))
let operationObject = TimezoneDataOperations(with: timezoneObject) let operationObject = TimezoneDataOperations(with: timezoneObject)
let calculatedSubtitleSize = compactModeTimeFont.size(operationObject.compactMenuSubtitle(), precalculatedWidth, attributes: timeBasedAttributes) let calculatedSubtitleSize = compactModeTimeFont.size(for: operationObject.compactMenuSubtitle(),
let calculatedTitleSize = compactModeTimeFont.size(operationObject.compactMenuTitle(), precalculatedWidth, attributes: timeBasedAttributes) width: precalculatedWidth,
let showSeconds = timezoneObject.shouldShowSeconds(DataStore.shared().timezoneFormat()) attributes: timeBasedAttributes)
let calculatedTitleSize = compactModeTimeFont.size(for: operationObject.compactMenuTitle(),
width: precalculatedWidth,
attributes: timeBasedAttributes)
let showSeconds = timezoneObject.shouldShowSeconds(store.timezoneFormat())
let secondsBuffer: CGFloat = showSeconds ? 7 : 0 let secondsBuffer: CGFloat = showSeconds ? 7 : 0
return result + max(calculatedTitleSize.width, calculatedSubtitleSize.width) + bufferWidth + secondsBuffer return result + max(calculatedTitleSize.width, calculatedSubtitleSize.width) + bufferWidth + secondsBuffer
} }
return result + CGFloat(bufferCalculatedWidth()) return result + CGFloat(bufferContainerWidth)
} }
if showUpcomingEventView { if showUpcomingEventView {
@ -127,7 +116,7 @@ class StatusContainerView: NSView {
} }
let calculatedWidth = min(compressedWidth, let calculatedWidth = min(compressedWidth,
CGFloat(timezones.count * bufferCalculatedWidth())) CGFloat(timezones.count * bufferContainerWidth))
return calculatedWidth return calculatedWidth
} }
@ -170,9 +159,12 @@ class StatusContainerView: NSView {
] ]
let operation = TimezoneDataOperations(with: timezone) let operation = TimezoneDataOperations(with: timezone)
let bestSize = compactModeTimeFont.size(operation.compactMenuSubtitle(), let bestSize = compactModeTimeFont.size(for: operation.compactMenuSubtitle(),
Double(compactWidth(for: timezone)), attributes: timeBasedAttributes) width: Double(compactWidth(for: timezone, with: store)),
let bestTitleSize = compactModeTimeFont.size(operation.compactMenuTitle(), Double(compactWidth(for: timezone)), attributes: timeBasedAttributes) attributes: timeBasedAttributes)
let bestTitleSize = compactModeTimeFont.size(for: operation.compactMenuTitle(),
width: Double(compactWidth(for: timezone, with: store)),
attributes: timeBasedAttributes)
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)
} }
@ -191,11 +183,11 @@ class StatusContainerView: NSView {
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
] ]
let bestSize = compactModeTimeFont.size(eventInfo.metadataForMeeting(), let bestSize = compactModeTimeFont.size(for: eventInfo.metadataForMeeting(),
55, // Default for a location based status view width: 55, // Default for a location based status view
attributes: timeBasedAttributes) attributes: timeBasedAttributes)
let bestTitleSize = compactModeTimeFont.size(eventInfo.event.title, let bestTitleSize = compactModeTimeFont.size(for: eventInfo.event.title,
70, // Little more buffer since meeting titles tend to be longer width: 70, // Little more buffer since meeting titles tend to be longer
attributes: timeBasedAttributes) attributes: timeBasedAttributes)
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)

55
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -22,7 +22,7 @@ class StatusItemHandler: NSObject {
return statusItem return statusItem
}() }()
private var menubarTitleHandler = MenubarTitleProvider(with: DataStore.shared()) private lazy var menubarTitleHandler = MenubarTitleProvider(with: self.store)
private var statusContainerView: StatusContainerView? private var statusContainerView: StatusContainerView?
@ -32,6 +32,8 @@ class StatusItemHandler: NSObject {
private var userNotificationsDidChangeNotif: NSObjectProtocol? private var userNotificationsDidChangeNotif: NSObjectProtocol?
private let store: DataStore
// Current State might be set twice when the user first launches an app. // Current State might be set twice when the user first launches an app.
// First, when StatusItemHandler() is instantiated in AppDelegate // First, when StatusItemHandler() is instantiated in AppDelegate
// Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet. // Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet.
@ -63,7 +65,8 @@ class StatusItemHandler: NSObject {
} }
} }
override init() { init(with dataStore: DataStore) {
store = dataStore
super.init() super.init()
setupStatusItem() setupStatusItem()
@ -74,10 +77,10 @@ class StatusItemHandler: NSObject {
// Let's figure out the initial menubar state // Let's figure out the initial menubar state
var menubarState = MenubarState.icon var menubarState = MenubarState.icon
let shouldTextBeDisplayed = DataStore.shared().menubarTimezones()?.isEmpty ?? true let shouldTextBeDisplayed = store.menubarTimezones()?.isEmpty ?? true
if !shouldTextBeDisplayed || DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if !shouldTextBeDisplayed || store.shouldDisplay(.showMeetingInMenubar) {
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if store.shouldDisplay(.menubarCompactMode) {
menubarState = .compactText menubarState = .compactText
} else { } else {
menubarState = .standardText menubarState = .standardText
@ -138,14 +141,16 @@ class StatusItemHandler: NSObject {
private func constructCompactView(with upcomingEventView: Bool = false) { private func constructCompactView(with upcomingEventView: Bool = false) {
statusContainerView = nil statusContainerView = nil
let menubarTimezones = DataStore.shared().menubarTimezones() ?? [] let menubarTimezones = store.menubarTimezones() ?? []
if menubarTimezones.isEmpty { if menubarTimezones.isEmpty {
currentState = .icon currentState = .icon
return return
} }
statusContainerView = StatusContainerView(with: menubarTimezones, statusContainerView = StatusContainerView(with: menubarTimezones,
showUpcomingEventView: upcomingEventView) store: store,
showUpcomingEventView: upcomingEventView,
bufferContainerWidth: bufferCalculatedWidth())
statusItem.view = statusContainerView statusItem.view = statusContainerView
statusItem.view?.window?.backgroundColor = NSColor.clear statusItem.view?.window?.backgroundColor = NSColor.clear
} }
@ -155,7 +160,7 @@ class StatusItemHandler: NSObject {
// Our icon is template, so it changes automatically; so is our standard status bar text // Our icon is template, so it changes automatically; so is our standard status bar text
// Only need to handle the compact mode! // Only need to handle the compact mode!
@objc func respondToInterfaceStyleChange() { @objc func respondToInterfaceStyleChange() {
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if store.shouldDisplay(.menubarCompactMode) {
updateCompactMenubar() updateCompactMenubar()
} }
} }
@ -199,11 +204,11 @@ class StatusItemHandler: NSObject {
} }
private func shouldDisplaySecondsInMenubar() -> Bool { private func shouldDisplaySecondsInMenubar() -> Bool {
let syncedTimezones = DataStore.shared().menubarTimezones() ?? [] let syncedTimezones = store.menubarTimezones() ?? []
let timezonesSupportingSeconds = syncedTimezones.filter { data in let timezonesSupportingSeconds = syncedTimezones.filter { data in
if let timezoneObj = TimezoneData.customObject(from: data) { if let timezoneObj = TimezoneData.customObject(from: data) {
return timezoneObj.shouldShowSeconds(DataStore.shared().timezoneFormat()) return timezoneObj.shouldShowSeconds(store.timezoneFormat())
} }
return false return false
} }
@ -213,7 +218,7 @@ class StatusItemHandler: NSObject {
private func calculateFireDate() -> Date? { private func calculateFireDate() -> Date? {
let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() let shouldDisplaySeconds = shouldDisplaySecondsInMenubar()
let menubarFavourites = DataStore.shared().menubarTimezones() let menubarFavourites = store.menubarTimezones()
if !units.contains(.second), shouldDisplaySeconds { if !units.contains(.second), shouldDisplaySeconds {
units.insert(.second) units.insert(.second)
@ -298,9 +303,9 @@ class StatusItemHandler: NSObject {
// Check if user is not showing // Check if user is not showing
// 1. Timezones // 1. Timezones
// 2. Upcoming Event // 2. Upcoming Event
let menubarFavourites = DataStore.shared().menubarTimezones() ?? [] let menubarFavourites = store.menubarTimezones() ?? []
if menubarFavourites.isEmpty, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { if menubarFavourites.isEmpty, store.shouldDisplay(.showMeetingInMenubar) == false {
Logger.info("Invalidating menubar timer!") Logger.info("Invalidating menubar timer!")
invalidation() invalidation()
@ -347,7 +352,7 @@ class StatusItemHandler: NSObject {
if let menubarTitle = menubarTitleHandler.titleForMenubar() { if let menubarTitle = menubarTitleHandler.titleForMenubar() {
menubarText = menubarTitle menubarText = menubarTitle
} else if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { } else if store.shouldDisplay(.showMeetingInMenubar) {
// Don't have any meeting to show // Don't have any meeting to show
} else { } else {
// We have no favourites to display and no meetings to show. // We have no favourites to display and no meetings to show.
@ -383,4 +388,26 @@ class StatusItemHandler: NSObject {
}) })
return upcomingEventView return upcomingEventView
} }
private func bufferCalculatedWidth() -> Int {
var totalWidth = 55
if store.shouldShowDayInMenubar() {
totalWidth += 12
}
if store.isBufferRequiredForTwelveHourFormats() {
totalWidth += 20
}
if store.shouldShowDateInMenubar() {
totalWidth += 20
}
if store.shouldDisplay(.showMeetingInMenubar) {
totalWidth += 100
}
return totalWidth
}
} }

7
Clocker/fastlane/Appfile

@ -1,7 +0,0 @@
app_identifier "com.abhishek.Clocker" # The bundle identifier of your app
apple_id "abhishekbanthia1712@gmail.com" # Your Apple email address
team_id "[[DEV_PORTAL_TEAM_ID]]" # Developer Portal Team ID
# you can even provide different app identifiers, Apple IDs and team names per lane:
# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md

73
Clocker/fastlane/Fastfile

@ -1,73 +0,0 @@
# Customise this file, documentation can be found here:
# https://github.com/fastlane/fastlane/tree/master/fastlane/docs
# All available actions: https://docs.fastlane.tools/actions
# can also be listed using the `fastlane actions` command
# Change the syntax highlighting to Ruby
# All lines starting with a # are ignored when running `fastlane`
# If you want to automatically update fastlane if a new version is available:
# update_fastlane
# This is the minimum version number required.
# Update this, if you use features of a newer version
fastlane_version "2.36.0"
default_platform :ios
platform :ios do
before_all do
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..."
end
desc "Runs all the tests"
lane :test do
scan
end
desc "Submit a new Beta Build to Apple TestFlight"
desc "This will also make sure the profile is up to date"
lane :beta do
# match(type: "appstore") # more information: https://codesigning.guide
gym # Build your app - more options available
pilot
# sh "your_script.sh"
# You can also use other beta testing services here (run `fastlane actions`)
end
desc "Deploy a new version to the App Store"
lane :release do
# match(type: "appstore")
# snapshot
gym # Build your app - more options available
deliver(force: true)
# frameit
end
# You can define as many lanes as you want
after_all do |lane|
# This block is called, only if the executed lane was successful
# slack(
# message: "Successfully deployed new App Update."
# )
end
error do |lane, exception|
# slack(
# message: exception.message,
# success: false
# )
end
end
# More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md
# All available actions: https://docs.fastlane.tools/actions
# fastlane reports which actions are used. No personal data is recorded.
# Learn more at https://github.com/fastlane/fastlane#metrics

BIN
Clocker/fastlane/screenshots/Screenshot1 2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

BIN
Clocker/fastlane/screenshots/Screenshot1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 KiB

BIN
Clocker/fastlane/screenshots/Screenshot2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

BIN
Clocker/fastlane/screenshots/Screenshot3 2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

BIN
Clocker/fastlane/screenshots/Screenshot3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

171
Clocker/fastlane/test_output/report.html

@ -1,171 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test Results | xcpretty</title>
<style type="text/css">
body { font-family:Avenir Next, Helvetica Neue, sans-serif; color: #4A4A4A; background-color: #F0F3FB; margin:0;}
h1 { font-weight: normal; font-size: 24px; margin: 10px 0 0 0;}
h3 { font-weight: normal; margin: 2px; font-size: 1.1em;}
header { position: fixed;width: 100%;background: rgba(249, 254, 255, 0.9);margin: 0;padding: 10px;}
header:before, header:after { content:""; display:table;}
header:after { clear:both;}
a:link { color: #A1D761;}
footer { clear: both;position: relative;z-index: 10;height: 40px;margin-top: -10px; margin-left:30px; font-size:12px;}
table { width:100%; border-collapse: collapse;}
tr td:first-child { width:7%}
.left { float: left; margin-left:30px;}
.right { float: right; margin-right: 40px; margin-top: 0; margin-bottom:0;}
.test-suite { margin: 0 0 30px 0;}
.test-suite > .heading { font-family:Menlo, Monaco, monospace; font-weight: bold; border-color: #A1D761; background-color: #B8E986; border-width: 1px;}
.test-suite.failing > .heading { border-color: #C84F5E; background-color: #E58591;}
.test-suite > .heading > .title { margin-top: 4px; margin-left: 10px;}
.tests { overflow: scroll;margin: 0 30px 0 60px;}
.test, .test-suite > .heading { height: 30px; overflow: hidden; margin: 0 30px;}
.test, .test-suite > .heading { border-width: 1px; border-collapse: collapse; border-style: solid; }
.test { margin-left: 30px; border-top:none;}
.test.failing { border-color: #C84F5E; background-color: #F4DDE0;}
.test.passing { border-color: #A1D761;}
.test.failing { background-color: #E7A1AA;}
.test.passing { background-color: #CAF59F;}
.test.failing.odd { background-color: #EEC7CC;}
.test.passing.odd { background-color: #E5FBCF;}
.details { background-color: #F4DDE0; border: 1px solid #C84F5E;}
.test .test-detail:last-child { padding-bottom: 8px;}
.test .title { float: left; font-size: 0.9em; margin-top: 8px; font-family: Menlo, Monaco, monospace;}
.test .time { float: left;margin: 4px 10px 0 20px;}
.test-detail { font-family:Menlo, Monaco, monospace; font-size: 0.9em; margin: 5px 0 5px 0px;}
.screenshots { height: auto; overflow: hidden; padding: 4px 4px 0 4px; background-color: #B8E986; border: #A1D761; border-width: 0 1px; border-style: solid; }
.screenshots.failing { border-color: #C84F5E; background-color: #E58591; }
.screenshot { max-height: 60px; float: left; transition: max-height 0.2s; margin: 0 4px 4px 0 }
.screenshot.selected { max-height: 568px; }
#test-suites { display: inline-block; width: 100%;margin-top:100px;}
#segment-bar { margin-top: 10px;margin-left: 14px;float:right;}
#segment-bar a:first-child { border-radius: 9px 0 0 9px; border-right: none;}
#segment-bar a:last-child { border-radius: 0 9px 9px 0; border-left: none;}
#segment-bar > a { color: #565656; border: 2px solid #7B7B7B; width: 80px; font-weight: bold; display:inline-block;text-align:center; font-weight: normal;}
#segment-bar > a.selected { background-color: #979797; color: #F0F3FB;}
#counters { float: left;margin: 10px;text-align: right;}
#counters h2 { font-size: 16px; font-family: Avenir, sans-serif; font-weight: lighter; display:inline;}
#counters .number { font-size: 20px;}
#fail-count { color: #D0021B; margin-left:10px;}
@media (max-width: 640px) {
h1, #counters, #segment-bar { margin: 5px auto; text-align:center;}
header, #segment-bar { width: 100%; position: relative; background:none;}
.left, .right { float:none; margin:0;}
#test-suites { margin-top: 0;}
#counters { float:none;}
}
</style>
<script type="text/javascript">
var hide = function(element) { element.style.display = 'none';}
var show = function(element) { element.style.display = '';}
var isHidden = function(element) { return element.style.display == 'none';}
var isSelected = function(element) { return element.classList.contains("selected");}
var deselect = function(element) { return element.classList.remove("selected");}
var select = function(element) { return element.classList.add("selected");}
var toggle = function(element) { isHidden(element) ? show(element) : hide(element);};
var toggleTests = function(heading) { toggle(heading.parentNode.children[1]);};
var toggleDetails = function(detailClass) {
var details = document.querySelectorAll('.' + detailClass);
for (var i = details.length - 1; i >= 0; i--) { toggle(details[i]);};
};
var hideAll = function(collection) {
for (var i = collection.length - 1; i >= 0; i--) { hide(collection[i]); };
}
var showAll = function(collection) {
for (var i = collection.length - 1; i >= 0; i--) { show(collection[i]); };
}
var selectSegment = function(segment) {
if (isSelected(segment)) return;
var segments = document.querySelectorAll('#segment-bar > a');
for (var i = segments.length - 1; i >= 0; i--) { deselect(segments[i]);};
select(segment);
if (segment.id == "all-segment") {
showAll(document.querySelectorAll('.test-suite'));
showAll(document.querySelectorAll('.test'));
} else if (segment.id == "failing-segment") {
hideAll(document.querySelectorAll('.test.passing'));
showAll(document.querySelectorAll('.test.failing'));
hideAll(document.querySelectorAll('.test-suite.passing'));
showAll(document.querySelectorAll('.test-suite.failing'));
} else if (segment.id == "passing-segment") {
hideAll(document.querySelectorAll('.test.failing'));
showAll(document.querySelectorAll('.test.passing'));
hideAll(document.querySelectorAll('.test-suite.failing'));
showAll(document.querySelectorAll('.test-suite.passing'));
}
}
var toggleScreenshot = function(suiteName, index) {
var screenshot = document.getElementById("screenshot-" + suiteName + "-" + index);
isSelected(screenshot) ? deselect(screenshot) : select(screenshot);
}
</script>
</head>
<body>
<header>
<section class="left">
<h1>Test Results</h1>
</section>
<section class="right">
<section id="counters">
<h2 id="test-count"><span class="number">3</span> tests</h2>
</section>
<section id="segment-bar">
<a id="all-segment" onclick="selectSegment(this);" class="selected">All</a><a id="failing-segment" onclick="selectSegment(this);">Failing</a><a id="passing-segment" onclick="selectSegment(this);">Passing</a>
</section>
</section>
</header>
<section id="test-suites">
<section class="test-suite passing" id="ClockerTests">
<section class="heading" onclick="toggleTests(this);">
<h3 class="title">ClockerTests</h3>
</section>
<section class="tests">
<table>
<tr class="test passing " onclick="toggleDetails('testAddingPlaceToUserDefaults');">
<td>
<h3 class="time">0.154s</h3>
</td>
<td><h3 class="title">testAddingPlaceToUserDefaults</h3></td>
</tr>
<tr class="test passing odd" onclick="toggleDetails('testDeletingPlaceFromUserDefaults');">
<td>
<h3 class="time">0.002s</h3>
</td>
<td><h3 class="title">testDeletingPlaceFromUserDefaults</h3></td>
</tr>
<tr class="test passing " onclick="toggleDetails('testIfReminderIsCreated');">
<td>
<h3 class="time">0.022s</h3>
</td>
<td><h3 class="title">testIfReminderIsCreated</h3></td>
</tr>
</table>
</section>
</section>
</section>
<footer>Report generated with <a href="https://github.com/supermarin/xcpretty">xcpretty</a></footer>
</body>
</html>

8
Clocker/fastlane/test_output/report.junit

@ -1,8 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<testsuites name='ClockerTests.xctest' tests='3' failures='0'>
<testsuite name='ClockerTests' tests='3' failures='0'>
<testcase classname='ClockerTests' name='testAddingPlaceToUserDefaults' time='0.154'/>
<testcase classname='ClockerTests' name='testDeletingPlaceFromUserDefaults' time='0.002'/>
<testcase classname='ClockerTests' name='testIfReminderIsCreated' time='0.022'/>
</testsuite>
</testsuites>
Loading…
Cancel
Save