Repository: n0shake/Clocker Branch: master Commit: 18778bad0ec9 Files: 547 Total size: 2.4 MB Directory structure: gitextract_ww47k156/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .swiftformat ├── .swiftlint.yml ├── .travis.yml ├── Archive/ │ └── New Icons/ │ └── Older Icons/ │ ├── New Icons/ │ │ └── NewIcon.xcf │ └── Old Icons/ │ └── ClockerIcon.xcf ├── CLAUDE.md ├── Clocker/ │ ├── AppDelegate.swift │ ├── Clocker/ │ │ ├── Clocker-Info.plist │ │ ├── Clocker-Prefix.pch │ │ ├── Clocker.entitlements │ │ ├── LocationController.swift │ │ ├── MainMenu.xib │ │ ├── ShortcutRecorder-master/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE.md │ │ │ ├── Library/ │ │ │ │ ├── Info.plist │ │ │ │ ├── Prefix.pch │ │ │ │ ├── SRCommon.h │ │ │ │ ├── SRCommon.m │ │ │ │ ├── SRKeyCodeTransformer.h │ │ │ │ ├── SRKeyCodeTransformer.m │ │ │ │ ├── SRKeyEquivalentModifierMaskTransformer.h │ │ │ │ ├── SRKeyEquivalentModifierMaskTransformer.m │ │ │ │ ├── SRKeyEquivalentTransformer.h │ │ │ │ ├── SRKeyEquivalentTransformer.m │ │ │ │ ├── SRModifierFlagsTransformer.h │ │ │ │ ├── SRModifierFlagsTransformer.m │ │ │ │ ├── SRRecorderControl.h │ │ │ │ ├── SRRecorderControl.m │ │ │ │ ├── SRValidator.h │ │ │ │ ├── SRValidator.m │ │ │ │ └── ShortcutRecorder.h │ │ │ ├── PTHotKey/ │ │ │ │ ├── Info.plist │ │ │ │ ├── PTHotKey+ShortcutRecorder.h │ │ │ │ ├── PTHotKey+ShortcutRecorder.m │ │ │ │ ├── PTHotKey.h │ │ │ │ ├── PTHotKey.m │ │ │ │ ├── PTHotKeyCenter.h │ │ │ │ ├── PTHotKeyCenter.m │ │ │ │ ├── PTKeyCodeTranslator.h │ │ │ │ ├── PTKeyCodeTranslator.m │ │ │ │ ├── PTKeyCombo.h │ │ │ │ └── PTKeyCombo.m │ │ │ ├── README.md │ │ │ ├── Resources/ │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── ShorcutRecorder Yosemite.sketch │ │ │ │ ├── ar.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── ca.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── cs.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── de.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── el.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── en.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── es-MX.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── es.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── fr.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── it.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── ja.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── ko.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── nb.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── nl.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── pl.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── pt-BR.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── pt.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── ro.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── ru.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── sk.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── sv.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── th.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── tr.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ ├── zh-Hans.lproj/ │ │ │ │ │ └── ShortcutRecorder.strings │ │ │ │ └── zh-Hant.lproj/ │ │ │ │ └── ShortcutRecorder.strings │ │ │ ├── ShortcutRecorder.podspec │ │ │ └── ShortcutRecorder.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ ├── PTHotKey.framework.xcscheme │ │ │ └── ShortcutRecorder.framework.xcscheme │ │ ├── ar.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── ca.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── com.abhishek.ClockerHelper.plist │ │ ├── de.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── en.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ ├── Localizable.strings │ │ │ └── Panel.xib │ │ ├── es.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── fr.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── hi.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── hr.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── it.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── ja.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── ko.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── main.m │ │ ├── nl.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── pl.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── pt-BR.lproj/ │ │ │ └── Localizable.strings │ │ ├── pt-PT.lproj/ │ │ │ └── InfoPlist.strings │ │ ├── ru.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── tr.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ ├── uk.lproj/ │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj/ │ │ └── Localizable.strings │ ├── Clocker-Bridging-Header.h │ ├── Clocker.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata/ │ │ │ │ ├── Clocker.xcscmblueprint │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcuserdata/ │ │ │ ├── abhishek_banthia.xcuserdatad/ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ ├── abhishekbanthia.xcuserdatad/ │ │ │ │ └── IDEFindNavigatorScopes.plist │ │ │ └── ban.xcuserdatad/ │ │ │ └── IDEFindNavigatorScopes.plist │ │ ├── xcshareddata/ │ │ │ ├── IDETemplateMacros.plist │ │ │ └── xcschemes/ │ │ │ ├── Clocker.xcscheme │ │ │ └── Tests.xcscheme │ │ └── xcuserdata/ │ │ ├── abhi.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ └── xcschememanagement.plist │ │ ├── abhishek_banthia.xcuserdatad/ │ │ │ └── xcschemes/ │ │ │ ├── Clocker.xcscheme │ │ │ ├── ClockerHelper.xcscheme │ │ │ └── xcschememanagement.plist │ │ ├── abhishekbanthia.xcuserdatad/ │ │ │ ├── xcdebugger/ │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes/ │ │ │ ├── ClockerHelper.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── ban.xcuserdatad/ │ │ ├── xcdebugger/ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes/ │ │ └── xcschememanagement.plist │ ├── ClockerHelper/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── ClockerHelper.entitlements │ │ ├── Info.plist │ │ ├── de.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Main.strings │ │ ├── main.m │ │ ├── ru.lproj/ │ │ │ ├── InfoPlist.strings │ │ │ └── Main.strings │ │ └── zh-Hans.lproj/ │ │ ├── InfoPlist.strings │ │ └── Main.strings │ ├── ClockerUITests/ │ │ ├── AboutUsTests.swift │ │ ├── ClockerUITests-Bridging-Header.h │ │ ├── ClockerUITests.m │ │ ├── CopyToClipboardTests.swift │ │ ├── FloatingWindowTests.swift │ │ ├── Info.plist │ │ ├── NetworkDisconnectionTests.swift │ │ ├── OnboardingSearchTests.swift │ │ ├── OnboardingTests.swift │ │ ├── PanelTests.swift │ │ ├── PermissionsTests.swift │ │ ├── PreferencesTest.swift │ │ ├── ReviewTests.swift │ │ ├── ShortcutTests.swift │ │ ├── de.lproj/ │ │ │ └── InfoPlist.strings │ │ ├── ru.lproj/ │ │ │ └── InfoPlist.strings │ │ └── zh-Hans.lproj/ │ │ └── InfoPlist.strings │ ├── ClockerUnitTests/ │ │ ├── AppDelegateTests.swift │ │ ├── ClockerUnitTests.swift │ │ ├── DateFormatterManagerTests.swift │ │ ├── EventInfoTests.swift │ │ ├── Info.plist │ │ ├── ReviewControllerTests.swift │ │ ├── SearchDataSourceTests.swift │ │ ├── StandardMenubarHandlerTests.swift │ │ └── ThemerTests.swift │ ├── CoreLoggerKit/ │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources/ │ │ │ └── CoreLoggerKit/ │ │ │ └── Logger.swift │ │ └── Tests/ │ │ ├── CoreLoggerKitTests/ │ │ │ ├── CoreLoggerKitTests.swift │ │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ ├── CoreModelKit/ │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources/ │ │ │ └── CoreModelKit/ │ │ │ ├── SearchResults.swift │ │ │ └── TimezoneData.swift │ │ └── Tests/ │ │ ├── CoreModelKitTests/ │ │ │ ├── TimezoneDataEqualityTests.swift │ │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ ├── Dependencies/ │ │ ├── Date Additions/ │ │ │ ├── Constants.swift │ │ │ ├── Date+Bundle.swift │ │ │ ├── Date+Comparators.swift │ │ │ ├── Date+Components.swift │ │ │ ├── Date+Format.swift │ │ │ ├── Date+Inits.swift │ │ │ ├── Date+Manipulations.swift │ │ │ ├── Date+TimeAgo.swift │ │ │ ├── DateTools.bundle/ │ │ │ │ ├── am.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ar.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── bg.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ca.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── cs.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── cy.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── da.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── de.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── en.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── es.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── eu.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── fi.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── fr.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── gre.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── gu.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── he.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── hi.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── hr.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── hu.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── id.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── is.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── it.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ja.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ko.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── lv.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ms.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── nb.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── nl.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── pl.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── pt-PT.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── pt.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ro.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── ru.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── sk.lproj/ │ │ │ │ │ └── NSDateTimeAgo.strings │ │ │ │ ├── sl.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── sv.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── th.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── tr.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── uk.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── vi.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ ├── zh-Hans.lproj/ │ │ │ │ │ └── DateTools.strings │ │ │ │ └── zh-Hant.lproj/ │ │ │ │ └── DateTools.strings │ │ │ ├── Enums.swift │ │ │ ├── Integer+DateTools.swift │ │ │ └── TimeChunk.swift │ │ ├── Solar.swift │ │ └── iVersion/ │ │ ├── iVersion.bundle/ │ │ │ ├── da.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── de.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── el.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── en-GB.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── en.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── es.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── fr.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── it.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── ja.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── pt-PT.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── pt.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── ru.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── tr.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── zh-Hans.lproj/ │ │ │ │ └── Localizable.strings │ │ │ └── zh-Hant.lproj/ │ │ │ └── Localizable.strings │ │ ├── iVersion.h │ │ └── iVersion.m │ ├── Events and Reminders/ │ │ ├── CalendarHandler.swift │ │ ├── EventCenter.swift │ │ └── RemindersHandler.swift │ ├── Firebase.h │ ├── Frameworks/ │ │ └── Firebase/ │ │ ├── FirebaseCore.framework/ │ │ │ ├── FirebaseCore │ │ │ ├── Headers/ │ │ │ │ ├── FIRApp.h │ │ │ │ ├── FIRConfiguration.h │ │ │ │ ├── FIRLoggerLevel.h │ │ │ │ ├── FIROptions.h │ │ │ │ ├── FIRVersion.h │ │ │ │ └── FirebaseCore.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── FirebaseCoreDiagnostics.framework/ │ │ │ ├── FirebaseCoreDiagnostics │ │ │ ├── Headers/ │ │ │ │ ├── FIRCoreDiagnostics.h │ │ │ │ └── FirebaseCoreDiagnostics-umbrella.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── FirebaseCrashlytics.framework/ │ │ │ ├── FirebaseCrashlytics │ │ │ ├── Headers/ │ │ │ │ ├── FIRCrashlytics.h │ │ │ │ ├── FIRCrashlyticsReport.h │ │ │ │ ├── FIRExceptionModel.h │ │ │ │ ├── FIRStackFrame.h │ │ │ │ └── FirebaseCrashlytics.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── FirebaseDatabase.framework/ │ │ │ ├── FirebaseDatabase │ │ │ ├── Headers/ │ │ │ │ ├── FIRDataEventType.h │ │ │ │ ├── FIRDataSnapshot.h │ │ │ │ ├── FIRDatabase.h │ │ │ │ ├── FIRDatabaseQuery.h │ │ │ │ ├── FIRDatabaseReference.h │ │ │ │ ├── FIRMutableData.h │ │ │ │ ├── FIRServerValue.h │ │ │ │ ├── FIRTransactionResult.h │ │ │ │ └── FirebaseDatabase.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── FirebaseInstallations.framework/ │ │ │ ├── FirebaseInstallations │ │ │ ├── Headers/ │ │ │ │ ├── FIRInstallations.h │ │ │ │ ├── FIRInstallationsAuthTokenResult.h │ │ │ │ ├── FIRInstallationsErrors.h │ │ │ │ └── FirebaseInstallations.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── GoogleDataTransport.framework/ │ │ │ ├── GoogleDataTransport │ │ │ ├── Headers/ │ │ │ │ ├── GDTCORClock.h │ │ │ │ ├── GDTCORConsoleLogger.h │ │ │ │ ├── GDTCOREndpoints.h │ │ │ │ ├── GDTCOREvent.h │ │ │ │ ├── GDTCOREventDataObject.h │ │ │ │ ├── GDTCOREventTransformer.h │ │ │ │ ├── GDTCORTargets.h │ │ │ │ ├── GDTCORTransport.h │ │ │ │ └── GoogleDataTransport.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── GoogleUtilities.framework/ │ │ │ ├── GoogleUtilities │ │ │ ├── Headers/ │ │ │ │ ├── GULAppDelegateSwizzler.h │ │ │ │ ├── GULAppEnvironmentUtil.h │ │ │ │ ├── GULApplication.h │ │ │ │ ├── GULHeartbeatDateStorable.h │ │ │ │ ├── GULHeartbeatDateStorage.h │ │ │ │ ├── GULHeartbeatDateStorageUserDefaults.h │ │ │ │ ├── GULKeychainStorage.h │ │ │ │ ├── GULKeychainUtils.h │ │ │ │ ├── GULLogger.h │ │ │ │ ├── GULLoggerLevel.h │ │ │ │ ├── GULMutableDictionary.h │ │ │ │ ├── GULNSData+zlib.h │ │ │ │ ├── GULNetwork.h │ │ │ │ ├── GULNetworkConstants.h │ │ │ │ ├── GULNetworkLoggerProtocol.h │ │ │ │ ├── GULNetworkMessageCode.h │ │ │ │ ├── GULNetworkURLSession.h │ │ │ │ ├── GULReachabilityChecker.h │ │ │ │ ├── GULSceneDelegateSwizzler.h │ │ │ │ ├── GULSecureCoding.h │ │ │ │ ├── GULURLSessionDataResponse.h │ │ │ │ ├── GULUserDefaults.h │ │ │ │ ├── GoogleUtilities-umbrella.h │ │ │ │ └── NSURLSession+GULPromises.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ ├── PromisesObjC.framework/ │ │ │ ├── Headers/ │ │ │ │ ├── FBLPromise+All.h │ │ │ │ ├── FBLPromise+Always.h │ │ │ │ ├── FBLPromise+Any.h │ │ │ │ ├── FBLPromise+Async.h │ │ │ │ ├── FBLPromise+Await.h │ │ │ │ ├── FBLPromise+Catch.h │ │ │ │ ├── FBLPromise+Delay.h │ │ │ │ ├── FBLPromise+Do.h │ │ │ │ ├── FBLPromise+Race.h │ │ │ │ ├── FBLPromise+Recover.h │ │ │ │ ├── FBLPromise+Reduce.h │ │ │ │ ├── FBLPromise+Retry.h │ │ │ │ ├── FBLPromise+Testing.h │ │ │ │ ├── FBLPromise+Then.h │ │ │ │ ├── FBLPromise+Timeout.h │ │ │ │ ├── FBLPromise+Validate.h │ │ │ │ ├── FBLPromise+Wrap.h │ │ │ │ ├── FBLPromise.h │ │ │ │ ├── FBLPromiseError.h │ │ │ │ ├── FBLPromises.h │ │ │ │ └── PromisesObjC-umbrella.h │ │ │ ├── Info.plist │ │ │ ├── Modules/ │ │ │ │ └── module.modulemap │ │ │ └── PromisesObjC │ │ ├── leveldb-library.framework/ │ │ │ ├── Headers/ │ │ │ │ ├── c.h │ │ │ │ ├── cache.h │ │ │ │ ├── comparator.h │ │ │ │ ├── db.h │ │ │ │ ├── dumpfile.h │ │ │ │ ├── env.h │ │ │ │ ├── export.h │ │ │ │ ├── filter_policy.h │ │ │ │ ├── iterator.h │ │ │ │ ├── leveldb-library-umbrella.h │ │ │ │ ├── options.h │ │ │ │ ├── slice.h │ │ │ │ ├── status.h │ │ │ │ ├── table.h │ │ │ │ ├── table_builder.h │ │ │ │ └── write_batch.h │ │ │ ├── Info.plist │ │ │ ├── Modules/ │ │ │ │ └── module.modulemap │ │ │ └── leveldb-library │ │ └── nanopb.framework/ │ │ ├── Headers/ │ │ │ ├── nanopb-umbrella.h │ │ │ ├── pb.h │ │ │ ├── pb_common.h │ │ │ ├── pb_decode.h │ │ │ └── pb_encode.h │ │ ├── Info.plist │ │ ├── Modules/ │ │ │ └── module.modulemap │ │ └── nanopb │ ├── GoogleService-Info.plist │ ├── Keys.plist.example │ ├── Media.xcassets/ │ │ ├── Accent Color.colorset/ │ │ │ └── Contents.json │ │ ├── Add Icon/ │ │ │ ├── Add Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Add Highlighted.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Add Icon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Add White.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Calendar Icon Remove/ │ │ │ ├── Contents.json │ │ │ ├── Remove Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Remove.imageset/ │ │ │ │ └── Contents.json │ │ │ └── WhiteRemove.imageset/ │ │ │ └── Contents.json │ │ ├── Clocker Icon/ │ │ │ ├── ClockerIcon-512.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Extra Options/ │ │ │ ├── Contents.json │ │ │ ├── Extra Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Extra.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ExtraHighlighted Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ExtraHighlighted.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ExtraWhite.imageset/ │ │ │ │ └── Contents.json │ │ │ └── ExtraWhiteHighlighted.imageset/ │ │ │ └── Contents.json │ │ ├── Location/ │ │ │ ├── Contents.json │ │ │ ├── CurrentLocation.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── CurrentLocationDynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ └── CurrentLocationWhite.imageset/ │ │ │ └── Contents.json │ │ ├── Menubar Icons/ │ │ │ ├── Contents.json │ │ │ └── LightModeIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Onboarding/ │ │ │ ├── Contents.json │ │ │ ├── Dark Menubar.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Dynamic Menubar.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Light Menubar.imageset/ │ │ │ └── Contents.json │ │ ├── Pin/ │ │ │ ├── Contents.json │ │ │ ├── Float-White.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Float.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Pin.imageset/ │ │ │ └── Contents.json │ │ ├── Power Icon/ │ │ │ ├── Contents.json │ │ │ ├── Power.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── PowerIcon-White.imageset/ │ │ │ │ └── Contents.json │ │ │ └── PowerIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Preferences Toolbar.imageset/ │ │ │ └── Contents.json │ │ ├── Privacy/ │ │ │ ├── Contents.json │ │ │ ├── Privacy Dark.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Privacy Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Privacy.imageset/ │ │ │ └── Contents.json │ │ ├── Settings/ │ │ │ ├── Contents.json │ │ │ ├── Settings-White.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Settings.imageset/ │ │ │ └── Contents.json │ │ ├── Sharing/ │ │ │ ├── Contents.json │ │ │ ├── Sharing Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Sharing.imageset/ │ │ │ │ └── Contents.json │ │ │ └── SharingDarkIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Sunrise/ │ │ │ ├── Contents.json │ │ │ ├── Sunrise Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Sunrise.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Sunset Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Sunset.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── WhiteSunrise.imageset/ │ │ │ │ └── Contents.json │ │ │ └── WhiteSunset.imageset/ │ │ │ └── Contents.json │ │ ├── Tabs/ │ │ │ ├── Appearance Dark.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Appearance Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Appearance.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Calendar Tab Dark.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Calendar Tab Dynamic.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Calendar Tab Icon.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Trash.imageset/ │ │ └── Contents.json │ ├── Menu Bar/ │ │ ├── MenubarHandler.swift │ │ ├── StatusContainerView.swift │ │ ├── StatusItemHandler.swift │ │ └── StatusItemView.swift │ ├── Onboarding/ │ │ ├── FinalOnboardingViewController.swift │ │ ├── Onboarding.storyboard │ │ ├── OnboardingController.swift │ │ ├── OnboardingParentViewController.swift │ │ ├── OnboardingPermissionsViewController.swift │ │ ├── OnboardingSearchController.swift │ │ ├── OnboardingWelcomeViewController.swift │ │ ├── StartAtLoginViewController.swift │ │ └── WelcomeView.xib │ ├── Overall App/ │ │ ├── AppDefaults.swift │ │ ├── AppKit + Additions.swift │ │ ├── ConfigExport.swift │ │ ├── DataStore.swift │ │ ├── DateFormatterManager.swift │ │ ├── Foundation + Additions.swift │ │ ├── NetworkManager.swift │ │ ├── Reach.swift │ │ ├── String + Additions.swift │ │ ├── Strings.swift │ │ ├── Themer.swift │ │ ├── Timer.swift │ │ ├── UserDefaults + KVOExtensions.swift │ │ └── VersionUpdateHandler.swift │ ├── Panel/ │ │ ├── Data Layer/ │ │ │ └── TimezoneDataOperations.swift │ │ ├── FloatingWindowController.swift │ │ ├── Notes Popover/ │ │ │ ├── NotesPopover.swift │ │ │ ├── NotesPopover.xib │ │ │ └── TextViewWithPlaceholder.swift │ │ ├── PanelController.swift │ │ ├── ParentPanelController+ModernSlider.swift │ │ ├── ParentPanelController.swift │ │ ├── Rate Controller/ │ │ │ ├── ReviewController.swift │ │ │ └── UpcomingEventView.swift │ │ ├── UI/ │ │ │ ├── AddTableViewCell.swift │ │ │ ├── BackgroundPanelView.swift │ │ │ ├── CustomSliderCell.swift │ │ │ ├── FloatingWindow.xib │ │ │ ├── HourMarkerViewItem.xib │ │ │ ├── NoTimezoneView.swift │ │ │ ├── PanelTableView.swift │ │ │ ├── Panelr.swift │ │ │ ├── TimeMarkerViewItem.swift │ │ │ ├── TimezoneCellView.swift │ │ │ ├── TimezoneDataSource.swift │ │ │ └── Toasty.swift │ │ └── Upcoming Events/ │ │ ├── ParentPanelController+UpcomingEvents.swift │ │ ├── UpcomingEventViewItem.swift │ │ ├── UpcomingEventViewItem.xib │ │ └── UpcomingEventsDataSource.swift │ ├── Preferences/ │ │ ├── About/ │ │ │ ├── AboutViewController.swift │ │ │ └── PointingHandCursorButton.swift │ │ ├── App Feedback/ │ │ │ ├── AppFeedbackWindow.xib │ │ │ └── AppFeedbackWindowController.swift │ │ ├── Appearance/ │ │ │ └── AppearanceViewController.swift │ │ ├── Calendar/ │ │ │ └── CalendarViewController.swift │ │ ├── General/ │ │ │ ├── PreferencesDataSource.swift │ │ │ ├── PreferencesViewController.swift │ │ │ └── SearchDataSource.swift │ │ ├── Menu Bar/ │ │ │ ├── MenubarTitleProvider.swift │ │ │ ├── StatusContainerView.swift │ │ │ ├── StatusItemHandler.swift │ │ │ ├── StatusItemView.swift │ │ │ └── UpcomingEventStatusItemView.swift │ │ ├── OneWindowController.swift │ │ ├── ParentViewController.swift │ │ ├── Permissions/ │ │ │ └── PermissionsViewController.swift │ │ ├── Preferences.storyboard │ │ └── StartupManager.swift │ ├── Releases/ │ │ ├── appcast.xml │ │ └── changelog.md │ ├── StartupKit/ │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ └── Sources/ │ │ └── StartupKit/ │ │ └── StartupManager.swift │ ├── release.py │ ├── run │ └── upload-symbols ├── Readme.md └── swiftlint-install.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: n0shake ================================================ FILE: .gitignore ================================================ *.xcuserstate .DS_Store .DS_Store Clocker/Clocker.xcodeproj/project.xcworkspace/xcuserdata/abhishek_banthia.xcuserdatad/UserInterfaceState.xcuserstate Clocker/Clocker.xcodeproj/xcuserdata/abhishek_banthia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist *.xcuserstate .DS_Store **/Keys.plist ================================================ FILE: .swiftformat ================================================ --disable ================================================ FILE: .swiftlint.yml ================================================ disabled_rules: # rule identifiers to exclude from running - type_body_length - file_length - type_name - variable_name - nesting opt_in_rules: # some rules are only opt-in - empty_count # included: # paths to include during linting. `--path` is ignored if present. # - Clocker excluded: # paths to ignore during linting. Takes precedence over `included`. - Clocker/Dependencies - Clocker/ClockerUnitTests - Clocker/ClockerUITests # - Pods # - Source/ExcludedFolder # - Source/ExcludedFile.swift # - Source/*/ExcludedFile.swift # Exclude files with a wildcard analyzer_rules: # Rules run by `swiftlint analyze` (experimental) - explicit_self # configurable rules can be customized from this configuration file # binary rules can set their severity level force_cast: warning # implicitly force_try: severity: warning # explicitly # rules that have both warning and error levels, can set just the warning level # implicitly line_length: 200 # they can set both implicitly with an array type_body_length: - 300 # warning - 400 # error # or they can set both explicitly file_length: warning: 500 error: 1200 # naming rules can set warnings/errors for min_length and max_length # additionally they can set excluded names type_name: min_length: 3 # only warning max_length: # warning and error warning: 40 error: 50 excluded: iPhone # excluded via string identifier_name: min_length: # only min_length error: 3 # only error # excluded: # excluded via string array # - id # - URL # - GlobalAPIKey reporter: "xcode" ================================================ FILE: .travis.yml ================================================ os: osx language: swift xcode_project: Clocker.xcodeproj # path to your xcodeproj folder osx_image: xcode12.2 before_install: - export TZ=America/Chicago before_script: - xcodebuild -version install: - ./swiftlint-install.sh script: - set -o pipefail && env "NSUnbufferedIO=YES" - pwd - swiftlint - xcodebuild -project Clocker/Clocker.xcodeproj -scheme Clocker build analyze CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY= | xcpretty - xcodebuild -project Clocker/Clocker.xcodeproj -scheme Clocker test CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY= | xcpretty ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Clocker is a macOS menu bar utility (Swift 5, AppKit) that helps users track time across different time zones. It's a native macOS app targeting Sierra 10.12+, distributed via the Mac App Store and Homebrew. ## Build & Test Commands ```bash # Build xcodebuild -project Clocker/Clocker.xcodeproj -scheme Clocker build # Lint swiftlint lint --path Clocker/Clocker # Lint with analyzer rules (requires build) swiftlint analyze --compiler-log-path build/CompileSwift.log ``` The project uses Xcode directly — open `Clocker/Clocker.xcodeproj` for day-to-day development. ## Verification Policy **Only build to verify changes — never run unit tests.** Use `xcodebuild ... build` to confirm the project compiles. Tests must be run manually from within Xcode. Unit tests via `xcodebuild ... test` launch the full app host process, which can produce misleading fatal errors unrelated to the change under review. UI tests cannot be run via xcodebuild at all due to a code signing Team ID mismatch between the app and the UI test runner bundle. ## Architecture ### Module Structure The app is split into the main `Clocker` target plus three Swift packages: - **CoreModelKit** — data models (`TimezoneData`, `SearchResults`, etc.) - **CoreLoggerKit** — centralized logging - **StartupKit** — app initialization/startup orchestration ### Feature Modules (inside `Clocker/Clocker/`) | Directory | Purpose | |-----------|---------| | `Overall App/` | App-wide services: `DataStore`, `AppDefaults`, `Themer`, `DateFormatterManager`, `NetworkManager` | | `Menu Bar/` | `StatusItemHandler`, `MenubarHandler` — manages the menu bar status item | | `Panel/` | Main UI panel: `ParentPanelController` (primary controller), `PanelController`, `FloatingWindowController`, `TimezoneDataSource`, `TimezoneDataOperations` | | `Preferences/` | Settings UI split into General, Appearance, Menu Bar, Calendar, and About sub-controllers | | `Onboarding/` | First-run flow: search, permissions, start-at-login, and final screens | | `Events and Reminders/` | `EventCenter`, `CalendarHandler`, `RemindersHandler` for OS calendar/reminder integration | ### Key Data Flow ``` AppDelegate └── StatusItemHandler (menu bar) └── PanelController / FloatingWindowController └── ParentPanelController ├── TimezoneDataSource ←→ DataStore (UserDefaults) └── UpcomingEventsDataSource ←→ EventCenter ``` `DataStore` is the single source of truth for persisted timezone list and settings; it wraps `UserDefaults` and is accessed throughout the app as a shared instance. ### Panel vs. Floating Window The app has two display modes controlled by a user preference: - **Panel** (`PanelController`) — anchored to the menu bar item - **Floating Window** (`FloatingWindowController`) — free-floating, always-on-top window Both share `ParentPanelController` for the core UI logic (time display, slider scrubbing, upcoming events). ### Theming `Themer.swift` manages light/dark/system appearance. UI components observe `Themer` notifications to update colors. Always use `Themer` for colors rather than hardcoded values. ## SwiftLint Configuration Line length limit: 200. `explicit_self` is enforced via the analyzer rules. Tests and `Dependencies/` are excluded from linting. ## Release Process `release.py` automates version bumping (`agvtool`), clean build+analyze, test run, and GitHub release creation. Firebase Crashlytics is integrated for crash reporting in Release builds only. ================================================ FILE: Clocker/AppDelegate.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import FirebaseCore import FirebaseCrashlytics open class AppDelegate: NSObject, NSApplicationDelegate { private lazy var floatingWindow = FloatingWindowController.shared() internal lazy var panelController = PanelController(windowNibName: .panel) private var statusBarHandler: StatusItemHandler! // TODO: Replace iVersion with this! // private let versionUpdateHandler: VersionUpdateHandler = VersionUpdateHandler(with: DataStore.shared()) override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { if let path = keyPath, path == PreferencesConstants.hotKeyPathIdentifier { let hotKeyCenter = PTHotKeyCenter.shared() // Unregister old hot key let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path) hotKeyCenter?.unregisterHotKey(oldHotKey) // We don't register unless there's a valid key combination guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else { return } // Register new key let newHotKey = PTHotKey(identifier: keyPath, keyCombo: newShortcut, target: self, action: #selector(ping(_:))) hotKeyCenter?.register(newHotKey) } } public func applicationDidFinishLaunching(_: Notification) { AppDefaults.initialize(with: DataStore.shared(), defaults: UserDefaults.standard) // Check if we can show the onboarding flow! showOnboardingFlowIfEligible() // Ratings Controller initialization ReviewController.applicationDidLaunch(UserDefaults.standard) #if RELEASE FirebaseApp.configure() checkIfRunFromApplicationsFolder() #endif } public func applicationWillFinishLaunching(_: Notification) { iVersion.sharedInstance().useAllAvailableLanguages = true iVersion.sharedInstance().verboseLogging = false } public func applicationDockMenu(_: NSApplication) -> NSMenu? { let menu = NSMenu(title: "Quick Access") let toggleMenuItem = NSMenuItem(title: "Toggle Panel", action: #selector(AppDelegate.togglePanel(_:)), keyEquivalent: "") let openPreferences = NSMenuItem(title: "Settings", action: #selector(AppDelegate.openPreferencesWindow), keyEquivalent: ",") let hideFromDockMenuItem = NSMenuItem(title: "Hide from Dock", action: #selector(AppDelegate.hideFromDock), keyEquivalent: "") [toggleMenuItem, openPreferences, hideFromDockMenuItem].forEach { $0.isEnabled = true menu.addItem($0) } return menu } @objc private func openPreferencesWindow() { let displayMode = DataStore.shared().shouldDisplay(.showAppInForeground) if displayMode { let floatingWindow = FloatingWindowController.shared() floatingWindow.openPreferences(NSButton()) } else { panelController.openPreferences(NSButton()) } } @objc func hideFromDock() { UserDefaults.standard.set(0, forKey: UserDefaultKeys.appDisplayOptions) NSApp.setActivationPolicy(.accessory) } private var controller: OnboardingController? private func showOnboardingFlowIfEligible() { let isTestInProgress = ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.onboardingTestsLaunchArgument) let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: UserDefaultKeys.showOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty) || isTestInProgress if shouldLaunchOnboarding { let onboardingStoryboard = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil) controller = onboardingStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController controller?.launch() } else { continueUsually() } } func continueUsually() { // Cleanup onboarding controller after its done! if controller != nil { controller = nil } // Check if another instance of the app is already running. If so, then stop this one. checkIfAppIsAlreadyOpen() // Install the menubar item! statusBarHandler = StatusItemHandler(with: DataStore.shared()) if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.testingLaunchArgument) { FirebaseApp.configure() ReviewController.setPreviewMode(true) } UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true]) assignShortcut() let defaults = UserDefaults.standard setActivationPolicy() // Set the display mode default as panel! if let displayMode = defaults.object(forKey: UserDefaultKeys.showAppInForeground) as? NSNumber, displayMode.intValue == 1 { showFloatingWindow() } else if let displayMode = defaults.object(forKey: UserDefaultKeys.showAppInForeground) as? Int, displayMode == 1 { showFloatingWindow() } } // Should we have a dock icon or just stay in the menubar? private func setActivationPolicy() { let defaults = UserDefaults.standard let currentActivationPolicy = NSRunningApplication.current.activationPolicy let activationPolicy: NSApplication.ActivationPolicy = defaults.integer(forKey: UserDefaultKeys.appDisplayOptions) == 0 ? .accessory : .regular if currentActivationPolicy != activationPolicy { NSApp.setActivationPolicy(activationPolicy) } } private func checkIfAppIsAlreadyOpen() { guard let bundleID = Bundle.main.bundleIdentifier else { return } let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) if apps.count > 1 { let currentApplication = NSRunningApplication.current for app in apps where app != currentApplication { app.terminate() } } } private func showAlert(message: String, informativeText: String, buttonTitle: String) { NSApplication.shared.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = message alert.informativeText = informativeText alert.addButton(withTitle: buttonTitle) alert.runModal() } @IBAction func ping(_ sender: NSButton) { if let statusItemButton = statusBarHandler.statusItem.button { statusItemButton.state = statusItemButton.state == .on ? .off : .on togglePanel(statusItemButton) } } private func retrieveLatestLocation() { let locationController = LocationController(withStore: DataStore.shared()) locationController.determineAndRequestLocationAuthorization() } private func showFloatingWindow() { // Display the Floating Window! floatingWindow.showWindow(nil) floatingWindow.updateTableContent() floatingWindow.startWindowTimer() NSApp.activate(ignoringOtherApps: true) } private func assignShortcut() { NSUserDefaultsController.shared.addObserver(self, forKeyPath: PreferencesConstants.hotKeyPathIdentifier, options: [.initial, .new], context: nil) } private func checkIfRunFromApplicationsFolder() { if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true { return } let bundlePath = Bundle.main.bundlePath let applicationDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationDirectory, FileManager.SearchPathDomainMask.localDomainMask, true) for appDir in applicationDirectory { if bundlePath.hasPrefix(appDir) { return } } let informativeText = """ Clocker must be run from the Applications folder in order to work properly. Please quit Clocker, move it to the Applications folder, and relaunch. Current folder: \(applicationDirectory)" """ // Clocker is installed out of Applications directory // This breaks start at login! Time to show an alert and terminate showAlert(message: "Move Clocker to the Applications folder", informativeText: informativeText, buttonTitle: "Quit") // Terminate NSApp.terminate(nil) } @IBAction open func togglePanel(_ sender: NSButton) { Logger.info("Toggle Panel called with sender state \(sender.state.rawValue)") let displayMode = UserDefaults.standard.integer(forKey: UserDefaultKeys.showAppInForeground) if displayMode == 1 { // No need to call NSApp.activate here since `showFloatingWindow` takes care of this showFloatingWindow() } else { panelController.showWindow(nil) panelController.setActivePanel(newValue: sender.state == .on) NSApp.activate(ignoringOtherApps: true) } } open func setupFloatingWindow(_ hide: Bool) { hide ? floatingWindow.window?.close() : showFloatingWindow() } func statusItemForPanel() -> StatusItemHandler { return statusBarHandler } open func setupMenubarTimer() { statusBarHandler.setupStatusItem() } open func invalidateMenubarTimer(_ showIcon: Bool) { statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true) } } ================================================ FILE: Clocker/Clocker/Clocker-Info.plist ================================================ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName Clocker - World Clock CFBundlePackageType APPL CFBundleShortVersionString $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} LSUIElement NSAppTransportSecurity NSAllowsArbitraryLoads NSCalendarsUsageDescription Clocker can be more useful when it can display upcoming events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy NSHumanReadableCopyright Copyright © 2024, Abhishek Banthia NSLocationAlwaysAndWhenInUseUsageDescription Clocker can be more useful when it can use your location to determine your current timezone. NSLocationUsageDescription Clocker can be more useful when it can use your location to determine your current timezone. NSMainNibFile MainMenu NSPrincipalClass NSApplication NSRemindersUsageDescription Clocker can be more useful when it can set reminders for your selected timezone(s). You can change this setting in System Preferences › Security & Privacy › Privacy. NSSupportsAutomaticGraphicsSwitching RequestsOpenAccess YES ================================================ FILE: Clocker/Clocker/Clocker-Prefix.pch ================================================ // // Prefix header for all source files of the 'Clocker' target in the 'Clocker' project // #ifdef __OBJC__ #import #endif ================================================ FILE: Clocker/Clocker/Clocker.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.disable-library-validation com.apple.security.network.client com.apple.security.personal-information.calendars com.apple.security.temporary-exception.apple-events com.apple.reminders ================================================ FILE: Clocker/Clocker/LocationController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLocation import CoreLoggerKit import CoreModelKit protocol LocationControllerDelegate: NSObject { func didChangeAuthorizationStatus() } class LocationController: NSObject { private let store: DataStore init(withStore dataStore: DataStore) { store = dataStore super.init() } private var locationManager: CLLocationManager = { let locationManager = CLLocationManager() locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers return locationManager }() func authorizationStatus() -> CLAuthorizationStatus { return locationManager.authorizationStatus } func locationAccessNotDetermined() -> Bool { return locationManager.authorizationStatus == .notDetermined } func locationAccessGranted() -> Bool { print("Location Status is ", locationManager.authorizationStatus.rawValue.description) return locationManager.authorizationStatus == .authorizedAlways || locationManager.authorizationStatus == .authorized } func locationAccessDenied() -> Bool { return locationManager.authorizationStatus == .restricted || locationManager.authorizationStatus == .denied } func setDelegate() { locationManager.delegate = self } func determineAndRequestLocationAuthorization() { setDelegate() if CLLocationManager.locationServicesEnabled() { locationManager.startUpdatingLocation() } switch authorizationStatus() { case .authorizedAlways: locationManager.startUpdatingLocation() case .notDetermined: locationManager.startUpdatingLocation() case .denied, .restricted: locationManager.startUpdatingLocation() default: fatalError("Unexpected Authorization Status") } } private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) { let timezones = store.timezones() var timezoneObjects: [TimezoneData] = [] for timezone in timezones { if let model = TimezoneData.customObject(from: timezone) { timezoneObjects.append(model) } } for timezoneObject in timezoneObjects where timezoneObject.isSystemTimezone == true { timezoneObject.setLabel(customLabel) if let latlong = coordinates { timezoneObject.longitude = latlong.longitude timezoneObject.latitude = latlong.latitude } } var datas: [Data] = [] for updatedObject in timezoneObjects { guard let dataObject = NSKeyedArchiver.clocker_archive(with: updatedObject) else { continue } datas.append(dataObject) } store.setTimezones(datas) } } extension LocationController: CLLocationManagerDelegate { func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard !locations.isEmpty, let coordinates = locations.first?.coordinate else { return } let reverseGeoCoder = CLGeocoder() reverseGeoCoder.reverseGeocodeLocation(locations[0]) { placemarks, _ in guard let customLabel = placemarks?.first?.locality else { return } self.updateHomeObject(with: customLabel, coordinates: coordinates) self.locationManager.stopUpdatingLocation() } } func locationManager(_: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { if status == .denied || status == .restricted { updateHomeObject(with: TimeZone.autoupdatingCurrent.identifier, coordinates: nil) locationManager.stopUpdatingLocation() } else if status == .notDetermined || status == .authorized || status == .authorizedAlways { locationManager.startUpdatingLocation() } } func locationManager(_: CLLocationManager, didFailWithError error: Error) { Logger.info(error.localizedDescription) } } ================================================ FILE: Clocker/Clocker/MainMenu.xib ================================================ ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/.gitignore ================================================ # Xcode build/* *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 *.xcworkspace !default.xcworkspace xcuserdata profile *.moved-aside ## Ignore incredibly annoying .DS_Store files .DS_Store ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/LICENSE.md ================================================ # ShortcutRecorder Copyright (c) 2006, Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # PTHotKey Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ShortcutRecorder CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleShortVersionString 2.17 CFBundleSignature ???? CFBundleVersion 2.17 ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/Prefix.pch ================================================ #ifdef __OBJC__ #import #import #endif ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRCommon.h ================================================ // // SRCommon.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Andy Kim // Ilya Kulakov #import #import /*! Mask representing subset of Cocoa modifier flags suitable for shortcuts. */ static const NSEventModifierFlags SRCocoaModifierFlagsMask = NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagControl; /*! Mask representing subset of Carbon modifier flags suitable for shortcuts. */ static const NSUInteger SRCarbonModifierFlagsMask = cmdKey | optionKey | shiftKey | controlKey; /*! Converts carbon modifier flags to cocoa. */ FOUNDATION_STATIC_INLINE NSEventModifierFlags SRCarbonToCocoaFlags(UInt32 aCarbonFlags) { NSEventModifierFlags cocoaFlags = 0; if (aCarbonFlags & cmdKey) cocoaFlags |= NSEventModifierFlagCommand; if (aCarbonFlags & optionKey) cocoaFlags |= NSEventModifierFlagOption; if (aCarbonFlags & controlKey) cocoaFlags |= NSEventModifierFlagControl; if (aCarbonFlags & shiftKey) cocoaFlags |= NSEventModifierFlagShift; return cocoaFlags; } /*! Converts cocoa modifier flags to carbon. */ FOUNDATION_STATIC_INLINE UInt32 SRCocoaToCarbonFlags(NSEventModifierFlags aCocoaFlags) { UInt32 carbonFlags = 0; if (aCocoaFlags & NSEventModifierFlagCommand) carbonFlags |= cmdKey; if (aCocoaFlags & NSEventModifierFlagOption) carbonFlags |= optionKey; if (aCocoaFlags & NSEventModifierFlagControl) carbonFlags |= controlKey; if (aCocoaFlags & NSEventModifierFlagShift) carbonFlags |= shiftKey; return carbonFlags; } /*! Return Bundle where resources can be found. @discussion Throws NSInternalInconsistencyException if bundle cannot be found. */ NSBundle *SRBundle(void); /*! Convenient method to get localized string from the framework bundle. */ NSString *SRLoc(NSString *aKey); /*! Convenient method to get image from the framework bundle. */ NSImage *SRImage(NSString *anImageName); /*! Returns string representation of shortcut with modifier flags replaced with their localized readable equivalents (e.g. ? -> Option). */ NSString *SRReadableStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode); /*! Returns string representation of shortcut with modifier flags replaced with their localized readable equivalents (e.g. ? -> Option) and ASCII character for key code. */ NSString *SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode); /*! Determines if given key code with flags is equal to key equivalent and flags (usually taken from NSButton or NSMenu). @discussion On Mac OS X some key combinations can have "alternates". E.g. option-A can be represented both as option-A and as . */ BOOL SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, NSEventModifierFlags aKeyCodeFlags, NSString *aKeyEquivalent, NSEventModifierFlags aKeyEquivalentModifierFlags); ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRCommon.m ================================================ // // SRCommon.m // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Andy Kim // Ilya Kulakov #import "SRCommon.h" #import "SRKeyCodeTransformer.h" NSBundle *SRBundle(void) { static dispatch_once_t onceToken; static NSBundle *Bundle = nil; dispatch_once(&onceToken, ^{ Bundle = [NSBundle bundleWithIdentifier:@"com.kulakov.ShortcutRecorder"]; if (!Bundle) { // Could be a CocoaPods framework with embedded resources bundle. // Look up "use_frameworks!" and "resources_bundle" in CocoaPods documentation. Bundle = [NSBundle bundleWithIdentifier:@"org.cocoapods.ShortcutRecorder"]; if (!Bundle) { Class c = NSClassFromString(@"SRRecorderControl"); if (c) { Bundle = [NSBundle bundleForClass:c]; } } if (Bundle) { Bundle = [NSBundle bundleWithPath:[Bundle pathForResource:@"ShortcutRecorder" ofType:@"bundle"]]; } } }); if (!Bundle) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Unable to find bundle with resources." userInfo:nil]; } else { return Bundle; } } NSString *SRLoc(NSString *aKey) { return NSLocalizedStringFromTableInBundle(aKey, @"ShortcutRecorder", SRBundle(), nil); } NSImage *SRImage(NSString *anImageName) { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) return [[NSImage alloc] initByReferencingURL:[SRBundle() URLForImageResource:anImageName]]; else return [SRBundle() imageForResource:anImageName]; } NSString *SRReadableStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) { SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedPlainTransformer]; NSString *c = [t transformedValue:@(aKeyCode)]; return [NSString stringWithFormat:@"%@%@%@%@%@", (aModifierFlags & NSEventModifierFlagCommand ? SRLoc(@"Command-") : @""), (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), (aModifierFlags & NSEventModifierFlagShift ? SRLoc(@"Shift-") : @""), c]; } NSString *SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) { SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedPlainASCIITransformer]; NSString *c = [t transformedValue:@(aKeyCode)]; return [NSString stringWithFormat:@"%@%@%@%@%@", (aModifierFlags & NSEventModifierFlagCommand ? SRLoc(@"Command-") : @""), (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), (aModifierFlags & NSEventModifierFlagShift ? SRLoc(@"Shift-") : @""), c]; } static BOOL _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, NSEventModifierFlags aKeyCodeFlags, NSString *aKeyEquivalent, NSEventModifierFlags aKeyEquivalentModifierFlags, SRKeyCodeTransformer *aTransformer) { if (!aKeyEquivalent) return NO; aKeyCodeFlags &= SRCocoaModifierFlagsMask; aKeyEquivalentModifierFlags &= SRCocoaModifierFlagsMask; if (aKeyCodeFlags == aKeyEquivalentModifierFlags) { NSString *keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) withImplicitModifierFlags:nil explicitModifierFlags:@(aKeyCodeFlags)]; return [keyCodeRepresentation isEqual:aKeyEquivalent]; } else if (!aKeyEquivalentModifierFlags || (aKeyCodeFlags & aKeyEquivalentModifierFlags) == aKeyEquivalentModifierFlags) { // Some key equivalent modifier flags can be implicitly set by using special unicode characters. E.g. insetead of opt-a. // However all modifier flags explictily set in key equivalent MUST be also set in key code flags. // E.g. ctrl-/ctrl-opt-a and /opt-a match this condition, but cmd-/ctrl-opt-a doesn't. NSString *keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) withImplicitModifierFlags:nil explicitModifierFlags:@(aKeyCodeFlags)]; if ([keyCodeRepresentation isEqual:aKeyEquivalent]) { // Key code and key equivalent are not equal key code representation matches key equivalent, but modifier flags are not. return NO; } else { NSEventModifierFlags possiblyImplicitFlags = aKeyCodeFlags & ~aKeyEquivalentModifierFlags; keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) withImplicitModifierFlags:@(possiblyImplicitFlags) explicitModifierFlags:@(aKeyEquivalentModifierFlags)]; return [keyCodeRepresentation isEqual:aKeyEquivalent]; } } else return NO; } BOOL SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, NSEventModifierFlags aKeyCodeFlags, NSString *aKeyEquivalent, NSEventModifierFlags aKeyEquivalentModifierFlags) { BOOL isEqual = _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, aKeyCodeFlags, aKeyEquivalent, aKeyEquivalentModifierFlags, [SRKeyCodeTransformer sharedASCIITransformer]); if (!isEqual) { isEqual = _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, aKeyCodeFlags, aKeyEquivalent, aKeyEquivalentModifierFlags, [SRKeyCodeTransformer sharedTransformer]); } return isEqual; } ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyCodeTransformer.h ================================================ // // SRKeyCodeTransformer.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Ilya Kulakov // Silvio Rizzi #import #import /*! Transforms key code into unicode character or plain string. */ @interface SRKeyCodeTransformer : NSValueTransformer /*! Returns initialized key code transformer. @param aUsesASCII Determines whether transformer uses only ASCII capable keyboard input source. @param aUsesPlainStrings Determines whether key codes without readable glyphs (e.g. F1...F19) are transformed to to unicode characters (NSF1FunctionKey...NSF19FunctionKey) suitable for setting key equivalents of Cocoa controls or to plain strings (@"F1"...@"F19") suitable for drawing, logging and accessibility. @discussion This method is the designated initializer for SRKeyCodeTransformer. */ - (instancetype)initWithASCIICapableKeyboardInputSource:(BOOL)aUsesASCII plainStrings:(BOOL)aUsesPlainStrings; /*! Determines whether transformer uses ASCII capable keyboard input source. */ @property (readonly) BOOL usesASCIICapableKeyboardInputSource; /*! Determines whether key codes without readable glyphs are transformed to unicode characters suitable for setting keqEquivalents or to plain strings suitable for drawing, logging and accessibility. */ @property (readonly) BOOL usesPlainStrings; /*! Returns the shared transformer. */ + (instancetype)sharedTransformer; /*! Returns the shared transformer configured to use only ASCII capable keyboard input source. */ + (instancetype)sharedASCIITransformer; /*! Returns the shared transformer configured to transform key codes to plain strings. */ + (SRKeyCodeTransformer *)sharedPlainTransformer; /*! Returns the shared transformer configured to use only ASCII capable keyboard input source and to transform key codes to plain strings. */ + (SRKeyCodeTransformer *)sharedPlainASCIITransformer; /*! Returns mapping from special key codes to unicode characters. */ + (NSDictionary *)specialKeyCodesToUnicodeCharactersMapping; /*! Returns mapping from special key codes to plain strings. */ + (NSDictionary *)specialKeyCodesToPlainStringsMapping; /*! Determines whether key code is special. @param aKeyCode Key code to be checked. */ - (BOOL)isKeyCodeSpecial:(unsigned short)aKeyCode; /*! Transforms given special key code into unicode character by taking into account modifier flags. @discussion E.g. the key code 0x30 is transformed to ⇥. But if shift is pressed, it is transformed to ⇤. @result Unicode character or plain string. nil if not a special key code. */ - (NSString *)transformedSpecialKeyCode:(NSNumber *)aKeyCode withExplicitModifierFlags:(NSNumber *)aModifierFlags; /*! Shorcut to [self transformedValue:aValue withImplicitModifierFlags:aModifierFlags explicitModifierFlags:0] */ - (NSString *)transformedValue:(NSNumber *)aValue withModifierFlags:(NSNumber *)aModifierFlags; /*! Transfroms given key code into unicode character by taking into account modifier flags. @param aValue An instance of NSNumber (unsigned short) that represents key code. @param anImplicitModifierFlags An instance of NSNumber (NSEventModifierFlags) that represents implicit modifier flags like opt in å. @param anExplicitModifierFlags An instance of NSNumber (NSEventModifierFlags) that represents explicit modifier flags like shift in shift-⇤. */ - (NSString *)transformedValue:(NSNumber *)aValue withImplicitModifierFlags:(NSNumber *)anImplicitModifierFlags explicitModifierFlags:(NSNumber *)anExplicitModifierFlags; @end /*! These constants represents drawable unicode characters for key codes that do not have appropriate constants in Carbon and Cocoa. */ typedef NS_ENUM(unichar, SRKeyCodeGlyph) { SRKeyCodeGlyphTabRight = 0x21E5, // ⇥ SRKeyCodeGlyphTabLeft = 0x21E4, // ⇤ SRKeyCodeGlyphReturn = 0x2305, // ⌅ SRKeyCodeGlyphReturnR2L = 0x21A9, // ↩ SRKeyCodeGlyphDeleteLeft = 0x232B, // ⌫ SRKeyCodeGlyphDeleteRight = 0x2326, // ⌦ SRKeyCodeGlyphPadClear = 0x2327, // ⌧ SRKeyCodeGlyphLeftArrow = 0x2190, // ← SRKeyCodeGlyphRightArrow = 0x2192, // → SRKeyCodeGlyphUpArrow = 0x2191, // ↑ SRKeyCodeGlyphDownArrow = 0x2193, // ↓ SRKeyCodeGlyphPageDown = 0x21DF, // ⇟ SRKeyCodeGlyphPageUp = 0x21DE, // ⇞ SRKeyCodeGlyphNorthwestArrow = 0x2196, // ↖ SRKeyCodeGlyphSoutheastArrow = 0x2198, // ↘ SRKeyCodeGlyphEscape = 0x238B, // ⎋ SRKeyCodeGlyphSpace = 0x0020, // ' ' }; ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyCodeTransformer.m ================================================ // // SRKeyCodeTransformer.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Ilya Kulakov // Silvio Rizzi #import "SRKeyCodeTransformer.h" #import "SRCommon.h" FOUNDATION_STATIC_INLINE NSString* _SRUnicharToString(unichar aChar) { return [NSString stringWithFormat: @"%C", aChar]; } @implementation SRKeyCodeTransformer - (instancetype)initWithASCIICapableKeyboardInputSource:(BOOL)aUsesASCII plainStrings:(BOOL)aUsesPlainStrings { self = [super init]; if (self) { _usesASCIICapableKeyboardInputSource = aUsesASCII; _usesPlainStrings = aUsesPlainStrings; } return self; } - (instancetype)init { return [self initWithASCIICapableKeyboardInputSource:NO plainStrings:NO]; } #pragma mark Methods + (instancetype)sharedTransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO plainStrings:NO]; }); return Transformer; } + (instancetype)sharedASCIITransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES plainStrings:NO]; }); return Transformer; } + (instancetype)sharedPlainTransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO plainStrings:YES]; }); return Transformer; } + (SRKeyCodeTransformer *)sharedPlainASCIITransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES plainStrings:YES]; }); return Transformer; } + (NSDictionary *)specialKeyCodesToUnicodeCharactersMapping { // Most of these keys are system constans. // Values for rest of the keys were given by setting key equivalents in IB. static dispatch_once_t OnceToken; static NSDictionary *Mapping = nil; dispatch_once(&OnceToken, ^{ Mapping = @{ @(kVK_F1): _SRUnicharToString(NSF1FunctionKey), @(kVK_F2): _SRUnicharToString(NSF2FunctionKey), @(kVK_F3): _SRUnicharToString(NSF3FunctionKey), @(kVK_F4): _SRUnicharToString(NSF4FunctionKey), @(kVK_F5): _SRUnicharToString(NSF5FunctionKey), @(kVK_F6): _SRUnicharToString(NSF6FunctionKey), @(kVK_F7): _SRUnicharToString(NSF7FunctionKey), @(kVK_F8): _SRUnicharToString(NSF8FunctionKey), @(kVK_F9): _SRUnicharToString(NSF9FunctionKey), @(kVK_F10): _SRUnicharToString(NSF10FunctionKey), @(kVK_F11): _SRUnicharToString(NSF11FunctionKey), @(kVK_F12): _SRUnicharToString(NSF12FunctionKey), @(kVK_F13): _SRUnicharToString(NSF13FunctionKey), @(kVK_F14): _SRUnicharToString(NSF14FunctionKey), @(kVK_F15): _SRUnicharToString(NSF15FunctionKey), @(kVK_F16): _SRUnicharToString(NSF16FunctionKey), @(kVK_F17): _SRUnicharToString(NSF17FunctionKey), @(kVK_F18): _SRUnicharToString(NSF18FunctionKey), @(kVK_F19): _SRUnicharToString(NSF19FunctionKey), @(kVK_F20): _SRUnicharToString(NSF20FunctionKey), @(kVK_Space): _SRUnicharToString(' '), @(kVK_Delete): _SRUnicharToString(NSBackspaceCharacter), @(kVK_ForwardDelete): _SRUnicharToString(NSDeleteCharacter), @(kVK_ANSI_KeypadClear): _SRUnicharToString(NSClearLineFunctionKey), @(kVK_LeftArrow): _SRUnicharToString(NSLeftArrowFunctionKey), @(kVK_RightArrow): _SRUnicharToString(NSRightArrowFunctionKey), @(kVK_UpArrow): _SRUnicharToString(NSUpArrowFunctionKey), @(kVK_DownArrow): _SRUnicharToString(NSDownArrowFunctionKey), @(kVK_End): _SRUnicharToString(NSEndFunctionKey), @(kVK_Home): _SRUnicharToString(NSHomeFunctionKey), @(kVK_Escape): _SRUnicharToString('\e'), @(kVK_PageDown): _SRUnicharToString(NSPageDownFunctionKey), @(kVK_PageUp): _SRUnicharToString(NSPageUpFunctionKey), @(kVK_Return): _SRUnicharToString(NSCarriageReturnCharacter), @(kVK_ANSI_KeypadEnter): _SRUnicharToString(NSEnterCharacter), @(kVK_Tab): _SRUnicharToString(NSTabCharacter), @(kVK_Help): _SRUnicharToString(NSHelpFunctionKey) }; }); return Mapping; } + (NSDictionary *)specialKeyCodesToPlainStringsMapping { static dispatch_once_t OnceToken; static NSDictionary *Mapping = nil; dispatch_once(&OnceToken, ^{ Mapping = @{ @(kVK_F1): @"F1", @(kVK_F2): @"F2", @(kVK_F3): @"F3", @(kVK_F4): @"F4", @(kVK_F5): @"F5", @(kVK_F6): @"F6", @(kVK_F7): @"F7", @(kVK_F8): @"F8", @(kVK_F9): @"F9", @(kVK_F10): @"F10", @(kVK_F11): @"F11", @(kVK_F12): @"F12", @(kVK_F13): @"F13", @(kVK_F14): @"F14", @(kVK_F15): @"F15", @(kVK_F16): @"F16", @(kVK_F17): @"F17", @(kVK_F18): @"F18", @(kVK_F19): @"F19", @(kVK_F20): @"F20", @(kVK_Space): SRLoc(@"Space"), @(kVK_Delete): _SRUnicharToString(SRKeyCodeGlyphDeleteLeft), @(kVK_ForwardDelete): _SRUnicharToString(SRKeyCodeGlyphDeleteRight), @(kVK_ANSI_KeypadClear): _SRUnicharToString(SRKeyCodeGlyphPadClear), @(kVK_LeftArrow): _SRUnicharToString(SRKeyCodeGlyphLeftArrow), @(kVK_RightArrow): _SRUnicharToString(SRKeyCodeGlyphRightArrow), @(kVK_UpArrow): _SRUnicharToString(SRKeyCodeGlyphUpArrow), @(kVK_DownArrow): _SRUnicharToString(SRKeyCodeGlyphDownArrow), @(kVK_End): _SRUnicharToString(SRKeyCodeGlyphSoutheastArrow), @(kVK_Home): _SRUnicharToString(SRKeyCodeGlyphNorthwestArrow), @(kVK_Escape): _SRUnicharToString(SRKeyCodeGlyphEscape), @(kVK_PageDown): _SRUnicharToString(SRKeyCodeGlyphPageDown), @(kVK_PageUp): _SRUnicharToString(SRKeyCodeGlyphPageUp), @(kVK_Return): _SRUnicharToString(SRKeyCodeGlyphReturnR2L), @(kVK_ANSI_KeypadEnter): _SRUnicharToString(SRKeyCodeGlyphReturn), @(kVK_Tab): _SRUnicharToString(SRKeyCodeGlyphTabRight), @(kVK_Help): @"?⃝" }; }); return Mapping; } - (BOOL)isKeyCodeSpecial:(unsigned short)aKeyCode { switch (aKeyCode) { case kVK_F1: case kVK_F2: case kVK_F3: case kVK_F4: case kVK_F5: case kVK_F6: case kVK_F7: case kVK_F8: case kVK_F9: case kVK_F10: case kVK_F11: case kVK_F12: case kVK_F13: case kVK_F14: case kVK_F15: case kVK_F16: case kVK_F17: case kVK_F18: case kVK_F19: case kVK_F20: case kVK_Space: case kVK_Delete: case kVK_ForwardDelete: case kVK_ANSI_KeypadClear: case kVK_LeftArrow: case kVK_RightArrow: case kVK_UpArrow: case kVK_DownArrow: case kVK_End: case kVK_Home: case kVK_Escape: case kVK_PageDown: case kVK_PageUp: case kVK_Return: case kVK_ANSI_KeypadEnter: case kVK_Tab: case kVK_Help: return YES; default: return NO; } } #pragma mark NSValueTransformer + (BOOL)allowsReverseTransformation { return NO; } + (Class)transformedValueClass; { return [NSString class]; } - (NSString *)transformedValue:(NSNumber *)aValue { return [self transformedValue:aValue withModifierFlags:nil]; } - (NSString *)transformedValue:(NSNumber *)aValue withModifierFlags:(NSNumber *)aModifierFlags { return [self transformedValue:aValue withImplicitModifierFlags:aModifierFlags explicitModifierFlags:nil]; } - (NSString *)transformedValue:(NSNumber *)aValue withImplicitModifierFlags:(NSNumber *)anImplicitModifierFlags explicitModifierFlags:(NSNumber *)anExplicitModifierFlags { if ([anImplicitModifierFlags unsignedIntegerValue] & [anExplicitModifierFlags unsignedIntegerValue] & SRCocoaModifierFlagsMask) { [NSException raise:NSInvalidArgumentException format:@"anImplicitModifierFlags and anExplicitModifierFlags MUST NOT have common elements"]; } if (![aValue isKindOfClass:[NSNumber class]]) return @""; // Some key codes cannot be translated directly. NSString *unmappedString = [self transformedSpecialKeyCode:aValue withExplicitModifierFlags:anExplicitModifierFlags]; if (unmappedString) return unmappedString; CFDataRef layoutData = NULL; if (self.usesASCIICapableKeyboardInputSource) { TISInputSourceRef tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); } else { TISInputSourceRef tisSource = TISCopyCurrentKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); // For non-unicode layouts such as Chinese, Japanese, and Korean, get the ASCII capable layout if (!layoutData) { tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); } } if (!layoutData) return @""; const UCKeyboardLayout *keyLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); static const UniCharCount MaxLength = 255; UniCharCount actualLength = 0; UniChar chars[MaxLength] = {0}; UInt32 deadKeyState = 0; OSStatus err = UCKeyTranslate(keyLayout, [aValue unsignedShortValue], kUCKeyActionDisplay, SRCocoaToCarbonFlags([anImplicitModifierFlags unsignedIntegerValue]) >> 8, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, sizeof(chars) / sizeof(UniChar), &actualLength, chars); if (err != noErr) return @""; if (self.usesPlainStrings) return [[NSString stringWithCharacters:chars length:actualLength] uppercaseString]; else return [NSString stringWithCharacters:chars length:actualLength]; } - (NSString *)transformedSpecialKeyCode:(NSNumber *)aKeyCode withExplicitModifierFlags:(NSNumber *)anExplicitModifierFlags { if ([anExplicitModifierFlags unsignedIntegerValue] & NSEventModifierFlagShift && [aKeyCode unsignedShortValue] == kVK_Tab) { if (self.usesPlainStrings) return _SRUnicharToString(SRKeyCodeGlyphTabLeft); else return _SRUnicharToString(NSBackTabCharacter); } if (self.usesPlainStrings) return [[self class] specialKeyCodesToPlainStringsMapping][aKeyCode]; else return [[self class] specialKeyCodesToUnicodeCharactersMapping][aKeyCode]; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyEquivalentModifierMaskTransformer.h ================================================ // // SRKeyEquivalentModifierMaskTransformer.h // ShortcutRecorder // // Copyright 2012 Contributors. All rights reserved. // // License: BSD // // Contributors to this file: // Ilya Kulakov #import /*! Transform dictionary representation of shortcut into string suitable for -setKeyEquivalentModifierMask: of NSButton and NSMenuItem. */ @interface SRKeyEquivalentModifierMaskTransformer : NSValueTransformer @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyEquivalentModifierMaskTransformer.m ================================================ // // SRKeyEquivalentModifierMaskTransformer.m // ShortcutRecorder // // Copyright 2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // Ilya Kulakov #import "SRKeyEquivalentModifierMaskTransformer.h" #import "SRKeyCodeTransformer.h" #import "SRRecorderControl.h" @implementation SRKeyEquivalentModifierMaskTransformer #pragma mark NSValueTransformer + (BOOL)allowsReverseTransformation { return NO; } + (Class)transformedValueClass { return [NSNumber class]; } - (NSNumber *)transformedValue:(NSDictionary *)aValue { if (![aValue isKindOfClass:[NSDictionary class]]) return @(0); NSNumber *modifierFlags = aValue[SRShortcutModifierFlagsKey]; if (![modifierFlags isKindOfClass:[NSNumber class]]) return @(0); return modifierFlags; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyEquivalentTransformer.h ================================================ // // SRKeyEquivalentTransformer.h // ShortcutRecorder // // Copyright 2012 Contributors. All rights reserved. // // License: BSD // // Contributors to this file: // Ilya Kulakov #import /*! Transform dictionary representation of shortcut into string suitable for -setKeyEquivalent: of NSButton and NSMenuItem. */ @interface SRKeyEquivalentTransformer : NSValueTransformer @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRKeyEquivalentTransformer.m ================================================ // // SRKeyEquivalentTransformer.m // ShortcutRecorder // // Copyright 2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // Ilya Kulakov #import "SRKeyEquivalentTransformer.h" #import "SRKeyCodeTransformer.h" #import "SRRecorderControl.h" @implementation SRKeyEquivalentTransformer #pragma mark NSValueTransformer + (BOOL)allowsReverseTransformation { return NO; } + (Class)transformedValueClass { return [NSString class]; } - (NSString *)transformedValue:(NSDictionary *)aValue { if (![aValue isKindOfClass:[NSDictionary class]]) return @""; NSNumber *keyCode = aValue[SRShortcutKeyCode]; if (![keyCode isKindOfClass:[NSNumber class]]) return @""; NSNumber *modifierFlags = aValue[SRShortcutModifierFlagsKey]; if (![modifierFlags isKindOfClass:[NSNumber class]]) modifierFlags = @(0); SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedASCIITransformer]; return [t transformedValue:keyCode withImplicitModifierFlags:nil explicitModifierFlags:modifierFlags]; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRModifierFlagsTransformer.h ================================================ // // SRModifierFlagsTransformer.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // Ilya Kulakov #import /*! Transforms mask of Cocoa modifier flags to string of unicode characters. */ @interface SRModifierFlagsTransformer : NSValueTransformer - (instancetype)initWithPlainStrings:(BOOL)aUsesPlainStrings; /*! Determines whether modifier flags are transformed to unicode characters or to plain strings. */ @property (readonly) BOOL usesPlainStrings; /*! Returns the shared transformer. */ + (instancetype)sharedTransformer; /*! Returns the shared plain transformer. */ + (instancetype)sharedPlainTransformer; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRModifierFlagsTransformer.m ================================================ // // SRModifierFlagsTransformer.m // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // Ilya Kulakov #import "SRModifierFlagsTransformer.h" #import "SRCommon.h" @implementation SRModifierFlagsTransformer - (instancetype)initWithPlainStrings:(BOOL)aUsesPlainStrings { self = [super init]; if (self) { _usesPlainStrings = aUsesPlainStrings; } return self; } - (instancetype)init { return [self initWithPlainStrings:NO]; } #pragma mark Methods + (instancetype)sharedTransformer { static dispatch_once_t OnceToken; static SRModifierFlagsTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithPlainStrings:NO]; }); return Transformer; } + (instancetype)sharedPlainTransformer { static dispatch_once_t OnceToken; static SRModifierFlagsTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithPlainStrings:YES]; }); return Transformer; } #pragma mark NSValueTransformer + (Class)transformedValueClass { return [NSString class]; } + (BOOL)allowsReverseTransformation { return NO; } - (NSString *)transformedValue:(NSNumber *)aValue { if (![aValue isKindOfClass:[NSNumber class]]) return nil; else if (self.usesPlainStrings) { NSEventModifierFlags modifierFlags = [aValue unsignedIntegerValue]; NSMutableString *s = [NSMutableString string]; if (modifierFlags & NSEventModifierFlagControl) [s appendString:SRLoc(@"Control-")]; if (modifierFlags & NSEventModifierFlagOption) [s appendString:SRLoc(@"Option-")]; if (modifierFlags & NSEventModifierFlagShift) [s appendString:SRLoc(@"Shift-")]; if (modifierFlags & NSEventModifierFlagCommand) [s appendString:SRLoc(@"Command-")]; if (s.length > 0) [s deleteCharactersInRange:NSMakeRange(s.length - 1, 1)]; return s; } else { NSEventModifierFlags f = [aValue unsignedIntegerValue]; return [NSString stringWithFormat:@"%@%@%@%@", (f & NSEventModifierFlagControl ? @"⌃" : @""), (f & NSEventModifierFlagOption ? @"⌥" : @""), (f & NSEventModifierFlagShift ? @"⇧" : @""), (f & NSEventModifierFlagCommand ? @"⌘" : @"")]; } } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRRecorderControl.h ================================================ // // SRRecorderControl.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Ilya Kulakov #import #import /*! Key code. @discussion NSNumber representation of unsigned short. Required key of SRRecorderControl's objectValue. */ extern NSString *const SRShortcutKeyCode; /*! Modifier flags. @discussion NSNumber representation of NSEventModifierFlags. Optional key of SRRecorderControl's objectValue. */ extern NSString *const SRShortcutModifierFlagsKey; /*! Interpretation of key code and modifier flags depending on system locale and input source used when shortcut was taken. @discussion NSString. Optional key of SRRecorderControl's objectValue. */ extern NSString *const SRShortcutCharacters; /*! Interpretation of key code without modifier flags depending on system locale and input source used when shortcut was taken. @discussion NSString. Optional key of SRRecorderControl's objectValue. */ extern NSString *const SRShortcutCharactersIgnoringModifiers; @protocol SRRecorderControlDelegate; /*! An SRRecorderControl object is a control (but not a subclass of NSControl) that allows you to record shortcuts. @discussion In addition to NSView bindings, exposes: NSValueBinding. This binding supports 2 options: - NSValueTransformerBindingOption - NSValueTransformerNameBindingOption NSEnabledBinding. This binding supports 2 options: - NSValueTransformerBindingOption - NSValueTransformerNameBindingOption Note that at that moment, this binding _is not_ multivalue. Required height: 25 points Recommended min width: 100 points */ IB_DESIGNABLE @interface SRRecorderControl : NSView /* */ /*! The receiver’s delegate. @discussion A recorder control delegate responds to editing-related messages. You can use to to prevent editing in some cases or to validate typed shortcuts. */ @property (assign) IBOutlet NSObject *delegate; /*! Returns an integer bit field indicating allowed modifier flags. @discussion Defaults to SRCocoaModifierFlagsMask. */ @property (readonly) IBInspectable NSEventModifierFlags allowedModifierFlags; /*! Returns an integer bit field indicating required modifier flags. @discussion Defaults to 0. */ @property (readonly) IBInspectable NSEventModifierFlags requiredModifierFlags; /*! Determines whether shortcuts without modifier flags are allowed. @discussion Defaults to NO. */ @property (readonly) IBInspectable BOOL allowsEmptyModifierFlags; /*! Determines whether the control reinterpret key code and modifier flags using ASCII capable input source. @discussion Defaults to YES. If not set, the same key code may be draw differently depending on current input source. E.g. with US English input source key code 0x0 is interpreted as "a", however with Russian input source, it's interpreted as "ф". */ @property IBInspectable BOOL drawsASCIIEquivalentOfShortcut; /*! Determines whether Escape is used to cancel recording. @discussion Defaults to YES. If set, Escape without modifier flags cannot be recorded as shortcut. */ @property IBInspectable BOOL allowsEscapeToCancelRecording; /*! Determines whether delete (or forward delete) is used to remove current shortcut and end recording. @discussion Defaults to YES. If set, neither Delete nor Forward Delete without modifier flags can be recorded as shortcut. */ @property IBInspectable BOOL allowsDeleteToClearShortcutAndEndRecording; /*! Determines whether control enabled and can be edited or not. @discussion Defaults to YES. */ @property (nonatomic, getter=isEnabled) IBInspectable BOOL enabled; /*! Determines whether recording is in process. */ @property (nonatomic, readonly) BOOL isRecording; /*! Returns dictionary representation of receiver's shortcut. */ @property (nonatomic, copy) NSDictionary *objectValue; /*! Configures recording behavior of the control. @param newAllowedModifierFlags New allowed modifier flags. @param newRequiredModifierFlags New required modifier flags. @param newAllowsEmptyModifierFlags Determines whether empty modifier flags are allowed. @discussion Flags are filtered using SRCocoaModifierFlagsMask. Flags does not affect object values set manually. These restrictions can be ignored if delegate implements shortcutRecorder:shouldUnconditionallyAllowModifierFlags:forKeyCode: and returns YES for given modifier flags and key code. Throws NSInvalidArgumentException if either required flags are not allowed or required flags are not empty and no modifier flags are allowed. @see SRRecorderControlDelegate */ - (void)setAllowedModifierFlags:(NSEventModifierFlags)newAllowedModifierFlags requiredModifierFlags:(NSEventModifierFlags)newRequiredModifierFlags allowsEmptyModifierFlags:(BOOL)newAllowsEmptyModifierFlags; /*! Called to initialize internal state after either initWithFrame or awakeFromNib is called. */ - (void)_initInternalState; /*! Turns on the recording mode. @discussion You SHOULD not call this method directly. */ - (BOOL)beginRecording; /*! Turns off the recording mode. Current object value is preserved. @discussion You SHOULD not call this method directly. */ - (void)endRecording; /*! Clears object value and turns off the recording mode. @discussion You SHOULD not call this method directly. */ - (void)clearAndEndRecording; /*! Designated method to end recording. Sets a given object value, updates bindings and turns off the recording mode. @discussion You SHOULD not call this method directly. */ - (void)endRecordingWithObjectValue:(NSDictionary *)anObjectValue; /*! Returns shape of the control. @discussion Primarily used to draw appropriate focus ring. */ - (NSBezierPath *)controlShape; /*! Returns rect for label with given attributes. @param aLabel Label for drawing. @param anAttributes A dictionary of NSAttributedString text attributes to be applied to the string. */ - (NSRect)rectForLabel:(NSString *)aLabel withAttributes:(NSDictionary *)anAttributes; /*! Returns rect of the snap back button in the receiver coordinates. */ - (NSRect)snapBackButtonRect; /*! Returns rect of the clear button in the receiver coordinates. @discussion Returned rect will have empty width (other values will be valid) if button should not be drawn. */ - (NSRect)clearButtonRect; /*! Returns label to be displayed by the receiver. @discussion Returned value depends on isRecording state objectValue and currenlty pressed keys and modifier flags. */ - (NSString *)label; /*! Returns label for accessibility. @discussion Returned value depends on isRecording state objectValue and currenlty pressed keys and modifier flags. */ - (NSString *)accessibilityLabel; /*! Returns string representation of object value. */ - (NSString *)stringValue; /*! Returns string representation of object value for accessibility. */ - (NSString *)accessibilityStringValue; /*! Returns attirbutes of label to be displayed by the receiver according to current state. @see normalLabelAttributes @see recordingLabelAttributes @see disabledLabelAttributes */ - (NSDictionary *)labelAttributes; /*! Returns attributes of label to be displayed by the receiver in normal mode. */ - (NSDictionary *)normalLabelAttributes; /*! Returns attributes of label to be displayed by the receiver in recording mode. */ - (NSDictionary *)recordingLabelAttributes; /*! Returns attributes of label to be displayed by the receiver in disabled mode. */ - (NSDictionary *)disabledLabelAttributes; /*! Draws background of the receiver into current graphics context. */ - (void)drawBackground:(NSRect)aDirtyRect; /*! Draws interior of the receiver into current graphics context. */ - (void)drawInterior:(NSRect)aDirtyRect; /*! Draws label of the receiver into current graphics context. */ - (void)drawLabel:(NSRect)aDirtyRect; /*! Draws snap back button of the receiver into current graphics context. */ - (void)drawSnapBackButton:(NSRect)aDirtyRect; /*! Draws clear button of the receiver into current graphics context. */ - (void)drawClearButton:(NSRect)aDirtyRect; /*! Determines whether main button (representation of the receiver in normal mode) is highlighted. */ - (BOOL)isMainButtonHighlighted; /*! Determines whether snap back button is highlighted. */ - (BOOL)isSnapBackButtonHighlighted; /*! Determines whetehr clear button is highlighted. */ - (BOOL)isClearButtonHighlighted; /*! Determines whether modifier flags are valid for key code according to the receiver settings. @param aModifierFlags Proposed modifier flags. @param aKeyCode Code of the pressed key. @see allowedModifierFlags @see allowsEmptyModifierFlags @see requiredModifierFlags */ - (BOOL)areModifierFlagsValid:(NSEventModifierFlags)aModifierFlags forKeyCode:(unsigned short)aKeyCode; /*! A helper method to propagate view-driven changes back to model. @discussion This method makes it easier to propagate changes from a view back to the model without overriding bind:toObject:withKeyPath:options: @see http://tomdalling.com/blog/cocoa/implementing-your-own-cocoa-bindings/ */ - (void)propagateValue:(id)aValue forBinding:(NSString *)aBinding; @end @protocol SRRecorderControlDelegate @optional /*! Asks the delegate if editing should begin in the specified shortcut recorder. @param aRecorder The shortcut recorder which editing is about to begin. @result YES if an editing session should be initiated; otherwise, NO to disallow editing. @discussion Implementation of this method by the delegate is optional. If it is not present, editing proceeds as if this method had returned YES. */ - (BOOL)shortcutRecorderShouldBeginRecording:(SRRecorderControl *)aRecorder; /*! Gives a delegate opportunity to bypass rules specified by allowed and required modifier flags. @param aRecorder The shortcut recorder for which editing ended. @param aModifierFlags Proposed modifier flags. @param aKeyCode Code of the pressed key. @result YES if recorder should bypass key code with given modifier flags despite settings like required modifier flags, allowed modifier flags. @discussion Implementation of this method by the delegate is optional. Normally, you wouldn't allow a user to record shourcut without modifier flags set: disallow 'a', but allow cmd-'a'. However, some keys were designed to be key shortcuts by itself. E.g. Functional keys. By implementing this method a delegate can allow these special keys to be set without modifier flags even when the control is configured to disallow empty modifier flags. @see allowedModifierFlags @see allowsEmptyModifierFlags @see requiredModifierFlags */ - (BOOL)shortcutRecorder:(SRRecorderControl *)aRecorder shouldUnconditionallyAllowModifierFlags:(NSEventModifierFlags)aModifierFlags forKeyCode:(unsigned short)aKeyCode; /*! Asks the delegate if the shortcut can be set by the specified shortcut recorder. @param aRecorder The shortcut recorder which shortcut is beign to be recordered. @param aShortcut The Shortcut user typed. @result YES if shortcut can be recordered. Otherwise NO. @discussion Implementation of this method by the delegate is optional. If it is not present, shortcut is recordered as if this method had returned YES. You may implement this method to filter shortcuts that were already set by other recorders. @see SRValidator */ - (BOOL)shortcutRecorder:(SRRecorderControl *)aRecorder canRecordShortcut:(NSDictionary *)aShortcut; /*! Tells the delegate that editing stopped for the specified shortcut recorder. @param aRecorder The shortcut recorder for which editing ended. @discussion Implementation of this method by the delegate is optional. */ - (void)shortcutRecorderDidEndRecording:(SRRecorderControl *)aRecorder; @end FOUNDATION_STATIC_INLINE BOOL SRShortcutEqualToShortcut(NSDictionary *a, NSDictionary *b) { if (a == b) return YES; else if (a && !b) return NO; else if (!a && b) return NO; else return ([a[SRShortcutKeyCode] isEqual:b[SRShortcutKeyCode]] && [a[SRShortcutModifierFlagsKey] isEqual:b[SRShortcutModifierFlagsKey]]); } FOUNDATION_STATIC_INLINE NSDictionary *SRShortcutWithCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) { return @{SRShortcutKeyCode: @(aKeyCode), SRShortcutModifierFlagsKey: @(aModifierFlags)}; } ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRRecorderControl.m ================================================ // // SRRecorderControl.m // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Ilya Kulakov #include #import "SRRecorderControl.h" #import "SRKeyCodeTransformer.h" #import "SRModifierFlagsTransformer.h" NSString *const SRShortcutKeyCode = @"keyCode"; NSString *const SRShortcutModifierFlagsKey = @"modifierFlags"; NSString *const SRShortcutCharacters = @"characters"; NSString *const SRShortcutCharactersIgnoringModifiers = @"charactersIgnoringModifiers"; // Control Layout Constants static const CGFloat _SRRecorderControlYosemiteShapeXRadius = 2.0; static const CGFloat _SRRecorderControlYosemiteShapeYRadius = 2.0; static const CGFloat _SRRecorderControlShapeXRadius = 11.0; static const CGFloat _SRRecorderControlShapeYRadius = 12.0; static const CGFloat _SRRecorderControlHeight = 25.0; static const CGFloat _SRRecorderControlBottomShadowHeightInPixels = 1.0; // TODO: see baselineOffsetFromBottom // static const CGFloat _SRRecorderControlBaselineOffset = 5.0; // Clear Button Layout Constants static const CGFloat _SRRecorderControlClearButtonWidth = 14.0; static const CGFloat _SRRecorderControlClearButtonHeight = 14.0; static const CGFloat _SRRecorderControlClearButtonRightOffset = 4.0; static const CGFloat _SRRecorderControlClearButtonLeftOffset = 1.0; static const NSSize _SRRecorderControlClearButtonSize = {.width = _SRRecorderControlClearButtonWidth, .height = _SRRecorderControlClearButtonHeight}; // SanpBack Button Layout Constants static const CGFloat _SRRecorderControlSnapBackButtonWidth = 14.0; static const CGFloat _SRRecorderControlSnapBackButtonHeight = 14.0; static const CGFloat _SRRecorderControlSnapBackButtonRightOffset = 1.0; static const CGFloat _SRRecorderControlSnapBackButtonLeftOffset = 3.0; static const NSSize _SRRecorderControlSnapBackButtonSize = {.width = _SRRecorderControlSnapBackButtonWidth, .height = _SRRecorderControlSnapBackButtonHeight}; static NSImage *_SRImages[19]; typedef NS_ENUM(NSUInteger, _SRRecorderControlButtonTag) { _SRRecorderControlInvalidButtonTag = -1, _SRRecorderControlSnapBackButtonTag = 0, _SRRecorderControlClearButtonTag = 1, _SRRecorderControlMainButtonTag = 2 }; @implementation SRRecorderControl { NSTrackingArea *_mainButtonTrackingArea; NSTrackingArea *_snapBackButtonTrackingArea; NSTrackingArea *_clearButtonTrackingArea; _SRRecorderControlButtonTag _mouseTrackingButtonTag; NSToolTipTag _snapBackButtonToolTipTag; CGFloat _shapeXRadius; CGFloat _shapeYRadious; } - (instancetype)initWithFrame:(NSRect)aFrameRect { self = [super initWithFrame:aFrameRect]; if (self) { [self _initInternalState]; } return self; } - (void)_initInternalState { _allowsEmptyModifierFlags = NO; _drawsASCIIEquivalentOfShortcut = YES; _allowsEscapeToCancelRecording = YES; _allowsDeleteToClearShortcutAndEndRecording = YES; _enabled = YES; _allowedModifierFlags = SRCocoaModifierFlagsMask; _requiredModifierFlags = 0; _mouseTrackingButtonTag = _SRRecorderControlInvalidButtonTag; _snapBackButtonToolTipTag = NSIntegerMax; if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) { self.translatesAutoresizingMaskIntoConstraints = NO; [self setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; [self setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; [self setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; [self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; } if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) { _shapeXRadius = _SRRecorderControlShapeXRadius; _shapeYRadious = _SRRecorderControlShapeYRadius; } else { _shapeXRadius = _SRRecorderControlYosemiteShapeXRadius; _shapeYRadious = _SRRecorderControlYosemiteShapeYRadius; } [self setToolTip:SRLoc(@"Click to record shortcut")]; [self updateTrackingAreas]; } #pragma mark Properties - (void)setAllowedModifierFlags:(NSEventModifierFlags)newAllowedModifierFlags requiredModifierFlags:(NSEventModifierFlags)newRequiredModifierFlags allowsEmptyModifierFlags:(BOOL)newAllowsEmptyModifierFlags { newAllowedModifierFlags &= SRCocoaModifierFlagsMask; newRequiredModifierFlags &= SRCocoaModifierFlagsMask; if ((newAllowedModifierFlags & newRequiredModifierFlags) != newRequiredModifierFlags) { [NSException raise:NSInvalidArgumentException format:@"Required flags (%lu) MUST be allowed (%lu)", newAllowedModifierFlags, newRequiredModifierFlags]; } if (newAllowsEmptyModifierFlags && newRequiredModifierFlags != 0) { [NSException raise:NSInvalidArgumentException format:@"Empty modifier flags MUST be disallowed if required modifier flags are not empty."]; } _allowedModifierFlags = newAllowedModifierFlags; _requiredModifierFlags = newRequiredModifierFlags; _allowsEmptyModifierFlags = newAllowsEmptyModifierFlags; } - (void)setEnabled:(BOOL)newEnabled { _enabled = newEnabled; [self setNeedsDisplay:YES]; if (!_enabled) [self endRecording]; // Focus ring is only drawn when view is enabled if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) [self noteFocusRingMaskChanged]; } - (void)setObjectValue:(NSDictionary *)newObjectValue { // Cocoa KVO and KVC frequently uses NSNull as object substituation of nil. // SRRecorderControl expects either nil or valid object value, it's convenient // to handle NSNull here and convert it into nil. if ((NSNull *)newObjectValue == [NSNull null]) newObjectValue = nil; _objectValue = [newObjectValue copy]; [self propagateValue:_objectValue forBinding:NSValueBinding]; if (!self.isRecording) { NSAccessibilityPostNotification(self, NSAccessibilityTitleChangedNotification); [self setNeedsDisplay:YES]; } } #pragma mark Methods - (BOOL)beginRecording { if (!self.enabled) return NO; if (self.isRecording) return YES; [self setNeedsDisplay:YES]; if ([self.delegate respondsToSelector:@selector(shortcutRecorderShouldBeginRecording:)]) { if (![self.delegate shortcutRecorderShouldBeginRecording:self]) { NSBeep(); return NO; } } [self willChangeValueForKey:@"isRecording"]; _isRecording = YES; [self didChangeValueForKey:@"isRecording"]; [self updateTrackingAreas]; [self setToolTip:SRLoc(@"Type shortcut")]; NSAccessibilityPostNotification(self, NSAccessibilityTitleChangedNotification); return YES; } - (void)endRecording { [self endRecordingWithObjectValue:self.objectValue]; } - (void)clearAndEndRecording { [self endRecordingWithObjectValue:nil]; } - (void)endRecordingWithObjectValue:(NSDictionary *)anObjectValue { if (!self.isRecording) return; [self willChangeValueForKey:@"isRecording"]; _isRecording = NO; [self didChangeValueForKey:@"isRecording"]; self.objectValue = anObjectValue; [self updateTrackingAreas]; [self setToolTip:SRLoc(@"Click to record shortcut")]; [self setNeedsDisplay:YES]; NSAccessibilityPostNotification(self, NSAccessibilityTitleChangedNotification); if (self.window.firstResponder == self && ![self canBecomeKeyView]) [self.window makeFirstResponder:nil]; if ([self.delegate respondsToSelector:@selector(shortcutRecorderDidEndRecording:)]) [self.delegate shortcutRecorderDidEndRecording:self]; } #pragma mark - - (NSBezierPath *)controlShape { NSRect shapeBounds = self.bounds; shapeBounds.size.height = _SRRecorderControlHeight - self.alignmentRectInsets.bottom; if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) { shapeBounds = NSInsetRect(shapeBounds, 1.0, 1.0); } return [NSBezierPath bezierPathWithRoundedRect:shapeBounds xRadius:_shapeXRadius yRadius:_shapeYRadious]; } - (NSRect)rectForLabel:(NSString *)aLabel withAttributes:(NSDictionary *)anAttributes { NSSize labelSize = [aLabel sizeWithAttributes:anAttributes]; NSRect enclosingRect = NSInsetRect(self.bounds, _shapeXRadius, 0.0); labelSize.width = fmin(ceil(labelSize.width), NSWidth(enclosingRect)); labelSize.height = ceil(labelSize.height); CGFloat fontBaselineOffsetFromTop = labelSize.height + [anAttributes[NSFontAttributeName] descender]; CGFloat baselineOffsetFromTop = _SRRecorderControlHeight - self.baselineOffsetFromBottom; NSRect labelRect = { .origin = NSMakePoint(NSMidX(enclosingRect) - labelSize.width / 2.0, baselineOffsetFromTop - fontBaselineOffsetFromTop), .size = labelSize }; labelRect = [self centerScanRect:labelRect]; // Ensure label and buttons do not overlap. if (self.isRecording) { CGFloat rightOffsetFromButtons = NSMinX(self.snapBackButtonRect) - NSMaxX(labelRect); if (rightOffsetFromButtons < 0.0) { labelRect = NSOffsetRect(labelRect, rightOffsetFromButtons, 0.0); if (NSMinX(labelRect) < NSMinX(enclosingRect)) { labelRect.size.width -= NSMinX(enclosingRect) - NSMinX(labelRect); labelRect.origin.x = NSMinX(enclosingRect); } } } #ifdef DEBUG if (labelRect.size.width < labelSize.width || labelRect.size.height < labelSize.height) NSLog(@"WARNING: label rect (%@) is smaller than label size (%@). You may want to adjust size of the control.", NSStringFromRect(labelRect), NSStringFromSize(labelSize)); #endif return labelRect; } - (NSRect)snapBackButtonRect { NSRect clearButtonRect = self.clearButtonRect; NSRect bounds = self.bounds; NSRect snapBackButtonRect = NSZeroRect; snapBackButtonRect.origin.x = NSMinX(clearButtonRect) - _SRRecorderControlSnapBackButtonRightOffset - _SRRecorderControlSnapBackButtonSize.width - _SRRecorderControlSnapBackButtonLeftOffset; snapBackButtonRect.origin.y = NSMinY(bounds); snapBackButtonRect.size.width = fdim(NSMinX(clearButtonRect), NSMinX(snapBackButtonRect)); snapBackButtonRect.size.height = _SRRecorderControlHeight; return snapBackButtonRect; } - (NSRect)clearButtonRect { NSRect bounds = self.bounds; if ([self.objectValue count]) { NSRect clearButtonRect = NSZeroRect; clearButtonRect.origin.x = NSMaxX(bounds) - _SRRecorderControlClearButtonRightOffset - _SRRecorderControlClearButtonSize.width - _SRRecorderControlClearButtonLeftOffset; clearButtonRect.origin.y = NSMinY(bounds); clearButtonRect.size.width = fdim(NSMaxX(bounds), NSMinX(clearButtonRect)); clearButtonRect.size.height = _SRRecorderControlHeight; return clearButtonRect; } else { return NSMakeRect(NSMaxX(bounds) - _SRRecorderControlClearButtonRightOffset - _SRRecorderControlClearButtonLeftOffset, NSMinY(bounds), 0.0, _SRRecorderControlHeight); } } #pragma mark - - (NSString *)label { NSString *label = nil; if (self.isRecording) { NSEventModifierFlags modifierFlags = [NSEvent modifierFlags] & self.allowedModifierFlags; if (modifierFlags) label = [[SRModifierFlagsTransformer sharedTransformer] transformedValue:@(modifierFlags)]; else label = self.stringValue; if (![label length]) label = SRLoc(@"Type shortcut"); } else { label = self.stringValue; if (![label length]) label = SRLoc(@"Click to record shortcut"); } return label; } - (NSString *)accessibilityLabel { NSString *label = nil; if (self.isRecording) { NSEventModifierFlags modifierFlags = [NSEvent modifierFlags] & self.allowedModifierFlags; label = [[SRModifierFlagsTransformer sharedPlainTransformer] transformedValue:@(modifierFlags)]; if (![label length]) label = SRLoc(@"Type shortcut"); } else { label = self.accessibilityStringValue; if (![label length]) label = SRLoc(@"Click to record shortcut"); } return label; } - (NSString *)stringValue { if (![self.objectValue count]) return nil; NSString *f = [[SRModifierFlagsTransformer sharedTransformer] transformedValue:self.objectValue[SRShortcutModifierFlagsKey]]; SRKeyCodeTransformer *transformer = nil; if (self.drawsASCIIEquivalentOfShortcut) transformer = [SRKeyCodeTransformer sharedPlainASCIITransformer]; else transformer = [SRKeyCodeTransformer sharedPlainTransformer]; NSString *c = [transformer transformedValue:self.objectValue[SRShortcutKeyCode] withImplicitModifierFlags:nil explicitModifierFlags:self.objectValue[SRShortcutModifierFlagsKey]]; return [NSString stringWithFormat:@"%@%@", f, c]; } - (NSString *)accessibilityStringValue { if (![self.objectValue count]) return nil; NSString *f = [[SRModifierFlagsTransformer sharedPlainTransformer] transformedValue:self.objectValue[SRShortcutModifierFlagsKey]]; NSString *c = nil; if (self.drawsASCIIEquivalentOfShortcut) c = [[SRKeyCodeTransformer sharedPlainASCIITransformer] transformedValue:self.objectValue[SRShortcutKeyCode]]; else c = [[SRKeyCodeTransformer sharedPlainTransformer] transformedValue:self.objectValue[SRShortcutKeyCode]]; if ([f length] > 0) return [NSString stringWithFormat:@"%@-%@", f, c]; else return [NSString stringWithFormat:@"%@", c]; } - (NSDictionary *)labelAttributes { if (self.enabled) { if (self.isRecording) return [self recordingLabelAttributes]; else return [self normalLabelAttributes]; } else return [self disabledLabelAttributes]; } - (NSDictionary *)normalLabelAttributes { static dispatch_once_t OnceToken; static NSDictionary *NormalAttributes = nil; dispatch_once(&OnceToken, ^{ NSMutableParagraphStyle *p = [[NSMutableParagraphStyle alloc] init]; p.alignment = NSTextAlignmentCenter; p.lineBreakMode = NSLineBreakByTruncatingTail; p.baseWritingDirection = NSWritingDirectionLeftToRight; NormalAttributes = @{ NSParagraphStyleAttributeName: [p copy], NSFontAttributeName: [NSFont fontWithName:@"Avenir" size:[NSFont systemFontSize]], NSForegroundColorAttributeName: [NSColor controlTextColor] }; }); return NormalAttributes; } - (NSDictionary *)recordingLabelAttributes { static dispatch_once_t OnceToken; static NSDictionary *RecordingAttributes = nil; dispatch_once(&OnceToken, ^{ NSMutableParagraphStyle *p = [[NSMutableParagraphStyle alloc] init]; p.alignment = NSTextAlignmentCenter; p.lineBreakMode = NSLineBreakByTruncatingTail; p.baseWritingDirection = NSWritingDirectionLeftToRight; RecordingAttributes = @{ NSParagraphStyleAttributeName: [p copy], NSFontAttributeName: [NSFont fontWithName:@"Avenir" size:[NSFont systemFontSize]], NSForegroundColorAttributeName: [NSColor disabledControlTextColor] }; }); return RecordingAttributes; } - (NSDictionary *)disabledLabelAttributes { static dispatch_once_t OnceToken; static NSDictionary *DisabledAttributes = nil; dispatch_once(&OnceToken, ^{ NSMutableParagraphStyle *p = [[NSMutableParagraphStyle alloc] init]; p.alignment = NSTextAlignmentCenter; p.lineBreakMode = NSLineBreakByTruncatingTail; p.baseWritingDirection = NSWritingDirectionLeftToRight; DisabledAttributes = @{ NSParagraphStyleAttributeName: [p copy], NSFontAttributeName: [NSFont labelFontOfSize:[NSFont systemFontSize]], NSForegroundColorAttributeName: [NSColor disabledControlTextColor] }; }); return DisabledAttributes; } #pragma mark - - (void)drawBackground:(NSRect)aDirtyRect { NSRect frame = self.bounds; frame.size.height = _SRRecorderControlHeight; if (![self needsToDrawRect:frame]) return; [NSGraphicsContext saveGraphicsState]; if (self.isRecording) { NSDrawThreePartImage(frame, _SRImages[3], _SRImages[4], _SRImages[5], NO, NSCompositingOperationSourceOver, 1.0, self.isFlipped); } else { if (self.isMainButtonHighlighted) { if ([NSColor currentControlTint] == NSBlueControlTint) { NSDrawThreePartImage(frame, _SRImages[0], _SRImages[1], _SRImages[2], NO, NSCompositingOperationSourceOver, 1.0, self.isFlipped); } else { NSDrawThreePartImage(frame, _SRImages[6], _SRImages[7], _SRImages[8], NO, NSCompositingOperationSourceOver, 1.0, self.isFlipped); } } else if (self.enabled) { NSDrawThreePartImage(frame, _SRImages[9], _SRImages[10], _SRImages[11], NO, NSCompositingOperationSourceOver, 1.0, self.isFlipped); } else { NSDrawThreePartImage(frame, _SRImages[16], _SRImages[17], _SRImages[18], NO, NSCompositingOperationSourceOver, 1.0, self.isFlipped); } } [NSGraphicsContext restoreGraphicsState]; } - (void)drawInterior:(NSRect)aDirtyRect { [self drawLabel:aDirtyRect]; if (self.isRecording) { [self drawSnapBackButton:aDirtyRect]; [self drawClearButton:aDirtyRect]; } } - (void)drawLabel:(NSRect)aDirtyRect { NSString *label = self.label; NSDictionary *labelAttributes = self.labelAttributes; NSRect labelRect = [self rectForLabel:label withAttributes:labelAttributes]; if (![self needsToDrawRect:labelRect]) return; [NSGraphicsContext saveGraphicsState]; [label drawInRect:labelRect withAttributes:labelAttributes]; [NSGraphicsContext restoreGraphicsState]; } - (void)drawSnapBackButton:(NSRect)aDirtyRect { NSRect imageRect = self.snapBackButtonRect; imageRect.origin.x += _SRRecorderControlSnapBackButtonLeftOffset; imageRect.origin.y += floor(self.alignmentRectInsets.top + (NSHeight(imageRect) - _SRRecorderControlSnapBackButtonSize.height) / 2.0); imageRect.size = _SRRecorderControlSnapBackButtonSize; imageRect = [self centerScanRect:imageRect]; if (![self needsToDrawRect:imageRect]) return; [NSGraphicsContext saveGraphicsState]; if (self.isSnapBackButtonHighlighted) { [_SRImages[14] drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; } else { [_SRImages[15] drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; } [NSGraphicsContext restoreGraphicsState]; } - (void)drawClearButton:(NSRect)aDirtyRect { NSRect imageRect = self.clearButtonRect; // If there is no reason to draw clear button (e.g. no shortcut was set) // rect will have empty width. if (NSWidth(imageRect) == 0.0) return; imageRect.origin.x += _SRRecorderControlClearButtonLeftOffset; imageRect.origin.y += floor(self.alignmentRectInsets.top + (NSHeight(imageRect) - _SRRecorderControlClearButtonSize.height) / 2.0); imageRect.size = _SRRecorderControlClearButtonSize; imageRect = [self centerScanRect:imageRect]; if (![self needsToDrawRect:imageRect]) return; [NSGraphicsContext saveGraphicsState]; if (self.isClearButtonHighlighted) { [_SRImages[12] drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; } else { [_SRImages[13] drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; } [NSGraphicsContext restoreGraphicsState]; } - (CGFloat)backingScaleFactor { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6 || self.window == nil) return 1.0; else return self.window.backingScaleFactor; } #pragma mark - - (BOOL)isMainButtonHighlighted { if (_mouseTrackingButtonTag == _SRRecorderControlMainButtonTag) { NSPoint locationInView = [self convertPoint:self.window.mouseLocationOutsideOfEventStream fromView:nil]; return [self mouse:locationInView inRect:self.bounds]; } else return NO; } - (BOOL)isSnapBackButtonHighlighted { if (_mouseTrackingButtonTag == _SRRecorderControlSnapBackButtonTag) { NSPoint locationInView = [self convertPoint:self.window.mouseLocationOutsideOfEventStream fromView:nil]; return [self mouse:locationInView inRect:self.snapBackButtonRect]; } else return NO; } - (BOOL)isClearButtonHighlighted { if (_mouseTrackingButtonTag == _SRRecorderControlClearButtonTag) { NSPoint locationInView = [self convertPoint:self.window.mouseLocationOutsideOfEventStream fromView:nil]; return [self mouse:locationInView inRect:self.clearButtonRect]; } else return NO; } - (BOOL)areModifierFlagsValid:(NSEventModifierFlags)aModifierFlags forKeyCode:(unsigned short)aKeyCode { aModifierFlags &= SRCocoaModifierFlagsMask; if ([self.delegate respondsToSelector:@selector(shortcutRecorder:shouldUnconditionallyAllowModifierFlags:forKeyCode:)] && [self.delegate shortcutRecorder:self shouldUnconditionallyAllowModifierFlags:aModifierFlags forKeyCode:aKeyCode]) { return YES; } else if (aModifierFlags == 0 && !self.allowsEmptyModifierFlags) return NO; else if ((aModifierFlags & self.requiredModifierFlags) != self.requiredModifierFlags) return NO; else if ((aModifierFlags & self.allowedModifierFlags) != aModifierFlags) return NO; else return YES; } #pragma mark - - (void)propagateValue:(id)aValue forBinding:(NSString *)aBinding { NSParameterAssert(aBinding != nil); NSDictionary* bindingInfo = [self infoForBinding:aBinding]; if(!bindingInfo || (id)bindingInfo == [NSNull null]) return; NSObject *boundObject = bindingInfo[NSObservedObjectKey]; if(!boundObject || (id)boundObject == [NSNull null]) [NSException raise:NSInternalInconsistencyException format:@"NSObservedObjectKey MUST NOT be nil for binding \"%@\"", aBinding]; NSString* boundKeyPath = bindingInfo[NSObservedKeyPathKey]; if(!boundKeyPath || (id)boundKeyPath == [NSNull null]) [NSException raise:NSInternalInconsistencyException format:@"NSObservedKeyPathKey MUST NOT be nil for binding \"%@\"", aBinding]; NSDictionary* bindingOptions = bindingInfo[NSOptionsKey]; if(bindingOptions) { NSValueTransformer* transformer = [bindingOptions valueForKey:NSValueTransformerBindingOption]; if(!transformer || (id)transformer == [NSNull null]) { NSString* transformerName = [bindingOptions valueForKey:NSValueTransformerNameBindingOption]; if(transformerName && (id)transformerName != [NSNull null]) transformer = [NSValueTransformer valueTransformerForName:transformerName]; } if(transformer && (id)transformer != [NSNull null]) { if([[transformer class] allowsReverseTransformation]) aValue = [transformer reverseTransformedValue:aValue]; #ifdef DEBUG else NSLog(@"WARNING: binding \"%@\" has value transformer, but it doesn't allow reverse transformations in %s", aBinding, __PRETTY_FUNCTION__); #endif } } [boundObject setValue:aValue forKeyPath:boundKeyPath]; } + (BOOL)automaticallyNotifiesObserversOfValue { return NO; } - (void)setValue:(id)newValue { if (NSIsControllerMarker(newValue)) [NSException raise:NSInternalInconsistencyException format:@"SRRecorderControl's NSValueBinding does not support controller value markers."]; self.objectValue = newValue; } - (id)value { return self.objectValue; } #pragma mark NSToolTipOwner - (NSString *)view:(NSView *)aView stringForToolTip:(NSToolTipTag)aTag point:(NSPoint)aPoint userData:(void *)aData { if (aTag == _snapBackButtonToolTipTag) return SRLoc(@"Use old shortcut"); else return [super view:aView stringForToolTip:aTag point:aPoint userData:aData]; } #pragma mark NSCoding - (instancetype)initWithCoder:(NSCoder *)aCoder { // Since Xcode 6.x, user can configure xib to Prefer Coder. // In that case view will be instantiated with initWithCoder. // // awakeFromNib cannot be used to set up defaults for IBDesignable, // because at the time it's called, it's impossible to know whether properties // were set by a user in xib or they are compilation-time defaults. self = [super initWithCoder:aCoder]; if (self) { [self _initInternalState]; } return self; } #pragma mark NSView - (BOOL)isOpaque { return NO; } - (BOOL)isFlipped { return YES; } - (void)viewWillDraw { [super viewWillDraw]; static dispatch_once_t OnceToken; dispatch_once(&OnceToken, ^{ if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) { _SRImages[0] = SRImage(@"shortcut-recorder-bezel-blue-highlighted-left"); _SRImages[1] = SRImage(@"shortcut-recorder-bezel-blue-highlighted-middle"); _SRImages[2] = SRImage(@"shortcut-recorder-bezel-blue-highlighted-right"); _SRImages[3] = SRImage(@"shortcut-recorder-bezel-editing-left"); _SRImages[4] = SRImage(@"shortcut-recorder-bezel-editing-middle"); _SRImages[5] = SRImage(@"shortcut-recorder-bezel-editing-right"); _SRImages[6] = SRImage(@"shortcut-recorder-bezel-graphite-highlight-mask-left"); _SRImages[7] = SRImage(@"shortcut-recorder-bezel-graphite-highlight-mask-middle"); _SRImages[8] = SRImage(@"shortcut-recorder-bezel-graphite-highlight-mask-right"); _SRImages[9] = SRImage(@"shortcut-recorder-bezel-left"); _SRImages[10] = SRImage(@"shortcut-recorder-bezel-middle"); _SRImages[11] = SRImage(@"shortcut-recorder-bezel-right"); _SRImages[12] = SRImage(@"shortcut-recorder-clear-highlighted"); _SRImages[13] = SRImage(@"shortcut-recorder-clear"); _SRImages[14] = SRImage(@"shortcut-recorder-snapback-highlighted"); _SRImages[15] = SRImage(@"shortcut-recorder-snapback"); _SRImages[16] = SRImage(@"shortcut-recorder-bezel-disabled-left"); _SRImages[17] = SRImage(@"shortcut-recorder-bezel-disabled-middle"); _SRImages[18] = SRImage(@"shortcut-recorder-bezel-disabled-right"); } else { _SRImages[0] = SRImage(@"shortcut-recorder-yosemite-bezel-blue-highlighted-left"); _SRImages[1] = SRImage(@"shortcut-recorder-yosemite-bezel-blue-highlighted-middle"); _SRImages[2] = SRImage(@"shortcut-recorder-yosemite-bezel-blue-highlighted-right"); _SRImages[3] = SRImage(@"shortcut-recorder-yosemite-bezel-editing-left"); _SRImages[4] = SRImage(@"shortcut-recorder-yosemite-bezel-editing-middle"); _SRImages[5] = SRImage(@"shortcut-recorder-yosemite-bezel-editing-right"); _SRImages[6] = SRImage(@"shortcut-recorder-yosemite-bezel-graphite-highlight-mask-left"); _SRImages[7] = SRImage(@"shortcut-recorder-yosemite-bezel-graphite-highlight-mask-middle"); _SRImages[8] = SRImage(@"shortcut-recorder-yosemite-bezel-graphite-highlight-mask-right"); _SRImages[9] = SRImage(@"shortcut-recorder-yosemite-bezel-left"); _SRImages[10] = SRImage(@"shortcut-recorder-yosemite-bezel-middle"); _SRImages[11] = SRImage(@"shortcut-recorder-yosemite-bezel-right"); _SRImages[12] = SRImage(@"shortcut-recorder-yosemite-clear-highlighted"); _SRImages[13] = SRImage(@"shortcut-recorder-yosemite-clear"); _SRImages[14] = SRImage(@"shortcut-recorder-yosemite-snapback-highlighted"); _SRImages[15] = SRImage(@"shortcut-recorder-yosemite-snapback"); _SRImages[16] = SRImage(@"shortcut-recorder-yosemite-bezel-disabled-left"); _SRImages[17] = SRImage(@"shortcut-recorder-yosemite-bezel-disabled-middle"); _SRImages[18] = SRImage(@"shortcut-recorder-yosemite-bezel-disabled-right"); } }); } - (void)drawRect:(NSRect)aDirtyRect { [self drawBackground:aDirtyRect]; [self drawInterior:aDirtyRect]; if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) { if (self.enabled && self.window.firstResponder == self) { [NSGraphicsContext saveGraphicsState]; NSSetFocusRingStyle(NSFocusRingOnly); [self.controlShape fill]; [NSGraphicsContext restoreGraphicsState]; } } } - (void)drawFocusRingMask { if (self.enabled && self.window.firstResponder == self) [self.controlShape fill]; } - (NSRect)focusRingMaskBounds { if (self.enabled && self.window.firstResponder == self) return self.controlShape.bounds; else return NSZeroRect; } - (NSEdgeInsets)alignmentRectInsets { return NSEdgeInsetsMake(0.0, 0.0, _SRRecorderControlBottomShadowHeightInPixels / self.backingScaleFactor, 0.0); } - (CGFloat)baselineOffsetFromBottom { // True method to calculate is presented below. Unfortunately Cocoa implementation of Mac OS X 10.8.2 expects this value to be persistant. // If baselineOffsetFromBottom depends on some other properties and may return different values for different calls, // NSLayoutFormatAlignAllBaseline may not work. For this reason we return the constant. // If you're going to change layout of the view, uncomment the line below, look what it typically returns and update the constant. // TODO: Hopefully it will be fixed some day in Cocoa and therefore in SRRecorderControl. // CGFloat baseline = fdim(NSHeight(self.bounds), _SRRecorderControlHeight) + floor(_SRRecorderControlBaselineOffset - [self.labelAttributes[NSFontAttributeName] descender]); return 8.0; } - (NSSize)intrinsicContentSize { return NSMakeSize(NSWidth([self rectForLabel:SRLoc(@"Click to record shortcut") withAttributes:self.normalLabelAttributes]) + _shapeXRadius + _shapeXRadius, _SRRecorderControlHeight); } - (void)updateTrackingAreas { static const NSTrackingAreaOptions TrackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveWhenFirstResponder | NSTrackingEnabledDuringMouseDrag; if (_mainButtonTrackingArea) [self removeTrackingArea:_mainButtonTrackingArea]; _mainButtonTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:TrackingOptions owner:self userInfo:nil]; [self addTrackingArea:_mainButtonTrackingArea]; if (_snapBackButtonTrackingArea) { [self removeTrackingArea:_snapBackButtonTrackingArea]; _snapBackButtonTrackingArea = nil; } if (_clearButtonTrackingArea) { [self removeTrackingArea:_clearButtonTrackingArea]; _clearButtonTrackingArea = nil; } if (_snapBackButtonToolTipTag != NSIntegerMax) { [self removeToolTip:_snapBackButtonToolTipTag]; _snapBackButtonToolTipTag = NSIntegerMax; } if (self.isRecording) { _snapBackButtonTrackingArea = [[NSTrackingArea alloc] initWithRect:self.snapBackButtonRect options:TrackingOptions owner:self userInfo:nil]; [self addTrackingArea:_snapBackButtonTrackingArea]; _clearButtonTrackingArea = [[NSTrackingArea alloc] initWithRect:self.clearButtonRect options:TrackingOptions owner:self userInfo:nil]; [self addTrackingArea:_clearButtonTrackingArea]; // Since this method is used to set up tracking rects of aux buttons, the rest of the code is aware // it should be called whenever geometry or apperance changes. Therefore it's a good place to set up tooltip rects. _snapBackButtonToolTipTag = [self addToolTipRect:[_snapBackButtonTrackingArea rect] owner:self userData:NULL]; } } - (void)viewWillMoveToWindow:(NSWindow *)aWindow { // We want control to end recording whenever window resigns first responder status. // Otherwise we could end up with "dangling" recording. if (self.window) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:self.window]; } if (aWindow) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endRecording) name:NSWindowDidResignKeyNotification object:aWindow]; } [super viewWillMoveToWindow:aWindow]; } #pragma mark NSResponder - (BOOL)acceptsFirstResponder { return self.enabled; } - (BOOL)becomeFirstResponder { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) [self setKeyboardFocusRingNeedsDisplayInRect:self.bounds]; return [super becomeFirstResponder]; } - (BOOL)resignFirstResponder { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) [self setKeyboardFocusRingNeedsDisplayInRect:self.bounds]; [self endRecording]; _mouseTrackingButtonTag = _SRRecorderControlInvalidButtonTag; return [super resignFirstResponder]; } - (BOOL)acceptsFirstMouse:(NSEvent *)anEvent { return YES; } - (BOOL)canBecomeKeyView { // SRRecorderControl uses the button metaphor, but buttons cannot become key unless // Full Keyboard Access is enabled. Respect this. return [super canBecomeKeyView] && [NSApp isFullKeyboardAccessEnabled]; } - (BOOL)needsPanelToBecomeKey { return YES; } - (void)mouseDown:(NSEvent *)anEvent { if (!self.enabled) { [super mouseDown:anEvent]; return; } NSPoint locationInView = [self convertPoint:anEvent.locationInWindow fromView:nil]; if (self.isRecording) { if ([self mouse:locationInView inRect:self.snapBackButtonRect]) { _mouseTrackingButtonTag = _SRRecorderControlSnapBackButtonTag; [self setNeedsDisplayInRect:self.snapBackButtonRect]; } else if ([self mouse:locationInView inRect:self.clearButtonRect]) { _mouseTrackingButtonTag = _SRRecorderControlClearButtonTag; [self setNeedsDisplayInRect:self.clearButtonRect]; } else [super mouseDown:anEvent]; } else if ([self mouse:locationInView inRect:self.bounds]) { _mouseTrackingButtonTag = _SRRecorderControlMainButtonTag; [self setNeedsDisplay:YES]; } else [super mouseDown:anEvent]; } - (void)mouseUp:(NSEvent *)anEvent { if (!self.enabled) { [super mouseUp:anEvent]; return; } if (_mouseTrackingButtonTag != _SRRecorderControlInvalidButtonTag) { if (!self.window.isKeyWindow) { // It's possible to receive this event after window resigned its key status // e.g. when shortcut brings new window and makes it key. [self setNeedsDisplay:YES]; } else { NSPoint locationInView = [self convertPoint:anEvent.locationInWindow fromView:nil]; if (_mouseTrackingButtonTag == _SRRecorderControlMainButtonTag && [self mouse:locationInView inRect:self.bounds]) { [self beginRecording]; } else if (_mouseTrackingButtonTag == _SRRecorderControlSnapBackButtonTag && [self mouse:locationInView inRect:self.snapBackButtonRect]) { [self endRecording]; } else if (_mouseTrackingButtonTag == _SRRecorderControlClearButtonTag && [self mouse:locationInView inRect:self.clearButtonRect]) { [self clearAndEndRecording]; } } _mouseTrackingButtonTag = _SRRecorderControlInvalidButtonTag; } else [super mouseUp:anEvent]; } - (void)mouseEntered:(NSEvent *)anEvent { if (!self.enabled) { [super mouseEntered:anEvent]; return; } if ((_mouseTrackingButtonTag == _SRRecorderControlMainButtonTag && anEvent.trackingArea == _mainButtonTrackingArea) || (_mouseTrackingButtonTag == _SRRecorderControlSnapBackButtonTag && anEvent.trackingArea == _snapBackButtonTrackingArea) || (_mouseTrackingButtonTag == _SRRecorderControlClearButtonTag && anEvent.trackingArea == _clearButtonTrackingArea)) { [self setNeedsDisplayInRect:anEvent.trackingArea.rect]; } [super mouseEntered:anEvent]; } - (void)mouseExited:(NSEvent *)anEvent { if (!self.enabled) { [super mouseExited:anEvent]; return; } if ((_mouseTrackingButtonTag == _SRRecorderControlMainButtonTag && anEvent.trackingArea == _mainButtonTrackingArea) || (_mouseTrackingButtonTag == _SRRecorderControlSnapBackButtonTag && anEvent.trackingArea == _snapBackButtonTrackingArea) || (_mouseTrackingButtonTag == _SRRecorderControlClearButtonTag && anEvent.trackingArea == _clearButtonTrackingArea)) { [self setNeedsDisplayInRect:anEvent.trackingArea.rect]; } [super mouseExited:anEvent]; } - (void)keyDown:(NSEvent *)anEvent { if (![self performKeyEquivalent:anEvent]) [super keyDown:anEvent]; } - (BOOL)performKeyEquivalent:(NSEvent *)anEvent { if (!self.enabled) return NO; if (self.window.firstResponder != self) return NO; if (_mouseTrackingButtonTag != _SRRecorderControlInvalidButtonTag) return NO; if (self.isRecording) { if (anEvent.keyCode == USHRT_MAX) { // This shouldn't really happen ever, but was rarely observed. // See https://github.com/Kentzo/ShortcutRecorder/issues/40 return NO; } else if (self.allowsEscapeToCancelRecording && anEvent.keyCode == kVK_Escape && (anEvent.modifierFlags & SRCocoaModifierFlagsMask) == 0) { [self endRecording]; return YES; } else if (self.allowsDeleteToClearShortcutAndEndRecording && (anEvent.keyCode == kVK_Delete || anEvent.keyCode == kVK_ForwardDelete) && (anEvent.modifierFlags & SRCocoaModifierFlagsMask) == 0) { [self clearAndEndRecording]; return YES; } else if ([self areModifierFlagsValid:anEvent.modifierFlags forKeyCode:anEvent.keyCode]) { NSDictionary *newObjectValue = @{ SRShortcutKeyCode: @(anEvent.keyCode), SRShortcutModifierFlagsKey: @(anEvent.modifierFlags & SRCocoaModifierFlagsMask), SRShortcutCharacters: anEvent.characters, SRShortcutCharactersIgnoringModifiers: anEvent.charactersIgnoringModifiers }; if ([self.delegate respondsToSelector:@selector(shortcutRecorder:canRecordShortcut:)]) { if (![self.delegate shortcutRecorder:self canRecordShortcut:newObjectValue]) { // We acutally handled key equivalent, because client likely performs some action // to represent an error (e.g. beep and error dialog). // Do not end editing, because if client do not use additional window to show an error // first responder will not change. Allow a user to make another attempt. return YES; } } [self endRecordingWithObjectValue:newObjectValue]; return YES; } } else if (anEvent.keyCode == kVK_Space) return [self beginRecording]; return NO; } - (void)flagsChanged:(NSEvent *)anEvent { if (self.isRecording) { NSEventModifierFlags modifierFlags = anEvent.modifierFlags & SRCocoaModifierFlagsMask; if (modifierFlags != 0 && ![self areModifierFlagsValid:modifierFlags forKeyCode:anEvent.keyCode]) NSBeep(); [self setNeedsDisplay:YES]; } [super flagsChanged:anEvent]; } #pragma mark NSObject + (void)initialize { if (self == [SRRecorderControl class]) { [self exposeBinding:NSValueBinding]; [self exposeBinding:NSEnabledBinding]; } } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRValidator.h ================================================ // // SRValidator.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Andy Kim // Silvio Rizzi // Ilya Kulakov #import @protocol SRValidatorDelegate; /*! Implements logic to check whether shortcut is taken by other parts of the application and system. */ @interface SRValidator : NSObject @property (assign) NSObject *delegate; - (instancetype)initWithDelegate:(NSObject *)aDelegate; /*! Determines whether shortcut is taken. @discussion Key is checked in the following order: 1. If delegate implements shortcutValidator:isKeyCode:andFlagsTaken:reason: 2. If delegate allows system-wide shortcuts are checked 3. If delegate allows application menu it checked @see SRValidatorDelegate */ - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags error:(NSError **)outError; /*! Determines whether shortcut is taken in delegate. @discussion If delegate does not implement appropriate method, returns immediately. */ - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagTakenInDelegate:(NSEventModifierFlags)aFlags error:(NSError **)outError; /*! Determines whether shortcut is taken by system-wide shortcuts. @discussion Does not check whether delegate allows or disallows checking in system shortcuts. */ - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTakenInSystemShortcuts:(NSEventModifierFlags)aFlags error:(NSError **)outError; /*! Determines whether shortcut is taken by application menu item. @discussion Does not check whether delegate allows or disallows checking in application menu. */ - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlags:(NSEventModifierFlags)aFlags takenInMenu:(NSMenu *)aMenu error:(NSError **)outError; @end @protocol SRValidatorDelegate @optional /*! Asks the delegate if aKeyCode and aFlags are valid. @param aValidator The validator that validates key code and flags. @param aKeyCode Key code to validate. @param aFlags Flags to validate. @param outReason If delegate decides that shortcut is invalid, it may pass here an error message. @result YES if shortcut is valid. Otherwise NO. @discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. */ - (BOOL)shortcutValidator:(SRValidator *)aValidator isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags reason:(NSString **)outReason; /*! Asks the delegate whether validator should check key equivalents of app's menu items. @param aValidator The validator that going to check app's menu items. @result YES if validator should check key equivalents of app's menu items. Otherwise NO. @discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. */ - (BOOL)shortcutValidatorShouldCheckMenu:(SRValidator *)aValidator; /*! Asks the delegate whether it should check system shortcuts. @param aValidator The validator that going to check system shortcuts. @result YES if validator should check system shortcuts. Otherwise NO. @discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. */ - (BOOL)shortcutValidatorShouldCheckSystemShortcuts:(SRValidator *)aValidator; /*! Asks the delegate whether it should use ASCII representation of key code when making error messages. @param aValidator The validator that is about to make an error message. @result YES if validator should use ASCII representation. Otherwise NO. @discussion Implementation of this method by the delegate is optional. If it is not present, ASCII representation of key code is used. */ - (BOOL)shortcutValidatorShouldUseASCIIStringForKeyCodes:(SRValidator *)aValidator; @end @interface NSMenuItem (SRValidator) /*! Returns full path to the menu item. E.g. "Window ➝ Zoom" */ - (NSString *)SR_path; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/SRValidator.m ================================================ // // SRValidator.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Andy Kim // Silvio Rizzi // Ilya Kulakov #import "SRValidator.h" #import "SRCommon.h" #import "SRKeyCodeTransformer.h" @implementation SRValidator - (instancetype)initWithDelegate:(NSObject *)aDelegate; { self = [super init]; if (self) { _delegate = aDelegate; } return self; } - (instancetype)init { return [self initWithDelegate:nil]; } #pragma mark Methods - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags error:(NSError **)outError; { if ([self isKeyCode:aKeyCode andFlagTakenInDelegate:aFlags error:outError]) return YES; if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckSystemShortcuts:)] || [self.delegate shortcutValidatorShouldCheckSystemShortcuts:self]) && [self isKeyCode:aKeyCode andFlagsTakenInSystemShortcuts:aFlags error:outError]) { return YES; } if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckMenu:)] || [self.delegate shortcutValidatorShouldCheckMenu:self]) && [self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:[NSApp mainMenu] error:outError]) { return YES; } return NO; } - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagTakenInDelegate:(NSEventModifierFlags)aFlags error:(NSError **)outError { if (self.delegate) { NSString *delegateReason = nil; if ([self.delegate respondsToSelector:@selector(shortcutValidator:isKeyCode:andFlagsTaken:reason:)] && [self.delegate shortcutValidator:self isKeyCode:aKeyCode andFlagsTaken:aFlags reason:&delegateReason]) { if (outError) { BOOL isASCIIOnly = YES; if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); NSString *failureReason = [NSString stringWithFormat: SRLoc(@"The key combination \"%@\" can't be used!"), shortcut]; NSString *description = [NSString stringWithFormat: SRLoc(@"The key combination \"%@\" can't be used because %@."), shortcut, [delegateReason length] ? delegateReason : @"it's already used"]; NSDictionary *userInfo = @{ NSLocalizedFailureReasonErrorKey : failureReason, NSLocalizedDescriptionKey: description }; *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; } return YES; } } return NO; } - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTakenInSystemShortcuts:(NSEventModifierFlags)aFlags error:(NSError **)outError { CFArrayRef s = NULL; OSStatus err = CopySymbolicHotKeys(&s); if (err != noErr) return YES; NSArray *symbolicHotKeys = (NSArray *)CFBridgingRelease(s); aFlags &= SRCocoaModifierFlagsMask; for (NSDictionary *symbolicHotKey in symbolicHotKeys) { if ((__bridge CFBooleanRef)symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyEnabled] != kCFBooleanTrue) continue; unsigned short symbolicHotKeyCode = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyCode] integerValue]; if (symbolicHotKeyCode == aKeyCode) { UInt32 symbolicHotKeyFlags = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyModifiers] unsignedIntValue]; symbolicHotKeyFlags &= SRCarbonModifierFlagsMask; if (SRCarbonToCocoaFlags(symbolicHotKeyFlags) == aFlags) { if (outError) { BOOL isASCIIOnly = YES; if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); NSString *failureReason = [NSString stringWithFormat: SRLoc(@"The key combination \"%@\" can't be used!"), shortcut]; NSString *description = [NSString stringWithFormat: SRLoc(@"The key combination \"%@\" can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this key combination, most shortcuts can be changed in the Keyboard panel in System Preferences."), shortcut]; NSDictionary *userInfo = @{ NSLocalizedFailureReasonErrorKey: failureReason, NSLocalizedDescriptionKey: description }; *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; } return YES; } } } return NO; } - (BOOL)isKeyCode:(unsigned short)aKeyCode andFlags:(NSEventModifierFlags)aFlags takenInMenu:(NSMenu *)aMenu error:(NSError **)outError { aFlags &= SRCocoaModifierFlagsMask; for (NSMenuItem *menuItem in [aMenu itemArray]) { if (menuItem.hasSubmenu && [self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:menuItem.submenu error:outError]) return YES; NSString *keyEquivalent = menuItem.keyEquivalent; if (![keyEquivalent length]) continue; NSEventModifierFlags keyEquivalentModifierMask = menuItem.keyEquivalentModifierMask; if (SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, aFlags, keyEquivalent, keyEquivalentModifierMask)) { if (outError) { BOOL isASCIIOnly = YES; if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); NSString *failureReason = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used!"), shortcut]; NSString *description = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used because it's already used by the menu item \"%@\"."), shortcut, menuItem.SR_path]; NSDictionary *userInfo = @{ NSLocalizedFailureReasonErrorKey: failureReason, NSLocalizedDescriptionKey: description }; *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; } return YES; } } return NO; } @end @implementation NSMenuItem (SRValidator) - (NSString *)SR_path { NSMutableArray *items = [NSMutableArray array]; static const NSUInteger Limit = 1000; NSMenuItem *currentMenuItem = self; NSUInteger i = 0; do { [items insertObject:currentMenuItem atIndex:0]; currentMenuItem = currentMenuItem.parentItem; ++i; } while (currentMenuItem && i < Limit); NSMutableString *path = [NSMutableString string]; for (NSMenuItem *menuItem in items) [path appendFormat:@"%@➝", menuItem.title]; if ([path length] > 1) [path deleteCharactersInRange:NSMakeRange([path length] - 1, 1)]; return path; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Library/ShortcutRecorder.h ================================================ // // ShortcutRecorder.h // ShortcutRecorder // Copyright 2012 Contributors. All rights reserved. // // License: BSD // // Contributors to this file: // Jesper // Ilya Kulakov #import #import #import #import #import #import #import #import #ifndef IBInspectable #define IBInspectable #endif #ifndef IB_DESIGNABLE #define IB_DESIGNABLE #endif #ifndef NSAppKitVersionNumber10_6 #define NSAppKitVersionNumber10_6 1038 #endif #ifndef NSAppKitVersionNumber10_9 #define NSAppKitVersionNumber10_9 1265 #endif #ifndef NSEDGEINSETS_DEFINED typedef struct NSEdgeInsets { CGFloat top; CGFloat left; CGFloat bottom; CGFloat right; } NSEdgeInsets; NS_INLINE NSEdgeInsets NSEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right) { NSEdgeInsets e; e.top = top; e.left = left; e.bottom = bottom; e.right = right; return e; } #endif ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleShortVersionString 1.5 CFBundleSignature ???? CFBundleVersion 1.5 ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKey+ShortcutRecorder.h ================================================ // // PTHotKey+ShortcutRecorder.h // ShortcutRecorder // // Created by Ilya Kulakov on 27.02.11. // Copyright 2011 Wireload. All rights reserved. // #import #import "PTHotKey.h" @interface PTHotKey (ShortcutRecorder) + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction; + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction keyUpAction:(SEL)aKeyUpAction; + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction withObject:(id)anObject; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKey+ShortcutRecorder.m ================================================ // // PTHotKey+ShortcutRecorder.m // ShortcutRecorder // // Created by Ilya Kulakov on 27.02.11. // Copyright 2011 Wireload. All rights reserved. // #import "PTHotKey+ShortcutRecorder.h" #import "SRRecorderControl.h" @implementation PTHotKey (ShortcutRecorder) + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction { return [PTHotKey hotKeyWithIdentifier:anIdentifier keyCombo:aKeyCombo target:aTarget action:anAction withObject:nil]; } + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction withObject:(id)anObject { NSInteger keyCode = [[aKeyCombo objectForKey:@"keyCode"] integerValue]; NSUInteger modifiers = SRCocoaToCarbonFlags([[aKeyCombo objectForKey:@"modifierFlags"] unsignedIntegerValue]); PTKeyCombo *newKeyCombo = [[PTKeyCombo alloc] initWithKeyCode:keyCode modifiers:modifiers]; PTHotKey *newHotKey = [[PTHotKey alloc] initWithIdentifier:anIdentifier keyCombo:newKeyCombo]; [newHotKey setTarget:aTarget]; [newHotKey setAction:anAction]; [newHotKey setObject:anObject]; return newHotKey; } + (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier keyCombo:(NSDictionary *)aKeyCombo target:(id)aTarget action:(SEL)anAction keyUpAction:(SEL)aKeyUpAction { PTHotKey *newHotKey = [PTHotKey hotKeyWithIdentifier:anIdentifier keyCombo:aKeyCombo target:aTarget action:anAction]; [newHotKey setKeyUpAction:aKeyUpAction]; return newHotKey; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKey.h ================================================ // // PTHotKey.h // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // // Contributors: // Andy Kim #import #import #import "PTKeyCombo.h" @interface PTHotKey : NSObject { NSString* mIdentifier; NSString* mName; PTKeyCombo* mKeyCombo; id mTarget; id mObject; SEL mAction; SEL mKeyUpAction; UInt32 mCarbonHotKeyID; EventHotKeyRef mCarbonEventHotKeyRef; } - (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo; - (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo withObject: (id)object; - (id)init; - (void)setIdentifier: (id)ident; - (id)identifier; - (void)setName: (NSString*)name; - (NSString*)name; - (void)setKeyCombo: (PTKeyCombo*)combo; - (PTKeyCombo*)keyCombo; - (void)setTarget: (id)target; - (id)target; - (void)setObject: (id)object; - (id)object; - (void)setAction: (SEL)action; - (SEL)action; - (void)setKeyUpAction: (SEL)action; - (SEL)keyUpAction; - (UInt32)carbonHotKeyID; - (void)setCarbonHotKeyID: (UInt32)hotKeyID; - (EventHotKeyRef)carbonEventHotKeyRef; - (void)setCarbonEventHotKeyRef:(EventHotKeyRef)hotKeyRef; - (void)invoke; - (void)uninvoke; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKey.m ================================================ // // PTHotKey.m // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // #import "PTHotKey.h" #import "PTHotKeyCenter.h" #import "PTKeyCombo.h" @implementation PTHotKey - (id)init { return [self initWithIdentifier: nil keyCombo: nil withObject:nil]; } - (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo { return [self initWithIdentifier: identifier keyCombo: combo withObject:nil]; } - (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo withObject: (id)object { self = [super init]; if( self ) { [self setIdentifier: identifier]; [self setKeyCombo: combo]; [self setObject: object]; } return self; } - (NSString*)description { return [NSString stringWithFormat: @"<%@: %@, %@>", NSStringFromClass( [self class] ), [self identifier], [self keyCombo]]; } #pragma mark - - (void)setIdentifier: (id)ident { mIdentifier = ident; } - (id)identifier { return mIdentifier; } - (void)setKeyCombo: (PTKeyCombo*)combo { if( combo == nil ) combo = [PTKeyCombo clearKeyCombo]; mKeyCombo = combo; } - (PTKeyCombo*)keyCombo { return mKeyCombo; } - (void)setName: (NSString*)name { mName = name; } - (NSString*)name { return mName; } - (void)setTarget: (id)target { mTarget = target; } - (id)target { return mTarget; } - (void)setObject:(id)object { mObject = object; } - (id)object { return mObject; } - (void)setAction: (SEL)action { mAction = action; } - (SEL)action { return mAction; } - (void)setKeyUpAction: (SEL)action { mKeyUpAction = action; } - (SEL)keyUpAction { return mKeyUpAction; } - (UInt32)carbonHotKeyID { return mCarbonHotKeyID; } - (void)setCarbonHotKeyID: (UInt32)hotKeyID; { mCarbonHotKeyID = hotKeyID; } - (EventHotKeyRef)carbonEventHotKeyRef { return mCarbonEventHotKeyRef; } - (void)setCarbonEventHotKeyRef: (EventHotKeyRef)hotKeyRef { mCarbonEventHotKeyRef = hotKeyRef; } #pragma mark - - (void)invoke { if(mAction) [NSApp sendAction:mAction to:mTarget from:self]; } - (void)uninvoke { if(mKeyUpAction) [NSApp sendAction:mKeyUpAction to:mTarget from:self]; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKeyCenter.h ================================================ // // PTHotKeyCenter.h // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // // Contributors: // Quentin D. Carnicelli // Finlay Dobbie // Vincent Pottier // Andy Kim #import #import @class PTHotKey; @interface PTHotKeyCenter : NSObject { NSMutableDictionary* mHotKeys; //Keys are carbon hot key IDs BOOL mEventHandlerInstalled; UInt32 mHotKeyCount; // Used to assign new hot key ID BOOL mIsPaused; EventHandlerRef mEventHandler; } + (PTHotKeyCenter *)sharedCenter; - (BOOL)registerHotKey: (PTHotKey*)hotKey; - (void)unregisterHotKey: (PTHotKey*)hotKey; - (NSArray*)allHotKeys; - (PTHotKey*)hotKeyWithIdentifier: (id)ident; - (void)sendEvent: (NSEvent*)event; - (void)pause; - (void)resume; - (BOOL)isPaused; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTHotKeyCenter.m ================================================ // // PTHotKeyCenter.m // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // #import "PTHotKeyCenter.h" #import "PTHotKey.h" #import "PTKeyCombo.h" @interface PTHotKeyCenter (Private) - (PTHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey; - (PTHotKey*)_hotKeyForCarbonHotKeyID: (EventHotKeyID)hotKeyID; - (void)_updateEventHandler; - (void)_hotKeyDown: (PTHotKey*)hotKey; - (void)_hotKeyUp: (PTHotKey*)hotKey; static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ); @end @implementation PTHotKeyCenter static PTHotKeyCenter *_sharedHotKeyCenter = nil; + (PTHotKeyCenter*)sharedCenter { if( _sharedHotKeyCenter == nil ) { _sharedHotKeyCenter = [[self alloc] init]; } return _sharedHotKeyCenter; } - (id)init { self = [super init]; if( self ) { mHotKeys = [[NSMutableDictionary alloc] init]; } return self; } #pragma mark - - (BOOL)registerHotKey: (PTHotKey*)hotKey { if ( mIsPaused == NO ) { OSStatus err; EventHotKeyID hotKeyID; EventHotKeyRef carbonHotKey; if( [[self allHotKeys] containsObject: hotKey] == YES ) [self unregisterHotKey: hotKey]; if( [[hotKey keyCombo] isValidHotKeyCombo] == NO ) return YES; hotKeyID.signature = 'PTHk'; hotKeyID.id = ++mHotKeyCount; err = RegisterEventHotKey( (SInt32)[[hotKey keyCombo] keyCode], (UInt32)[[hotKey keyCombo] modifiers], hotKeyID, GetEventDispatcherTarget(), 0, &carbonHotKey ); if( err ) return NO; [hotKey setCarbonHotKeyID:hotKeyID.id]; [hotKey setCarbonEventHotKeyRef:carbonHotKey]; if( hotKey ) [mHotKeys setObject: hotKey forKey: [NSNumber numberWithInteger:hotKeyID.id]]; [self _updateEventHandler]; } else { EventHotKeyID hotKeyID = {'PTHk', ++mHotKeyCount}; [hotKey setCarbonHotKeyID:hotKeyID.id]; if( hotKey ) [mHotKeys setObject: hotKey forKey: [NSNumber numberWithInteger:hotKeyID.id]]; } return YES; } - (void)unregisterHotKey: (PTHotKey*)hotKey { if ( mIsPaused == NO ) { EventHotKeyRef carbonHotKey; if( [[self allHotKeys] containsObject: hotKey] == NO ) return; carbonHotKey = [hotKey carbonEventHotKeyRef]; if( carbonHotKey ) { UnregisterEventHotKey( carbonHotKey ); //Watch as we ignore 'err': [mHotKeys removeObjectForKey: [NSNumber numberWithInteger:[hotKey carbonHotKeyID]]]; [hotKey setCarbonHotKeyID:0]; [hotKey setCarbonEventHotKeyRef:NULL]; [self _updateEventHandler]; //See that? Completely ignored } } else { [mHotKeys removeObjectForKey: [NSNumber numberWithInteger:[hotKey carbonHotKeyID]]]; [hotKey setCarbonHotKeyID:0]; [hotKey setCarbonEventHotKeyRef:NULL]; } } - (NSArray*)allHotKeys { return [mHotKeys allValues]; } - (PTHotKey*)hotKeyWithIdentifier: (id)ident { NSEnumerator* hotKeysEnum = [[self allHotKeys] objectEnumerator]; PTHotKey* hotKey; if( !ident ) return nil; while( (hotKey = [hotKeysEnum nextObject]) != nil ) { if( [[hotKey identifier] isEqual: ident] ) return hotKey; } return nil; } #pragma mark - - (PTHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKeyRef { NSEnumerator *e = [mHotKeys objectEnumerator]; PTHotKey *hotkey = nil; while( (hotkey = [e nextObject]) ) { if( [hotkey carbonEventHotKeyRef] == carbonHotKeyRef ) return hotkey; } return nil; } - (PTHotKey*)_hotKeyForCarbonHotKeyID: (EventHotKeyID)hotKeyID { return [mHotKeys objectForKey:[NSNumber numberWithInteger:hotKeyID.id]]; } - (void)_updateEventHandler { if( [mHotKeys count] && mEventHandlerInstalled == NO ) { EventTypeSpec eventSpec[2] = { { kEventClassKeyboard, kEventHotKeyPressed }, { kEventClassKeyboard, kEventHotKeyReleased } }; InstallEventHandler( GetEventDispatcherTarget(), (EventHandlerProcPtr)hotKeyEventHandler, 2, eventSpec, nil, &mEventHandler); mEventHandlerInstalled = YES; } } - (void)_hotKeyDown: (PTHotKey*)hotKey { [hotKey invoke]; } - (void)_hotKeyUp: (PTHotKey*)hotKey { [hotKey uninvoke]; } - (void)sendEvent: (NSEvent*)event { // Not sure why this is needed? - Andy Kim (Aug 23, 2009) short subType; EventHotKeyRef carbonHotKey; if( [event type] == NSEventTypeSystemDefined ) { subType = [event subtype]; if( subType == 6 ) //6 is hot key down { carbonHotKey= (EventHotKeyRef)[event data1]; //data1 is our hot key ref if( carbonHotKey != nil ) { PTHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey]; [self _hotKeyDown: hotKey]; } } else if( subType == 9 ) //9 is hot key up { carbonHotKey= (EventHotKeyRef)[event data1]; if( carbonHotKey != nil ) { PTHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey]; [self _hotKeyUp: hotKey]; } } } } - (OSStatus)sendCarbonEvent: (EventRef)event { OSStatus err; EventHotKeyID hotKeyID; PTHotKey* hotKey; NSAssert( GetEventClass( event ) == kEventClassKeyboard, @"Unknown event class" ); err = GetEventParameter( event, kEventParamDirectObject, typeEventHotKeyID, nil, sizeof(EventHotKeyID), nil, &hotKeyID ); if( err ) return err; if( hotKeyID.signature != 'PTHk' ) return eventNotHandledErr; if (hotKeyID.id == 0) return eventNotHandledErr; hotKey = [self _hotKeyForCarbonHotKeyID:hotKeyID]; switch( GetEventKind( event ) ) { case kEventHotKeyPressed: [self _hotKeyDown: hotKey]; break; case kEventHotKeyReleased: [self _hotKeyUp: hotKey]; break; default: return eventNotHandledErr; break; } return noErr; } - (void)pause { if ( mIsPaused ) return; mIsPaused = YES; for (NSNumber *hotKeyID in mHotKeys) { PTHotKey *hotKey = [mHotKeys objectForKey:hotKeyID]; EventHotKeyRef carbonHotKey = [hotKey carbonEventHotKeyRef]; UnregisterEventHotKey( carbonHotKey ); [hotKey setCarbonEventHotKeyRef:NULL]; } if (mEventHandler != NULL) { RemoveEventHandler(mEventHandler); mEventHandler = NULL; mEventHandlerInstalled = NO; } } - (void)resume { if ( mIsPaused == NO) return; mIsPaused = NO; for (NSNumber *hotKeyIDNumber in mHotKeys) { PTHotKey *hotKey = [mHotKeys objectForKey:hotKeyIDNumber]; EventHotKeyRef carbonHotKey = NULL; EventHotKeyID hotKeyID; hotKeyID.signature = 'PTHk'; hotKeyID.id = [hotKey carbonHotKeyID]; RegisterEventHotKey( (SInt32)[[hotKey keyCombo] keyCode], (UInt32)[[hotKey keyCombo] modifiers], hotKeyID, GetEventDispatcherTarget(), 0, &carbonHotKey ); [hotKey setCarbonEventHotKeyRef:carbonHotKey]; } [self _updateEventHandler]; } - (BOOL)isPaused { return mIsPaused; } static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ) { return [[PTHotKeyCenter sharedCenter] sendCarbonEvent: inEvent]; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTKeyCodeTranslator.h ================================================ // // PTKeyCodeTranslator.h // Chercher // // Created by Finlay Dobbie on Sat Oct 11 2003. // Copyright (c) 2003 Cliché Software. All rights reserved. // #import @interface PTKeyCodeTranslator : NSObject { TISInputSourceRef keyboardLayout; const UCKeyboardLayout *uchrData; UInt32 keyTranslateState; UInt32 deadKeyState; } + (id)currentTranslator; - (id)initWithKeyboardLayout:(TISInputSourceRef)aLayout; - (NSString *)translateKeyCode:(short)keyCode; - (TISInputSourceRef)keyboardLayout; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTKeyCodeTranslator.m ================================================ // // PTKeyCodeTranslator.m // Chercher // // Created by Finlay Dobbie on Sat Oct 11 2003. // Copyright (c) 2003 Cliché Software. All rights reserved. // #import "PTKeyCodeTranslator.h" @implementation PTKeyCodeTranslator + (id)currentTranslator { static PTKeyCodeTranslator *current = nil; TISInputSourceRef currentLayout = TISCopyCurrentKeyboardLayoutInputSource(); if (current == nil) { current = [[PTKeyCodeTranslator alloc] initWithKeyboardLayout:currentLayout]; } else if ([current keyboardLayout] != currentLayout) { current = [[PTKeyCodeTranslator alloc] initWithKeyboardLayout:currentLayout]; } CFRelease(currentLayout); return current; } - (id)initWithKeyboardLayout:(TISInputSourceRef)aLayout { if ((self = [super init]) != nil) { keyboardLayout = aLayout; CFRetain(keyboardLayout); CFDataRef uchr = TISGetInputSourceProperty( keyboardLayout , kTISPropertyUnicodeKeyLayoutData ); uchrData = ( const UCKeyboardLayout* )CFDataGetBytePtr(uchr); } return self; } - (void)dealloc { CFRelease(keyboardLayout); } - (NSString *)translateKeyCode:(short)keyCode { UniCharCount maxStringLength = 4, actualStringLength; UniChar unicodeString[4]; UCKeyTranslate( uchrData, keyCode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxStringLength, &actualStringLength, unicodeString ); return [NSString stringWithCharacters:unicodeString length:1]; } - (TISInputSourceRef)keyboardLayout { return keyboardLayout; } - (NSString *)description { NSString *kind; kind = @"uchr"; NSString *layoutName; layoutName = (__bridge NSString *)(TISGetInputSourceProperty( keyboardLayout, kTISPropertyLocalizedName )); return [NSString stringWithFormat:@"PTKeyCodeTranslator layout=%@ (%@)", layoutName, kind]; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTKeyCombo.h ================================================ // // PTKeyCombo.h // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // #import @interface PTKeyCombo : NSObject { NSInteger mKeyCode; NSUInteger mModifiers; } + (id)clearKeyCombo; + (id)keyComboWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers; - (id)initWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers; - (id)initWithPlistRepresentation: (id)plist; - (id)plistRepresentation; - (BOOL)isEqual: (PTKeyCombo*)combo; - (NSInteger)keyCode; - (NSUInteger)modifiers; - (BOOL)isClearCombo; - (BOOL)isValidHotKeyCombo; @end @interface PTKeyCombo (UserDisplayAdditions) - (NSString*)keyCodeString; - (NSUInteger)modifierMask; @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/PTHotKey/PTKeyCombo.m ================================================ // // PTKeyCombo.m // Protein // // Created by Quentin Carnicelli on Sat Aug 02 2003. // Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. // #import "PTKeyCombo.h" #import "PTKeyCodeTranslator.h" @implementation PTKeyCombo + (id)clearKeyCombo { return [self keyComboWithKeyCode: -1 modifiers: -1]; } + (id)keyComboWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers { return [[self alloc] initWithKeyCode: keyCode modifiers: modifiers]; } - (id)initWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers { self = [super init]; if( self ) { switch ( keyCode ) { case kVK_F1: case kVK_F2: case kVK_F3: case kVK_F4: case kVK_F5: case kVK_F6: case kVK_F7: case kVK_F8: case kVK_F9: case kVK_F10: case kVK_F11: case kVK_F12: case kVK_F13: case kVK_F14: case kVK_F15: case kVK_F16: case kVK_F17: case kVK_F18: case kVK_F19: case kVK_F20: mModifiers = modifiers | NSEventModifierFlagFunction; break; default: mModifiers = modifiers; break; } mKeyCode = keyCode; } return self; } - (id)initWithPlistRepresentation: (id)plist { int keyCode, modifiers; if( !plist || ![plist count] ) { keyCode = -1; modifiers = -1; } else { keyCode = [[plist objectForKey: @"keyCode"] intValue]; if( keyCode < 0 ) keyCode = -1; modifiers = [[plist objectForKey: @"modifiers"] intValue]; if( modifiers <= 0 ) modifiers = -1; } return [self initWithKeyCode: keyCode modifiers: modifiers]; } - (id)plistRepresentation { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger: [self keyCode]], @"keyCode", [NSNumber numberWithInteger: [self modifiers]], @"modifiers", nil]; } - (id)copyWithZone:(NSZone*)zone; { return self; } - (BOOL)isEqual: (PTKeyCombo*)combo { return [self keyCode] == [combo keyCode] && [self modifiers] == [combo modifiers]; } #pragma mark - - (NSInteger)keyCode { return mKeyCode; } - (NSUInteger)modifiers { return mModifiers; } - (BOOL)isValidHotKeyCombo { return mKeyCode >= 0 && mModifiers > 0; } - (BOOL)isClearCombo { return mKeyCode == -1 && mModifiers == 0; } @end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/README.md ================================================ ShortcutRecorder 2 ==================== ![pre-Yosemite ShortcutRecorder Preview](Demo/example.png) ![Yosemite ShortcutRecorder Preview](Demo/example-yosemite.png) The only user interface control to record shortcuts. For Mac OS X 10.6+, 64bit. - :microscope: Support for Xcode 6 Quick Help - :microscope: Support for Xcode 6 Interface Builder integration - Fresh Look & Feel (brought to you by [Wireload](http://wireload.net) and [John Wells](https://github.com/jwells89)) - With Retina support - Auto Layout ready - Correct drawing on Layer-backed and Layer-hosted views - Accessibility for people with disabilities - Revised codebase with Automatic Reference Counting support - Translated into 24 languages Includes framework to set global shortcuts (PTHotKey). Get Sources ----------- The preferred way to add the ShortcutRecorder to your project is to use git submodules: `git submodule add git://github.com/Kentzo/ShortcutRecorder.git` You can download sources from the site as well. Integrate into your project --------------------------- First, add ShortcutRecorder.xcodeproj to your workspace via Xcode ([Apple docs](https://developer.apple.com/library/mac/recipes/xcode_help-structure_navigator/articles/Adding_an_Existing_Project_to_a_Workspace.html)). Don't have a workspace? No problem, just add ShortcutRecorder.xcodeproj via the "Add Files to" dialog. Next step is to ensure your target is linked against the ShortcutRecorder or/and PTHotKey frameworks ([Apple docs](http://developer.apple.com/library/ios/#recipes/xcode_help-project_editor/Articles/AddingaLibrarytoaTarget.html#//apple_ref/doc/uid/TP40010155-CH17)). Desired frameworks will be listed under *Workspace*. Now it's time to make frameworks part of your app. To do this, you need to add custom Build Phase ([Apple docs](http://developer.apple.com/library/ios/#recipes/xcode_help-project_editor/Articles/CreatingaCopyFilesBuildPhase.html)). Remember to set *Destination* to *Frameworks* and clean up *Subpath*. Finally, ensure your app will find frameworks upon start. Open Build Settings of your target, look up *Runtime Search Paths*. Add `@executable_path/../Frameworks` to the list of paths. Add control in Interface Builder -------------------------------- Since Xcode 4 Apple removed Interface Builder Plugins. You can only use it to add and position/resize ShortcutRecorder control. To do this, add Custom View and set its class to SRRecorderControl. SRRecorderControl has fixed height of 25 points so ensure you do not use autoresizing masks/layout rules which allows vertical resizing. I recommend you to pin height in case you're using Auto Layout. Usage ----- First, we want to keep value of the control across relaunches of the app. We can simply achieve this by using NSUserDefaultsController and bindings: [self.pingShortcutRecorder bind:NSValueBinding toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.ping" options:nil]; The value can be used to set key equivalent of NSMenuItem or NSButton. It can also be used to register a global shortcut. Setting key equivalent of NSMenuItem using bindings: [self.pingItem bind:@"keyEquivalent" toObject:defaults withKeyPath:@"values.ping" options:@{NSValueTransformerBindingOption: [SRKeyEquivalentTransformer new]}]; [self.pingItem bind:@"keyEquivalentModifierMask" toObject:defaults withKeyPath:@"values.ping" options:@{NSValueTransformerBindingOption: [SRKeyEquivalentModifierMaskTransformer new]}]; Setting key equivalent of NSButton using bindings: [self.pingButton bind:@"keyEquivalent" toObject:defaults withKeyPath:@"values.ping" options:@{NSValueTransformerBindingOption: [SRKeyEquivalentTransformer new]}]; [self.pingButton bind:@"keyEquivalentModifierMask" toObject:defaults withKeyPath:@"values.ping" options:@{NSValueTransformerBindingOption: [SRKeyEquivalentModifierMaskTransformer new]}]; Setting global shortcut using PTHotKeyCenter: PTHotKeyCenter *hotKeyCenter = [PTHotKeyCenter sharedCenter]; PTHotKey *oldHotKey = [hotKeyCenter hotKeyWithIdentifier:aKeyPath]; [hotKeyCenter unregisterHotKey:oldHotKey]; PTHotKey *newHotKey = [PTHotKey hotKeyWithIdentifier:aKeyPath keyCombo:newShortcut target:self action:@selector(ping:)]; [hotKeyCenter registerHotKey:newHotKey]; Key Equivalents and Keyboard Layout ---------------------------------------------------- While ShortcutRecorder keeps your shortcuts as combination of *key code* and modifier masks, key equivalents are expressed using *key character* and modifier mask. The difference is that position of key code on keyboard does not depend on current keyboard layout while position of key character does. ShortcutRecorder includes two special transformers to simplify binding to the key equivalents of NSMenuItem and NSButton: - SRKeyEquivalentTransformer - SRKeyEquivalentModifierMaskTransformer SRKeyEquivalentTransformer uses ASCII keyboard layout to convert key code into character, therefore resulting character does not depend on keyboard layout. The drawback is that position of the character on keyboard may change depending on layout and used modifier keys (primarly Option and Shift). NSButton -------- If you're going to bind ShortcutRecorder to key equivalent of NSButton, I encourage you to require `NSCommandKeyMask`. This is because NSButton handles key equivalents very strange. Rather than investigating full information of the keyboard event, it just asks for `charactersIgnoringModifiers` and compares returned value with its `keyEquivalent`. Unfortunately, Cocoa returns layout-independent (ASCII) representation of characters only when NSCommandKeyMask is set. If it's not set, assigned shortcut likely won't work with other layouts. Questions --------- Still have questions? [Create an issue](https://github.com/Kentzo/ShortcutRecorder/issues/new) immediately and feel free to ping me. Paid Support ------------ If functional you need is missing but you're ready to pay for it, feel free to contact me. If not, create an issue anyway, I'll take a look as soon as I can. ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/Resources/LICENSE.txt ================================================ Copyright (c) 2006, contributors to ShortcutRecorder. (See the contributors listed in detail later in the file, or see .) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ===================================================================== Contributors to Shortcut Recorder, in no order in particular: Jesper, waffle software, . Initial idea and concept, first shot at implementation using NSView. David Dauer, . Refinement, cleaner reimplementation, documentation, IB Palette. Jamie Kirkpatrick, Kirk Consulting Ltd, . Further modularisation and re-factoring, and general bug fixes. Ilya Kulakov, . ShortcutRecorder 2.0 and further support. Alexander Ljungberg, . Graphics for ShortcutRecorder 2.0 ===================================================================== Some rights reserved: For more information, visit ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/ShortcutRecorder.podspec ================================================ Pod::Spec.new do |s| s.name = 'ShortcutRecorder' s.homepage = 'https://github.com/Kentzo/ShortcutRecorder' s.summary = '' s.version = '2.17' s.source = { :git => 'git://github.com/Kentzo/ShortcutRecorder.git', :branch => 'master' } s.author = { 'Ilya Kulakov' => 'kulakov.ilya@gmail.com' } s.frameworks = 'Carbon', 'Cocoa' s.platform = :osx, "10.6" s.subspec 'Core' do |core| core.source_files = 'Library/*.{h,m}' core.resource_bundles = { "ShortcutRecorder" => ['Resources/*.lproj', 'Resources/*.png'] } core.requires_arc = true core.prefix_header_file = 'Library/Prefix.pch' end s.subspec 'PTHotKey' do |hotkey| hotkey.source_files = 'PTHotKey/*.{h,m}' hotkey.requires_arc = false hotkey.dependency 'ShortcutRecorder/Core' end end ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/ShortcutRecorder.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 939837950DA429EC007F53F3 /* SRRecorderControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8E29C109CDB9360085E9ED /* SRRecorderControl.m */; }; 939837980DA429EC007F53F3 /* SRKeyCodeTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 74C3670D0A246B4900B69171 /* SRKeyCodeTransformer.m */; }; 939837990DA429EC007F53F3 /* SRValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 74C3670F0A246B4900B69171 /* SRValidator.m */; }; 9398379A0DA429EC007F53F3 /* SRCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 74C367110A246B4900B69171 /* SRCommon.m */; }; 939839390DA4350B007F53F3 /* ShortcutRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 939837B50DA42DFB007F53F3 /* ShortcutRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9398393B0DA4350B007F53F3 /* SRRecorderControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B8E29C009CDB9360085E9ED /* SRRecorderControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9398393E0DA4350B007F53F3 /* SRKeyCodeTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C3670C0A246B4900B69171 /* SRKeyCodeTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9398393F0DA4350B007F53F3 /* SRValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C3670E0A246B4900B69171 /* SRValidator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 939839400DA4350B007F53F3 /* SRCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C367100A246B4900B69171 /* SRCommon.h */; settings = {ATTRIBUTES = (Public, ); }; }; BA547C271A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C011A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left.png */; }; BA547C281A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C021A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png */; }; BA547C291A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C031A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png */; }; BA547C2A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C041A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png */; }; BA547C2B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C051A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right.png */; }; BA547C2C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C061A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png */; }; BA547C2D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C071A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left.png */; }; BA547C2E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C081A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left@2x.png */; }; BA547C2F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C091A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle.png */; }; BA547C301A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle@2x.png */; }; BA547C311A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right.png */; }; BA547C321A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right@2x.png */; }; BA547C331A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left.png */; }; BA547C341A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left@2x.png */; }; BA547C351A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C0F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle.png */; }; BA547C361A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C101A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle@2x.png */; }; BA547C371A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C111A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right.png */; }; BA547C381A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C121A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right@2x.png */; }; BA547C391A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C131A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png */; }; BA547C3A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C141A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png */; }; BA547C3B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C151A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png */; }; BA547C3C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C161A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png */; }; BA547C3D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C171A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png */; }; BA547C3E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C181A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png */; }; BA547C3F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C191A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left.png */; }; BA547C401A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left@2x.png */; }; BA547C411A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle.png */; }; BA547C421A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle@2x.png */; }; BA547C431A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right.png */; }; BA547C441A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right@2x.png */; }; BA547C451A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C1F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted.png */; }; BA547C461A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C201A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted@2x.png */; }; BA547C471A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C211A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear.png */; }; BA547C481A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C221A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear@2x.png */; }; BA547C491A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C231A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted.png */; }; BA547C4A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C241A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted@2x.png */; }; BA547C4B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C251A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback.png */; }; BA547C4C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA547C261A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback@2x.png */; }; E20A897917F3322300502E33 /* shortcut-recorder-bezel-disabled-left.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897317F3322300502E33 /* shortcut-recorder-bezel-disabled-left.png */; }; E20A897A17F3322300502E33 /* shortcut-recorder-bezel-disabled-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897417F3322300502E33 /* shortcut-recorder-bezel-disabled-left@2x.png */; }; E20A897B17F3322300502E33 /* shortcut-recorder-bezel-disabled-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897517F3322300502E33 /* shortcut-recorder-bezel-disabled-middle.png */; }; E20A897C17F3322300502E33 /* shortcut-recorder-bezel-disabled-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897617F3322300502E33 /* shortcut-recorder-bezel-disabled-middle@2x.png */; }; E20A897D17F3322300502E33 /* shortcut-recorder-bezel-disabled-right.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897717F3322300502E33 /* shortcut-recorder-bezel-disabled-right.png */; }; E20A897E17F3322300502E33 /* shortcut-recorder-bezel-disabled-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E20A897817F3322300502E33 /* shortcut-recorder-bezel-disabled-right@2x.png */; }; E225E4671679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4471679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left.png */; }; E225E4681679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4481679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left@2x.png */; }; E225E4691679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4491679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle.png */; }; E225E46A1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44A1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle@2x.png */; }; E225E46B1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44B1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right.png */; }; E225E46C1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44C1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right@2x.png */; }; E225E46D1679E30E00A00529 /* shortcut-recorder-bezel-editing-left.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44D1679E30E00A00529 /* shortcut-recorder-bezel-editing-left.png */; }; E225E46E1679E30E00A00529 /* shortcut-recorder-bezel-editing-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44E1679E30E00A00529 /* shortcut-recorder-bezel-editing-left@2x.png */; }; E225E46F1679E30E00A00529 /* shortcut-recorder-bezel-editing-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E44F1679E30E00A00529 /* shortcut-recorder-bezel-editing-middle.png */; }; E225E4701679E30E00A00529 /* shortcut-recorder-bezel-editing-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4501679E30E00A00529 /* shortcut-recorder-bezel-editing-middle@2x.png */; }; E225E4711679E30E00A00529 /* shortcut-recorder-bezel-editing-right.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4511679E30E00A00529 /* shortcut-recorder-bezel-editing-right.png */; }; E225E4721679E30E00A00529 /* shortcut-recorder-bezel-editing-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4521679E30E00A00529 /* shortcut-recorder-bezel-editing-right@2x.png */; }; E225E4731679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4531679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left.png */; }; E225E4741679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4541679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left@2x.png */; }; E225E4751679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4551679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle.png */; }; E225E4761679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4561679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle@2x.png */; }; E225E4771679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4571679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right.png */; }; E225E4781679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4581679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right@2x.png */; }; E225E4791679E30E00A00529 /* shortcut-recorder-bezel-left.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4591679E30E00A00529 /* shortcut-recorder-bezel-left.png */; }; E225E47A1679E30E00A00529 /* shortcut-recorder-bezel-left@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45A1679E30E00A00529 /* shortcut-recorder-bezel-left@2x.png */; }; E225E47B1679E30E00A00529 /* shortcut-recorder-bezel-middle.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45B1679E30E00A00529 /* shortcut-recorder-bezel-middle.png */; }; E225E47C1679E30E00A00529 /* shortcut-recorder-bezel-middle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45C1679E30E00A00529 /* shortcut-recorder-bezel-middle@2x.png */; }; E225E47D1679E30E00A00529 /* shortcut-recorder-bezel-right.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45D1679E30E00A00529 /* shortcut-recorder-bezel-right.png */; }; E225E47E1679E30E00A00529 /* shortcut-recorder-bezel-right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45E1679E30E00A00529 /* shortcut-recorder-bezel-right@2x.png */; }; E225E47F1679E30E00A00529 /* shortcut-recorder-clear-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E45F1679E30E00A00529 /* shortcut-recorder-clear-highlighted.png */; }; E225E4801679E30E00A00529 /* shortcut-recorder-clear-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4601679E30E00A00529 /* shortcut-recorder-clear-highlighted@2x.png */; }; E225E4811679E30E00A00529 /* shortcut-recorder-clear.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4611679E30E00A00529 /* shortcut-recorder-clear.png */; }; E225E4821679E30E00A00529 /* shortcut-recorder-clear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4621679E30E00A00529 /* shortcut-recorder-clear@2x.png */; }; E225E4831679E30E00A00529 /* shortcut-recorder-snapback-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4631679E30E00A00529 /* shortcut-recorder-snapback-highlighted.png */; }; E225E4841679E30E00A00529 /* shortcut-recorder-snapback-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4641679E30E00A00529 /* shortcut-recorder-snapback-highlighted@2x.png */; }; E225E4851679E30E00A00529 /* shortcut-recorder-snapback.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4651679E30E00A00529 /* shortcut-recorder-snapback.png */; }; E225E4861679E30E00A00529 /* shortcut-recorder-snapback@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E225E4661679E30E00A00529 /* shortcut-recorder-snapback@2x.png */; }; E273123A1349EDB500A84433 /* PTHotKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B8E2A0309CDBADE0085E9ED /* PTHotKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; E273123B1349EDB500A84433 /* PTHotKeyCenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B8E2A0509CDBADE0085E9ED /* PTHotKeyCenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; E273123C1349EDB500A84433 /* PTKeyCodeTranslator.h in Headers */ = {isa = PBXBuildFile; fileRef = A1125B75104C8AF3005C6F7B /* PTKeyCodeTranslator.h */; settings = {ATTRIBUTES = (Public, ); }; }; E273123D1349EDB500A84433 /* PTKeyCombo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B8E2A0709CDBADE0085E9ED /* PTKeyCombo.h */; settings = {ATTRIBUTES = (Public, ); }; }; E27312401349EDCA00A84433 /* PTHotKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8E2A0409CDBADE0085E9ED /* PTHotKey.m */; }; E27312411349EDCA00A84433 /* PTHotKeyCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8E2A0609CDBADE0085E9ED /* PTHotKeyCenter.m */; }; E27312421349EDCA00A84433 /* PTKeyCodeTranslator.m in Sources */ = {isa = PBXBuildFile; fileRef = A1125B76104C8AF3005C6F7B /* PTKeyCodeTranslator.m */; }; E27312431349EDCA00A84433 /* PTKeyCombo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8E2A0809CDBADE0085E9ED /* PTKeyCombo.m */; }; E273124C1349EE6500A84433 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E273124B1349EE6500A84433 /* Carbon.framework */; }; E2741AC51673C79F00A139BD /* ShortcutRecorder.strings in Resources */ = {isa = PBXBuildFile; fileRef = E2741AC31673C79F00A139BD /* ShortcutRecorder.strings */; }; E2741ADF1673C83800A139BD /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = E2741ADE1673C83800A139BD /* LICENSE.txt */; }; E2741AE11673C90B00A139BD /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2741AE01673C90B00A139BD /* Cocoa.framework */; }; E28113AD167B8A9D001E118E /* SRModifierFlagsTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = E28113AB167B8A9D001E118E /* SRModifierFlagsTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E28113AE167B8A9D001E118E /* SRModifierFlagsTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = E28113AC167B8A9D001E118E /* SRModifierFlagsTransformer.m */; }; E2BE924D16ABEFE400827E8C /* SRKeyEquivalentTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = E2BE924B16ABEFE400827E8C /* SRKeyEquivalentTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2BE924E16ABEFE400827E8C /* SRKeyEquivalentTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE924C16ABEFE400827E8C /* SRKeyEquivalentTransformer.m */; }; E2BE925216ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = E2BE925016ABF84100827E8C /* SRKeyEquivalentModifierMaskTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2BE925316ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE925116ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.m */; }; E2BE927C16AC052000827E8C /* PTHotKey+ShortcutRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = E279508E131A7498008AE1DA /* PTHotKey+ShortcutRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2BE927D16AC052600827E8C /* PTHotKey+ShortcutRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = E279508F131A7498008AE1DA /* PTHotKey+ShortcutRecorder.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0B8E29C009CDB9360085E9ED /* SRRecorderControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRRecorderControl.h; path = Library/SRRecorderControl.h; sourceTree = ""; }; 0B8E29C109CDB9360085E9ED /* SRRecorderControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRRecorderControl.m; path = Library/SRRecorderControl.m; sourceTree = ""; }; 0B8E2A0309CDBADE0085E9ED /* PTHotKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTHotKey.h; sourceTree = ""; }; 0B8E2A0409CDBADE0085E9ED /* PTHotKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTHotKey.m; sourceTree = ""; }; 0B8E2A0509CDBADE0085E9ED /* PTHotKeyCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTHotKeyCenter.h; sourceTree = ""; }; 0B8E2A0609CDBADE0085E9ED /* PTHotKeyCenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTHotKeyCenter.m; sourceTree = ""; }; 0B8E2A0709CDBADE0085E9ED /* PTKeyCombo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTKeyCombo.h; sourceTree = ""; }; 0B8E2A0809CDBADE0085E9ED /* PTKeyCombo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTKeyCombo.m; sourceTree = ""; }; 0D6B246B180304DE00CE1142 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 0D6B246C180304DE00CE1142 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 0D6B246D180304DE00CE1142 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 0D6B2487180304DE00CE1142 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 32CA4F630368D1EE00C91783 /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Prefix.pch; path = Library/Prefix.pch; sourceTree = ""; }; 3552066127AF63DC000EF08F /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/ShortcutRecorder.strings; sourceTree = ""; }; 74C3670C0A246B4900B69171 /* SRKeyCodeTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRKeyCodeTransformer.h; path = Library/SRKeyCodeTransformer.h; sourceTree = ""; }; 74C3670D0A246B4900B69171 /* SRKeyCodeTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRKeyCodeTransformer.m; path = Library/SRKeyCodeTransformer.m; sourceTree = ""; }; 74C3670E0A246B4900B69171 /* SRValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRValidator.h; path = Library/SRValidator.h; sourceTree = ""; }; 74C3670F0A246B4900B69171 /* SRValidator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRValidator.m; path = Library/SRValidator.m; sourceTree = ""; }; 74C367100A246B4900B69171 /* SRCommon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = SRCommon.h; path = Library/SRCommon.h; sourceTree = ""; }; 74C367110A246B4900B69171 /* SRCommon.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = SRCommon.m; path = Library/SRCommon.m; sourceTree = ""; }; 939837800DA42965007F53F3 /* ShortcutRecorder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ShortcutRecorder.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 939837B50DA42DFB007F53F3 /* ShortcutRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ShortcutRecorder.h; path = Library/ShortcutRecorder.h; sourceTree = SOURCE_ROOT; }; 9AA522D823416E6A00C9E005 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/ShortcutRecorder.strings; sourceTree = ""; }; A1125B75104C8AF3005C6F7B /* PTKeyCodeTranslator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTKeyCodeTranslator.h; sourceTree = ""; }; A1125B76104C8AF3005C6F7B /* PTKeyCodeTranslator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTKeyCodeTranslator.m; sourceTree = ""; }; BA547C011A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-left.png"; sourceTree = ""; }; BA547C021A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png"; sourceTree = ""; }; BA547C031A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png"; sourceTree = ""; }; BA547C041A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png"; sourceTree = ""; }; BA547C051A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-right.png"; sourceTree = ""; }; BA547C061A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png"; sourceTree = ""; }; BA547C071A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-left.png"; sourceTree = ""; }; BA547C081A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-left@2x.png"; sourceTree = ""; }; BA547C091A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-middle.png"; sourceTree = ""; }; BA547C0A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-middle@2x.png"; sourceTree = ""; }; BA547C0B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-right.png"; sourceTree = ""; }; BA547C0C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-disabled-right@2x.png"; sourceTree = ""; }; BA547C0D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-left.png"; sourceTree = ""; }; BA547C0E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-left@2x.png"; sourceTree = ""; }; BA547C0F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-middle.png"; sourceTree = ""; }; BA547C101A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-middle@2x.png"; sourceTree = ""; }; BA547C111A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-right.png"; sourceTree = ""; }; BA547C121A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-editing-right@2x.png"; sourceTree = ""; }; BA547C131A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png"; sourceTree = ""; }; BA547C141A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png"; sourceTree = ""; }; BA547C151A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png"; sourceTree = ""; }; BA547C161A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png"; sourceTree = ""; }; BA547C171A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png"; sourceTree = ""; }; BA547C181A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png"; sourceTree = ""; }; BA547C191A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-left.png"; sourceTree = ""; }; BA547C1A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-left@2x.png"; sourceTree = ""; }; BA547C1B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-middle.png"; sourceTree = ""; }; BA547C1C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-middle@2x.png"; sourceTree = ""; }; BA547C1D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-right.png"; sourceTree = ""; }; BA547C1E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-bezel-right@2x.png"; sourceTree = ""; }; BA547C1F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-clear-highlighted.png"; sourceTree = ""; }; BA547C201A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-clear-highlighted@2x.png"; sourceTree = ""; }; BA547C211A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-clear.png"; sourceTree = ""; }; BA547C221A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-clear@2x.png"; sourceTree = ""; }; BA547C231A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-snapback-highlighted.png"; sourceTree = ""; }; BA547C241A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-snapback-highlighted@2x.png"; sourceTree = ""; }; BA547C251A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-snapback.png"; sourceTree = ""; }; BA547C261A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-yosemite-snapback@2x.png"; sourceTree = ""; }; E20A897317F3322300502E33 /* shortcut-recorder-bezel-disabled-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-left.png"; sourceTree = ""; }; E20A897417F3322300502E33 /* shortcut-recorder-bezel-disabled-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-left@2x.png"; sourceTree = ""; }; E20A897517F3322300502E33 /* shortcut-recorder-bezel-disabled-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-middle.png"; sourceTree = ""; }; E20A897617F3322300502E33 /* shortcut-recorder-bezel-disabled-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-middle@2x.png"; sourceTree = ""; }; E20A897717F3322300502E33 /* shortcut-recorder-bezel-disabled-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-right.png"; sourceTree = ""; }; E20A897817F3322300502E33 /* shortcut-recorder-bezel-disabled-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-disabled-right@2x.png"; sourceTree = ""; }; E225E4471679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-left.png"; sourceTree = ""; }; E225E4481679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-left@2x.png"; sourceTree = ""; }; E225E4491679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-middle.png"; sourceTree = ""; }; E225E44A1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-middle@2x.png"; sourceTree = ""; }; E225E44B1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-right.png"; sourceTree = ""; }; E225E44C1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-blue-highlighted-right@2x.png"; sourceTree = ""; }; E225E44D1679E30E00A00529 /* shortcut-recorder-bezel-editing-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-left.png"; sourceTree = ""; }; E225E44E1679E30E00A00529 /* shortcut-recorder-bezel-editing-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-left@2x.png"; sourceTree = ""; }; E225E44F1679E30E00A00529 /* shortcut-recorder-bezel-editing-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-middle.png"; sourceTree = ""; }; E225E4501679E30E00A00529 /* shortcut-recorder-bezel-editing-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-middle@2x.png"; sourceTree = ""; }; E225E4511679E30E00A00529 /* shortcut-recorder-bezel-editing-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-right.png"; sourceTree = ""; }; E225E4521679E30E00A00529 /* shortcut-recorder-bezel-editing-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-editing-right@2x.png"; sourceTree = ""; }; E225E4531679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-left.png"; sourceTree = ""; }; E225E4541679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-left@2x.png"; sourceTree = ""; }; E225E4551679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-middle.png"; sourceTree = ""; }; E225E4561679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-middle@2x.png"; sourceTree = ""; }; E225E4571679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-right.png"; sourceTree = ""; }; E225E4581679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-graphite-highlighted-right@2x.png"; sourceTree = ""; }; E225E4591679E30E00A00529 /* shortcut-recorder-bezel-left.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-left.png"; sourceTree = ""; }; E225E45A1679E30E00A00529 /* shortcut-recorder-bezel-left@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-left@2x.png"; sourceTree = ""; }; E225E45B1679E30E00A00529 /* shortcut-recorder-bezel-middle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-middle.png"; sourceTree = ""; }; E225E45C1679E30E00A00529 /* shortcut-recorder-bezel-middle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-middle@2x.png"; sourceTree = ""; }; E225E45D1679E30E00A00529 /* shortcut-recorder-bezel-right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-right.png"; sourceTree = ""; }; E225E45E1679E30E00A00529 /* shortcut-recorder-bezel-right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-bezel-right@2x.png"; sourceTree = ""; }; E225E45F1679E30E00A00529 /* shortcut-recorder-clear-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-clear-highlighted.png"; sourceTree = ""; }; E225E4601679E30E00A00529 /* shortcut-recorder-clear-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-clear-highlighted@2x.png"; sourceTree = ""; }; E225E4611679E30E00A00529 /* shortcut-recorder-clear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-clear.png"; sourceTree = ""; }; E225E4621679E30E00A00529 /* shortcut-recorder-clear@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-clear@2x.png"; sourceTree = ""; }; E225E4631679E30E00A00529 /* shortcut-recorder-snapback-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-snapback-highlighted.png"; sourceTree = ""; }; E225E4641679E30E00A00529 /* shortcut-recorder-snapback-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-snapback-highlighted@2x.png"; sourceTree = ""; }; E225E4651679E30E00A00529 /* shortcut-recorder-snapback.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-snapback.png"; sourceTree = ""; }; E225E4661679E30E00A00529 /* shortcut-recorder-snapback@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shortcut-recorder-snapback@2x.png"; sourceTree = ""; }; E273122E1349EC9000A84433 /* PTHotKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PTHotKey.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E273122F1349EC9000A84433 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = PTHotKey/Info.plist; sourceTree = SOURCE_ROOT; }; E273124B1349EE6500A84433 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; E2741AC41673C79F00A139BD /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AC61673C7A400A139BD /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AC71673C7A600A139BD /* el */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AC81673C7A800A139BD /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AC91673C7AA00A139BD /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ACB1673C7AD00A139BD /* ko */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ACC1673C7AF00A139BD /* nb */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ACD1673C7B200A139BD /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ACE1673C7B300A139BD /* pl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ACF1673C7B500A139BD /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/ShortcutRecorder.strings"; sourceTree = ""; }; E2741AD01673C7B600A139BD /* ro */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD11673C7B800A139BD /* ru */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD21673C7BB00A139BD /* sk */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD31673C7BC00A139BD /* sv */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD41673C7BD00A139BD /* th */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD61673C7C000A139BD /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD71673C7C200A139BD /* es-MX */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/ShortcutRecorder.strings"; sourceTree = ""; }; E2741AD81673C7C300A139BD /* ca */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741AD91673C7C600A139BD /* cs */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ADA1673C7C800A139BD /* pt */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/ShortcutRecorder.strings; sourceTree = ""; }; E2741ADE1673C83800A139BD /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; E2741AE01673C90B00A139BD /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; E2741AE81673CCBA00A139BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Library/Info.plist; sourceTree = ""; }; E279508E131A7498008AE1DA /* PTHotKey+ShortcutRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PTHotKey+ShortcutRecorder.h"; sourceTree = ""; }; E279508F131A7498008AE1DA /* PTHotKey+ShortcutRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "PTHotKey+ShortcutRecorder.m"; sourceTree = ""; }; E28113AB167B8A9D001E118E /* SRModifierFlagsTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRModifierFlagsTransformer.h; path = Library/SRModifierFlagsTransformer.h; sourceTree = ""; }; E28113AC167B8A9D001E118E /* SRModifierFlagsTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRModifierFlagsTransformer.m; path = Library/SRModifierFlagsTransformer.m; sourceTree = ""; }; E2BE924B16ABEFE400827E8C /* SRKeyEquivalentTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRKeyEquivalentTransformer.h; path = Library/SRKeyEquivalentTransformer.h; sourceTree = ""; }; E2BE924C16ABEFE400827E8C /* SRKeyEquivalentTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRKeyEquivalentTransformer.m; path = Library/SRKeyEquivalentTransformer.m; sourceTree = ""; }; E2BE925016ABF84100827E8C /* SRKeyEquivalentModifierMaskTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRKeyEquivalentModifierMaskTransformer.h; path = Library/SRKeyEquivalentModifierMaskTransformer.h; sourceTree = ""; }; E2BE925116ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRKeyEquivalentModifierMaskTransformer.m; path = Library/SRKeyEquivalentModifierMaskTransformer.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9398377E0DA42965007F53F3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E2741AE11673C90B00A139BD /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; E273122C1349EC9000A84433 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E273124C1349EE6500A84433 /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 080E96DDFE201D6D7F000001 /* ShortcutRecorder */ = { isa = PBXGroup; children = ( 29B97317FDCFA39411CA2CEA /* Resources */, BA10595B1689CEB000A3DDF5 /* Supporting Files */, 939837B50DA42DFB007F53F3 /* ShortcutRecorder.h */, 74C3670C0A246B4900B69171 /* SRKeyCodeTransformer.h */, 74C3670D0A246B4900B69171 /* SRKeyCodeTransformer.m */, E28113AB167B8A9D001E118E /* SRModifierFlagsTransformer.h */, E28113AC167B8A9D001E118E /* SRModifierFlagsTransformer.m */, E2BE924B16ABEFE400827E8C /* SRKeyEquivalentTransformer.h */, E2BE924C16ABEFE400827E8C /* SRKeyEquivalentTransformer.m */, E2BE925016ABF84100827E8C /* SRKeyEquivalentModifierMaskTransformer.h */, E2BE925116ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.m */, 0B8E29C009CDB9360085E9ED /* SRRecorderControl.h */, 0B8E29C109CDB9360085E9ED /* SRRecorderControl.m */, 74C3670E0A246B4900B69171 /* SRValidator.h */, 74C3670F0A246B4900B69171 /* SRValidator.m */, 74C367100A246B4900B69171 /* SRCommon.h */, 74C367110A246B4900B69171 /* SRCommon.m */, ); name = ShortcutRecorder; sourceTree = ""; }; 0D6B246A180304DE00CE1142 /* Other Frameworks */ = { isa = PBXGroup; children = ( 0D6B246B180304DE00CE1142 /* AppKit.framework */, 0D6B246C180304DE00CE1142 /* CoreData.framework */, 0D6B246D180304DE00CE1142 /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 0DB3F54B1802EE0B00576055 /* Frameworks */ = { isa = PBXGroup; children = ( E2741AE01673C90B00A139BD /* Cocoa.framework */, E273124B1349EE6500A84433 /* Carbon.framework */, 0D6B2487180304DE00CE1142 /* XCTest.framework */, 0D6B246A180304DE00CE1142 /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 939837800DA42965007F53F3 /* ShortcutRecorder.framework */, E273122E1349EC9000A84433 /* PTHotKey.framework */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* ShortcutRecorder */ = { isa = PBXGroup; children = ( E2741AE71673CC3D00A139BD /* PTHotKey */, 080E96DDFE201D6D7F000001 /* ShortcutRecorder */, 0DB3F54B1802EE0B00576055 /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = ShortcutRecorder; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( BA547C011A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left.png */, BA547C021A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png */, BA547C031A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png */, BA547C041A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png */, BA547C051A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right.png */, BA547C061A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png */, BA547C071A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left.png */, BA547C081A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left@2x.png */, BA547C091A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle.png */, BA547C0A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle@2x.png */, BA547C0B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right.png */, BA547C0C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right@2x.png */, BA547C0D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left.png */, BA547C0E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left@2x.png */, BA547C0F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle.png */, BA547C101A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle@2x.png */, BA547C111A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right.png */, BA547C121A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right@2x.png */, BA547C131A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png */, BA547C141A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png */, BA547C151A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png */, BA547C161A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png */, BA547C171A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png */, BA547C181A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png */, BA547C191A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left.png */, BA547C1A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left@2x.png */, BA547C1B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle.png */, BA547C1C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle@2x.png */, BA547C1D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right.png */, BA547C1E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right@2x.png */, BA547C1F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted.png */, BA547C201A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted@2x.png */, BA547C211A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear.png */, BA547C221A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear@2x.png */, BA547C231A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted.png */, BA547C241A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted@2x.png */, BA547C251A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback.png */, BA547C261A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback@2x.png */, E20A897317F3322300502E33 /* shortcut-recorder-bezel-disabled-left.png */, E20A897417F3322300502E33 /* shortcut-recorder-bezel-disabled-left@2x.png */, E20A897517F3322300502E33 /* shortcut-recorder-bezel-disabled-middle.png */, E20A897617F3322300502E33 /* shortcut-recorder-bezel-disabled-middle@2x.png */, E20A897717F3322300502E33 /* shortcut-recorder-bezel-disabled-right.png */, E20A897817F3322300502E33 /* shortcut-recorder-bezel-disabled-right@2x.png */, E225E4471679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left.png */, E225E4481679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left@2x.png */, E225E4491679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle.png */, E225E44A1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle@2x.png */, E225E44B1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right.png */, E225E44C1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right@2x.png */, E225E44D1679E30E00A00529 /* shortcut-recorder-bezel-editing-left.png */, E225E44E1679E30E00A00529 /* shortcut-recorder-bezel-editing-left@2x.png */, E225E44F1679E30E00A00529 /* shortcut-recorder-bezel-editing-middle.png */, E225E4501679E30E00A00529 /* shortcut-recorder-bezel-editing-middle@2x.png */, E225E4511679E30E00A00529 /* shortcut-recorder-bezel-editing-right.png */, E225E4521679E30E00A00529 /* shortcut-recorder-bezel-editing-right@2x.png */, E225E4531679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left.png */, E225E4541679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left@2x.png */, E225E4551679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle.png */, E225E4561679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle@2x.png */, E225E4571679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right.png */, E225E4581679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right@2x.png */, E225E4591679E30E00A00529 /* shortcut-recorder-bezel-left.png */, E225E45A1679E30E00A00529 /* shortcut-recorder-bezel-left@2x.png */, E225E45B1679E30E00A00529 /* shortcut-recorder-bezel-middle.png */, E225E45C1679E30E00A00529 /* shortcut-recorder-bezel-middle@2x.png */, E225E45D1679E30E00A00529 /* shortcut-recorder-bezel-right.png */, E225E45E1679E30E00A00529 /* shortcut-recorder-bezel-right@2x.png */, E225E45F1679E30E00A00529 /* shortcut-recorder-clear-highlighted.png */, E225E4601679E30E00A00529 /* shortcut-recorder-clear-highlighted@2x.png */, E225E4611679E30E00A00529 /* shortcut-recorder-clear.png */, E225E4621679E30E00A00529 /* shortcut-recorder-clear@2x.png */, E225E4631679E30E00A00529 /* shortcut-recorder-snapback-highlighted.png */, E225E4641679E30E00A00529 /* shortcut-recorder-snapback-highlighted@2x.png */, E225E4651679E30E00A00529 /* shortcut-recorder-snapback.png */, E225E4661679E30E00A00529 /* shortcut-recorder-snapback@2x.png */, E2741ADE1673C83800A139BD /* LICENSE.txt */, E2741AC31673C79F00A139BD /* ShortcutRecorder.strings */, ); path = Resources; sourceTree = SOURCE_ROOT; }; BA10595B1689CEB000A3DDF5 /* Supporting Files */ = { isa = PBXGroup; children = ( 32CA4F630368D1EE00C91783 /* Prefix.pch */, E2741AE81673CCBA00A139BD /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; BA10595C1689CEBF00A3DDF5 /* Supporting Files */ = { isa = PBXGroup; children = ( E273122F1349EC9000A84433 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; E2741AE71673CC3D00A139BD /* PTHotKey */ = { isa = PBXGroup; children = ( BA10595C1689CEBF00A3DDF5 /* Supporting Files */, 0B8E2A0309CDBADE0085E9ED /* PTHotKey.h */, 0B8E2A0409CDBADE0085E9ED /* PTHotKey.m */, 0B8E2A0509CDBADE0085E9ED /* PTHotKeyCenter.h */, 0B8E2A0609CDBADE0085E9ED /* PTHotKeyCenter.m */, A1125B75104C8AF3005C6F7B /* PTKeyCodeTranslator.h */, A1125B76104C8AF3005C6F7B /* PTKeyCodeTranslator.m */, 0B8E2A0709CDBADE0085E9ED /* PTKeyCombo.h */, 0B8E2A0809CDBADE0085E9ED /* PTKeyCombo.m */, E279508E131A7498008AE1DA /* PTHotKey+ShortcutRecorder.h */, E279508F131A7498008AE1DA /* PTHotKey+ShortcutRecorder.m */, ); path = PTHotKey; sourceTree = SOURCE_ROOT; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 9398394A0DA4351C007F53F3 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 939839390DA4350B007F53F3 /* ShortcutRecorder.h in Headers */, 9398393B0DA4350B007F53F3 /* SRRecorderControl.h in Headers */, E28113AD167B8A9D001E118E /* SRModifierFlagsTransformer.h in Headers */, E2BE924D16ABEFE400827E8C /* SRKeyEquivalentTransformer.h in Headers */, E2BE925216ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.h in Headers */, 9398393E0DA4350B007F53F3 /* SRKeyCodeTransformer.h in Headers */, 9398393F0DA4350B007F53F3 /* SRValidator.h in Headers */, 939839400DA4350B007F53F3 /* SRCommon.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; E27312291349EC9000A84433 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( E273123A1349EDB500A84433 /* PTHotKey.h in Headers */, E273123B1349EDB500A84433 /* PTHotKeyCenter.h in Headers */, E273123C1349EDB500A84433 /* PTKeyCodeTranslator.h in Headers */, E273123D1349EDB500A84433 /* PTKeyCombo.h in Headers */, E2BE927C16AC052000827E8C /* PTHotKey+ShortcutRecorder.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 9398377F0DA42965007F53F3 /* ShortcutRecorder.framework */ = { isa = PBXNativeTarget; buildConfigurationList = 939837840DA42965007F53F3 /* Build configuration list for PBXNativeTarget "ShortcutRecorder.framework" */; buildPhases = ( E2BADF0A134F13CD00F65E14 /* Resources */, 9398377D0DA42965007F53F3 /* Sources */, 9398377E0DA42965007F53F3 /* Frameworks */, 9398394A0DA4351C007F53F3 /* Headers */, ); buildRules = ( ); dependencies = ( ); name = ShortcutRecorder.framework; productName = ShortcutRecorder.framework; productReference = 939837800DA42965007F53F3 /* ShortcutRecorder.framework */; productType = "com.apple.product-type.framework"; }; E273122D1349EC9000A84433 /* PTHotKey.framework */ = { isa = PBXNativeTarget; buildConfigurationList = E27312381349EC9000A84433 /* Build configuration list for PBXNativeTarget "PTHotKey.framework" */; buildPhases = ( E273122B1349EC9000A84433 /* Sources */, E273122C1349EC9000A84433 /* Frameworks */, E27312291349EC9000A84433 /* Headers */, ); buildRules = ( ); dependencies = ( ); name = PTHotKey.framework; productName = PTHotKey; productReference = E273122E1349EC9000A84433 /* PTHotKey.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1250; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "ShortcutRecorder" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, sv, Dutch, ru, de, el, en, fr, it, ja, ko, nb, nl, pl, "pt-BR", ro, sk, th, "zh-Hans", es, "es-MX", ca, cs, pt, "zh-Hant", Base, tr, ar, ); mainGroup = 29B97314FDCFA39411CA2CEA /* ShortcutRecorder */; projectDirPath = ""; projectRoot = ""; targets = ( 9398377F0DA42965007F53F3 /* ShortcutRecorder.framework */, E273122D1349EC9000A84433 /* PTHotKey.framework */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ E2BADF0A134F13CD00F65E14 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( BA547C3C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle@2x.png in Resources */, E20A897E17F3322300502E33 /* shortcut-recorder-bezel-disabled-right@2x.png in Resources */, BA547C371A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right.png in Resources */, E2741AC51673C79F00A139BD /* ShortcutRecorder.strings in Resources */, BA547C351A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle.png in Resources */, E2741ADF1673C83800A139BD /* LICENSE.txt in Resources */, BA547C3E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right@2x.png in Resources */, BA547C481A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear@2x.png in Resources */, E20A897C17F3322300502E33 /* shortcut-recorder-bezel-disabled-middle@2x.png in Resources */, E225E4671679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left.png in Resources */, BA547C441A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right@2x.png in Resources */, E225E4681679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-left@2x.png in Resources */, BA547C291A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle.png in Resources */, BA547C451A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted.png in Resources */, BA547C3B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-middle.png in Resources */, E225E4691679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle.png in Resources */, E225E46A1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-middle@2x.png in Resources */, E225E46B1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right.png in Resources */, E20A897B17F3322300502E33 /* shortcut-recorder-bezel-disabled-middle.png in Resources */, E225E46C1679E30E00A00529 /* shortcut-recorder-bezel-blue-highlighted-right@2x.png in Resources */, E225E46D1679E30E00A00529 /* shortcut-recorder-bezel-editing-left.png in Resources */, E225E46E1679E30E00A00529 /* shortcut-recorder-bezel-editing-left@2x.png in Resources */, E225E46F1679E30E00A00529 /* shortcut-recorder-bezel-editing-middle.png in Resources */, E225E4701679E30E00A00529 /* shortcut-recorder-bezel-editing-middle@2x.png in Resources */, BA547C421A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle@2x.png in Resources */, E225E4711679E30E00A00529 /* shortcut-recorder-bezel-editing-right.png in Resources */, BA547C361A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-middle@2x.png in Resources */, BA547C321A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right@2x.png in Resources */, BA547C4A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted@2x.png in Resources */, E225E4721679E30E00A00529 /* shortcut-recorder-bezel-editing-right@2x.png in Resources */, E225E4731679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left.png in Resources */, BA547C2F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle.png in Resources */, E225E4741679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-left@2x.png in Resources */, E225E4751679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle.png in Resources */, E225E4761679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-middle@2x.png in Resources */, BA547C341A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left@2x.png in Resources */, E225E4771679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right.png in Resources */, BA547C431A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-right.png in Resources */, BA547C2B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right.png in Resources */, BA547C461A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear-highlighted@2x.png in Resources */, BA547C381A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-right@2x.png in Resources */, E225E4781679E30E00A00529 /* shortcut-recorder-bezel-graphite-highlighted-right@2x.png in Resources */, BA547C281A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left@2x.png in Resources */, BA547C301A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-middle@2x.png in Resources */, BA547C3D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-right.png in Resources */, E225E4791679E30E00A00529 /* shortcut-recorder-bezel-left.png in Resources */, E225E47A1679E30E00A00529 /* shortcut-recorder-bezel-left@2x.png in Resources */, BA547C391A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left.png in Resources */, BA547C471A5FD45B00C45C31 /* shortcut-recorder-yosemite-clear.png in Resources */, E225E47B1679E30E00A00529 /* shortcut-recorder-bezel-middle.png in Resources */, BA547C2C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-right@2x.png in Resources */, E225E47C1679E30E00A00529 /* shortcut-recorder-bezel-middle@2x.png in Resources */, BA547C2A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-middle@2x.png in Resources */, E225E47D1679E30E00A00529 /* shortcut-recorder-bezel-right.png in Resources */, BA547C3A1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-graphite-highlighted-left@2x.png in Resources */, E225E47E1679E30E00A00529 /* shortcut-recorder-bezel-right@2x.png in Resources */, BA547C271A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-blue-highlighted-left.png in Resources */, BA547C311A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-right.png in Resources */, E225E47F1679E30E00A00529 /* shortcut-recorder-clear-highlighted.png in Resources */, E225E4801679E30E00A00529 /* shortcut-recorder-clear-highlighted@2x.png in Resources */, E20A897A17F3322300502E33 /* shortcut-recorder-bezel-disabled-left@2x.png in Resources */, BA547C3F1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left.png in Resources */, BA547C331A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-editing-left.png in Resources */, BA547C401A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-left@2x.png in Resources */, E20A897D17F3322300502E33 /* shortcut-recorder-bezel-disabled-right.png in Resources */, E225E4811679E30E00A00529 /* shortcut-recorder-clear.png in Resources */, BA547C2D1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left.png in Resources */, BA547C491A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback-highlighted.png in Resources */, BA547C2E1A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-disabled-left@2x.png in Resources */, E225E4821679E30E00A00529 /* shortcut-recorder-clear@2x.png in Resources */, E225E4831679E30E00A00529 /* shortcut-recorder-snapback-highlighted.png in Resources */, E225E4841679E30E00A00529 /* shortcut-recorder-snapback-highlighted@2x.png in Resources */, E225E4851679E30E00A00529 /* shortcut-recorder-snapback.png in Resources */, E225E4861679E30E00A00529 /* shortcut-recorder-snapback@2x.png in Resources */, BA547C4C1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback@2x.png in Resources */, BA547C411A5FD45B00C45C31 /* shortcut-recorder-yosemite-bezel-middle.png in Resources */, BA547C4B1A5FD45B00C45C31 /* shortcut-recorder-yosemite-snapback.png in Resources */, E20A897917F3322300502E33 /* shortcut-recorder-bezel-disabled-left.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9398377D0DA42965007F53F3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 939837950DA429EC007F53F3 /* SRRecorderControl.m in Sources */, 939837980DA429EC007F53F3 /* SRKeyCodeTransformer.m in Sources */, 939837990DA429EC007F53F3 /* SRValidator.m in Sources */, 9398379A0DA429EC007F53F3 /* SRCommon.m in Sources */, E28113AE167B8A9D001E118E /* SRModifierFlagsTransformer.m in Sources */, E2BE924E16ABEFE400827E8C /* SRKeyEquivalentTransformer.m in Sources */, E2BE925316ABF84200827E8C /* SRKeyEquivalentModifierMaskTransformer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E273122B1349EC9000A84433 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E27312401349EDCA00A84433 /* PTHotKey.m in Sources */, E27312411349EDCA00A84433 /* PTHotKeyCenter.m in Sources */, E27312421349EDCA00A84433 /* PTKeyCodeTranslator.m in Sources */, E27312431349EDCA00A84433 /* PTKeyCombo.m in Sources */, E2BE927D16AC052600827E8C /* PTHotKey+ShortcutRecorder.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ E2741AC31673C79F00A139BD /* ShortcutRecorder.strings */ = { isa = PBXVariantGroup; children = ( E2741AC41673C79F00A139BD /* en */, E2741AC61673C7A400A139BD /* de */, E2741AC71673C7A600A139BD /* el */, E2741AC81673C7A800A139BD /* fr */, E2741AC91673C7AA00A139BD /* it */, E2741ACB1673C7AD00A139BD /* ko */, E2741ACC1673C7AF00A139BD /* nb */, E2741ACD1673C7B200A139BD /* nl */, E2741ACE1673C7B300A139BD /* pl */, E2741ACF1673C7B500A139BD /* pt-BR */, E2741AD01673C7B600A139BD /* ro */, E2741AD11673C7B800A139BD /* ru */, E2741AD21673C7BB00A139BD /* sk */, E2741AD31673C7BC00A139BD /* sv */, E2741AD41673C7BD00A139BD /* th */, E2741AD61673C7C000A139BD /* es */, E2741AD71673C7C200A139BD /* es-MX */, E2741AD81673C7C300A139BD /* ca */, E2741AD91673C7C600A139BD /* cs */, E2741ADA1673C7C800A139BD /* pt */, 9AA522D823416E6A00C9E005 /* tr */, 3552066127AF63DC000EF08F /* ar */, ); name = ShortcutRecorder.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 939837820DA42965007F53F3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 2.6; DYLIB_CURRENT_VERSION = 2.17; FRAMEWORK_VERSION = A; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Library/Prefix.pch; INFOPLIST_FILE = Library/Info.plist; INSTALL_PATH = "@rpath"; MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.kulakov.ShortcutRecorder; PRODUCT_NAME = ShortcutRecorder; PROVISIONING_PROFILE = ""; }; name = Debug; }; 939837830DA42965007F53F3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 2.6; DYLIB_CURRENT_VERSION = 2.17; FRAMEWORK_VERSION = A; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Library/Prefix.pch; INFOPLIST_FILE = Library/Info.plist; INSTALL_PATH = "@rpath"; MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.kulakov.ShortcutRecorder; PRODUCT_NAME = ShortcutRecorder; PROVISIONING_PROFILE = ""; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_X86_VECTOR_INSTRUCTIONS = ssse3; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = DEBUG; GCC_VERSION = ""; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wall"; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_X86_VECTOR_INSTRUCTIONS = ssse3; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = ""; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wall"; }; name = Release; }; E27312361349EC9000A84433 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1.5; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; INFOPLIST_FILE = PTHotKey/Info.plist; INSTALL_PATH = "@rpath"; MACOSX_DEPLOYMENT_TARGET = 10.13; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PRODUCT_BUNDLE_IDENTIFIER = com.kulakov.PTHotKey; PRODUCT_NAME = PTHotKey; }; name = Debug; }; E27312371349EC9000A84433 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1.5; FRAMEWORK_VERSION = A; GCC_PRECOMPILE_PREFIX_HEADER = YES; INFOPLIST_FILE = PTHotKey/Info.plist; INSTALL_PATH = "@rpath"; MACOSX_DEPLOYMENT_TARGET = 10.13; OTHER_LDFLAGS = ( "-framework", Foundation, "-framework", AppKit, ); PRODUCT_BUNDLE_IDENTIFIER = com.kulakov.PTHotKey; PRODUCT_NAME = PTHotKey; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 939837840DA42965007F53F3 /* Build configuration list for PBXNativeTarget "ShortcutRecorder.framework" */ = { isa = XCConfigurationList; buildConfigurations = ( 939837820DA42965007F53F3 /* Debug */, 939837830DA42965007F53F3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "ShortcutRecorder" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E27312381349EC9000A84433 /* Build configuration list for PBXNativeTarget "PTHotKey.framework" */ = { isa = XCConfigurationList; buildConfigurations = ( E27312361349EC9000A84433 /* Debug */, E27312371349EC9000A84433 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/ShortcutRecorder.xcodeproj/xcshareddata/xcschemes/PTHotKey.framework.xcscheme ================================================ ================================================ FILE: Clocker/Clocker/ShortcutRecorder-master/ShortcutRecorder.xcodeproj/xcshareddata/xcschemes/ShortcutRecorder.framework.xcscheme ================================================ ================================================ FILE: Clocker/Clocker/ar.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/ar.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "كلوكر"; "Thank you for helping make Clocker even better!" = "شكرا للمساعدة في جعل Clocker افضل!"; "iRateMessageTitle" = "قيّم %@"; "iRateAppMessage" = "اذا ما اعجبك استخدام %@، فهل من الممكن ان نأخذ دقيقة من وقتك لتقييمه ؟ لن يأخذ أكثر من دقيقة من وقتك. نشكرك لدعمك!"; "iRateGameMessage" = "إذا أعجبك %@، فهل من الممكن أن نأخذ دقيقة من وقتك لتقييمه؟ لن تأخذ عملية التقييم أكثر من دقيقة من وقتك. نشكرك لدعمك!"; "iRateCancelButton" = "لا، شكراً"; "iRateRateButton" = "قيّم الآن"; "iRateRemindButton" = "ذكرني لاحقاً"; "iRateUpdateMessage" = "التحديث الان؟"; "ClockerVersion" = "الإصدار %@"; "CLFeedbackAlertTitle" = "شكرا للمساعدة في جعل Clocker افضل!"; "app-name" = "Clocker"; "start-at-login" = "بدء عند تسجيل الدخول"; "setup-steps" = "يتطلب الأمر فقط 3 خطوات لضبط Clocker"; "Permissions-Header" = "الصلاحيات"; "See your next Calendar event here." = "تابع حدثك القادم هنا."; "Click here to start." = "أضغط هنا لتبدأ."; "Reminders Access" = "صلاحيات الوصول إلى التذكيرات"; "Calendar Access" = "صلاحيات الوصول إلى التقويم"; "Permissions" = "الصلاحيات"; "Calendar Detail" = "الأحداث القادمة من تقويماتك الشخصية والمشتركة يمكن أن تظهر في شريط القائمة ولوحة التحكم."; "Reminders Detail" = "عين تذكيرات في المنطقة الزمني للموقع الذي تريده. يتم تخزين تذكيراتك في تطبيق التذكير بشكل افتراضي."; "Privacy Text" = "يمكنك تغيير هذا لاحقًا في قسم اعدادات الخصوصية المتواجد بتفضيلات النظام."; "Granted Button Text" = "مسموح"; "Denied Button Text" = "مرفوض"; "Grant Button Text" = "منح"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "يتطلب الأمر فقط 3 خطوات لتفعيل Clocker."; "Get Started" = "ابدأ الاستخدام"; // Tab Item Titles "Preferences Tab" = "التفضيلات"; "Appearance Tab" = "إعدادات المظهر"; "Calendar Tab" = "التقويم"; "About Tab" = "حول"; "Permissions Tab" = "الصلاحيات"; // General Preferences Screen "Start at Login" = "بدأ Clocker عند تسجيل الدخول"; "Sort by Time Difference" = "الترتيب حسب الفارق الزمني"; "Sort by Name" = "الترتيب حسب الاسم"; "Sort by Label" = "الترتيب حسب العلامة"; "Search Field Placeholder" = "أدخل اسم المدينة أو الولاية أو البلد"; "No Timezone Selected" = "رجاء اختر منطقتك الزمنية!"; "Max Timezones Selected" = "الحد الأقصى المسموح به 100 منطقة زمنية!"; "Max Search Characters" = "الحد الأقصى 50 حرفًا!"; "Add Button Title" = "إضافة"; "Close Button Title" = "إغلاق"; // Onboarding "Open Clocker At Login" = "بدأ \"كلوكر\" عند تسجيل الدخول"; "Launch Clocker" = "تشغيل Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "يتطلب الأمر فقط 3 خطوات لتفعيل Clocker."; "Get Started" = "ابدأ الاستخدام"; // Permissions "Calendar Access Title" = "صلاحيات الوصول إلى التقويم"; "Reminders Access Title" = "صلاحيات الوصول إلى التذكيرات"; "Later Config Description" = "يمكن إعداد هذا لاحقًا في تفضيلات النظام."; "Back" = "عودة"; "Continue" = "متابعة"; "Clocker is more useful when it can display events from your calendars." = "يكون Clocker مفيدًا أكثر عندما يكون لديه صلاحيات عرض أحداث من تقويمك."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "يكون Clocker أكثر فائدةُ عندما يكون لديه صلاحيات عرض أحداث من تقويمك. يمكنك تغير هذا في تفضيلات النظام > اعدادات الأمان والخصوصية > أعدادات الخصوصية."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "الانتقال إلى تفضيلات النظام"; "Grant Access" = "منح صلاحية الوصول"; "Upcoming events from your calendars can be shown in the menubar + panel." = "الأحداث القادمة من تقويماتك الشخصية والمشتركة يمكن أن تظهر في شريط القائمة ولوحة التحكم."; "Granted" = "سماح"; "Denied" = "رفض"; "Grant" = "منح"; "Unexpected" = "غير متوقع"; // Onboarding Search "Quick Add Locations" = "إضافة سريعة للمواقع"; "More search options in Clocker Preferences." = "المزيد من خيارات البحث في إعدادات تفضيلات Clocker."; "Enter 3 or more characters for locations you'll like to add" = "أدخل على الأقل ثلاثة أحرف من الموقع الذي ترغب بإضافته"; // Start at Login "Launch at Login" = "تشغيل عند تسجيل الدخول"; "This can be configured later in Clocker Preferences." = "يمكن إعداد هذا لاحقًا في تفضيلات النظام."; "Should Clocker open automatically on startup?" = "هل تريد أن يبدأ Clocker عند بدأ التشغيل؟"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; /* Appears in Preferences -> Appearance which allows the user to switch between the compact and standard menubar mode. It doesn't lead anywhere. */ "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/ca.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/ca.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Rate %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "No, Thanks"; "iRateRateButton" = "Rate It Now"; "iRateRemindButton" = "Remind Me Later"; "iRateUpdateMessage" = "Update now?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Thank you for helping make Clocker even better!"; "app-name" = "Clocker"; "start-at-login" = "Start At Login"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "Permissions"; "See your next Calendar event here." = "See your next Calendar event here."; "Click here to start." = "Click here to start."; "Reminders Access" = "Reminders Access"; "Calendar Access" = "Calendar Access"; "Permissions" = "Permissions"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Granted"; "Denied Button Text" = "Denied"; "Grant Button Text" = "Grant"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Preferences"; "Appearance Tab" = "Appearance"; "Calendar Tab" = "Calendar"; "About Tab" = "About"; "Permissions Tab" = "Permissions"; // General Preferences Screen "Start at Login" = "Start Clocker at Login"; "Sort by Time Difference" = "Sort by Time Difference"; "Sort by Name" = "Sort by Name"; "Sort by Label" = "Sort by Label"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "Add"; "Close Button Title" = "Close"; // Onboarding "Open Clocker At Login" = "Open Clocker At Login"; "Launch Clocker" = "Launch Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Permissions "Calendar Access Title" = "Calendar Access"; "Reminders Access Title" = "Reminders Access"; "Later Config Description" = "These can be configured later in System Preferences."; "Back" = "Back"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/com.abhishek.ClockerHelper.plist ================================================ Program /Applications/Clocker.app/Contents/MacOS/Clocker Label com.abhishek.ClockerHelper RunAtLoad KeepAlive ================================================ FILE: Clocker/Clocker/de.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; /* (No Comment) */ "CFBundleName" = "Clocker"; /* Privacy - Calendars Usage Description */ "NSCalendarsUsageDescription" = "Clocker kann nützlicher sein, wenn es kommende Termine von Ihren Kalender anzeigen kann. Sie können diese Einstellung in Systemeinstellungen › Sicherheit Privatsphäre › Privatsphäre ändern"; /* Copyright (human-readable) */ "NSHumanReadableCopyright" = "Copyright © 2016, Abhishek Banthia"; /* Privacy - Location Always and When In Use Usage Description */ "NSLocationAlwaysAndWhenInUseUsageDescription" = "Clocker kann nützlicher sein, wenn es Ihren Standort zur Bestimmung Ihrer aktuellen Zeitzone verwenden kann."; /* Privacy - Location Usage Description */ "NSLocationUsageDescription" = "Clocker kann nützlicher sein, wenn es Ihren Standort zur Bestimmung Ihrer aktuellen Zeitzone verwenden kann."; /* Privacy - Reminders Usage Description */ "NSRemindersUsageDescription" = "Clocker kann nützlicher sein, wenn es Erinnerungen für Ihre ausgewählte Zeitzone(n) setzen kann. Sie können diese Einstellung in Systemeinstellungen › Sicherheit und Privatsphäre › Privatsphäre ändern."; ================================================ FILE: Clocker/Clocker/de.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Danke, dass du dabei geholfen hast, Clocker noch besser zu machen!"; "iRateMessageTitle" = "%@ bewerten"; "iRateAppMessage" = "Wenn dir %@ gefällt, dann bewerte uns doch im App Store. Das dauert kaum eine Minute und hilft uns sehr. Vielen Dank für deine Unterstützung!"; "iRateGameMessage" = "Wenn du Spaß an %@ hast, dann bewerte uns doch im App Store. Das dauert kaum eine Minute und hilft uns sehr. Vielen Dank, für deine Unterstützung!"; "iRateCancelButton" = "Nein, danke"; "iRateRateButton" = "Jetzt bewerten"; "iRateRemindButton" = "Später erinnern"; "iRateUpdateMessage" = "Jetzt aktualisieren?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Danke, dass Du hilfst Clocker noch besser zu machen!"; "app-name" = "Clocker"; "start-at-login" = "Bei der Anmeldung starten"; "setup-steps" = "In nur drei Schritten ist Clocker fertig eingerichtet"; "Permissions-Header" = "Berechtigungen"; "See your next Calendar event here." = "Siehe bevorstehendes Kalenderereignis?"; "Click here to start." = "Bevorstehendes Kalenderereignis hier anzeigen"; "Reminders Access" = "Erinnerungszugriff"; "Calendar Access" = "Kalenderzugriff"; "Permissions" = "Berechtigungen"; "Calendar Detail" = "Anstehende Termine aus Deinem persönlichen und geteilten Kalendern können in der Menüleiste und im Fenster angezeigt werden."; "Reminders Detail" = "Erinnerungen in der Zeitzone Deiner Wahl festlegen. Deine Erinnerungen werden in der Standard-Erinnerungs-App gespeichert."; "Privacy Text" = "Du kannst das später im Bereich Datenschutz in den Systemeinstellungen ändern."; "Granted Button Text" = "Gewährt"; "Denied Button Text" = "Verweigert"; "Grant Button Text" = "Gewähren"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "In nur drei Schritten ist Clocker fertig eingerichtet."; "Get Started" = "Erste Schritte"; // Tab Item Titles "Preferences Tab" = "Einstellungen"; "Appearance Tab" = "Erscheinungsbild"; "Calendar Tab" = "Kalender"; "About Tab" = "Über"; "Permissions Tab" = "Berechtigungen"; // General Preferences Screen "Start at Login" = "Clocker bei der Anmeldung starten"; "Sort by Time Difference" = "Nach Zeitdifferenz sortieren"; "Sort by Name" = "Nach Namen sortieren"; "Sort by Label" = "Nach Bezeichnung sortieren"; "Search Field Placeholder" = "Gebe eine Stadt, ein Bundesland oder Land ein"; "No Timezone Selected" = "Bitte wähle eine Zeitzone!"; "Max Timezones Selected" = "Es werden maximal 100 Zeitzonen unterstützt!"; "Max Search Characters" = "Es sind nur 50 Zeichen erlaubt!"; "Add Button Title" = "Hinzufügen"; "Close Button Title" = "Schließen"; // Onboarding "Open Clocker At Login" = "Clocker bei der Anmeldung starten"; "Launch Clocker" = "Clocker starten"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "In nur drei Schritten ist Clocker fertig eingerichtet."; "Get Started" = "Erste Schritte"; // Permissions "Calendar Access Title" = "Kalender-Zugriff"; "Reminders Access Title" = "Zugriff auf Erinnerungen"; "Later Config Description" = "Dies kann später in den Clocker Einstellungen konfiguriert werden."; "Back" = "Zurück"; "Continue" = "Fortfahren"; "Clocker is more useful when it can display events from your calendars." = "Clocker ist nützlicher, wenn es Ereignisse aus Deinem Kalendern anzeigen kann."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker ist nützlicher, wenn es Ereignisse aus Deinem Kalendern anzeigen kann. Du kannst diese Einstellung in Systemeinstellungen › Sicherheit Privatsphäre › Privatsphäre ändern."; "Launch Preferences" = "Einstellungen öffnen"; "Grant Access" = "Zugriff erlauben"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Anstehende Termine aus deinem persönlichen und geteilten Kalendern können in der Menüleiste und im Fenster angezeigt werden."; "Granted" = "Gewährt"; "Denied" = "Abgelehnt"; "Grant" = "Gewähren"; "Unexpected" = "Unerwartet"; // Onboarding Search "Quick Add Locations" = "Orte schnell hinzufügen"; "More search options in Clocker Preferences." = "Weitere Suchoptionen in den Clocker Einstellungen."; "Enter 3 or more characters for locations you'll like to add" = "Gebe 3 oder mehr Zeichen für den Orte ein, die Du hinzufügen möchtest"; // Start at Login "Launch at Login" = "Bei der Anmelden starten"; "This can be configured later in Clocker Preferences." = "Dies kann später in den Clocker Einstellungen konfiguriert werden."; "Should Clocker open automatically on startup?" = "Soll Clocker beim Start automatisch geöffnet werden?"; // Final Onboarding Screen "You're all set!" = "Du bist startklar!"; "Thank you for the details." = "Vielen Dank für die Details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Du wirst ein Uhr-Symbol in Ihrer Menüleiste sehen, wenn Du die App startest. Wenn Du ein Dock-Symbol sehen möchtest, gehe in die Einstellungen."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Wenn Du uns helfen möchtest die App in Deine Sprache zu übersetzen oder von Zeit zu Zeit Informationen zur App erhalten möchtest, gibt bitte hier Deine E-Mail ein!"; // Appearance Tab "Panel Theme" = "Erscheinungsbild"; "Favourite a timezone to enable menubar display options." = "Favorisiere eine Zeitzone, um diese in der Menüleiste zu zeigen."; "Main Panel Options" = "Optionen für Programmfenster"; "Time Format" = "Zeitformat"; "Day Display Options" = "Anzeigeoptionen für den Tag"; "Show Future Slider" = "\"Future Slider\" anzeigen"; "Show Sunrise/Sunset" = "Sonnenaufgang/-untergang anzeigen"; "Display the time in seconds" = "Zeit in Sekunden anzeigen"; "Larger Text" = "Größerer Text"; "Future Slider Range" = "Bereich für den \"Future Slider\""; "Include Date" = "Datum hinzufügen"; "Include Day" = "Tag hinzufügen"; "Include Place Name" = "Ortsname hinzufügen"; "Menubar Display Options" = "Anzeigeoptionen für die Menüleiste"; "Menubar Mode" = "Menüleisten-Modus"; "Preview" = "Vorschau"; "Miscellaneous" = "Verschiedenes"; // Empty View "No places added" = "Keine Orte hinzugefügt"; // Panel "No upcoming event." = "Kein anstehendes Ereignis."; "You have no events scheduled for tomorrow." = "Du hast keine Kalendereinträge für morgen."; // Review "Enjoy using Clocker?" = "Gefällt Dir Clocker?"; // App Feedback "Tell us what you think!" = "Sag uns, was Du denkst!"; "Contact Information (Optional)" = "Kontaktinformationen (optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Kontaktinformationen sind optional! Diese helfen uns jedoch Dich zu erreichen, falls wir weitere Informationen benötigen oder helfen können!"; // About View Screen "Feedback is always welcome:" = "Feedback ist immer willkommen:"; // Calendars View "Upcoming Event View Options" = "Zukünftige Ereignisse zeigen"; "Show Upcoming Event View" = "Anzeigeoptionen für zukünftige Ereignisse"; "Show All Day Meetings" = "Ganztägige Ereignisse anzeigen"; "Show Next Meeting Title in Menubar" = "Das nächste Ereignis in der Menüliste zeigen"; "Truncate menubar text longer than" = "Text in der Menüleiste kürzen, der länger ist als"; "characters" = "Zeichen"; "Show events from" = "Zeige Ereignisse aus"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Wenn der Besprechungstitel \"Besprechung mit Neel\" ist und die Kürzungslänge auf 5 eingestellt wurde, dann erscheint der Text in der Menüleiste als \"Bespr...\""; // Notes Popover "Reminder Set" = "Erinnerung gespeichert"; "Successfully set." = "Erfolgreich gespeichert."; // Errors "You're offline, maybe?" = "Bist Du eventuell offline?"; "Try again, maybe?" = "Versuche es nochmal!"; "The Internet connection appears to be offline." = "Scheinbar besteht keine Verbindung zum Internet."; // UI Tests "New Zealand" = "Neuseeland"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/en.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/en.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Rate %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "No, Thanks"; "iRateRateButton" = "Rate It Now"; "iRateRemindButton" = "Remind Me Later"; "iRateUpdateMessage" = "Update now?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Thank you for helping make Clocker even better!"; "app-name" = "Clocker"; "start-at-login" = "Start At Login"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "Permissions"; "See your next Calendar event here." = "See your next Calendar event here."; "Click here to start." = "Click here to start."; "Reminders Access" = "Reminders Access"; "Calendar Access" = "Calendar Access"; "Permissions" = "Permissions"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Granted"; "Denied Button Text" = "Denied"; "Grant Button Text" = "Grant"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Preferences"; "Appearance Tab" = "Appearance"; "Calendar Tab" = "Calendar"; "About Tab" = "About"; "Permissions Tab" = "Permissions"; // General Preferences Screen "Start at Login" = "Start Clocker at Login"; "Sort by Time Difference" = "Sort by Time Difference"; "Sort by Name" = "Sort by Name"; "Sort by Label" = "Sort by Label"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "Add"; "Close Button Title" = "Close"; // Onboarding "Open Clocker At Login" = "Open Clocker At Login"; "Launch Clocker" = "Launch Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Permissions "Calendar Access Title" = "Calendar Access"; "Reminders Access Title" = "Reminders Access"; "Later Config Description" = "These can be configured later in System Preferences."; "Back" = "Back"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar and the panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Time Scroller" = "Time Scroller"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; // Upcoming Event View "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/en.lproj/Panel.xib ================================================ ================================================ FILE: Clocker/Clocker/es.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/es.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "¡Gracias por ayudar a mejorar Clocker!"; "iRateMessageTitle" = "Valora %@"; "iRateAppMessage" = "Si te gusta %@, ¿podrías escribirnos una reseña? No te tomará más de un minuto. ¡Gracias por tu apoyo!"; "iRateGameMessage" = "Si te gusta %@, ¿te importaría dedicar un momento para valorarla? No tardarás nada. ¡Gracias por tu apoyo!"; "iRateCancelButton" = "No, gracias"; "iRateRateButton" = "Califícalo ahora"; "iRateRemindButton" = "Recordarme más tarde"; "iRateUpdateMessage" = "¿Actualizar ahora?"; "ClockerVersion" = "Versión %@"; "CLFeedbackAlertTitle" = "¡Gracias por ayudar a mejorar Clocker!"; "app-name" = "Clocker"; "start-at-login" = "Abrir en el inicio de sesión"; "setup-steps" = "Sólo te tomará 3 pasos configurar Clocker"; "Permissions-Header" = "Permisos"; "See your next Calendar event here." = "Vea aquí su próximo evento del calendario."; "Click here to start." = "Haga clic aquí para comenzar."; "Reminders Access" = "Acceder a Recordatorios"; "Calendar Access" = "Acceder al calendario"; "Permissions" = "Permisos"; "Calendar Detail" = "Los próximos eventos de sus calendarios personales y compartidos pueden mostrarse en la barra de menú y en el panel."; "Reminders Detail" = "Establece recordatorios en la zona horaria de la ubicación seleccionada. Los recordatorios serán guardados en la aplicación Recordatorios predeterminada."; "Privacy Text" = "Puede cambiar esto más tarde en la sección de Privacidad de las Preferencias del Sistema."; "Granted Button Text" = "Permitido"; "Denied Button Text" = "Denegado"; "Grant Button Text" = "Permitir"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Sólo te tomará 3 pasos configurar Clocker."; "Get Started" = "Comenzar ahora"; // Tab Item Titles "Preferences Tab" = "Preferencias"; "Appearance Tab" = "Aspecto"; "Calendar Tab" = "Calendario"; "About Tab" = "Acerca de"; "Permissions Tab" = "Permisos"; // General Preferences Screen "Start at Login" = "Abrir Clocker al inicio de sesión"; "Sort by Time Difference" = "Ordenar por diferencia horaria"; "Sort by Name" = "Ordenar por nombre"; "Sort by Label" = "Ordenar por etiqueta"; "Search Field Placeholder" = "Introduzca un nombre de ciudad, estado o país"; "No Timezone Selected" = "¡Por favor, selecciona una zona horaria!"; "Max Timezones Selected" = "¡Se permiten maximo 100 zonas horarias!"; "Max Search Characters" = "¡Solo se permiten 50 caracteres!"; "Add Button Title" = "Añadir"; "Close Button Title" = "Cerrar"; // Onboarding "Open Clocker At Login" = "Abrir Clocker al iniciar sesión"; "Launch Clocker" = "Abrir Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Sólo te tomará 3 pasos configurar Clocker."; "Get Started" = "Comenzar ahora"; // Permissions "Calendar Access Title" = "Acceder al calendario"; "Reminders Access Title" = "Acceder a Recordatorios"; "Later Config Description" = "Se puede configurar más tarde en Preferencias del sistema."; "Back" = "Regresar"; "Continue" = "Continuar"; "Clocker is more useful when it can display events from your calendars." = "Clocker es más útil cuando puede mostrar eventos de tus calendarios."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker es más útil cuando puede mostrar eventos de tus calendarios. Puede cambiar esta configuración en Preferencias del Sistema › Seguridad y privacidad › Privacidad."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Abrir las preferencias"; "Grant Access" = "Permitir acceso"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Los próximos eventos de sus calendarios personales y compartidos pueden mostrarse en la barra de menú y en el panel."; "Granted" = "Permitido"; "Denied" = "Denegado"; "Grant" = "Permitir"; "Unexpected" = "Inesperado"; // Onboarding Search "Quick Add Locations" = "Agregar ubicaciones rápidamente"; "More search options in Clocker Preferences." = "Más opciones de búsqueda en las preferencias de Clocker."; "Enter 3 or more characters for locations you'll like to add" = "Introduce 3 o más caracteres de las ubicaciones que quisiera añadir"; // Start at Login "Launch at Login" = "Ejecutar cuando inicia la sesión"; "This can be configured later in Clocker Preferences." = "Esto se puede configurar más tarde en las Preferencias de Clocker."; "Should Clocker open automatically on startup?" = "¿Quieres que Clocker se abra automáticamente al inicio?"; // Final Onboarding Screen "You're all set!" = "¡Todo listo!"; "Thank you for the details." = "Gracias por informarnos."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Verás un icono de reloj en tu barra de menú cuando inicies la aplicación. Si quieres ver el icono en el Dock, ve a Preferencias."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Si quieres ayudarnos a traducir la aplicación en tu idioma o recibir actualizaciones relacionadas con la aplicación, ¡Por favor escribe tu correo electrónico!"; // Appearance Tab "Panel Theme" = "Apariencia de la aplicación"; "Favourite a timezone to enable menubar display options." = "Elija una zona horaria como favorita para habilitar las opciones de visualización de la barra de menú."; "Main Panel Options" = "Opciones del Panel Principal"; "Time Format" = "Formato de hora"; "Day Display Options" = "Formato de visualización del día"; "Show Future Slider" = "Mostrar el deslizador para horas futuras"; "Show Sunrise/Sunset" = "Mostrar salida de sol/puesta de sol"; "Display the time in seconds" = "Mostrar el tiempo en segundos"; "Larger Text" = "Texto más grande"; "Future Slider Range" = "Deslizador de rango de tiempo"; "Include Date" = "Mostrar fecha"; "Include Day" = "Mostrar día"; "Include Place Name" = "Mostrar nombre del lugar"; "Menubar Display Options" = "Opciones de visualización del reloj en la barra de menú"; "Menubar Mode" = "Modo en la barra de menú"; "Preview" = "Vista previa"; "Miscellaneous" = "Varios"; // Empty View "No places added" = "No hay zonas horarias añadidas"; // Panel "No upcoming event." = "No hay próximos eventos."; "You have no events scheduled for tomorrow." = "No tienes eventos programados para mañana."; // Review "Enjoy using Clocker?" = "¿Te gusta Clocker?"; // App Feedback "Tell us what you think!" = "¡Queremos conocer tu opinión!"; "Contact Information (Optional)" = "Información de contacto (opcional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "¡Los campos de contacto son opcionales! ¡Su información de contacto nos permitirá contactarnos con usted en caso de que necesitemos más información o podamos ayudar!"; // About View Screen "Feedback is always welcome:" = "Tus comentarios son bienvenidos:"; // Calendars View "Upcoming Event View Options" = "Opciones de visualización de los próximos eventos"; "Show Upcoming Event View" = "Mostrar el próximo evento"; "Show All Day Meetings" = "Mostrar todas las reuniones del día"; "Show Next Meeting Title in Menubar" = "Mostrar el título de la próxima reunión en la barra de menú"; "Truncate menubar text longer than" = "Cortar el texto de la barra de menú si es mayor que"; "characters" = "caracteres"; "Show events from" = "Mostrar eventos de"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Si el título de la reunión es \"Reunión con Neel\" y la longitud de corte está establecida en 5 caracteres, el texto en la barra de menú aparecerá como \"Reuni…\""; // Notes Popover "Reminder Set" = "Recordatorio establecido"; "Successfully set." = "Configurado con éxito."; // Errors "You're offline, maybe?" = "¿Quizás estás sin conexión a internet?"; "Try again, maybe?" = "¿Podrías intentarlo otra vez?"; "The Internet connection appears to be offline." = "Parece que no hay conexión a internet."; // UI Tests "New Zealand" = "Nueva Zelanda"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/fr.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/fr.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Merci d'aider à améliorer Clocker !"; "iRateMessageTitle" = "Noter %@"; "iRateAppMessage" = "Si vous aimez utiliser %@, pourriez-vous prendre un moment pour l'évaluer ? Cela ne prendra pas plus d'une minute. Merci pour votre soutien !"; "iRateGameMessage" = "Si vous aimez utiliser %@, pourriez-vous prendre un moment pour l'évaluer ? Cela ne prendra pas plus d'une minute. Merci pour votre soutien !"; "iRateCancelButton" = "Non Merci"; "iRateRateButton" = "Évaluez-le maintenant"; "iRateRemindButton" = "Me le rappeler ultérieurement"; "iRateUpdateMessage" = "Mettre à jour maintenant ?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Merci pour votre aide à rendre Clocker encore meilleur!"; "app-name" = "Clocker"; "start-at-login" = "Activer avec la session"; "setup-steps" = "Plus que trois marches à gravir et vous aurez ajusté Clocker"; "Permissions-Header" = "Droits d'accès"; "See your next Calendar event here." = "Retrouvez votre prochain événement de calendrier ici."; "Click here to start." = "Cliquez ici pour commencer."; "Reminders Access" = "Accès aux rappels"; "Calendar Access" = "Accès au Calendrier"; "Permissions" = "Autorisations"; "Calendar Detail" = "Les événements à venir de vos calendriers personnels et partagés peuvent être affichés dans la barre de menus et dans le panneau."; "Reminders Detail" = "Placez des rappels dans le fuseau horaire de votre choix. Vos rappels seront enregistrés dans l'application 'Rappels' par défaut."; "Privacy Text" = "Vous pourrez modifier cela plus tard dans la section Confidentialité des Préférences Système."; "Granted Button Text" = "Autorisé"; "Denied Button Text" = "Refusée"; "Grant Button Text" = "Autoriser"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Cela ne prend que 3 étapes pour configurer Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Préférences"; "Appearance Tab" = "Apparence"; "Calendar Tab" = "Calendrier"; "About Tab" = "À propos"; "Permissions Tab" = "Autorisations"; // General Preferences Screen "Start at Login" = "Démarrer Clocker à la connexion"; "Sort by Time Difference" = "Trier par différence de temps"; "Sort by Name" = "Tri par nom"; "Sort by Label" = "Trier par étiquette"; "Search Field Placeholder" = "Saisissez une ville, une région ou un pays"; "No Timezone Selected" = "Veuillez sélectionner un fuseau horaire !"; "Max Timezones Selected" = "100 fuseaux horaires maximum autorisés !"; "Max Search Characters" = "50 caractères maximum autorisés !"; "Add Button Title" = "Ajouter"; "Close Button Title" = "Fermer"; // Onboarding "Open Clocker At Login" = "Ouvrir Clocker à la connexion"; "Launch Clocker" = "Lancer Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Cela ne prend que 3 étapes pour configurer Clocker."; "Get Started" = "Commencer"; // Permissions "Calendar Access Title" = "Accès au calendrier"; "Reminders Access Title" = "Accès aux rappels"; "Later Config Description" = "Ces paramètres peuvent être configurés plus tard dans les Préférences Système."; "Back" = "Retour"; "Continue" = "Continuer"; "Clocker is more useful when it can display events from your calendars." = "Clocker est plus utile quand il peut afficher des événements de vos calendriers."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker est plus utile quand il peut afficher des événements de vos calendriers. Vous pouvez modifier ce paramètre dans Préférences Système › Sécurité & Confidentialité › Confidentialité."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Préférences de démarrage"; "Grant Access" = "Autoriser l'accès"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Les événements à venir de vos calendriers personnels et partagés peuvent être affichés dans la barre de menus et dans le panneau."; "Granted" = "Autorisé"; "Denied" = "Refusé"; "Grant" = "Autoriser"; "Unexpected" = "Inattendu"; // Onboarding Search "Quick Add Locations" = "Ajouter rapidement des emplacements"; "More search options in Clocker Preferences." = "Plus d'options de recherche dans les Préférences de Clocker."; "Enter 3 or more characters for locations you'll like to add" = "Entrez 3 caractères ou plus pour les emplacements que vous souhaitez ajouter"; // Start at Login "Launch at Login" = "Démarrer à la connexion"; "This can be configured later in Clocker Preferences." = "Ceci peut être configuré plus tard dans les préférences de Clocker."; "Should Clocker open automatically on startup?" = "Clocker doit-il s'ouvrir automatiquement au démarrage ?"; // Final Onboarding Screen "You're all set!" = "Tout est prêt !"; "Thank you for the details." = "Merci pour votre commentaire."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Vous verrez une icône d'horloge dans votre barre de menu lorsque vous lancez l'application. Si vous souhaitez voir une icône de dock, allez dans Préférences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Si vous souhaitez nous aider à localiser l'application dans votre langue ou recevoir des mises à jour peu fréquentes liées à l'application, veuillez entrer votre adresse e-mail !"; // Appearance Tab "Panel Theme" = "Apparence de la fenêtre"; "Favourite a timezone to enable menubar display options." = "Mettre en favori un fuseau horaire pour activer les options d'affichage de la barre de menus."; "Main Panel Options" = "Options de la fenêtre principale"; "Time Format" = "Format de l'heure"; "Day Display Options" = "Options d'affichage du jour"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Afficher le lever/coucher du soleil"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Prévisualisation"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "Aucun lieu ajouté"; // Panel "No upcoming event." = "Aucun événement à venir."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Floride"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/hi.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "विश्व का समय"; "CFBundleName" = "विश्व का समय"; ================================================ FILE: Clocker/Clocker/hi.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "विश्व का समय"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Rate %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "No, Thanks"; "iRateRateButton" = "Rate It Now"; "iRateRemindButton" = "Remind Me Later"; "iRateUpdateMessage" = "Update now?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Thank you for helping make Clocker even better!"; "app-name" = "Clocker"; "start-at-login" = "Start At Login"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "अनुमति"; "See your next Calendar event here." = "अपना अगला कैलेंडर कार्यक्रम यहां देखें।"; "Click here to start." = "शुरू करने के लिए यहाँ क्लिक करें।"; "Reminders Access" = "रिमाइंडर अनुमति"; "Calendar Access" = "कैलेंडर अनुमति"; "Permissions" = "अनुमति"; "Reminders Detail" = "अपनी पसंद के स्थान के समय क्षेत्र में रिमाइंडर सेट करें। आपके रिमाइंडर डिफ़ॉल्ट रिमाइंडर ऐप में संग्रहीत किए जाते हैं।"; "Calendar Detail" = "अपने निजी और साझा कैलेंडर से आने वाली घटनाओं मेनूबार और पैनल में दिखाया जा सकता है।"; "Privacy Text" = "आप आपकी पसंद भविष्य में सिस्टम प्रिफ्रेंसेज के प्राइवसी अनुभाग में बदल सकते हैं।"; "Granted Button Text" = "स्वीकृत"; "Denied Button Text" = "इनकार किया"; "Grant Button Text" = "अनुदान"; // Tab Item Titles "Preferences Tab" = "प्रिफ्रेंसेज"; "Appearance Tab" = "दिखावट"; "Calendar Tab" = "कैलेंडर"; "About Tab" = "अबाउट"; "Permissions Tab" = "अनुमति"; // General Preferencess "Start at Login" = "लॉगिन पर शुरू करें"; "Sort by Time Difference" = "समय अंतर द्वारा सॉर्ट करें"; "Sort by Name" = "नाम द्वारा सॉर्ट करें"; "Sort by Label" = "लेबल द्वारा सॉर्ट करें"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "ऐड "; "Close Button Title" = "बंद करे"; // Onboarding "Open Clocker At Login" = "Open Clocker At Login"; "Launch Clocker" = "Launch Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "बस तीन स्टेप् और एप्लिकेशन तैयार।"; "Get Started" = "शुरु करे"; // Permissions "Calendar Access Title" = "कैलेंडर एक्सेस"; "Reminders Access Title" = "रिमाइंडर एक्सेस"; "Later Config Description" = "इन्हें बाद में सिस्टम प्राथमिकता में कॉन्फ़िगर किया जा सकता है।"; "Back" = "वापस"; "Continue" = "जारी रखें"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "प्रकटन"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "न्यूज़ीलैंड"; "Florida" = "फ्लोरिडा"; "San Francisco" = "सान फ्रांसिस्को"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/hr.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/hr.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 7/9/21. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Hvala što pomažeš poboljšati Clocker!"; "iRateMessageTitle" = "Ocijeni %@"; "iRateAppMessage" = "Ako voliš koristiti %@, ocijeni program. Postupak ocjenjivanja traje manje od jedne minute. Hvala na podršci!"; "iRateGameMessage" = "Ako voliš igrati %@, ocijeni program. Postupak ocjenjivanja traje manje od jedne minute. Hvala na podršci!"; "iRateCancelButton" = "Ne, hvala"; "iRateRateButton" = "Ocijeni"; "iRateRemindButton" = "Podsjeti me kasnije"; "iRateUpdateMessage" = "Sada aktualizirati?"; "ClockerVersion" = "Verzija %@"; "CLFeedbackAlertTitle" = "Hvala što pomažeš poboljšati Clocker!"; "app-name" = "Clocker"; "start-at-login" = "Pokreni nakon prijave"; "setup-steps" = "Za postavljanje programa Clocker potrebna su samo tri koraka"; "Permissions-Header" = "Dozvole"; "See your next Calendar event here." = "Ovdje pogledaj svoje sljedeće kalendarske događaje."; "Click here to start." = "Pritisni ovdje za pokretanje."; "Reminders Access" = "Pristup podsjetnicima"; "Calendar Access" = "Pristup kalendaru"; "Permissions" = "Dozvole"; "Calendar Detail" = "Predstojeći događaji iz tvojih osobnih i zajedničkih kalendara mogu se prikazati u traci izbornika i na ploči."; "Reminders Detail" = "Postavi podsjetnike u tvoju vremensku zonu mjesta. Tvoji se podsjetnici spremaju u standardni program Podsjetnici."; "Privacy Text" = "Ovo možeš kasnije promijeniti u odjeljku Privatnost u Postavkama sustava."; "Granted Button Text" = "Dozvoljeno"; "Denied Button Text" = "Zabranjeno"; "Grant Button Text" = "Dozvoli"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Za postavljanje programa Clocker potrebna su samo tri koraka."; "Get Started" = "Početak"; // Tab Item Titles "Preferences Tab" = "Postavke"; "Appearance Tab" = "Izgled"; "Calendar Tab" = "Kalendar"; "About Tab" = "Informacije"; "Permissions Tab" = "Dozvole"; // General Preferences Screen "Start at Login" = "Pokreni Clocker nakon prijave"; "Sort by Time Difference" = "Razvrstaj po vremenskoj ralzici"; "Sort by Name" = "Razvrstaj po imenu"; "Sort by Label" = "Razvrstaj po oznaci"; "Search Field Placeholder" = "Upiši grad, državu ili zemlju"; "No Timezone Selected" = "Odaberi vremensku zonu!"; "Max Timezones Selected" = "Ograničeno na 100 vremenskih zona!"; "Max Search Characters" = "Ograničeno na 50 znakova!"; "Add Button Title" = "Dodaj"; "Close Button Title" = "Zatvori"; // Onboarding "Open Clocker At Login" = "Otvori Clocker nakon prijave"; "Launch Clocker" = "Pokreni Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Za postavljanje programa Clocker potrebna su samo tri koraka."; "Get Started" = "Početak"; // Permissions "Calendar Access Title" = "Pristup kalendaru"; "Reminders Access Title" = "Pristup podsjetnicima"; "Later Config Description" = "Mogu se kasnije konfigurirati u postavkama sustava."; "Back" = "Natrag"; "Continue" = "Nastavi"; "Clocker is more useful when it can display events from your calendars." = "Clocker je korisniji kad može prikazivati događaje u tvojim kalendarima."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker je korisniji kad može prikazivati događaje u tvojim kalendarima. Ovu postavku možeš promijeniti u Postavke sustava › Sigurnost i privatnost › Privatnost."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Pokreni postavke"; "Grant Access" = "Dopusti pristup"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Predstojeći događaji iz tvojih osobnih i zajedničkih kalendara mogu se prikazati u traci izbornika i na ploči."; "Granted" = "Dodijeljeno"; "Denied" = "Odbijeno"; "Grant" = "Odobrenje"; "Unexpected" = "Neočekivano"; // Onboarding Search "Quick Add Locations" = "Brzo dodavanje mjesta"; "More search options in Clocker Preferences." = "Daljnje opcije pretage u Clocker postavkama."; "Enter 3 or more characters for locations you'll like to add" = "Upiši tri ili više znakova za mjesta koja želiš dodati"; // Start at Login "Launch at Login" = "Pokreni nakon prijave"; "This can be configured later in Clocker Preferences." = "Ovo se može kasnije konfigurirati u Clocker postavkama."; "Should Clocker open automatically on startup?" = "Želiš li automatski pokrenuti Clocker nakon pokretanja računala?"; // Final Onboarding Screen "You're all set!" = "Sve je spremno!"; "Thank you for the details." = "Hvala ti na detalju."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Kad pokreneš program, u traci izbornika vidjet ćeš ikonu sata. Ako želiš vidjeti ikonu u traci programa, idi na Postavke."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Ako želiš pomoći prevesti program na tvoj jezik ili primati rijetke obavijesti o novim verzijama programa, upiši tvoju e-mail adresu!"; // Appearance Tab "Panel Theme" = "Tema ploče"; "Favourite a timezone to enable menubar display options." = "Odaberi vremensku zonu za aktiviranje opcija prikaza trake izbornika."; "Main Panel Options" = "Opcije glavne ploče"; "Time Format" = "Format za vrijeme"; "Day Display Options" = "Opcije za prikaz dana"; "Show Future Slider" = "Prikaži klizač budućnosti"; "Show Sunrise/Sunset" = "Prikaži izlazak/zalazak sunca"; "Display the time in seconds" = "Prikaži vrijeme u sekundama"; "Larger Text" = "Veći tekst"; "Future Slider Range" = "Klizač za raspon budućnosti"; "Include Date" = "Uključi datum"; "Include Day" = "Uključi dan"; "Include Place Name" = "Uključi ime mjesta"; "Menubar Display Options" = "Opcije prikaza izbornika"; /* Appears in Preferences -> Appearance which allows the user to switch between the compact and standard menubar mode. It doesn't lead anywhere. */ "Menubar Mode" = "Modus izbornika"; "Preview" = " Pregled"; "Miscellaneous" = "Razno"; // Empty View "No places added" = "Nema dodanih mjesta"; // Panel "No upcoming event." = "Nema predstojećih događaja."; "You have no events scheduled for tomorrow." = "Za sutra nemaš zakazanih događaja."; // Review "Enjoy using Clocker?" = "Voliš Clocker?"; // App Feedback "Tell us what you think!" = "Javi nam što misliš!"; "Contact Information (Optional)" = "Kontaktni podaci (opcionalno)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Polja za kontakt nisu obavezna! Tvoji kontaktni podaci omogućit će nam da te kontaktiramo u slučaju da trebamo daljnje informacije ili ako možemo pomoći!"; // About View Screen "Feedback is always welcome:" = "Uvijek se radujemo povratnim informacijama:"; // Calendars View "Upcoming Event View Options" = "Opcije prikaza predstojećih događaja"; "Show Upcoming Event View" = "Prikaži prikaz predstojećih događaja"; "Show All Day Meetings" = "Prikaži cjelodnevne sastanke"; "Show Next Meeting Title in Menubar" = "Prikaži sljedeći sastanak u traci izbornika"; "Truncate menubar text longer than" = "Skrati tekst u traci izbornika koji je duži od"; "characters" = "znakova"; "Show events from" = "Prikaži događaje od"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Ako je naslov sastanka „Sastanak s Vladimirom”, a kraćenje je postavljeno na 5, tekst na traci pojavit će se kao „Sasta …”"; // Notes Popover "Reminder Set" = "Postavljanje podsjetnika"; "Successfully set." = "Uspješno postavljeno."; // Errors "You're offline, maybe?" = "Možda nemaš vezu s internetom?"; "Try again, maybe?" = "Želiš li ponovo pokušati?"; "The Internet connection appears to be offline." = "Čini se da ne postoji veza s internetom."; // UI Tests "New Zealand" = "Novi Zeland"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Prijelaz na ljetno računanje vremena dogodit će se za < 24 sata"; "Copied to Clipboard" = "Kopirano u međuspremnik"; "No upcoming events for today!" = "Za danas nema predstojećih događaja 🎉"; "Great going." = "Super."; "Happy Weekend." = "Uživaj vikend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/it.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/it.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Rate %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "No, Thanks"; "iRateRateButton" = "Rate It Now"; "iRateRemindButton" = "Remind Me Later"; "iRateUpdateMessage" = "Update now?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Thank you for helping make Clocker even better!"; "app-name" = "Clocker"; "start-at-login" = "Start At Login"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "Permissions"; "See your next Calendar event here." = "See your next Calendar event here."; "Click here to start." = "Click here to start."; "Reminders Access" = "Reminders Access"; "Calendar Access" = "Calendar Access"; "Permissions" = "Permissions"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Granted"; "Denied Button Text" = "Denied"; "Grant Button Text" = "Grant"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Preferences"; "Appearance Tab" = "Appearance"; "Calendar Tab" = "Calendar"; "About Tab" = "About"; "Permissions Tab" = "Permissions"; // General Preferences Screen "Start at Login" = "Start Clocker at Login"; "Selected Timezones" = "Selected Timezones"; "Sort by Time Difference" = "Sort by Time Difference"; "Sort by Name" = "Sort by Name"; "Sort by Label" = "Sort by Label"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "Add"; "Close Button Title" = "Close"; // Onboarding "Open Clocker At Login" = "Open Clocker At Login"; "Launch Clocker" = "Launch Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Permissions "Calendar Access Title" = "Calendar Access"; "Reminders Access Title" = "Reminders Access"; "Later Config Description" = "These can be configured later in System Preferences."; "Back" = "Back"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Search Locations" = "Search Locations"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/ja.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/ja.lproj/Localizable.strings ================================================ "About Tab" = "このプログラムについて"; "Add Button Title" = "追加"; "Appearance Tab" = "外観"; "Back" = "戻る"; /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CLFeedbackAlertTitle" = "Clocker の品質向上にご協力いただきありがとうございます!"; "Calendar Access" = "カレンダーアクセス"; /* Permissions */ "Calendar Access Title" = "カレンダーアクセス"; "Calendar Detail" = "個人カレンダーおよび共有カレンダーからの今後のイベントをメニューバーとパネルに表示させることができます。"; "Calendar Tab" = "カレンダー"; "Click here to start." = "ここをクリックして開始します。"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "ClockerVersion" = "バージョン %@"; "Close Button Title" = "閉じる"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; "Continue" = "続ける"; "Copied to Clipboard" = "Copied to Clipboard"; "Day Display Options" = "Day Display Options"; /* DST changes */ "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Denied" = "Denied"; "Denied Button Text" = "拒否されています"; "Display the time in seconds" = "Display the time in seconds"; /* Review */ "Enjoy using Clocker?" = "Enjoy using Clocker?"; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; "Favourite a timezone to enable menubar display options." = "メニューバーの表示オプションを有効にするには、タイムゾーンをお気に入りとして登録してください。"; /* About View Screen */ "Feedback is always welcome:" = "Feedback is always welcome:"; "Florida" = "Florida"; "Future Slider Range" = "Future Slider Range"; "Get Started" = "はじめに"; "Get Started" = "はじめに"; "Grant" = "Grant"; "Grant Access" = "Grant Access"; "Grant Button Text" = "許可"; "Granted" = "Granted"; "Granted Button Text" = "許可されています"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; /* Welcome Onboarding */ "It only takes 3 steps to set up Clocker." = "Clocker のセットアップは3ステップのみです。"; /* Welcome Onboarding */ "It only takes 3 steps to setup Clocker." = "Clocker のセットアップは3ステップのみです。"; "Larger Text" = "Larger Text"; "Later Config Description" = "これらはシステム環境設定で後から設定できます。"; "Launch Clocker" = "Clocker を起動"; "Launch Preferences" = "Launch Preferences"; /* Start at Login */ "Launch at Login" = "Launch at Login"; "Main Panel Options" = "Main Panel Options"; "Max Search Characters" = "設定可能な文字数は50です!"; "Max Timezones Selected" = "最大で100の時間帯が設定可能です!"; "Menubar Display Options" = "メニューバーの表示オプション"; "Menubar Mode" = "メニューバーモード"; "Miscellaneous" = "その他"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; /* UI Tests */ "New Zealand" = "New Zealand"; "No Timezone Selected" = "時間帯を選択してください!"; /* Empty View */ "No places added" = "No places added"; /* Panel */ "No upcoming event." = "No upcoming event."; "No upcoming events for today!" = "No upcoming events for today 🎉"; /* Onboarding */ "Open Clocker At Login" = "ログイン時に Clocker を開始"; /* Appearance Tab */ "Panel Theme" = "Panel Theme"; "Permissions" = "アクセス許可"; "Permissions Tab" = "アクセス許可"; "Permissions-Header" = "アクセス許可"; /* Tab Item Titles */ "Preferences Tab" = "設定"; "Preview" = "プレビュー"; "Privacy Text" = "この設定はシステム環境設定のプライバシーセクションであとから変更が可能です。"; /* Onboarding Search */ "Quick Add Locations" = "場所を簡単に追加"; /* Notes Popover */ "Reminder Set" = "Reminder Set"; "Reminders Access" = "リマインダーアクセス"; "Reminders Access Title" = "リマインダーアクセス"; "Reminders Detail" = "選択したロケーションのタイムゾーンでリマインダーを設定します。リマインダーはデフォルトのリマインダーアプリに保存されます。"; "San Francisco" = "San Francisco"; "Search Field Placeholder" = "都市名、州や県、国名を入力してください"; "See your next Calendar event here." = "次回のカレンダーイベントはこちらをご覧ください。"; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Future Slider" = "Show Future Slider"; "Show Next Meeting Title in Menubar" = "次のミーティングタイトルをメニューバーに表示する"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show events from" = "Show events from"; "Sort by Label" = "ラベルで並べ替え"; "Sort by Name" = "地名で並べ替え"; "Sort by Time Difference" = "時差で並べ替え"; /* General Preferences Screen */ "Start at Login" = "ログイン時に Clocker を開始"; "Successfully set." = "Successfully set."; /* App Feedback */ "Tell us what you think!" = "Tell us what you think!"; "Thank you for helping make Clocker even better!" = "Clocker の品質向上にご協力いただきありがとうございます!"; "Thank you for the details." = "Thank you for the details."; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Time Format" = "表示フォーマット"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "Try again, maybe?" = "Try again, maybe?"; "Unexpected" = "Unexpected"; /* Calendars View */ "Upcoming Event View Options" = "Upcoming Event View Options"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "アプリを起動すると、メニューバーに時計のアイコンが表示されます。ドックのアイコンが表示される場合は、format@@0に進みます。"; /* Final Onboarding Screen */ "You're all set!" = "You're all set!"; /* Errors */ "You're offline, maybe?" = "オフラインのようです。"; "app-name" = "Clocker"; "characters" = "characters"; "iRateAppMessage" = "%@ がお気に召したのであれば、評価をして頂けますか? 1分とはかかりません。ご協力ありがとうございます!"; "iRateCancelButton" = "いいえ、結構です"; "iRateGameMessage" = "%@ がお気に召したのであれば、評価をして頂けますか? 1分とはかかりません。ご協力ありがとうございます!"; "iRateMessageTitle" = "%@ を評価"; "iRateRateButton" = "今すぐ評価します"; "iRateRemindButton" = "後で通知する"; "iRateUpdateMessage" = "今すぐアップデートしますか?"; "setup-steps" = "Clocker のセットアップは3ステップのみです"; "start-at-login" = "ログイン時に開始"; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/ko.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/ko.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "시계"; "Thank you for helping make Clocker even better!" = "Clocker가 더 발전할 수 있도록 도와주셔서 정말 감사합니다!"; "iRateMessageTitle" = "비율 %@ "; "iRateAppMessage" = "%@을 즐기고 계신다면, 잠시 평가를 위해 시간을 내주시겠습니까? 단 1분도 걸리지 않겠습니다. 지원해주셔서 감사합니다!"; "iRateGameMessage" = "%@을 즐기고 계신다면, 잠시 평가를 위해 시간을 내주시겠습니까? 단 1분도 걸리지 않겠습니다. 지원해주셔서 감사합니다!"; "iRateCancelButton" = "나중에 할게요"; "iRateRateButton" = "지금 평가하기"; "iRateRemindButton" = "나중에 다시 알리기"; "iRateUpdateMessage" = "지금 업데이트를 진행할까요?"; "ClockerVersion" = "버전 %@"; "CLFeedbackAlertTitle" = "Clocker가 더 발전할 수 있도록 도와주셔서 정말 감사합니다!"; "app-name" = "시계"; "start-at-login" = "로그인을 해주세요"; "setup-steps" = "시계를 설정하는데 3가지만 하시면 됩니다"; "Permissions-Header" = "권한"; "See your next Calendar event here." = "당신의 다음 일정을 여기서 보세요"; "Click here to start." = "여기를 눌러서 시작합니다."; "Reminders Access" = "리마인더 권한"; "Calendar Access" = "캘린더 권한"; "Permissions" = "권한"; "Calendar Detail" = "여러분의 개인&공유 캘린더에서 다가오는 이벤트들은 메뉴바와 패널에서 보여질 수 있습니다."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Granted"; "Denied Button Text" = "Denied"; "Grant Button Text" = "Grant"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Preferences"; "Appearance Tab" = "Appearance"; "Calendar Tab" = "Calendar"; "About Tab" = "About"; "Permissions Tab" = "Permissions"; // General Preferences Screen "Start at Login" = "Start Clocker at Login"; "Sort by Time Difference" = "Sort by Time Difference"; "Sort by Name" = "Sort by Name"; "Sort by Label" = "Sort by Label"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "50 문자까지만 허용됩니다!"; "Add Button Title" = "추가"; "Close Button Title" = "닫기"; // Onboarding "Open Clocker At Login" = "로그인해서 Clocker 열기"; "Launch Clocker" = "Clocker 실행하기"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Permissions "Calendar Access Title" = "캘린더 접근"; "Reminders Access Title" = "리마인더 권한"; "Later Config Description" = "이것들은 나중에 설정에서 바꾸실 수 있습니다"; "Back" = "Back"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "글씨를 크게하기"; "Future Slider Range" = "미래 슬라이더 범위"; "Include Date" = "날짜 추가하기"; "Include Day" = "시간 추가하기"; "Include Place Name" = "장소 이름"; "Menubar Display Options" = "메뉴바 화면표시 설정"; "Menubar Mode" = "메뉴바 모드"; "Preview" = "미리보기"; "Miscellaneous" = "기타"; // Empty View "No places added" = "추가된 장소가 없음"; // Panel "No upcoming event." = "예정된 이벤트 없음"; "You have no events scheduled for tomorrow." = "내일 일정이 없어요!"; // Review "Enjoy using Clocker?" = "Clocker 사용에 만족하십니까?"; // App Feedback "Tell us what you think!" = "여러분의 의견을 들려주세요."; "Contact Information (Optional)" = "연락처 정보 (선택)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "연락처 필드는 선택 사항입니다! 귀하의 연락처 정보는 추가 정보가 필요하거나 도움이 필요한 경우 연락을 드릴 것입니다!"; // About View Screen "Feedback is always welcome:" = "피드백은 언제나 환영입니다:"; // Calendars View "Upcoming Event View Options" = "다가오는 이벤트 보기 옵션"; "Show Upcoming Event View" = "다가오는 일정 보기"; "Show All Day Meetings" = "모든 일정 보기"; "Show Next Meeting Title in Menubar" = "다음 일정을 메뉴바에서 보기"; "Truncate menubar text longer than" = "다음보다 긴 메뉴 표시 줄 텍스트를 자릅니다. "; "characters" = "글자"; "Show events from" = "오늘부터 이벤트 표시"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "만약 미팅 제목이 \"Meeting with Neel\" 이면 5글자만 보여지기 때문에 주의해주세요. (\"Meeti...\")"; // Notes Popover "Reminder Set" = "알림 설정"; "Successfully set." = "설정이 완료되었습니다"; // Errors "You're offline, maybe?" = "당신은 오프라인입니다. 아마도?"; "Try again, maybe?" = "다시 시도 해보세요"; "The Internet connection appears to be offline." = "인터넷 연결이 오프라인 상태입니다."; // UI Tests "New Zealand" = "뉴질랜드"; "Florida" = "플로리다"; "San Francisco" = "샌프란시스코"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉!"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/main.m ================================================ // Copyright © 2015 Abhishek Banthia #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **)argv); } ================================================ FILE: Clocker/Clocker/nl.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/nl.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Rate %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "No, Thanks"; "iRateRateButton" = "Rate It Now"; "iRateRemindButton" = "Remind Me Later"; "iRateUpdateMessage" = "Update now?"; "ClockerVersion" = "Version %@"; "CLFeedbackAlertTitle" = "Thank you for helping make Clocker even better!"; "app-name" = "Clocker"; "start-at-login" = "Start At Login"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "Permissions"; "See your next Calendar event here." = "See your next Calendar event here."; "Click here to start." = "Click here to start."; "Reminders Access" = "Reminders Access"; "Calendar Access" = "Calendar Access"; "Permissions" = "Permissions"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Granted"; "Denied Button Text" = "Denied"; "Grant Button Text" = "Grant"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Tab Item Titles "Preferences Tab" = "Preferences"; "Appearance Tab" = "Appearance"; "Calendar Tab" = "Calendar"; "About Tab" = "About"; "Permissions Tab" = "Permissions"; // General Preferences Screen "Start at Login" = "Start Clocker at Login"; "Sort by Time Difference" = "Sort by Time Difference"; "Sort by Name" = "Sort by Name"; "Sort by Label" = "Sort by Label"; "Search Field Placeholder" = "Enter a city, state or country name"; "No Timezone Selected" = "Please select a timezone!"; "Max Timezones Selected" = "Maximum 100 timezones allowed!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "Add"; "Close Button Title" = "Close"; // Onboarding "Open Clocker At Login" = "Open Clocker At Login"; "Launch Clocker" = "Launch Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Get Started"; // Permissions "Calendar Access Title" = "Calendar Access"; "Reminders Access Title" = "Reminders Access"; "Later Config Description" = "These can be configured later in System Preferences."; "Back" = "Back"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker is more useful when it can display events from your calendars."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; "Launch Preferences" = "Launch Preferences"; "Grant Access" = "Grant Access"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Launch at Login"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Time Format"; "Day Display Options" = "Day Display Options"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Display the time in seconds"; "Larger Text" = "Larger Text"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/pl.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/pl.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Dziękujemy za pomoc przy rozwijaniu Clockera!"; "iRateMessageTitle" = "Oceń %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "Nie, dziękuję"; "iRateRateButton" = "Oceń teraz"; "iRateRemindButton" = "Przypomnij mi później"; "iRateUpdateMessage" = "Zaktualizuj teraz"; "ClockerVersion" = "Wersja %@"; "CLFeedbackAlertTitle" = "Dziękujemy za pomoc przy rozwijaniu Clockera!"; "app-name" = "Clocker"; "start-at-login" = "Uruchom przy logowaniu"; "setup-steps" = "It only takes 3 steps to set up Clocker"; "Permissions-Header" = "Uprawnienia"; "See your next Calendar event here." = "Tu zobaczysz swoje najbliższe wydarzenie z kalendarza."; "Click here to start." = "Kliknij tutaj, aby rozpocząć."; "Reminders Access" = "Dostęp do powiadomień"; "Calendar Access" = "Dostęp do kalendarza"; "Permissions" = "Uprawnienia"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Przyznane"; "Denied Button Text" = "Odrzucone"; "Grant Button Text" = "Przyznaj"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Rozpocznij"; // Tab Item Titles "Preferences Tab" = "Ustawienia"; "Appearance Tab" = "Wygląd"; "Calendar Tab" = "Kalendarz"; "About Tab" = "O aplikacji"; "Permissions Tab" = "Uprawnienia"; // General Preferences Screen "Start at Login" = "Uruchom Clockera przy logowaniu"; "Sort by Time Difference" = "Sortuj według Różnicy Czasu"; "Sort by Name" = "Sortuj wg Nazwy"; "Sort by Label" = "Sortuj według Etykiety"; "Search Field Placeholder" = "Wprowadź miasto, województwo lub nazwę kraju"; "No Timezone Selected" = "Wybierz strefę czasową!"; "Max Timezones Selected" = "Dozwolone maksymalnie 100 stref czasowych!"; "Max Search Characters" = "Dozwolone jest tylko 50 znaków!"; "Add Button Title" = "Dodaj"; "Close Button Title" = "Zamknij"; // Onboarding "Open Clocker At Login" = "Uruchom Clockera przy logowaniu"; "Launch Clocker" = "Uruchom Clockera"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "It only takes 3 steps to setup Clocker."; "Get Started" = "Rozpocznij"; // Permissions "Calendar Access Title" = "Dostęp do kalendarza"; "Reminders Access Title" = "Dostęp do powiadomień"; "Later Config Description" = "These can be configured later in System Preferences."; "Back" = "Wstecz"; "Continue" = "Dalej"; "Clocker is more useful when it can display events from your calendars." = "Clocker jest bardziej przydatny, gdy może wyświetlać wydarzenia z Twojego kalendarza."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Preferencje uruchomienia"; "Grant Access" = "Przyznaj dostęp"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Granted"; "Denied" = "Denied"; "Grant" = "Grant"; "Unexpected" = "Nieoczekiwany"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "More search options in Clocker Preferences."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Uruchom przy logowaniu"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "Wszystko gotowe!"; "Thank you for the details." = "Dziękuję za szczegóły."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Główny panel opcji"; "Time Format" = "Format czasu"; "Day Display Options" = "Opcje wyswietlania daty"; "Show Future Slider" = "Pokaż suwak przyszłości"; "Show Sunrise/Sunset" = "Pokaż wschód/zachód Słońca"; "Display the time in seconds" = "Wyświetl czas z sekundami"; "Larger Text" = "Powiększenie tekstu"; "Future Slider Range" = "Zakres suwaka przyszłości"; "Include Date" = "Pokaż datę"; "Include Day" = "Pokaż dzień"; "Include Place Name" = "Pokaż miejsce"; "Menubar Display Options" = "Opcje wyświetlania paska menu"; /* Appears in Preferences -> Appearance which allows the user to switch between the compact and standard menubar mode. It doesn't lead anywhere. */ "Menubar Mode" = "Wygląd na pasku menu"; "Preview" = "Podgląd"; "Miscellaneous" = "Pozostałe"; // Empty View "No places added" = "Brak dodanych miejsc"; // Panel "No upcoming event." = "Brak nadchodzących wydarzeń."; "You have no events scheduled for tomorrow." = "Nie masz zaplanowanych wydarzeń na jutro."; // Review "Enjoy using Clocker?" = "Podoba ci się Clocker?"; // App Feedback "Tell us what you think!" = "Tell us what you think!"; "Contact Information (Optional)" = "Dane kontaktowe (opcjonalnie)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Twoja opinia jest zawsze mile widziana:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Pokaż całodniowe spotkania"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "znaki"; "Show events from" = "Pokaż wydarzenia z"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Przypomnienie ustawione"; "Successfully set." = "Zapisane."; // Errors "You're offline, maybe?" = "Jesteś offline (?)"; "Try again, maybe?" = "Spróbuj ponownie później"; "The Internet connection appears to be offline." = "Wygląda na to, że nie działa połączenie z internetem."; // UI Tests "New Zealand" = "Nowa Zelandia"; "Florida" = "Floryda"; "San Francisco" = "San Francisco"; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/pt-BR.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Obrigado por ajudar a tornar o Clocker ainda melhor!"; "iRateMessageTitle" = "Avaliar %@"; "iRateAppMessage" = "Se você gosta de usar o %@, você se importaria de avaliá-lo? Não vai demorar mais de um minuto. Obrigado por seu apoio!"; "iRateGameMessage" = "Se você está gostando jogar %@, poderia avaliá-lo? Não levará mais de um minuto. Agradecemos o seu apoio!"; "iRateCancelButton" = "Não, Obrigado"; "iRateRateButton" = "Avaliar Agora"; "iRateRemindButton" = "Lembrar mais tarde"; "iRateUpdateMessage" = "Atualizar agora?"; "ClockerVersion" = "Versão %@"; "CLFeedbackAlertTitle" = "Obrigado por ajudar a tornar o Clocker ainda melhor!"; "app-name" = "Clocker"; "start-at-login" = "Iniciar ao iniciar sessão"; "setup-steps" = "São apenas 3 passos para configurar o Clocker"; "Permissions-Header" = "Permissões"; "See your next Calendar event here." = "Veja seu próximo evento do Calendário aqui."; "Click here to start." = "Clique aqui para iniciar."; "Reminders Access" = "Acessos aos Lembretes"; "Calendar Access" = "Acesso ao Calendário"; "Permissions" = "Permissões"; "Calendar Detail" = "Próximos eventos de seus calendários pessoais e compartilhados podem ser exibidos no menu e no painel."; "Reminders Detail" = "Defina lembretes no fuso horário da localização de sua escolha. Seus lembretes são armazenados no aplicativo padrão Lembretes."; "Privacy Text" = "Você pode alterar isso mais tarde na seção Privacidade das Preferências do Sistema."; "Granted Button Text" = "Concedida"; "Denied Button Text" = "Rejeitada"; "Grant Button Text" = "Conceder"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "São apenas 3 passos para configurar o Clocker."; "Get Started" = "Comece a Usar"; // Tab Item Titles "Preferences Tab" = "Preferências"; "Appearance Tab" = "Aparência"; "Calendar Tab" = "Calendário"; "About Tab" = "Sobre"; "Permissions Tab" = "Permissões"; // General Preferences Screen "Start at Login" = "Iniciar Clocker ao iniciar sessão"; "Sort by Time Difference" = "Ordenar por diferença de horário"; "Sort by Name" = "Ordenar por nome"; "Sort by Label" = "Ordenar por Etiqueta"; "Search Field Placeholder" = "Insira uma cidade, estado ou país"; "No Timezone Selected" = "Por favor, selecione um fuso horário!"; "Max Timezones Selected" = "Máximo de fusos horários permitido: 100!"; "Max Search Characters" = "Só são permitidos até 50 caracteres!"; "Add Button Title" = "Adicionar"; "Close Button Title" = "Fechar"; // Onboarding "Open Clocker At Login" = "Abrir o Clocker ao iniciar sessão"; "Launch Clocker" = "Iniciar Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "São apenas 3 passos para configurar o Clocker."; "Get Started" = "Comece a Usar"; // Permissions "Calendar Access Title" = "Acesso ao Calendário"; "Reminders Access Title" = "Acessos aos Lembretes"; "Later Config Description" = "Isso pode ser configurado mais tarde nas Preferências de Sistema."; "Back" = "Voltar"; "Continue" = "Continuar"; "Clocker is more useful when it can display events from your calendars." = "O bloqueador é mais útil quando pode exibir eventos de seus calendários."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "O bloqueador é mais útil quando pode exibir eventos de seus calendários. Você pode alterar essa configuração em Preferências do Sistema › Segurança e Privacidade › Privacidade."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Abrir Preferências"; "Grant Access" = "Conceder Acesso"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Próximos eventos de seus calendários pessoais e compartilhados podem ser exibidos no menu e no painel."; "Granted" = "Concedido"; "Denied" = "Negado"; "Grant" = "Conceder"; "Unexpected" = "Inesperado"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "Mais opções de busca em Preferências do Clocker."; "Enter 3 or more characters for locations you'll like to add" = "Insira 3 ou mais caracteres para locais que gostaria de adicionar"; // Start at Login "Launch at Login" = "Abrir ao iniciar sessão"; "This can be configured later in Clocker Preferences." = "Isso pode ser configurado mais tarde nas Preferências do Clocker."; "Should Clocker open automatically on startup?" = "O Clocker deve abrir automaticamente na inicialização?"; // Final Onboarding Screen "You're all set!" = "Tudo pronto!"; "Thank you for the details." = "Obrigado pelos detalhes."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Você verá um ícone de relógio na sua Barra de Menu ao iniciar o aplicativo. Se quiser ver um ícone do Dock, vá para Preferências."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Se você quiser nos ajudar a localizar o app no seu idioma ou receber atualizações pouco frequentes, digite seu e-mail!"; // Appearance Tab "Panel Theme" = "Tema do painel"; "Favourite a timezone to enable menubar display options." = "Favorite um fuso horário para habilitar opções de exibição na barra de menus."; "Main Panel Options" = "Opções do painel principal"; "Time Format" = "Formato da hora"; "Day Display Options" = "Opções de Exibição do Dia"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Mostrar Nascer do Sol / Pôr do Sol"; "Display the time in seconds" = "Exibir o tempo em segundos"; "Larger Text" = "Texto Maior"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Incluir Data"; "Include Day" = "Incluir Dia"; "Include Place Name" = "Incluir Nome do Lugar"; "Menubar Display Options" = "Opções de exibição da barra de menu"; "Menubar Mode" = "Modo da barra de menu"; "Preview" = "Pré-visualizar"; "Miscellaneous" = "Diversos"; // Empty View "No places added" = "Nenhum local adicionado"; // Panel "No upcoming event." = "Sem eventos futuros."; "You have no events scheduled for tomorrow." = "Você não tem eventos agendados para amanhã."; // Review "Enjoy using Clocker?" = "Gostando de usar o Clocker?"; // App Feedback "Tell us what you think!" = "Conte-nos o que você pensa!"; "Contact Information (Optional)" = "Informações de Contato (Opcional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Os campos de contato são opcionais! Suas informações de contato nos permitirão entrar em contato com você caso precisemos de mais informações ou possamos ajudar!"; // About View Screen "Feedback is always welcome:" = "Feedback é sempre bem-vindo:"; // Calendars View "Upcoming Event View Options" = "Upcoming Event View Options"; "Show Upcoming Event View" = "Mostrar Visualização de Eventos A Seguir"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Mostrar título da próxima reunião na barra de menus"; "Truncate menubar text longer than" = "Truncar texto de menu mais longo que"; "characters" = "caracteres"; "Show events from" = "Mostrar eventos de"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Se o título da reunião for “Reunião com Neel” e o comprimento de truncagem for definido como 5, o texto na barra de menu aparecerá como “Reuni...”"; // Notes Popover "Reminder Set" = "Lembrete definido"; "Successfully set." = "Definido com sucesso."; // Errors "You're offline, maybe?" = "Você está offline, talvez?"; "Try again, maybe?" = "Tentar de novo, talvez?"; "The Internet connection appears to be offline." = "A conexão à Internet parece estar desligada."; // UI Tests "New Zealand" = "Nova Zelândia"; "Florida" = "Flórida"; "San Francisco" = "San Francisco"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/pt-PT.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/ru.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; /* (No Comment) */ "CFBundleName" = "Clocker"; /* Privacy - Calendars Usage Description */ "NSCalendarsUsageDescription" = "Clocker can be more useful when it can display upcoming events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy"; /* Copyright (human-readable) */ "NSHumanReadableCopyright" = "Авторское право © 2016, Абхишек Бантия"; /* Privacy - Location Always and When In Use Usage Description */ "NSLocationAlwaysAndWhenInUseUsageDescription" = "Clocker can be more useful when it can use your location to determine your current timezone."; /* Privacy - Location Usage Description */ "NSLocationUsageDescription" = "Clocker can be more useful when it can use your location to determine your current timezone."; /* Privacy - Reminders Usage Description */ "NSRemindersUsageDescription" = "Clocker can be more useful when it can set reminders for your selected timezone(s). You can change this setting in System Preferences › Security & Privacy › Privacy."; ================================================ FILE: Clocker/Clocker/ru.lproj/Localizable.strings ================================================ /* (No Comment) */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Спасибо, что помогаете сделать Clocker еще лучше!"; "iRateMessageTitle" = "Оцените"; "iRateAppMessage" = "Если вам нравится %@, просьба уделить время, чтобы поставить оценку? Это займет не больше минуты. Спасибо за вашу поддержку!"; "iRateGameMessage" = "Если вам нравится%@, просьба уделить время, чтобы поставить оценку? Это займет не больше минуты. Спасибо за вашу поддержку!"; "iRateCancelButton" = "Нет, спасибо"; "iRateRateButton" = "Поставьте рейтинг сейчас"; "iRateRemindButton" = "Напомнить позже"; "iRateUpdateMessage" = "Обновить сейчас?"; "ClockerVersion" = "Версия"; "CLFeedbackAlertTitle" = "Благодарю! Вы помогаете мне сделать Clocker лучше!"; "app-name" = "Clocker"; "start-at-login" = "Запустить при логине"; "setup-steps" = "Нужно всего 3 шага что бы настроить Clocker"; "Permissions-Header" = "Права доступа"; "See your next Calendar event here." = "Отображать здесь Ваше следующее событие из календаря."; "Click here to start." = "Нажмите здесь что бы начать."; "Reminders Access" = "Доступ к напоминаниям"; "Calendar Access" = "Доступ к календарю"; "Permissions" = "Разрешения"; "Calendar Detail" = "Предстоящие события из вашего личного и общего календарей могут отображаться в строке меню и на панели."; "Reminders Detail" = "Установите напоминания в часовом поясе в выбранном вами месте. Ваши напоминания хранятся в приложении по умолчанию."; "Privacy Text" = "Вы можете изменить это позже в разделе «Конфиденциальность» системных настроек."; "Granted Button Text" = "Разрешено"; "Denied Button Text" = "Запрещено"; "Grant Button Text" = "Разрешение"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Для настройки Clocker требуется всего 3 шага."; "Get Started" = "Начнем"; // Tab Item Titles "Preferences Tab" = "Настройки"; "Appearance Tab" = "Представление"; "Calendar Tab" = "Календарь"; "About Tab" = "Об этом"; "Permissions Tab" = "Разрешения"; // General Preferences Screen "Start at Login" = "Запустить при логине"; "Sort by Time Difference" = "Сорт по разнице времени"; "Sort by Name" = "Сорт по имени"; "Sort by Label" = "Сорт по метке"; "Search Field Placeholder" = "Введите название города, штата или страны"; "No Timezone Selected" = "Пожалуйста, выберите часовой пояс!"; "Max Timezones Selected" = "Разрешено максимум 100 часовых поясов!"; "Max Search Characters" = "Разрешено всего 50 символов!"; "Add Button Title" = "Добавить"; "Close Button Title" = "Закрыть"; // Onboarding "Open Clocker At Login" = "Открыть Clocker при входе"; "Launch Clocker" = "Запустить Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Нужно всего 3 шага что бы настроить Clocker."; "Get Started" = "Начало работы"; // Permissions "Calendar Access Title" = "Доступ к календарю"; "Reminders Access Title" = "Доступ к напоминаниям"; "Later Config Description" = "Это можно настроить позже в Системных Настройках."; "Back" = "Назад"; "Continue" = "Продолжить"; "Clocker is more useful when it can display events from your calendars." = "Clocker полезнее когда он может отображать события из ваших календарей."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker полезнее когда он может отображать предстоящие события из ваших календарей. Вы можете изменить эти настройки перейдя в  Системные настройки › Защита и безопасность › Приватность."; "Launch Preferences" = "Параметры запуска"; "Grant Access" = "Разрешить доступ"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Предстоящие события из ваших персонального и общего календарей могут быть показаны в верхнем меню и панели."; "Granted" = "Предоставлено"; "Denied" = "Отказано"; "Grant" = "Разрешить"; "Unexpected" = "Неожиданный"; // Onboarding Search "Quick Add Locations" = "Быстрое добавление мест"; "More search options in Clocker Preferences." = "Больше вариантов поиска в настройках Clocker."; "Enter 3 or more characters for locations you'll like to add" = "Введите 3 или более символов для локаций, которые вы хотите добавить"; // Start at Login "Launch at Login" = "Запуск при входе"; "This can be configured later in Clocker Preferences." = "Это можно изменить позже в настройках Clocker."; "Should Clocker open automatically on startup?" = "Должен ли Clocker автоматически открываться при запуске?"; // Final Onboarding Screen "You're all set!" = "Все готово!"; "Thank you for the details." = "Спасибо Вам за подробности."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "При запуске приложения вы увидите значок часов в строке меню. Если вы хотите увидеть значок в панели Dock, перейдите в раздел Настройки."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Если вы хотите помочь нам локализовать приложение на вашем языке или получить обновления, связанные с приложениями, пожалуйста, введите ваш email!"; // Appearance Tab "Panel Theme" = "Тема панели"; "Favourite a timezone to enable menubar display options." = "Пометьте временную зону как избранную чтобы включить параметры отображения строки меню."; "Main Panel Options" = "Параметры главной панели"; "Time Format" = "Формат времени"; "Day Display Options" = "Параметры отображения дня"; "Show Future Slider" = "Показать ползунок"; "Show Sunrise/Sunset" = "Показывать Восход/Закат"; "Display the time in seconds" = "Отображать время в секундах"; "Larger Text" = "Увеличенный текст"; "Future Slider Range" = "Диапазон ползунка"; "Include Date" = "Добавить Дату"; "Include Day" = "Добавить День"; "Include Place Name" = "Добавить название места"; "Menubar Display Options" = "Параметры отображения меню"; "Menubar Mode" = "Режимы меню"; "Preview" = "Предпросмотр"; "Miscellaneous" = "Прочее"; // Empty View "No places added" = "Нет добавленных мест"; // Panel "No upcoming event." = "Нет предстоящих событий."; "You have no events scheduled for tomorrow." = "У вас нет запланированных на завтра событий."; // Review "Enjoy using Clocker?" = "Понравилось приложения Clocker?"; // App Feedback "Tell us what you think!" = "Расскажите нам, что вы думаете!"; "Contact Information (Optional)" = "Контактная информация (необязательно)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Поля контактной информации необязательны! Ваша контактная информация позволит нам связаться с вами в случае необходимости дополнительной информации или может помочь нам!"; // About View Screen "Feedback is always welcome:" = "Обратная связь всегда приветствуется:"; // Calendars View "Upcoming Event View Options" = "Параметры просмотра Предстоящих Событий"; "Show Upcoming Event View" = "Показывать предстоящие события"; "Show All Day Meetings" = "Показать все встречи за день"; "Show Next Meeting Title in Menubar" = "Показывать название следующей встречи в меню"; "Truncate menubar text longer than" = "Урезать текст поля меню более чем"; "characters" = "символы"; "Show events from" = "Отображать события из"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Если заголовок митинга \"Митинг с Кариной\" и длина обрезания текста установлена как 5, текст в верхнем меню будет отображаться как \"Митин...\""; // Notes Popover "Reminder Set" = "Напоминание установлено"; "Successfully set." = "Успешно установлено."; // Errors "You're offline, maybe?" = "Возможно Вы не в сети?"; "Try again, maybe?" = "Попробовать еще раз?"; "The Internet connection appears to be offline." = "Похоже, что подключение к Интернету недоступно."; // UI Tests "New Zealand" = "Новая Зеландия"; "Florida" = "Флорида"; "San Francisco" = "Сан-Франциско"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/tr.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CFBundleName" = "Clocker"; ================================================ FILE: Clocker/Clocker/tr.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Clocker'ı daha iyi hale getirmek için yaptığınız yardımlara teşekkürler!"; "iRateMessageTitle" = "OylamaMesajYorum%@"; "iRateAppMessage" = "%@ uygulamamız hoşunuza gittiyse, oy vermek ister misiniz? Bir dakikadan fazla sürmeyecektir. Desteğiniz için teşekkürler!"; "iRateGameMessage" = "%@ uygulamamız hoşunuza gittiyse, oy vermek ister misiniz? Bir dakikadan fazla sürmeyecektir. Desteğiniz için teşekkürler!"; "iRateCancelButton" = "Hayır, Teşekkürler"; "iRateRateButton" = "Şimdi Oyla"; "iRateRemindButton" = "Daha sonra hatırlat"; "iRateUpdateMessage" = "Şimdi güncelle?"; "ClockerVersion" = "Versiyon%@"; "CLFeedbackAlertTitle" = "Clocker'ı daha iyi hale getirmek için yaptığınız yardımlara teşekkürler!"; "app-name" = "Clocker"; "start-at-login" = "Oturum açarken başlat"; "setup-steps" = "Clocker'ı 3 adımda kur"; "Permissions-Header" = "Yetkiler"; "See your next Calendar event here." = "Bir sonraki takvim etkinliğini buradan gör."; "Click here to start." = "Başlamak için buraya tıklayın."; "Reminders Access" = "Hatırlatıcıya Giriş"; "Calendar Access" = "Takvim Erişimi"; "Permissions" = "Erişim Hakları"; "Calendar Detail" = "Kişisel veya paylaşılan takvimlerinizdeki yaklaşan etkinlikler menü çubuğunda veya panelde görünebilir."; "Reminders Detail" = "Seçtiğiniz yerin saat diliminde hatırlatıcılar ekleyin. Hatırlatıcılarınız, varsayılan uygulamasında saklanır."; "Privacy Text" = "Bunu daha sonra Sistem Tercihleri'nin Güvenlik ve Gizlilik bölümünden değiştirebilirsiniz."; "Granted Button Text" = "Onaylandı"; "Denied Button Text" = "Reddedildi"; "Grant Button Text" = "Onayla"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Clocker'ı 3 adımda kur."; "Get Started" = "Başlayın"; // Tab Item Titles "Preferences Tab" = "Ayarlar"; "Appearance Tab" = "Görünüm"; "Calendar Tab" = "Takvim"; "About Tab" = "Hakkında"; "Permissions Tab" = "Yetkiler"; // General Preferences Screen "Start at Login" = "Clocker'ı Başlangıçta başlat"; "Sort by Time Difference" = "Zaman farkına göre sırala"; "Sort by Name" = "Ada göre sırala"; "Sort by Label" = "Etikete göre sırala"; "Search Field Placeholder" = "Bir şehir, bölge veya ülke girin"; "No Timezone Selected" = "Lütfen saat dilimi seçin!"; "Max Timezones Selected" = "En fazla 100 saat dilimi kabul edilir!"; "Max Search Characters" = "Sadece 50 karakter kabul edilir!"; "Add Button Title" = "Ekle"; "Close Button Title" = "Kapat"; // Onboarding "Open Clocker At Login" = "Clocker'ı Başlangıçta başlat"; "Launch Clocker" = "Clocker'ı çalıştır"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Clocker'i 3 adımda kur."; "Get Started" = "Başlayın"; // Permissions "Calendar Access Title" = "Takvim Erişimi"; "Reminders Access Title" = "Hatırlatıcıya Giriş"; "Later Config Description" = "Bu, daha sonra Sistem Ayarlarında değiştirilebilir."; "Back" = "Geri"; "Continue" = "Devam et"; "Clocker is more useful when it can display events from your calendars." = "Clocker takvimlerdeki etkinliklerinizi gösterebilirse daha kullanışlı hale gelebilir."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker takviminizde bulunan sıradaki etkinlikleri gösterdiğinde daha kullanışlı hale gelir. Bu ayarı Sistem Tercihleri › Güvenlik & Gizlilik › Gizlilik kısmından değiştirebilirsiniz."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Başlatma Tercihleri"; "Grant Access" = "Erişim izni ver"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Kişisel veya paylaşılan takvimlerinizdeki yaklaşan etkinlikler menü çubuğunda veya panelde görünebilir."; "Granted" = "İzin verildi"; "Denied" = "Reddedildi"; "Grant" = "Erişim ver"; "Unexpected" = "Beklenmedik"; // Onboarding Search "Quick Add Locations" = "Hızlı lokasyon ekle"; "More search options in Clocker Preferences." = "Clocker Tercihlerinde daha fazla arama seçenekleri bulunur."; "Enter 3 or more characters for locations you'll like to add" = "Eklemek istediğiniz konum için 3 veya daha fazla karakter girin"; // Start at Login "Launch at Login" = "Açılışta Başlat"; "This can be configured later in Clocker Preferences." = "Bu daha sonra Clocker Tercihlerinde düzenlenebilir."; "Should Clocker open automatically on startup?" = "Clocker başlangıçta otomatik açılsın mı?"; // Final Onboarding Screen "You're all set!" = "Her şey tamam!"; "Thank you for the details." = "Detaylar için teşekkür ederiz."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "Uygulamayı başlattığınızda Menü Çubuğunuzda bir saat simgesi göreceksiniz. Bir Dock simgesi görmek isterseniz Tercihler'e gidin."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "Uygulamayı kendi dilinizde yerelleştirmemize veya nadiren uygulamayla ilgili güncellemeler almanıza yardımcı olmak isterseniz, lütfen e-postanızı girin!"; // Appearance Tab "Panel Theme" = "Panel Teması"; "Favourite a timezone to enable menubar display options." = "Menu çubuğu görünümü seçeneklerini etkinleştirmek için bir zaman dilimini favorileyin."; "Main Panel Options" = "Ana Panel Seçenekleri"; "Time Format" = "Zaman Formatı"; "Day Display Options" = "Gün Görünüm Seçenekleri"; "Show Future Slider" = "Zaman Kaydırıcısını Göster"; "Show Sunrise/Sunset" = "Gün Doğumu/Batımı'nı Göster"; "Display the time in seconds" = "Zamanı saniye şeklinde göster"; "Larger Text" = "Daha Büyük Yazı"; "Future Slider Range" = "Zaman Kaydırıcı Aralığı"; "Include Date" = "Tarihi Dahil Et"; "Include Day" = "Günü Dahil Et"; "Include Place Name" = "Yer Adını Dahil Et"; "Menubar Display Options" = "Menu Çubuğu Görünüm Seçenekleri"; /* Appears in Preferences -> Appearance which allows the user to switch between the compact and standard menubar mode. It doesn't lead anywhere. */ "Menubar Mode" = "Menü Çubuğu Mod'u"; "Preview" = "Ön İzleme"; "Miscellaneous" = "Çeşitli"; // Empty View "No places added" = "Yer eklenmedi"; // Panel "No upcoming event." = "Yaklaşan etkinlik yok."; "You have no events scheduled for tomorrow." = "Yarın için yapılacak işiniz yok."; // Review "Enjoy using Clocker?" = "Clocker'ı kullanmaktan memnun musun?"; // App Feedback "Tell us what you think!" = "Bize düşüncelerinizi bildiriniz!"; "Contact Information (Optional)" = "İletişim Bilgileri (İsteğe Bağlı)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Bağlantı girdileri isteğe bağlıdır! Bağlantı bilgilerinizi, sonrasında sizden daha fazla bilgi almak veya size yardım edebilmek için gerektiğinde kullanacağız!"; // About View Screen "Feedback is always welcome:" = "Geri bildirimleriniz memnuniyetle karşılanır:"; // Calendars View "Upcoming Event View Options" = "Sonraki etkinliği görüntüleme seçenekleri Takvimleri Görüntüle"; "Show Upcoming Event View" = "Yaklaşan Etkinlikleri Göster"; "Show All Day Meetings" = "Günün bütün etkinliklerini göster"; "Show Next Meeting Title in Menubar" = "Bir sonraki etkinlik başlığını Menüde göster"; "Truncate menubar text longer than" = "Menü çubuğu metnini kısalt"; "characters" = "karakterler"; "Show events from" = "Şuradaki etkinlikleri göster"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "Eğer buluşma adı \"Ahmet ile Buluşma\" ise ve kısaltma uzunluğu 5 olarak ayarlıysa, menü çubuğundaki yazı \"Ahmet...\" olarak görünecek"; // Notes Popover "Reminder Set" = "Hatırlatıcı Ayarlandı"; "Successfully set." = "Ayarlama başarılı."; // Errors "You're offline, maybe?" = "Acaba çevrimdışı mısınız?"; "Try again, maybe?" = "Belki tekrar denemek istersin?"; "The Internet connection appears to be offline." = "Internet bağlantınız çevrimdışı görünüyor."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/uk.lproj/Localizable.strings ================================================ /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "Thank you for helping make Clocker even better!" = "Thank you for helping make Clocker even better!"; "iRateMessageTitle" = "Оціни %@"; "iRateAppMessage" = "If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateGameMessage" = "If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"; "iRateCancelButton" = "Ні, дякую"; "iRateRateButton" = "Оцінити зараз"; "iRateRemindButton" = "Нагадати пізніше"; "iRateUpdateMessage" = "Оновити зараз?"; "ClockerVersion" = "Версія %@"; "CLFeedbackAlertTitle" = "Дякуємо, що допомагаєте зробити Clocker ще кращим!"; "app-name" = "Clocker"; "start-at-login" = "Запускати при вході"; "setup-steps" = "Налаштування Clocker займе лише 3 кроки"; "Permissions-Header" = "Дозволи"; "See your next Calendar event here." = "Побачте вашу наступну подію Календаря тут."; "Click here to start." = "Натисніть тут, щоб почати."; "Reminders Access" = "Доступ до Нагадувань"; "Calendar Access" = "Доступ до Календаря"; "Permissions" = "Дозволи"; "Calendar Detail" = "Upcoming events from your calendars can be shown in the menubar + panel."; "Reminders Detail" = "Встановіть нагадування в часовому поясі вибраного вами місця. Ваші нагадування зберігаються в програмі Нагадування за умовчанням."; "Privacy Text" = "You can change this later in the Privacy section of the System Preferences."; "Granted Button Text" = "Надано"; "Denied Button Text" = "Відмовлено"; "Grant Button Text" = "Надати"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "Налаштування Clocker займе лише 3 кроки."; "Get Started" = "Почати роботу"; // Tab Item Titles "Preferences Tab" = "Налаштування"; "Appearance Tab" = "Вигляд"; "Calendar Tab" = "Календар"; "About Tab" = "Про програму"; "Permissions Tab" = "Дозволи"; // General Preferences Screen "Start at Login" = "Запускати Clocker при вході"; "Sort by Time Difference" = "Сортувати за Різницею у часі"; "Sort by Name" = "Сортувати за Назвою"; "Sort by Label" = "Сортувати за Позначенням"; "Search Field Placeholder" = "Введіть місто, область або назву країни"; "No Timezone Selected" = "Будь ласка, оберіть часовий пояс!"; "Max Timezones Selected" = "Максимально доступно 100 часових поясів!"; "Max Search Characters" = "Only 50 characters allowed!"; "Add Button Title" = "Додати"; "Close Button Title" = "Закрити"; // Onboarding "Open Clocker At Login" = "Відкривати Clocker при вході"; "Launch Clocker" = "Запустити Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "Налаштування Clocker займе лише 3 кроки."; "Get Started" = "Почати роботу"; // Permissions "Calendar Access Title" = "Доступ до Календаря"; "Reminders Access Title" = "Доступ до Нагадувань"; "Later Config Description" = "Це можна налаштувати пізніше в Системних параметрах."; "Back" = "Назад"; "Continue" = "Continue"; "Clocker is more useful when it can display events from your calendars." = "Clocker більш корисний, коли може відображати події з ваших календарів."; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker більш корисний, коли може відображати події з ваших календарів. Ви можете змінити цей параметр у Системні параметри › Безпека та приватність › Приватність."; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "Відкрити Системні параметри"; "Grant Access" = "Надати доступ"; "Upcoming events from your calendars can be shown in the menubar + panel." = "Upcoming events from your calendars can be shown in the menubar + panel."; "Granted" = "Надано"; "Denied" = "Відмовлено"; "Grant" = "Надати"; "Unexpected" = "Unexpected"; // Onboarding Search "Quick Add Locations" = "Quick Add Locations"; "More search options in Clocker Preferences." = "Більше варіантів пошуку в налаштуваннях Clocker."; "Enter 3 or more characters for locations you'll like to add" = "Enter 3 or more characters for locations you'll like to add"; // Start at Login "Launch at Login" = "Запускати при вході"; "This can be configured later in Clocker Preferences." = "This can be configured later in Clocker Preferences."; "Should Clocker open automatically on startup?" = "Should Clocker open automatically on startup?"; // Final Onboarding Screen "You're all set!" = "You're all set!"; "Thank you for the details." = "Thank you for the details."; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences."; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; // Appearance Tab "Panel Theme" = "Panel Theme"; "Favourite a timezone to enable menubar display options." = "Favourite a timezone to enable menubar display options."; "Main Panel Options" = "Main Panel Options"; "Time Format" = "Формат часу"; "Day Display Options" = "Варіанти відображання дня"; "Show Future Slider" = "Show Future Slider"; "Show Sunrise/Sunset" = "Show Sunrise/Sunset"; "Display the time in seconds" = "Відображати час у секундах"; "Larger Text" = "Більший текст"; "Future Slider Range" = "Future Slider Range"; "Include Date" = "Include Date"; "Include Day" = "Include Day"; "Include Place Name" = "Include Place Name"; "Menubar Display Options" = "Menubar Display Options"; /* Appears in Preferences -> Appearance which allows the user to switch between the compact and standard menubar mode. It doesn't lead anywhere. */ "Menubar Mode" = "Menubar Mode"; "Preview" = "Preview"; "Miscellaneous" = "Miscellaneous"; // Empty View "No places added" = "No places added"; // Panel "No upcoming event." = "No upcoming event."; "You have no events scheduled for tomorrow." = "You have no events scheduled for tomorrow."; // Review "Enjoy using Clocker?" = "Enjoy using Clocker?"; // App Feedback "Tell us what you think!" = "Розкажіть нам, що ви думаєте!"; "Contact Information (Optional)" = "Contact Information (Optional)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!"; // About View Screen "Feedback is always welcome:" = "Feedback is always welcome:"; // Calendars View "Upcoming Event View Options" = "Варіанти перегляду майбутньої події"; "Show Upcoming Event View" = "Show Upcoming Event View"; "Show All Day Meetings" = "Show All Day Meetings"; "Show Next Meeting Title in Menubar" = "Show Next Meeting Title in Menubar"; "Truncate menubar text longer than" = "Truncate menubar text longer than"; "characters" = "characters"; "Show events from" = "Show events from"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""; // Notes Popover "Reminder Set" = "Reminder Set"; "Successfully set." = "Successfully set."; // Errors "You're offline, maybe?" = "You're offline, maybe?"; "Try again, maybe?" = "Try again, maybe?"; "The Internet connection appears to be offline." = "The Internet connection appears to be offline."; // UI Tests "New Zealand" = "New Zealand"; "Florida" = "Florida"; "San Francisco" = "San Francisco"; ================================================ FILE: Clocker/Clocker/zh-Hans.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; /* (No Comment) */ "CFBundleName" = "Clocker"; /* Privacy - Calendars Usage Description */ "NSCalendarsUsageDescription" = "Clocker在展示你即将到来的日程时非常有用。你可以在 系统设置› 安全和隐私› 隐私 中更改设置。"; /* Copyright (human-readable) */ "NSHumanReadableCopyright" = "版权所有© 2016, Abhishek Banthia"; /* Privacy - Location Always and When In Use Usage Description */ "NSLocationAlwaysAndWhenInUseUsageDescription" = "Clocker can be more useful when it can use your location to determine your current timezone."; /* Privacy - Location Usage Description */ "NSLocationUsageDescription" = "Clocker can be more useful when it can use your location to determine your current timezone."; /* Privacy - Reminders Usage Description */ "NSRemindersUsageDescription" = "Clocker can be more useful when it can set reminders for your selected timezone(s). You can change this setting in System Preferences › Security & Privacy › Privacy."; ================================================ FILE: Clocker/Clocker/zh-Hans.lproj/Localizable.strings ================================================ /* (No Comment) */ "CFBundleDisplayName" = "解锁器"; "Thank you for helping make Clocker even better!" = "感谢您帮助 Clocker 做得更好!"; "iRateMessageTitle" = "评价 %@"; "iRateAppMessage" = "如果您喜欢使用 %@,能否请您抽出时间来对它进行评价?评价不会超过一分钟。感谢您的支持 !"; "iRateGameMessage" = "如果您喜欢使用 %@,能否请您抽出时间来对它进行评价?评价不会超过一分钟。感谢您的支持 !"; "iRateCancelButton" = "不用了,谢谢"; "iRateRateButton" = "立即评分"; "iRateRemindButton" = "稍后提醒我"; "iRateUpdateMessage" = "现在更新?"; "ClockerVersion" = "版本 %@"; "CLFeedbackAlertTitle" = "感谢您帮助 Clocker 做得更好!"; "app-name" = "Clocker"; "start-at-login" = "开启时启动"; "setup-steps" = "只需要三步来设置 Clocker"; "Permissions-Header" = "权限"; "See your next Calendar event here." = "在这里查看你的下一个日历事件。"; "Click here to start." = "点此开始。"; "Reminders Access" = "提醒访问权限"; "Calendar Access" = "日历访问权限"; "Permissions" = "权限"; "Calendar Detail" = "未来你个人和分享的日历事件将显示在菜单栏和面板上。"; "Reminders Detail" = "在您选择的位置时区设置提醒。您的提醒存储在默认提醒应用中。"; "Privacy Text" = "您稍后可以在「系统偏好设置」的隐私部分更改此内容。"; "Granted Button Text" = "已授权"; "Denied Button Text" = "已拒绝"; "Grant Button Text" = "授权"; // Welcome Onboarding "It only takes 3 steps to setup Clocker." = "只需要 3 步来设置 Clocker。"; "Get Started" = "开始"; // Tab Item Titles "Preferences Tab" = "首选项"; "Appearance Tab" = "外观"; "Calendar Tab" = "日历"; "About Tab" = "关于"; "Permissions Tab" = "权限"; // General Preferences Screen "Start at Login" = "登录时启动 Clocker"; "Sort by Time Difference" = "按时间差排序"; "Sort by Name" = "按名称排序"; "Sort by Label" = "按标签排序"; "Search Field Placeholder" = "输入城市、省/州或国家名称"; "No Timezone Selected" = "请选择一个时区!"; "Max Timezones Selected" = "最多允许 100 个时区!"; "Max Search Characters" = "最多允许 50 个字符!"; "Add Button Title" = "添加"; "Close Button Title" = "关闭"; // Onboarding "Open Clocker At Login" = "登录时启动 Clocker"; "Launch Clocker" = "启动 Clocker"; // Welcome Onboarding "It only takes 3 steps to set up Clocker." = "只需要三步来设置 Clocker。"; "Get Started" = "现在开始"; // Permissions "Calendar Access Title" = "访问日历"; "Reminders Access Title" = "访问提醒"; "Later Config Description" = "这些可以稍后在系统偏好设置中配置。"; "Back" = "返回"; "Continue" = "继续"; "Clocker is more useful when it can display events from your calendars." = "Clocker 能显示日历中的事件时会更有用。"; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "Clocker 在能显示日历中的事件时更有用。你可以在「系统偏好设置 > 安全性与隐私 > 隐私」中更改设置。"; "Launch Preferences" = "打开首选项"; "Grant Access" = "授予访问权限"; "Upcoming events from your calendars can be shown in the menubar + panel." = "你个人和共享日历的未来事件将显示在菜单栏和面板上。"; "Granted" = "已授权"; "Denied" = "已拒绝"; "Grant" = "授权"; "Unexpected" = "意外的"; // Onboarding Search "Quick Add Locations" = "快速添加地点"; "More search options in Clocker Preferences." = "Clocker 首选项中的更多搜索选项。"; "Enter 3 or more characters for locations you'll like to add" = "为您想要添加的位置输入 3 个或更多字符"; // Start at Login "Launch at Login" = "登录时启动"; "This can be configured later in Clocker Preferences." = "这可以稍后在 Clocker 首选项中进行配置。"; "Should Clocker open automatically on startup?" = "是否在启动时自动打开 Clocker?"; // Final Onboarding Screen "You're all set!" = "已完成全部设置!"; "Thank you for the details." = "感谢您的详细信息。"; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "当你启动应用时,会在菜单栏看到一个时钟图标。如果想在 Dock 上显示图标,请在首选项设置。"; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "如果您想帮助翻译本应用或收到不定时的应用更新,请输入您的电子邮箱!"; // Appearance Tab "Panel Theme" = "面板主题"; "Favourite a timezone to enable menubar display options." = "收藏一个时区以在菜单栏上显示可选项。"; "Main Panel Options" = "主面板选项"; "Time Format" = "时间格式"; "Day Display Options" = "日显示选项"; "Show Future Slider" = "显示未来滑块"; "Show Sunrise/Sunset" = "显示日出/日落"; "Display the time in seconds" = "显示秒数"; "Larger Text" = "更大字号"; "Future Slider Range" = "未来滑块范围"; "Include Date" = "包含日期"; "Include Day" = "包含日"; "Include Place Name" = "包含地名"; "Menubar Display Options" = "菜单栏显示选项"; "Menubar Mode" = "菜单栏模式"; "Preview" = "预览"; "Miscellaneous" = "杂项设置"; // Empty View "No places added" = "没有添加地点"; // Panel "No upcoming event." = "暂无事项"; "You have no events scheduled for tomorrow." = "您明天没有安排。"; // Review "Enjoy using Clocker?" = "喜欢使用 Clocker 吗?"; // App Feedback "Tell us what you think!" = "告诉我们您的想法!"; "Contact Information (Optional)" = "联系信息 (可选)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "联系人字段是可选项!我们需要更多信息或帮助时,将使用联络信息联络您!"; // About View Screen "Feedback is always welcome:" = "欢迎提供反馈:"; // Calendars View "Upcoming Event View Options" = "即将到来的事件视图选项"; "Show Upcoming Event View" = "显示即将到来的事件视图"; "Show All Day Meetings" = "显示全天会议"; "Show Next Meeting Title in Menubar" = "在菜单栏中显示下一个会议标题"; "Truncate menubar text longer than" = "截断菜单栏文字长度大于"; "characters" = "字符"; "Show events from" = "显示事件来源"; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "如果会议标题是“Meeting with Neel”,并且截断长度设置为 5 ,菜单栏的文本将显示为“Meeti...”"; // Notes Popover "Reminder Set" = "提醒设置"; "Successfully set." = "设置成功"; // Errors "You're offline, maybe?" = "您已经离线?"; "Try again, maybe?" = "稍后再试?"; "The Internet connection appears to be offline." = "网络连接已断开。"; // UI Tests "New Zealand" = "新西兰"; "Florida" = "佛罗里达"; "San Francisco" = "旧金山"; // DST changes "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Copied to Clipboard" = "Copied to Clipboard"; "No upcoming events for today!" = "No upcoming events for today 🎉"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker/zh-Hant.lproj/Localizable.strings ================================================ "About Tab" = "關於"; "Add Button Title" = "新增"; "Appearance Tab" = "外觀"; "Back" = "返回"; /* Localizable.strings Clocker Created by Abhishek Banthia on 3/27/16. */ "CFBundleDisplayName" = "Clocker"; "CLFeedbackAlertTitle" = "感謝您的幫助,讓 Clocker 變得更好!"; "Calendar Access" = "取用您的行事曆"; /* Permissions */ "Calendar Access Title" = "取用您的行事曆"; "Calendar Detail" = "即將到來的行程會在選單列和面板中顯示。"; "Calendar Tab" = "行事曆"; "Click here to start." = "開始"; "Clocker is more useful when it can display events from your calendars." = "從您的行事曆顯示行程將使 Clocker 更實用。"; "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy." = "從您的行事曆顯示行程將使 Clocker 更實用。您可以在「系統偏好設定」>「安全性與隱私」>「隱私權」更改此設定。"; "ClockerVersion" = "版本:%@"; "Close Button Title" = "關閉"; "Contact Information (Optional)" = "聯絡資訊(可留空)"; "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!" = "聯絡欄為可選填欄位!我們需要更多資訊或協助時,將會透過聯絡資訊聯絡您!"; "Continue" = "繼續"; "Copied to Clipboard" = "Copied to Clipboard"; "Day Display Options" = "日期顯示選項"; /* DST changes */ "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Denied" = "拒絕"; "Denied Button Text" = "拒絕"; "Display the time in seconds" = "顯示時間秒數"; /* Review */ "Enjoy using Clocker?" = "喜歡使用 Clocker 嗎?"; "Enter 3 or more characters for locations you'll like to add" = "為您想加入的位置輸入 3 個或更多字元"; "Favourite a timezone to enable menubar display options." = "將一時區設為喜好項目以啟用選單列顯示選項。"; /* About View Screen */ "Feedback is always welcome:" = "Feedback is always welcome:"; "Florida" = "佛羅里達"; "Future Slider Range" = "未來滑桿範圍"; "Get Started" = "開始使用"; "Get Started" = "開始使用"; "Grant" = "允許"; "Grant Access" = "允許存取"; "Grant Button Text" = "允許"; "Granted" = "允許"; "Granted Button Text" = "允許"; "Great going." = "Great going."; "Happy Weekend." = "Happy Weekend."; "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" = "如果會議標題為「Meeting with Neel」、文字長度限制為 5,選單列中的文字將顯示為「Meeti...」。"; "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!" = "If you'd like to help us localize the app in your language or receive infrequent app-related updates, please enter your email!"; "Include Date" = "包含日期"; "Include Day" = "包含星期"; "Include Place Name" = "包含位置名稱"; /* Welcome Onboarding */ "It only takes 3 steps to set up Clocker." = "設定 Clocker 只需要 3 個步驟。"; /* Welcome Onboarding */ "It only takes 3 steps to setup Clocker." = "設定 Clocker 只需要 3 個步驟。"; "Larger Text" = "文字大小"; "Later Config Description" = "這些可以稍後在「系統偏好設定」中進行設置。"; "Launch Clocker" = "啟動 Clocker"; /* Text for button that takes the user to the System Preferences app. In case the user hasn't given Calendar/Reminders access permission, this button takes you to the System Preferences app where the user can give proper permissions to Clocker. */ "Launch Preferences" = "啟動偏好設定"; /* Start at Login */ "Launch at Login" = "登入時啟動"; "Main Panel Options" = "主要面板選項"; "Max Search Characters" = "只允許 50 個字元!"; "Max Timezones Selected" = "最多允許 100 個時區!"; "Menubar Display Options" = "選單列顯示選項"; "Menubar Mode" = "選單列模式"; "Miscellaneous" = "其他設定"; "More search options in Clocker Preferences." = "在 Clocker 偏好設定中更多的搜尋選項。"; /* UI Tests */ "New Zealand" = "紐西蘭"; "No Timezone Selected" = "請選取一個時區!"; /* Empty View */ "No places added" = "未加入地點"; /* Panel */ "No upcoming event." = "沒有即將到來的行程"; "No upcoming events for today!" = "No upcoming events for today 🎉"; /* Onboarding */ "Open Clocker At Login" = "在登入時打開 Clocker"; /* Appearance Tab */ "Panel Theme" = "面板主題"; "Permissions" = "權限"; "Permissions Tab" = "權限"; "Permissions-Header" = "權限"; /* Tab Item Titles */ "Preferences Tab" = "偏好設定"; "Preview" = "預覽"; "Privacy Text" = "您稍後可以在「系統偏好設定」>「安全性與隱私權」中更改。"; /* Onboarding Search */ "Quick Add Locations" = "快速加入位置"; /* Notes Popover */ "Reminder Set" = "Reminder Set"; "Reminders Access" = "取用提醒事項"; "Reminders Access Title" = "取用提醒事項"; "Reminders Detail" = "根據你選擇的位置設定提醒。您的提醒將儲存在預設的提醒事項應用程式。"; "San Francisco" = "舊金山"; "Search Field Placeholder" = "輸入一個地點的名稱"; "See your next Calendar event here." = "在此查看你的下一個日曆活動。"; "Should Clocker open automatically on startup?" = "啟動時自動打開 Clocker?"; "Show All Day Meetings" = "顯示整日的行程"; "Show Future Slider" = "顯示未來滑桿"; "Show Next Meeting Title in Menubar" = "在選單列中顯示下個行程"; "Show Sunrise/Sunset" = "顯示日出/日落"; "Show Upcoming Event View" = "顯示即將到來的行程"; "Show events from" = "Show events from"; "Sort by Label" = "按標籤排序"; "Sort by Name" = "按名稱排序"; "Sort by Time Difference" = "按時差排序"; /* General Preferences Screen */ "Start at Login" = "登入時打開 Clocker"; "Successfully set." = "設定成功。"; /* App Feedback */ "Tell us what you think!" = "告訴我們您的想法!"; "Thank you for helping make Clocker even better!" = "感謝您的幫助,讓 Clocker 變得更好!"; "Thank you for the details." = "Thank you for the details."; "The Internet connection appears to be offline." = "Internet連線已斷開。"; "This can be configured later in Clocker Preferences." = "這可以稍後在 Clocker 偏好設定中進行設置。"; "Time Format" = "時間格式"; "Truncate menubar text longer than" = "選單列文字長度限制"; "Try again, maybe?" = "再試一次?"; "Unexpected" = "無法預期的"; /* Calendars View */ "Upcoming Event View Options" = "即將到來的行程顯示選項"; "Upcoming events from your calendars can be shown in the menubar + panel." = "即將到來的行程會在選單列和面板中顯示。"; "You have no events scheduled for tomorrow." = "您明天沒有排任何行程。"; "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences." = "當應用程式啟動時,您將會在選單列看到 Clocker 圖像。如果您想在 Dock 上看到圖像,請前往偏好設定。"; /* Final Onboarding Screen */ "You're all set!" = "設定完成。"; /* Errors */ "You're offline, maybe?" = "You're offline, maybe?"; "app-name" = "Clocker"; "characters" = "字元"; "iRateAppMessage" = "如果您喜歡使用 %@ 的話,您是否願意花一點給予評價?不需要一分鐘就能完成。感謝您的支持!"; "iRateCancelButton" = "不用了,謝謝!"; "iRateGameMessage" = "如果您喜歡玩 %@ 的話,您是否願意花一點時間給予評價?不需一分鐘即可完成。感謝您的支持"; "iRateMessageTitle" = "評價 %@"; "iRateRateButton" = "現在評分吧!"; "iRateRemindButton" = "下次再提醒我"; "iRateUpdateMessage" = "立即更新"; "setup-steps" = "設定 Clocker 只需要 3 個步驟"; "start-at-login" = "在登入時啟動"; // iCloud "Enable iCloud Sync" = "Enable iCloud Sync"; ================================================ FILE: Clocker/Clocker-Bridging-Header.h ================================================ // // Clocker-Bridging-Header.h // Clocker // // Created by Banthia, Abhishek on 12/22/17. // #import "iVersion.h" #import #import #import ================================================ FILE: Clocker/Clocker.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 3508CC942599FFEC000E3530 /* MenubarTitleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */; }; 3508CC9A259A0001000E3530 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC99259A0001000E3530 /* StatusItemView.swift */; }; 3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */; }; 3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CCA9259A0027000E3530 /* StatusContainerView.swift */; }; 3531F7C226936C6E00DF0111 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */; }; 3531F7C326936C8300DF0111 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */; }; 3531F7C426936C8300DF0111 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */; }; 3531F7C526936C8400DF0111 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */; }; 3531F80626938D7700DF0111 /* GoogleUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FC26938D7600DF0111 /* GoogleUtilities.framework */; }; 3531F80726938D7700DF0111 /* GoogleUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FC26938D7600DF0111 /* GoogleUtilities.framework */; }; 3531F80826938D7700DF0111 /* GoogleUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FC26938D7600DF0111 /* GoogleUtilities.framework */; }; 3531F80926938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FD26938D7700DF0111 /* FirebaseCoreDiagnostics.framework */; }; 3531F80A26938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FD26938D7700DF0111 /* FirebaseCoreDiagnostics.framework */; }; 3531F80B26938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FD26938D7700DF0111 /* FirebaseCoreDiagnostics.framework */; }; 3531F80C26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FE26938D7700DF0111 /* FirebaseDatabase.framework */; }; 3531F80D26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FE26938D7700DF0111 /* FirebaseDatabase.framework */; }; 3531F80E26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FE26938D7700DF0111 /* FirebaseDatabase.framework */; }; 3531F80F26938D7700DF0111 /* PromisesObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FF26938D7700DF0111 /* PromisesObjC.framework */; }; 3531F81026938D7700DF0111 /* PromisesObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FF26938D7700DF0111 /* PromisesObjC.framework */; }; 3531F81126938D7700DF0111 /* PromisesObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F7FF26938D7700DF0111 /* PromisesObjC.framework */; }; 3531F81226938D7700DF0111 /* FirebaseCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80026938D7700DF0111 /* FirebaseCore.framework */; }; 3531F81326938D7700DF0111 /* FirebaseCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80026938D7700DF0111 /* FirebaseCore.framework */; }; 3531F81426938D7700DF0111 /* FirebaseCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80026938D7700DF0111 /* FirebaseCore.framework */; }; 3531F81526938D7700DF0111 /* nanopb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80126938D7700DF0111 /* nanopb.framework */; }; 3531F81626938D7700DF0111 /* nanopb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80126938D7700DF0111 /* nanopb.framework */; }; 3531F81726938D7700DF0111 /* nanopb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80126938D7700DF0111 /* nanopb.framework */; }; 3531F81826938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80226938D7700DF0111 /* FirebaseCrashlytics.framework */; }; 3531F81926938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80226938D7700DF0111 /* FirebaseCrashlytics.framework */; }; 3531F81A26938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80226938D7700DF0111 /* FirebaseCrashlytics.framework */; }; 3531F81B26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80326938D7700DF0111 /* FirebaseInstallations.framework */; }; 3531F81C26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80326938D7700DF0111 /* FirebaseInstallations.framework */; }; 3531F81D26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80326938D7700DF0111 /* FirebaseInstallations.framework */; }; 3531F81E26938D7700DF0111 /* leveldb-library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80426938D7700DF0111 /* leveldb-library.framework */; }; 3531F81F26938D7700DF0111 /* leveldb-library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80426938D7700DF0111 /* leveldb-library.framework */; }; 3531F82026938D7700DF0111 /* leveldb-library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80426938D7700DF0111 /* leveldb-library.framework */; }; 3531F82126938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; }; 3531F82226938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; }; 3531F82326938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; }; 353B5BC52698B78A0023858D /* UpcomingEventStatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353B5BC42698B78A0023858D /* UpcomingEventStatusItemView.swift */; }; 3548C45A26BECF1B00AFB533 /* UpcomingEventViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3548C45926BECF1B00AFB533 /* UpcomingEventViewItem.xib */; }; 3548C45D26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C45C26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift */; }; 3548C45F26BEEFB400AFB533 /* UpcomingEventsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C45E26BEEFB400AFB533 /* UpcomingEventsDataSource.swift */; }; 3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C46026BEEFE400AFB533 /* UpcomingEventViewItem.swift */; }; 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */; }; 35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */; }; 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */; }; 35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */; }; 357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* TimeMarkerViewItem.swift */; }; 357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357391862507277500D30819 /* HourMarkerViewItem.xib */; }; 3579765E2680208C009DDA6E /* ParentPanelController+ModernSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */; }; 3595FAD0227F88BC0044A12A /* UserDefaults + KVOExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */; }; 35B2FEC0259A186F005DA84D /* StartupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEBF259A186F005DA84D /* StartupKit */; }; 35B2FEDD259A2291005DA84D /* CoreLoggerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 35B2FEDC259A2291005DA84D /* CoreLoggerKit */; }; 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 */; }; 35C36EF222595F14002FA5C6 /* OnboardingWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */; }; 35C36EF322595F14002FA5C6 /* WelcomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEA22595F13002FA5C6 /* WelcomeView.xib */; }; 35C36EF422595F14002FA5C6 /* StartAtLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EEB22595F13002FA5C6 /* StartAtLoginViewController.swift */; }; 35C36EF522595F14002FA5C6 /* OnboardingSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EEC22595F13002FA5C6 /* OnboardingSearchController.swift */; }; 35C36EF622595F14002FA5C6 /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EED22595F13002FA5C6 /* OnboardingController.swift */; }; 35C36EF722595F14002FA5C6 /* FinalOnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EEE22595F13002FA5C6 /* FinalOnboardingViewController.swift */; }; 35C36EF822595F14002FA5C6 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */; }; 35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */; }; 35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFA2259616B002FA5C6 /* Solar.swift */; }; 35C36F0E225961DA002FA5C6 /* Date+Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */; }; 35C36F10225961DA002FA5C6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F00225961D9002FA5C6 /* Constants.swift */; }; 35C36F12225961DA002FA5C6 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F02225961DA002FA5C6 /* Date+Components.swift */; }; 35C36F13225961DA002FA5C6 /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */; }; 35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */; }; 35C36F15225961DA002FA5C6 /* TimeChunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F05225961DA002FA5C6 /* TimeChunk.swift */; }; 35C36F16225961DA002FA5C6 /* Date+Inits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F06225961DA002FA5C6 /* Date+Inits.swift */; }; 35C36F17225961DA002FA5C6 /* DateTools.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 35C36F07225961DA002FA5C6 /* DateTools.bundle */; }; 35C36F18225961DA002FA5C6 /* Date+Comparators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F08225961DA002FA5C6 /* Date+Comparators.swift */; }; 35C36F19225961DA002FA5C6 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F09225961DA002FA5C6 /* Enums.swift */; }; 35C36F1A225961DA002FA5C6 /* Date+Manipulations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */; }; 35C36F1B225961DA002FA5C6 /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0B225961DA002FA5C6 /* Date+Format.swift */; }; 35C36F2022596253002FA5C6 /* OneWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1E22596253002FA5C6 /* OneWindowController.swift */; }; 35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */; }; 35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */; }; 35C36F2C2259D6FA002FA5C6 /* PanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F282259D6FA002FA5C6 /* PanelController.swift */; }; 35C36F372259D7C3002FA5C6 /* AddTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F322259D7C3002FA5C6 /* AddTableViewCell.swift */; }; 35C36F412259D892002FA5C6 /* Themer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F392259D892002FA5C6 /* Themer.swift */; }; 35C36F422259D892002FA5C6 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F3A2259D892002FA5C6 /* Timer.swift */; }; 35C36F452259D892002FA5C6 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F3D2259D892002FA5C6 /* Strings.swift */; }; 35C36F462259D892002FA5C6 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F3E2259D892002FA5C6 /* DataStore.swift */; }; 35C36F472259D892002FA5C6 /* Reach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F3F2259D892002FA5C6 /* Reach.swift */; }; 35C36F482259D892002FA5C6 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F402259D892002FA5C6 /* NetworkManager.swift */; }; 35C36F4B2259D971002FA5C6 /* PointingHandCursorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F4A2259D971002FA5C6 /* PointingHandCursorButton.swift */; }; 35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F4C2259D981002FA5C6 /* DateFormatterManager.swift */; }; 35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F4D2259D981002FA5C6 /* AppDefaults.swift */; }; 35C36F572259DD8A002FA5C6 /* TimezoneDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F532259DD8A002FA5C6 /* TimezoneDataSource.swift */; }; 35C36F582259DD8A002FA5C6 /* PanelTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F542259DD8A002FA5C6 /* PanelTableView.swift */; }; 35C36F592259DD8A002FA5C6 /* TimezoneCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F552259DD8A002FA5C6 /* TimezoneCellView.swift */; }; 35C36F5A2259DD8A002FA5C6 /* Panelr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F562259DD8A002FA5C6 /* Panelr.swift */; }; 35C36F5D2259DD96002FA5C6 /* TimezoneDataOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F5B2259DD96002FA5C6 /* TimezoneDataOperations.swift */; }; 35C36F612259DE67002FA5C6 /* NotesPopover.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36F5F2259DE67002FA5C6 /* NotesPopover.xib */; }; 35C36F622259DE67002FA5C6 /* NotesPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F602259DE67002FA5C6 /* NotesPopover.swift */; }; 35C36F662259DF4C002FA5C6 /* UpcomingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F642259DF4C002FA5C6 /* UpcomingEventView.swift */; }; 35C36F672259DF4C002FA5C6 /* ReviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F652259DF4C002FA5C6 /* ReviewController.swift */; }; 35C36F692259DF55002FA5C6 /* TextViewWithPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F682259DF55002FA5C6 /* TextViewWithPlaceholder.swift */; }; 35C36F6B2259E0E1002FA5C6 /* FloatingWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36F6A2259E0E1002FA5C6 /* FloatingWindow.xib */; }; 35C36F6F2259E185002FA5C6 /* CustomSliderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F6C2259E185002FA5C6 /* CustomSliderCell.swift */; }; 35C36F702259E185002FA5C6 /* BackgroundPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F6D2259E185002FA5C6 /* BackgroundPanelView.swift */; }; 35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F6E2259E185002FA5C6 /* NoTimezoneView.swift */; }; 35C36F732259E1AA002FA5C6 /* FloatingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F722259E1AA002FA5C6 /* FloatingWindowController.swift */; }; 35C36F772259E1D0002FA5C6 /* AppKit + Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F742259E1CF002FA5C6 /* AppKit + Additions.swift */; }; 35C36F782259E1D0002FA5C6 /* Foundation + Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F752259E1CF002FA5C6 /* Foundation + Additions.swift */; }; 35C36F792259E1D0002FA5C6 /* String + Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F762259E1CF002FA5C6 /* String + Additions.swift */; }; 35C36F912259EAF4002FA5C6 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C36F902259EAF4002FA5C6 /* Preferences.storyboard */; }; 35C36F972259EBB1002FA5C6 /* AppFeedbackWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35C36F952259EBB1002FA5C6 /* AppFeedbackWindow.xib */; }; 35C36F982259EBB1002FA5C6 /* AppFeedbackWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F962259EBB1002FA5C6 /* AppFeedbackWindowController.swift */; }; 35C36FA02259ED6D002FA5C6 /* CalendarHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9D2259ED6D002FA5C6 /* CalendarHandler.swift */; }; 35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */; }; 35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */; }; 35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */; }; 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */; }; 35DFBCEF26A8468900D6648B /* ConfigExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DFBCEE26A8468900D6648B /* ConfigExport.swift */; }; 35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E65124268EDD2E00E3E1E3 /* Toasty.swift */; }; 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */; }; 9A0385C0269E8891003B5E72 /* PermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0385BF269E8891003B5E72 /* PermissionsTests.swift */; }; 9A0A1C8C20903DBD0012003B /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9E87651C1FEDC500A7A2DF /* CoreLocation.framework */; }; 9A13BAD61CA87F08007C6CBE /* Panel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAD81CA87F08007C6CBE /* Panel.xib */; }; 9A13BAE01CA882FA007C6CBE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */; }; 9A13BAEA1CA88A76007C6CBE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAEC1CA88A76007C6CBE /* Localizable.strings */; }; 9A20A04B1C4DEED200FB45AB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A20A04A1C4DEED200FB45AB /* IOKit.framework */; }; 9A24A1881ED902CC0095201E /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A24A1871ED902CC0095201E /* EventKit.framework */; }; 9A3169C11D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A3169C01D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist */; }; 9A43792A1BEC230A00F4E27F /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A4379291BEC230A00F4E27F /* libc++.tbd */; }; 9A56DB801C1CFB73004CE6AF /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */; }; 9A5E75E4204CC39700119939 /* ShortcutRecorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */; }; 9A5E75E5204CC39700119939 /* ShortcutRecorder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9A5E75E8204CC39700119939 /* PTHotKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BE1CE04F1600547EE7 /* PTHotKey.framework */; }; 9A5E75E9204CC39700119939 /* PTHotKey.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BE1CE04F1600547EE7 /* PTHotKey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9A6D93371CF3E82F005A8690 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6D93361CF3E82F005A8690 /* CoreImage.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 9A7547C91F183729004705EF /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A20A06F1C4E804D00FB45AB /* ServiceManagement.framework */; }; 9A7547D41F184DC3004705EF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A7547D31F184DC3004705EF /* AppDelegate.m */; }; 9A7547D71F184DC3004705EF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A7547D61F184DC3004705EF /* main.m */; }; 9A7547DC1F184DC3004705EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A7547DB1F184DC3004705EF /* Assets.xcassets */; }; 9A7547DF1F184DC3004705EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A7547DD1F184DC3004705EF /* Main.storyboard */; }; 9A7547E51F184E3F004705EF /* ClockerHelper.app in Login Item Helper */ = {isa = PBXBuildFile; fileRef = 9A7547D01F184DC3004705EF /* ClockerHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 9A8605AE1BEC148400A810A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A8605AD1BEC148400A810A4 /* main.m */; }; 9A8B256A232EFAD300204CAD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAEC1CA88A76007C6CBE /* Localizable.strings */; }; 9A97419B2455442100087B0D /* OnboardingSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A97419A2455442100087B0D /* OnboardingSearchTests.swift */; }; 9A9E87621C1FEDB500A7A2DF /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9E87611C1FEDB500A7A2DF /* CFNetwork.framework */; }; 9A9E876A1C1FEDDB00A7A2DF /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9E87691C1FEDDB00A7A2DF /* SystemConfiguration.framework */; }; 9AA522C023415BDD00C9E005 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9AA522BE23415BDD00C9E005 /* InfoPlist.strings */; }; 9AA522C323415BDD00C9E005 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9AA522C123415BDD00C9E005 /* InfoPlist.strings */; }; 9AB6F1562259CF3900A44663 /* CalendarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F1552259CF3900A44663 /* CalendarViewController.swift */; }; 9AB6F1582259CFFC00A44663 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F1572259CFFC00A44663 /* AboutViewController.swift */; }; 9AB6F15D2259D08300A44663 /* iVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F15A2259D08300A44663 /* iVersion.m */; }; 9AB6F15E2259D08300A44663 /* iVersion.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 9AB6F15C2259D08300A44663 /* iVersion.bundle */; }; 9AB6F1612259D1B000A44663 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F15F2259D1B000A44663 /* PreferencesViewController.swift */; }; 9AB6F1622259D1B000A44663 /* PreferencesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F1602259D1B000A44663 /* PreferencesDataSource.swift */; }; 9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F1632259D1B800A44663 /* ParentViewController.swift */; }; 9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB6F1662259D23200A44663 /* PermissionsViewController.swift */; }; 9AB89E031CE97A4900EC8EB1 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AB89E021CE97A4900EC8EB1 /* Media.xcassets */; }; 9ABF455B268FDABA002C779B /* CopyToClipboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABF4559268FDABA002C779B /* CopyToClipboardTests.swift */; }; 9ABFB3801CA6882000E10745 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFB37F1CA6882000E10745 /* ApplicationServices.framework */; }; 9AC678E41C1ABAB9003B4F6B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AC678E31C1ABAB9003B4F6B /* QuartzCore.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 9ACB31401EDA994200F3E1D3 /* ShortcutRecorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ACB313F1EDA994200F3E1D3 /* ShortcutRecorder.framework */; }; 9ACF469D1DCBD45200C49B51 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ACF469C1DCBD45200C49B51 /* Quartz.framework */; }; 9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */; }; C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20839C921515C1E00C86589 /* ClockerUnitTests.swift */; }; C213713420B4FD920024D5A4 /* FloatingWindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C213713320B4FD920024D5A4 /* FloatingWindowTests.swift */; }; C22F3D802107778A0001D5E1 /* ShortcutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22F3D7F2107778A0001D5E1 /* ShortcutTests.swift */; }; C264A0C520B897D800CCD875 /* PreferencesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C264A0C420B897D800CCD875 /* PreferencesTest.swift */; }; C264A0C820B898D600CCD875 /* PanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C264A0C720B898D600CCD875 /* PanelTests.swift */; }; C2AB022421AEED590014A401 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AB022321AEED590014A401 /* OnboardingTests.swift */; }; C2BFE3E62049F82300825BE5 /* ClockerUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = C2BFE3E52049F82300825BE5 /* ClockerUITests.m */; }; C2CCCD8220619C4C00F2DFC2 /* LocationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CCCD8120619C4C00F2DFC2 /* LocationController.swift */; }; C2D30A8A210245C6000BFAEE /* ReviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D30A89210245C6000BFAEE /* ReviewTests.swift */; }; C2D30A8D21025106000BFAEE /* NetworkDisconnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D30A8C21025106000BFAEE /* NetworkDisconnectionTests.swift */; }; C2F7821B20B70E3700B6CD07 /* AboutUsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7821A20B70E3700B6CD07 /* AboutUsTests.swift */; }; DD4F7C0913C30F9F00825C6E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD4F7C0813C30F9F00825C6E /* Cocoa.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 9A5E75E6204CC39700119939 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */; proxyType = 1; remoteGlobalIDString = 9398377F0DA42965007F53F3; remoteInfo = ShortcutRecorder.framework; }; 9A5E75EA204CC39700119939 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */; proxyType = 1; remoteGlobalIDString = E273122D1349EC9000A84433; remoteInfo = PTHotKey.framework; }; 9A86E2BB1CE04F1600547EE7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */; proxyType = 2; remoteGlobalIDString = 939837800DA42965007F53F3; remoteInfo = ShortcutRecorder.framework; }; 9A86E2BD1CE04F1600547EE7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */; proxyType = 2; remoteGlobalIDString = E273122E1349EC9000A84433; remoteInfo = PTHotKey.framework; }; C20839CC21515C1F00C86589 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DD4F7BFB13C30F9F00825C6E /* Project object */; proxyType = 1; remoteGlobalIDString = DD4F7C0313C30F9F00825C6E; remoteInfo = Clocker; }; C2BFE3E82049F82300825BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DD4F7BFB13C30F9F00825C6E /* Project object */; proxyType = 1; remoteGlobalIDString = DD4F7C0313C30F9F00825C6E; remoteInfo = Clocker; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9A20A0711C4E808500FB45AB /* Login Item Helper */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( 9A7547E51F184E3F004705EF /* ClockerHelper.app in Login Item Helper */, ); name = "Login Item Helper"; runOnlyForDeploymentPostprocessing = 0; }; 9A5E75EC204CC39700119939 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 9A5E75E9204CC39700119939 /* PTHotKey.framework in Embed Frameworks */, 9A5E75E5204CC39700119939 /* ShortcutRecorder.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenubarTitleProvider.swift; sourceTree = ""; }; 3508CC99259A0001000E3530 /* StatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = ""; }; 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemHandler.swift; sourceTree = ""; }; 3508CCA9259A0027000E3530 /* StatusContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = ""; }; 352AF497232E07B400D96FA7 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/InfoPlist.strings; sourceTree = ""; }; 352AF499232E07B400D96FA7 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 3531F7EA26936D8800DF0111 /* Firebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Firebase.h; sourceTree = ""; }; 3531F7F42693882300DF0111 /* Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Keys.plist; path = Internal/Keys.plist; sourceTree = ""; }; 3531F7FC26938D7600DF0111 /* GoogleUtilities.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleUtilities.framework; path = Frameworks/Firebase/GoogleUtilities.framework; sourceTree = ""; }; 3531F7FD26938D7700DF0111 /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/Firebase/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 3531F7FE26938D7700DF0111 /* FirebaseDatabase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseDatabase.framework; path = Frameworks/Firebase/FirebaseDatabase.framework; sourceTree = ""; }; 3531F7FF26938D7700DF0111 /* PromisesObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromisesObjC.framework; path = Frameworks/Firebase/PromisesObjC.framework; sourceTree = ""; }; 3531F80026938D7700DF0111 /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/Firebase/FirebaseCore.framework; sourceTree = ""; }; 3531F80126938D7700DF0111 /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/Firebase/nanopb.framework; sourceTree = ""; }; 3531F80226938D7700DF0111 /* FirebaseCrashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCrashlytics.framework; path = Frameworks/Firebase/FirebaseCrashlytics.framework; sourceTree = ""; }; 3531F80326938D7700DF0111 /* FirebaseInstallations.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseInstallations.framework; path = Frameworks/Firebase/FirebaseInstallations.framework; sourceTree = ""; }; 3531F80426938D7700DF0111 /* leveldb-library.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "leveldb-library.framework"; path = "Frameworks/Firebase/leveldb-library.framework"; sourceTree = ""; }; 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleDataTransport.framework; path = Frameworks/Firebase/GoogleDataTransport.framework; sourceTree = ""; }; 353B5BC42698B78A0023858D /* UpcomingEventStatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpcomingEventStatusItemView.swift; sourceTree = ""; }; 353B5BC72698D4BB0023858D /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; 3548C45926BECF1B00AFB533 /* UpcomingEventViewItem.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UpcomingEventViewItem.xib; sourceTree = ""; }; 3548C45C26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParentPanelController+UpcomingEvents.swift"; sourceTree = ""; }; 3548C45E26BEEFB400AFB533 /* UpcomingEventsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpcomingEventsDataSource.swift; sourceTree = ""; }; 3548C46026BEEFE400AFB533 /* UpcomingEventViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpcomingEventViewItem.swift; sourceTree = ""; }; 3552066027AF6277000EF08F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; 3552066227AF63DC000EF08F /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; 3552066327AF63DC000EF08F /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; 3552066427AF6488000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 3552066527AF6489000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemerTests.swift; sourceTree = ""; }; 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatterManagerTests.swift; sourceTree = ""; }; 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateTests.swift; sourceTree = ""; }; 35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDataSourceTests.swift; sourceTree = ""; }; 3567F7DB288DC6520049C7A9 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 3569A44E25441F320087E254 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 357391852507277500D30819 /* TimeMarkerViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeMarkerViewItem.swift; sourceTree = ""; }; 357391862507277500D30819 /* HourMarkerViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HourMarkerViewItem.xib; sourceTree = ""; }; 3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParentPanelController+ModernSlider.swift"; sourceTree = ""; }; 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults + KVOExtensions.swift"; sourceTree = ""; }; 35A6A4B925C5DEF300356073 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 35B2FEB1259A1649005DA84D /* StartupKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = StartupKit; sourceTree = ""; }; 35B2FED4259A2244005DA84D /* CoreLoggerKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreLoggerKit; sourceTree = ""; }; 35B2FEE4259A2C25005DA84D /* CoreModelKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CoreModelKit; sourceTree = ""; }; 35BD9A562807580800077443 /* EventInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventInfoTests.swift; sourceTree = ""; }; 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateHandler.swift; sourceTree = ""; }; 35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = ""; }; 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingWelcomeViewController.swift; sourceTree = ""; }; 35C36EEA22595F13002FA5C6 /* WelcomeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeView.xib; sourceTree = ""; }; 35C36EEB22595F13002FA5C6 /* StartAtLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartAtLoginViewController.swift; sourceTree = ""; }; 35C36EEC22595F13002FA5C6 /* OnboardingSearchController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSearchController.swift; sourceTree = ""; }; 35C36EED22595F13002FA5C6 /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = ""; }; 35C36EEE22595F13002FA5C6 /* FinalOnboardingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FinalOnboardingViewController.swift; sourceTree = ""; }; 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingParentViewController.swift; sourceTree = ""; }; 35C36EFA2259616B002FA5C6 /* Solar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Solar.swift; sourceTree = ""; }; 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Bundle.swift"; sourceTree = ""; }; 35C36F00225961D9002FA5C6 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 35C36F02225961DA002FA5C6 /* Date+Components.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Components.swift"; sourceTree = ""; }; 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = ""; }; 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Integer+DateTools.swift"; sourceTree = ""; }; 35C36F05225961DA002FA5C6 /* TimeChunk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeChunk.swift; sourceTree = ""; }; 35C36F06225961DA002FA5C6 /* Date+Inits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Inits.swift"; sourceTree = ""; }; 35C36F07225961DA002FA5C6 /* DateTools.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = DateTools.bundle; sourceTree = ""; }; 35C36F08225961DA002FA5C6 /* Date+Comparators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Comparators.swift"; sourceTree = ""; }; 35C36F09225961DA002FA5C6 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Manipulations.swift"; sourceTree = ""; }; 35C36F0B225961DA002FA5C6 /* Date+Format.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Format.swift"; sourceTree = ""; }; 35C36F1E22596253002FA5C6 /* OneWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneWindowController.swift; sourceTree = ""; }; 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = ""; }; 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParentPanelController.swift; sourceTree = ""; }; 35C36F282259D6FA002FA5C6 /* PanelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanelController.swift; sourceTree = ""; }; 35C36F322259D7C3002FA5C6 /* AddTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddTableViewCell.swift; sourceTree = ""; }; 35C36F392259D892002FA5C6 /* Themer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Themer.swift; sourceTree = ""; }; 35C36F3A2259D892002FA5C6 /* Timer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = ""; }; 35C36F3D2259D892002FA5C6 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 35C36F3E2259D892002FA5C6 /* DataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; 35C36F3F2259D892002FA5C6 /* Reach.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reach.swift; sourceTree = ""; }; 35C36F402259D892002FA5C6 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; 35C36F4A2259D971002FA5C6 /* PointingHandCursorButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointingHandCursorButton.swift; sourceTree = ""; }; 35C36F4C2259D981002FA5C6 /* DateFormatterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormatterManager.swift; sourceTree = ""; }; 35C36F4D2259D981002FA5C6 /* AppDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 35C36F532259DD8A002FA5C6 /* TimezoneDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimezoneDataSource.swift; sourceTree = ""; }; 35C36F542259DD8A002FA5C6 /* PanelTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanelTableView.swift; sourceTree = ""; }; 35C36F552259DD8A002FA5C6 /* TimezoneCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimezoneCellView.swift; sourceTree = ""; }; 35C36F562259DD8A002FA5C6 /* Panelr.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Panelr.swift; sourceTree = ""; }; 35C36F5B2259DD96002FA5C6 /* TimezoneDataOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimezoneDataOperations.swift; sourceTree = ""; }; 35C36F5F2259DE67002FA5C6 /* NotesPopover.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NotesPopover.xib; sourceTree = ""; }; 35C36F602259DE67002FA5C6 /* NotesPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesPopover.swift; sourceTree = ""; }; 35C36F642259DF4C002FA5C6 /* UpcomingEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpcomingEventView.swift; sourceTree = ""; }; 35C36F652259DF4C002FA5C6 /* ReviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewController.swift; sourceTree = ""; }; 35C36F682259DF55002FA5C6 /* TextViewWithPlaceholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewWithPlaceholder.swift; sourceTree = ""; }; 35C36F6A2259E0E1002FA5C6 /* FloatingWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FloatingWindow.xib; sourceTree = ""; }; 35C36F6C2259E185002FA5C6 /* CustomSliderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSliderCell.swift; sourceTree = ""; }; 35C36F6D2259E185002FA5C6 /* BackgroundPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundPanelView.swift; sourceTree = ""; }; 35C36F6E2259E185002FA5C6 /* NoTimezoneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoTimezoneView.swift; sourceTree = ""; }; 35C36F722259E1AA002FA5C6 /* FloatingWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingWindowController.swift; sourceTree = ""; }; 35C36F742259E1CF002FA5C6 /* AppKit + Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppKit + Additions.swift"; sourceTree = ""; }; 35C36F752259E1CF002FA5C6 /* Foundation + Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Foundation + Additions.swift"; sourceTree = ""; }; 35C36F762259E1CF002FA5C6 /* String + Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String + Additions.swift"; sourceTree = ""; }; 35C36F902259EAF4002FA5C6 /* Preferences.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Preferences.storyboard; sourceTree = ""; }; 35C36F952259EBB1002FA5C6 /* AppFeedbackWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AppFeedbackWindow.xib; sourceTree = ""; }; 35C36F962259EBB1002FA5C6 /* AppFeedbackWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppFeedbackWindowController.swift; sourceTree = ""; }; 35C36F9D2259ED6D002FA5C6 /* CalendarHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarHandler.swift; sourceTree = ""; }; 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventCenter.swift; sourceTree = ""; }; 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemindersHandler.swift; sourceTree = ""; }; 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewControllerTests.swift; sourceTree = ""; }; 35DFBCEE26A8468900D6648B /* ConfigExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigExport.swift; sourceTree = ""; }; 35E65124268EDD2E00E3E1E3 /* Toasty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toasty.swift; sourceTree = ""; }; 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardMenubarHandlerTests.swift; sourceTree = ""; }; 9A0385BF269E8891003B5E72 /* PermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsTests.swift; sourceTree = ""; }; 9A13BAD71CA87F08007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/Panel.xib; sourceTree = ""; }; 9A13BAE11CA882FA007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 9A13BAEB1CA88A76007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 9A20A04A1C4DEED200FB45AB /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 9A20A06F1C4E804D00FB45AB /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 9A24A1871ED902CC0095201E /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = System/Library/Frameworks/EventKit.framework; sourceTree = SDKROOT; }; 9A3169C01D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = com.abhishek.ClockerHelper.plist; path = Clocker/com.abhishek.ClockerHelper.plist; sourceTree = ""; }; 9A4379291BEC230A00F4E27F /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 9A43792B1BEC231100F4E27F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 9A4DC4DF2337F2EB00F03FA4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 9A4DC4E12337F2EB00F03FA4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 9A4DC4E22337F31200F03FA4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 9A4DC4E32337F31200F03FA4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 9A4DC4E42337F5BC00F03FA4 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 9A4DC4E52337F5BC00F03FA4 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 9A4DC4E62337F5C800F03FA4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; 9A4DC4E72337F5C800F03FA4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 9A4DC4E82337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; 9A4DC4E92337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainMenu.xib; path = Clocker/MainMenu.xib; sourceTree = ""; }; 9A5B1A8D1BECDFB700A77C68 /* Clocker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Clocker.entitlements; path = Clocker/Clocker.entitlements; sourceTree = ""; }; 9A5E6B9F1CAF71C1006E7C5C /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; }; 9A6D93361CF3E82F005A8690 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; 9A7547D01F184DC3004705EF /* ClockerHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ClockerHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9A7547D21F184DC3004705EF /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 9A7547D31F184DC3004705EF /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9A7547D61F184DC3004705EF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 9A7547DB1F184DC3004705EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9A7547DE1F184DC3004705EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 9A7547E01F184DC3004705EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9A8605AD1BEC148400A810A4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Clocker/main.m; sourceTree = ""; }; 9A8605C31BEC155B00A810A4 /* Clocker-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Clocker-Info.plist"; path = "Clocker/Clocker-Info.plist"; sourceTree = ""; }; 9A8605CC1BEC155B00A810A4 /* Clocker-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Clocker-Prefix.pch"; path = "Clocker/Clocker-Prefix.pch"; sourceTree = ""; }; 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ShortcutRecorder.xcodeproj; path = "Clocker/ShortcutRecorder-master/ShortcutRecorder.xcodeproj"; sourceTree = ""; }; 9A97419A2455442100087B0D /* OnboardingSearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSearchTests.swift; sourceTree = ""; }; 9A9E87611C1FEDB500A7A2DF /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 9A9E87631C1FEDBD00A7A2DF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 9A9E87651C1FEDC500A7A2DF /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 9A9E87671C1FEDD300A7A2DF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 9A9E87691C1FEDDB00A7A2DF /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 9A9E876B1C1FEDE700A7A2DF /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 9AA522BF23415BDD00C9E005 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522C223415BDD00C9E005 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522C423415BDD00C9E005 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 9AA522C623415BF600C9E005 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522C723415BF600C9E005 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522C823415BF600C9E005 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; 9AA522C923415C4F00C9E005 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 9AA522CA23415C4F00C9E005 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 9AA522CB23415C4F00C9E005 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; 9AA522CC234169E400C9E005 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522CD234169E400C9E005 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 9AA522CE234169F200C9E005 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522CF234169F200C9E005 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; 9AA522D023416A0E00C9E005 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522D123416A0E00C9E005 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 9AA522D323416A1B00C9E005 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoPlist.strings"; sourceTree = ""; }; 9AA522D423416A6000C9E005 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522D523416A6000C9E005 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 9AA522D623416E6000C9E005 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 9AA522D723416E6000C9E005 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 9AB6F1552259CF3900A44663 /* CalendarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarViewController.swift; sourceTree = ""; }; 9AB6F1572259CFFC00A44663 /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 9AB6F15A2259D08300A44663 /* iVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iVersion.m; sourceTree = ""; }; 9AB6F15B2259D08300A44663 /* iVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iVersion.h; sourceTree = ""; }; 9AB6F15C2259D08300A44663 /* iVersion.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = iVersion.bundle; sourceTree = ""; }; 9AB6F15F2259D1B000A44663 /* PreferencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; 9AB6F1602259D1B000A44663 /* PreferencesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesDataSource.swift; sourceTree = ""; }; 9AB6F1632259D1B800A44663 /* ParentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParentViewController.swift; sourceTree = ""; }; 9AB6F1662259D23200A44663 /* PermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsViewController.swift; sourceTree = ""; }; 9AB89E021CE97A4900EC8EB1 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 9ABF4559268FDABA002C779B /* CopyToClipboardTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyToClipboardTests.swift; sourceTree = ""; }; 9ABFB37F1CA6882000E10745 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; }; 9AC678E31C1ABAB9003B4F6B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 9ACB313F1EDA994200F3E1D3 /* ShortcutRecorder.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ShortcutRecorder.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Clocker-ewkrwqfbimlgoicxkolbqepjsbcy/Build/Products/Release/ShortcutRecorder.framework"; sourceTree = ""; }; 9ACF469C1DCBD45200C49B51 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDataSource.swift; sourceTree = ""; }; 9AFCC7FC1FD668FF00509B9C /* ClockerHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClockerHelper.entitlements; sourceTree = ""; }; C20839C721515C1E00C86589 /* ClockerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClockerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C20839C921515C1E00C86589 /* ClockerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockerUnitTests.swift; sourceTree = ""; }; C20839CB21515C1F00C86589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C213713220B4FD920024D5A4 /* ClockerUITests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ClockerUITests-Bridging-Header.h"; sourceTree = ""; }; C213713320B4FD920024D5A4 /* FloatingWindowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWindowTests.swift; sourceTree = ""; }; C22F3D7F2107778A0001D5E1 /* ShortcutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutTests.swift; sourceTree = ""; }; C264A0C420B897D800CCD875 /* PreferencesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTest.swift; sourceTree = ""; }; C264A0C720B898D600CCD875 /* PanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelTests.swift; sourceTree = ""; }; C2AB022321AEED590014A401 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; C2BFE3E32049F82300825BE5 /* ClockerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClockerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C2BFE3E52049F82300825BE5 /* ClockerUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ClockerUITests.m; sourceTree = ""; }; C2BFE3E72049F82300825BE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C2CCCD8120619C4C00F2DFC2 /* LocationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LocationController.swift; path = Clocker/LocationController.swift; sourceTree = ""; }; C2D30A89210245C6000BFAEE /* ReviewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewTests.swift; sourceTree = ""; }; C2D30A8C21025106000BFAEE /* NetworkDisconnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDisconnectionTests.swift; sourceTree = ""; }; C2F7821A20B70E3700B6CD07 /* AboutUsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUsTests.swift; sourceTree = ""; }; DD4F7C0413C30F9F00825C6E /* Clocker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Clocker.app; sourceTree = BUILT_PRODUCTS_DIR; }; DD4F7C0813C30F9F00825C6E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; DD4F7C0B13C30F9F00825C6E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9A7547CD1F184DC3004705EF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; C20839C421515C1E00C86589 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3531F80E26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */, 3531F81D26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */, 3531F81426938D7700DF0111 /* FirebaseCore.framework in Frameworks */, 3531F81A26938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */, 3531F80826938D7700DF0111 /* GoogleUtilities.framework in Frameworks */, 3531F82326938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */, 3531F82026938D7700DF0111 /* leveldb-library.framework in Frameworks */, 3531F81126938D7700DF0111 /* PromisesObjC.framework in Frameworks */, 3531F81726938D7700DF0111 /* nanopb.framework in Frameworks */, 3531F80B26938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C2BFE3E02049F82300825BE5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3531F80D26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */, 3531F81C26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */, 3531F81326938D7700DF0111 /* FirebaseCore.framework in Frameworks */, 3531F81926938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */, 3531F80726938D7700DF0111 /* GoogleUtilities.framework in Frameworks */, 3531F82226938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */, 3531F81F26938D7700DF0111 /* leveldb-library.framework in Frameworks */, 3531F81026938D7700DF0111 /* PromisesObjC.framework in Frameworks */, 3531F81626938D7700DF0111 /* nanopb.framework in Frameworks */, 3531F80A26938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; DD4F7C0113C30F9F00825C6E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3531F80F26938D7700DF0111 /* PromisesObjC.framework in Frameworks */, 3531F80C26938D7700DF0111 /* FirebaseDatabase.framework in Frameworks */, 9A0A1C8C20903DBD0012003B /* CoreLocation.framework in Frameworks */, 9A7547C91F183729004705EF /* ServiceManagement.framework in Frameworks */, 9A24A1881ED902CC0095201E /* EventKit.framework in Frameworks */, 3531F81E26938D7700DF0111 /* leveldb-library.framework in Frameworks */, 9ACF469D1DCBD45200C49B51 /* Quartz.framework in Frameworks */, 9AC678E41C1ABAB9003B4F6B /* QuartzCore.framework in Frameworks */, 3531F80626938D7700DF0111 /* GoogleUtilities.framework in Frameworks */, 9A5E75E4204CC39700119939 /* ShortcutRecorder.framework in Frameworks */, 35B2FEF1259A2DB1005DA84D /* CoreModelKit in Frameworks */, 3531F81526938D7700DF0111 /* nanopb.framework in Frameworks */, 35B2FEDD259A2291005DA84D /* CoreLoggerKit in Frameworks */, 35B2FEC0259A186F005DA84D /* StartupKit in Frameworks */, 9A6D93371CF3E82F005A8690 /* CoreImage.framework in Frameworks */, 9A5E75E8204CC39700119939 /* PTHotKey.framework in Frameworks */, 3531F81826938D7700DF0111 /* FirebaseCrashlytics.framework in Frameworks */, 3531F82126938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */, 9A9E876A1C1FEDDB00A7A2DF /* SystemConfiguration.framework in Frameworks */, 3531F81B26938D7700DF0111 /* FirebaseInstallations.framework in Frameworks */, 9A9E87621C1FEDB500A7A2DF /* CFNetwork.framework in Frameworks */, 9ABFB3801CA6882000E10745 /* ApplicationServices.framework in Frameworks */, 9A20A04B1C4DEED200FB45AB /* IOKit.framework in Frameworks */, 9A43792A1BEC230A00F4E27F /* libc++.tbd in Frameworks */, 3531F80926938D7700DF0111 /* FirebaseCoreDiagnostics.framework in Frameworks */, 3531F81226938D7700DF0111 /* FirebaseCore.framework in Frameworks */, DD4F7C0913C30F9F00825C6E /* Cocoa.framework in Frameworks */, 9ACB31401EDA994200F3E1D3 /* ShortcutRecorder.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3548C45B26BEEF2100AFB533 /* Upcoming Events */ = { isa = PBXGroup; children = ( 3548C45C26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift */, 3548C45E26BEEFB400AFB533 /* UpcomingEventsDataSource.swift */, 3548C46026BEEFE400AFB533 /* UpcomingEventViewItem.swift */, 3548C45926BECF1B00AFB533 /* UpcomingEventViewItem.xib */, ); path = "Upcoming Events"; sourceTree = ""; }; 35C36EB522595834002FA5C6 /* Dependencies */ = { isa = PBXGroup; children = ( 9AB6F1592259D05100A44663 /* iVersion */, 35C36EFC225961BC002FA5C6 /* Date Additions */, 35C36EFA2259616B002FA5C6 /* Solar.swift */, ); path = Dependencies; sourceTree = ""; }; 35C36EDF22595D9C002FA5C6 /* Menu Bar */ = { isa = PBXGroup; children = ( 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */, 3508CC99259A0001000E3530 /* StatusItemView.swift */, 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */, 3508CCA9259A0027000E3530 /* StatusContainerView.swift */, 353B5BC42698B78A0023858D /* UpcomingEventStatusItemView.swift */, ); path = "Menu Bar"; sourceTree = ""; }; 35C36EFC225961BC002FA5C6 /* Date Additions */ = { isa = PBXGroup; children = ( 35C36F00225961D9002FA5C6 /* Constants.swift */, 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */, 35C36F08225961DA002FA5C6 /* Date+Comparators.swift */, 35C36F02225961DA002FA5C6 /* Date+Components.swift */, 35C36F0B225961DA002FA5C6 /* Date+Format.swift */, 35C36F06225961DA002FA5C6 /* Date+Inits.swift */, 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */, 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */, 35C36F07225961DA002FA5C6 /* DateTools.bundle */, 35C36F09225961DA002FA5C6 /* Enums.swift */, 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */, 35C36F05225961DA002FA5C6 /* TimeChunk.swift */, ); path = "Date Additions"; sourceTree = ""; }; 35C36F1D22596212002FA5C6 /* Preferences */ = { isa = PBXGroup; children = ( 35C36F492259D8E3002FA5C6 /* App Feedback */, 9AB6F1652259D1FC00A44663 /* Permissions */, 9AB6F1542259CF1B00A44663 /* About */, 9AB6F1532259CF0300A44663 /* General */, 9AB6F1522259CEF400A44663 /* Appearance */, 9AB6F1512259CEE300A44663 /* Calendar */, 35C36F1E22596253002FA5C6 /* OneWindowController.swift */, 9AB6F1632259D1B800A44663 /* ParentViewController.swift */, 35C36F902259EAF4002FA5C6 /* Preferences.storyboard */, 35C36EDF22595D9C002FA5C6 /* Menu Bar */, ); path = Preferences; sourceTree = ""; }; 35C36F242259D64D002FA5C6 /* Panel */ = { isa = PBXGroup; children = ( 3548C45B26BEEF2100AFB533 /* Upcoming Events */, 35C36F632259DE9D002FA5C6 /* Rate Controller */, 35C36F522259DC9B002FA5C6 /* UI */, 35C36F512259DC85002FA5C6 /* Data Layer */, 35C36F502259DC7C002FA5C6 /* Notes Popover */, 35C36F282259D6FA002FA5C6 /* PanelController.swift */, 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */, 35C36F722259E1AA002FA5C6 /* FloatingWindowController.swift */, 3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */, ); path = Panel; sourceTree = ""; }; 35C36F382259D80C002FA5C6 /* Overall App */ = { isa = PBXGroup; children = ( 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */, 35C36F742259E1CF002FA5C6 /* AppKit + Additions.swift */, 35C36F752259E1CF002FA5C6 /* Foundation + Additions.swift */, 35C36F762259E1CF002FA5C6 /* String + Additions.swift */, 35C36F4D2259D981002FA5C6 /* AppDefaults.swift */, 35C36F4C2259D981002FA5C6 /* DateFormatterManager.swift */, 35C36F3E2259D892002FA5C6 /* DataStore.swift */, 35C36F402259D892002FA5C6 /* NetworkManager.swift */, 35C36F3F2259D892002FA5C6 /* Reach.swift */, 35C36F3D2259D892002FA5C6 /* Strings.swift */, 35C36F392259D892002FA5C6 /* Themer.swift */, 35C36F3A2259D892002FA5C6 /* Timer.swift */, 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */, 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */, 35DFBCEE26A8468900D6648B /* ConfigExport.swift */, ); path = "Overall App"; sourceTree = ""; }; 35C36F492259D8E3002FA5C6 /* App Feedback */ = { isa = PBXGroup; children = ( 35C36F952259EBB1002FA5C6 /* AppFeedbackWindow.xib */, 35C36F962259EBB1002FA5C6 /* AppFeedbackWindowController.swift */, ); path = "App Feedback"; sourceTree = ""; }; 35C36F502259DC7C002FA5C6 /* Notes Popover */ = { isa = PBXGroup; children = ( 35C36F682259DF55002FA5C6 /* TextViewWithPlaceholder.swift */, 35C36F602259DE67002FA5C6 /* NotesPopover.swift */, 35C36F5F2259DE67002FA5C6 /* NotesPopover.xib */, ); path = "Notes Popover"; sourceTree = ""; }; 35C36F512259DC85002FA5C6 /* Data Layer */ = { isa = PBXGroup; children = ( 35C36F5B2259DD96002FA5C6 /* TimezoneDataOperations.swift */, ); path = "Data Layer"; sourceTree = ""; }; 35C36F522259DC9B002FA5C6 /* UI */ = { isa = PBXGroup; children = ( 35C36F6D2259E185002FA5C6 /* BackgroundPanelView.swift */, 35C36F6C2259E185002FA5C6 /* CustomSliderCell.swift */, 35C36F6E2259E185002FA5C6 /* NoTimezoneView.swift */, 35C36F6A2259E0E1002FA5C6 /* FloatingWindow.xib */, 35C36F562259DD8A002FA5C6 /* Panelr.swift */, 35C36F542259DD8A002FA5C6 /* PanelTableView.swift */, 35C36F552259DD8A002FA5C6 /* TimezoneCellView.swift */, 35C36F532259DD8A002FA5C6 /* TimezoneDataSource.swift */, 35C36F322259D7C3002FA5C6 /* AddTableViewCell.swift */, 357391852507277500D30819 /* TimeMarkerViewItem.swift */, 357391862507277500D30819 /* HourMarkerViewItem.xib */, 35E65124268EDD2E00E3E1E3 /* Toasty.swift */, ); path = UI; sourceTree = ""; }; 35C36F632259DE9D002FA5C6 /* Rate Controller */ = { isa = PBXGroup; children = ( 35C36F652259DF4C002FA5C6 /* ReviewController.swift */, 35C36F642259DF4C002FA5C6 /* UpcomingEventView.swift */, ); path = "Rate Controller"; sourceTree = ""; }; 35C36F9B2259EC97002FA5C6 /* Events and Reminders */ = { isa = PBXGroup; children = ( 35C36F9D2259ED6D002FA5C6 /* CalendarHandler.swift */, 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */, 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */, ); path = "Events and Reminders"; sourceTree = ""; }; 9A2000C61BFBCEF6002BFDE8 /* Utilties */ = { isa = PBXGroup; children = ( 9A3169C01D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist */, 9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */, 9A13BAEC1CA88A76007C6CBE /* Localizable.strings */, C2CCCD8120619C4C00F2DFC2 /* LocationController.swift */, ); name = Utilties; sourceTree = ""; }; 9A7547D11F184DC3004705EF /* ClockerHelper */ = { isa = PBXGroup; children = ( 9AFCC7FC1FD668FF00509B9C /* ClockerHelper.entitlements */, 9A7547D21F184DC3004705EF /* AppDelegate.h */, 9A7547D31F184DC3004705EF /* AppDelegate.m */, 9A7547DB1F184DC3004705EF /* Assets.xcassets */, 9A7547DD1F184DC3004705EF /* Main.storyboard */, 9A7547E01F184DC3004705EF /* Info.plist */, 9AA522C123415BDD00C9E005 /* InfoPlist.strings */, 9A7547D51F184DC3004705EF /* Supporting Files */, ); path = ClockerHelper; sourceTree = ""; }; 9A7547D51F184DC3004705EF /* Supporting Files */ = { isa = PBXGroup; children = ( 9A7547D61F184DC3004705EF /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; 9A8605E01BEC15F400A810A4 /* XIB */ = { isa = PBXGroup; children = ( 9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */, 9A13BAD81CA87F08007C6CBE /* Panel.xib */, ); name = XIB; sourceTree = ""; }; 9A8605E41BEC164C00A810A4 /* Main */ = { isa = PBXGroup; children = ( 9A8605C31BEC155B00A810A4 /* Clocker-Info.plist */, 9A8605CC1BEC155B00A810A4 /* Clocker-Prefix.pch */, 9A8605AD1BEC148400A810A4 /* main.m */, ); name = Main; sourceTree = ""; }; 9A86E2B61CE04F1600547EE7 /* Products */ = { isa = PBXGroup; children = ( 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */, 9A86E2BE1CE04F1600547EE7 /* PTHotKey.framework */, ); name = Products; sourceTree = ""; }; 9AB6F1512259CEE300A44663 /* Calendar */ = { isa = PBXGroup; children = ( 9AB6F1552259CF3900A44663 /* CalendarViewController.swift */, ); path = Calendar; sourceTree = ""; }; 9AB6F1522259CEF400A44663 /* Appearance */ = { isa = PBXGroup; children = ( 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */, ); path = Appearance; sourceTree = ""; }; 9AB6F1532259CF0300A44663 /* General */ = { isa = PBXGroup; children = ( 9AB6F1682259D26400A44663 /* Models */, 9AB6F1602259D1B000A44663 /* PreferencesDataSource.swift */, 9AB6F15F2259D1B000A44663 /* PreferencesViewController.swift */, 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */, ); path = General; sourceTree = ""; }; 9AB6F1542259CF1B00A44663 /* About */ = { isa = PBXGroup; children = ( 35C36F4A2259D971002FA5C6 /* PointingHandCursorButton.swift */, 9AB6F1572259CFFC00A44663 /* AboutViewController.swift */, ); path = About; sourceTree = ""; }; 9AB6F1592259D05100A44663 /* iVersion */ = { isa = PBXGroup; children = ( 9AB6F15C2259D08300A44663 /* iVersion.bundle */, 9AB6F15B2259D08300A44663 /* iVersion.h */, 9AB6F15A2259D08300A44663 /* iVersion.m */, ); path = iVersion; sourceTree = ""; }; 9AB6F1652259D1FC00A44663 /* Permissions */ = { isa = PBXGroup; children = ( 9AB6F1662259D23200A44663 /* PermissionsViewController.swift */, ); path = Permissions; sourceTree = ""; }; 9AB6F1682259D26400A44663 /* Models */ = { isa = PBXGroup; children = ( ); path = Models; sourceTree = ""; }; 9ACB31381EDA98EA00F3E1D3 /* Frameworks */ = { isa = PBXGroup; children = ( ); path = Frameworks; sourceTree = ""; }; 9AEF66812693A6270036142F /* Recovered References */ = { isa = PBXGroup; children = ( 3531F7F42693882300DF0111 /* Keys.plist */, ); name = "Recovered References"; sourceTree = ""; }; C2021B89219F30960036C247 /* Onboarding */ = { isa = PBXGroup; children = ( 35C36EEE22595F13002FA5C6 /* FinalOnboardingViewController.swift */, 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */, 35C36EED22595F13002FA5C6 /* OnboardingController.swift */, 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */, 35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */, 35C36EEC22595F13002FA5C6 /* OnboardingSearchController.swift */, 35C36EEB22595F13002FA5C6 /* StartAtLoginViewController.swift */, 35C36EEA22595F13002FA5C6 /* WelcomeView.xib */, 35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */, ); path = Onboarding; sourceTree = ""; }; C20839C821515C1E00C86589 /* ClockerUnitTests */ = { isa = PBXGroup; children = ( C20839C921515C1E00C86589 /* ClockerUnitTests.swift */, C20839CB21515C1F00C86589 /* Info.plist */, 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */, 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */, 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */, 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */, 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */, 35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */, 35BD9A562807580800077443 /* EventInfoTests.swift */, ); path = ClockerUnitTests; sourceTree = ""; }; C2BFE3E42049F82300825BE5 /* ClockerUITests */ = { isa = PBXGroup; children = ( 9ABF4559268FDABA002C779B /* CopyToClipboardTests.swift */, C2BFE3E52049F82300825BE5 /* ClockerUITests.m */, C2BFE3E72049F82300825BE5 /* Info.plist */, 9AA522BE23415BDD00C9E005 /* InfoPlist.strings */, C213713320B4FD920024D5A4 /* FloatingWindowTests.swift */, C213713220B4FD920024D5A4 /* ClockerUITests-Bridging-Header.h */, C2F7821A20B70E3700B6CD07 /* AboutUsTests.swift */, C264A0C420B897D800CCD875 /* PreferencesTest.swift */, C264A0C720B898D600CCD875 /* PanelTests.swift */, C2D30A89210245C6000BFAEE /* ReviewTests.swift */, C2D30A8C21025106000BFAEE /* NetworkDisconnectionTests.swift */, C22F3D7F2107778A0001D5E1 /* ShortcutTests.swift */, C2AB022321AEED590014A401 /* OnboardingTests.swift */, 9A97419A2455442100087B0D /* OnboardingSearchTests.swift */, 9A0385BF269E8891003B5E72 /* PermissionsTests.swift */, ); path = ClockerUITests; sourceTree = ""; }; DD4F7BF913C30F9F00825C6E = { isa = PBXGroup; children = ( 35B2FEE4259A2C25005DA84D /* CoreModelKit */, 35B2FED4259A2244005DA84D /* CoreLoggerKit */, 35B2FEB1259A1649005DA84D /* StartupKit */, 35C36F9B2259EC97002FA5C6 /* Events and Reminders */, 35C36F382259D80C002FA5C6 /* Overall App */, 35C36F242259D64D002FA5C6 /* Panel */, 35C36F1D22596212002FA5C6 /* Preferences */, 35C36EB522595834002FA5C6 /* Dependencies */, C2021B89219F30960036C247 /* Onboarding */, 9AB89E021CE97A4900EC8EB1 /* Media.xcassets */, 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */, 9A5B1A8D1BECDFB700A77C68 /* Clocker.entitlements */, 9A2000C61BFBCEF6002BFDE8 /* Utilties */, 9A8605E01BEC15F400A810A4 /* XIB */, 9A8605E41BEC164C00A810A4 /* Main */, 9A7547D11F184DC3004705EF /* ClockerHelper */, C2BFE3E42049F82300825BE5 /* ClockerUITests */, C20839C821515C1E00C86589 /* ClockerUnitTests */, DD4F7C0713C30F9F00825C6E /* Frameworks */, 3531F7EA26936D8800DF0111 /* Firebase.h */, 3531F7C026936C6E00DF0111 /* GoogleService-Info.plist */, DD4F7C0513C30F9F00825C6E /* Products */, 9AEF66812693A6270036142F /* Recovered References */, ); sourceTree = ""; }; DD4F7C0513C30F9F00825C6E /* Products */ = { isa = PBXGroup; children = ( DD4F7C0413C30F9F00825C6E /* Clocker.app */, 9A7547D01F184DC3004705EF /* ClockerHelper.app */, C2BFE3E32049F82300825BE5 /* ClockerUITests.xctest */, C20839C721515C1E00C86589 /* ClockerUnitTests.xctest */, ); name = Products; sourceTree = ""; }; DD4F7C0713C30F9F00825C6E /* Frameworks */ = { isa = PBXGroup; children = ( 3531F80026938D7700DF0111 /* FirebaseCore.framework */, 3531F7FD26938D7700DF0111 /* FirebaseCoreDiagnostics.framework */, 3531F80226938D7700DF0111 /* FirebaseCrashlytics.framework */, 3531F7FE26938D7700DF0111 /* FirebaseDatabase.framework */, 3531F80326938D7700DF0111 /* FirebaseInstallations.framework */, 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */, 3531F7FC26938D7600DF0111 /* GoogleUtilities.framework */, 3531F80426938D7700DF0111 /* leveldb-library.framework */, 3531F80126938D7700DF0111 /* nanopb.framework */, 3531F7FF26938D7700DF0111 /* PromisesObjC.framework */, 9ACB313F1EDA994200F3E1D3 /* ShortcutRecorder.framework */, 9ACB31381EDA98EA00F3E1D3 /* Frameworks */, 9A24A1871ED902CC0095201E /* EventKit.framework */, 9ACF469C1DCBD45200C49B51 /* Quartz.framework */, 9A6D93361CF3E82F005A8690 /* CoreImage.framework */, 9A5E6B9F1CAF71C1006E7C5C /* libicucore.tbd */, 9ABFB37F1CA6882000E10745 /* ApplicationServices.framework */, 9A20A06F1C4E804D00FB45AB /* ServiceManagement.framework */, 9A20A04A1C4DEED200FB45AB /* IOKit.framework */, 9A9E876B1C1FEDE700A7A2DF /* libsqlite3.tbd */, 9A9E87691C1FEDDB00A7A2DF /* SystemConfiguration.framework */, 9A9E87671C1FEDD300A7A2DF /* Security.framework */, 9A9E87651C1FEDC500A7A2DF /* CoreLocation.framework */, 9A9E87631C1FEDBD00A7A2DF /* CoreGraphics.framework */, 9A9E87611C1FEDB500A7A2DF /* CFNetwork.framework */, 9AC678E31C1ABAB9003B4F6B /* QuartzCore.framework */, 9A43792B1BEC231100F4E27F /* libz.tbd */, 9A4379291BEC230A00F4E27F /* libc++.tbd */, DD4F7C0B13C30F9F00825C6E /* AppKit.framework */, DD4F7C0813C30F9F00825C6E /* Cocoa.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 9A7547CF1F184DC3004705EF /* ClockerHelper */ = { isa = PBXNativeTarget; buildConfigurationList = 9A7547E11F184DC3004705EF /* Build configuration list for PBXNativeTarget "ClockerHelper" */; buildPhases = ( 9A7547CC1F184DC3004705EF /* Sources */, 9A7547CD1F184DC3004705EF /* Frameworks */, 9A7547CE1F184DC3004705EF /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ClockerHelper; productName = ClockerHelper; productReference = 9A7547D01F184DC3004705EF /* ClockerHelper.app */; productType = "com.apple.product-type.application"; }; C20839C621515C1E00C86589 /* ClockerUnitTests */ = { isa = PBXNativeTarget; buildConfigurationList = C20839D221515C1F00C86589 /* Build configuration list for PBXNativeTarget "ClockerUnitTests" */; buildPhases = ( C20839C321515C1E00C86589 /* Sources */, C20839C421515C1E00C86589 /* Frameworks */, C20839C521515C1E00C86589 /* Resources */, ); buildRules = ( ); dependencies = ( C20839CD21515C1F00C86589 /* PBXTargetDependency */, ); name = ClockerUnitTests; productName = ClockerUnitTests; productReference = C20839C721515C1E00C86589 /* ClockerUnitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; C2BFE3E22049F82300825BE5 /* ClockerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = C2BFE3EA2049F82300825BE5 /* Build configuration list for PBXNativeTarget "ClockerUITests" */; buildPhases = ( C2BFE3DF2049F82300825BE5 /* Sources */, C2BFE3E02049F82300825BE5 /* Frameworks */, C2BFE3E12049F82300825BE5 /* Resources */, ); buildRules = ( ); dependencies = ( C2BFE3E92049F82300825BE5 /* PBXTargetDependency */, ); name = ClockerUITests; productName = ClockerUITests; productReference = C2BFE3E32049F82300825BE5 /* ClockerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; DD4F7C0313C30F9F00825C6E /* Clocker */ = { isa = PBXNativeTarget; buildConfigurationList = DD4F7C2213C30F9F00825C6E /* Build configuration list for PBXNativeTarget "Clocker" */; buildPhases = ( DD4F7C0013C30F9F00825C6E /* Sources */, DD4F7C0113C30F9F00825C6E /* Frameworks */, DD4F7C0213C30F9F00825C6E /* Resources */, 9A20A0711C4E808500FB45AB /* Login Item Helper */, 9A5E75EC204CC39700119939 /* Embed Frameworks */, 3531F7F326936F5000DF0111 /* ShellScript */, ); buildRules = ( ); dependencies = ( 9A5E75E7204CC39700119939 /* PBXTargetDependency */, 9A5E75EB204CC39700119939 /* PBXTargetDependency */, ); name = Clocker; packageProductDependencies = ( 35B2FEBF259A186F005DA84D /* StartupKit */, 35B2FEDC259A2291005DA84D /* CoreLoggerKit */, 35B2FEF0259A2DB1005DA84D /* CoreModelKit */, ); productName = Popup; productReference = DD4F7C0413C30F9F00825C6E /* Clocker.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ DD4F7BFB13C30F9F00825C6E /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = ""; DefaultBuildSystemTypeForWorkspace = Latest; LastSwiftUpdateCheck = 1000; LastUpgradeCheck = 1250; TargetAttributes = { 9A7547CF1F184DC3004705EF = { CreatedOnToolsVersion = 8.3.2; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; C20839C621515C1E00C86589 = { LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; C2BFE3E22049F82300825BE5 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1020; ProvisioningStyle = Manual; TestTargetID = DD4F7C0313C30F9F00825C6E; }; DD4F7C0313C30F9F00825C6E = { LastSwiftMigration = 1020; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.ApplicationGroups.Mac = { enabled = 0; }; com.apple.Sandbox = { enabled = 1; }; com.apple.iCloud = { enabled = 0; }; }; }; }; }; buildConfigurationList = DD4F7BFE13C30F9F00825C6E /* Build configuration list for PBXProject "Clocker" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, hi, ru, "zh-Hans", de, ja, ko, fr, ca, es, "pt-PT", nl, it, "pt-BR", "zh-Hant", hr, tr, ar, pl, uk, ); mainGroup = DD4F7BF913C30F9F00825C6E; productRefGroup = DD4F7C0513C30F9F00825C6E /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = 9A86E2B61CE04F1600547EE7 /* Products */; ProjectRef = 9A86E2B51CE04F1600547EE7 /* ShortcutRecorder.xcodeproj */; }, ); projectRoot = ""; targets = ( DD4F7C0313C30F9F00825C6E /* Clocker */, 9A7547CF1F184DC3004705EF /* ClockerHelper */, C2BFE3E22049F82300825BE5 /* ClockerUITests */, C20839C621515C1E00C86589 /* ClockerUnitTests */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = ShortcutRecorder.framework; remoteRef = 9A86E2BB1CE04F1600547EE7 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 9A86E2BE1CE04F1600547EE7 /* PTHotKey.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = PTHotKey.framework; remoteRef = 9A86E2BD1CE04F1600547EE7 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 9A7547CE1F184DC3004705EF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9A7547DC1F184DC3004705EF /* Assets.xcassets in Resources */, 3531F7C326936C8300DF0111 /* GoogleService-Info.plist in Resources */, 9AA522C323415BDD00C9E005 /* InfoPlist.strings in Resources */, 9A7547DF1F184DC3004705EF /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; C20839C521515C1E00C86589 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3531F7C526936C8400DF0111 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; C2BFE3E12049F82300825BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9A8B256A232EFAD300204CAD /* Localizable.strings in Resources */, 9AA522C023415BDD00C9E005 /* InfoPlist.strings in Resources */, 3531F7C426936C8300DF0111 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; DD4F7C0213C30F9F00825C6E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9A13BAEA1CA88A76007C6CBE /* Localizable.strings in Resources */, 357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */, 9AB6F15E2259D08300A44663 /* iVersion.bundle in Resources */, 35C36F972259EBB1002FA5C6 /* AppFeedbackWindow.xib in Resources */, 9A13BAE01CA882FA007C6CBE /* InfoPlist.strings in Resources */, 35C36F912259EAF4002FA5C6 /* Preferences.storyboard in Resources */, 9AB89E031CE97A4900EC8EB1 /* Media.xcassets in Resources */, 9A13BAD61CA87F08007C6CBE /* Panel.xib in Resources */, 35C36F6B2259E0E1002FA5C6 /* FloatingWindow.xib in Resources */, 3531F7C226936C6E00DF0111 /* GoogleService-Info.plist in Resources */, 35C36F17225961DA002FA5C6 /* DateTools.bundle in Resources */, 35C36EF322595F14002FA5C6 /* WelcomeView.xib in Resources */, 3548C45A26BECF1B00AFB533 /* UpcomingEventViewItem.xib in Resources */, 35C36EF822595F14002FA5C6 /* Onboarding.storyboard in Resources */, 35C36F612259DE67002FA5C6 /* NotesPopover.xib in Resources */, 9A3169C11D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist in Resources */, 9A56DB801C1CFB73004CE6AF /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3531F7F326936F5000DF0111 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$SRCROOT/run\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9A7547CC1F184DC3004705EF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9A7547D71F184DC3004705EF /* main.m in Sources */, 9A7547D41F184DC3004705EF /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C20839C321515C1E00C86589 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 35BD9A572807580800077443 /* EventInfoTests.swift in Sources */, 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */, 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */, 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */, 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */, 35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */, 35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */, C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C2BFE3DF2049F82300825BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9ABF455B268FDABA002C779B /* CopyToClipboardTests.swift in Sources */, C2F7821B20B70E3700B6CD07 /* AboutUsTests.swift in Sources */, C264A0C820B898D600CCD875 /* PanelTests.swift in Sources */, C2BFE3E62049F82300825BE5 /* ClockerUITests.m in Sources */, 9A0385C0269E8891003B5E72 /* PermissionsTests.swift in Sources */, C2AB022421AEED590014A401 /* OnboardingTests.swift in Sources */, C264A0C520B897D800CCD875 /* PreferencesTest.swift in Sources */, C213713420B4FD920024D5A4 /* FloatingWindowTests.swift in Sources */, C22F3D802107778A0001D5E1 /* ShortcutTests.swift in Sources */, C2D30A8A210245C6000BFAEE /* ReviewTests.swift in Sources */, 9A97419B2455442100087B0D /* OnboardingSearchTests.swift in Sources */, C2D30A8D21025106000BFAEE /* NetworkDisconnectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; DD4F7C0013C30F9F00825C6E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9AB6F15D2259D08300A44663 /* iVersion.m in Sources */, 35C36EF622595F14002FA5C6 /* OnboardingController.swift in Sources */, 9AB6F1622259D1B000A44663 /* PreferencesDataSource.swift in Sources */, 3508CC9A259A0001000E3530 /* StatusItemView.swift in Sources */, 35C36F672259DF4C002FA5C6 /* ReviewController.swift in Sources */, 35C36F472259D892002FA5C6 /* Reach.swift in Sources */, 35C36EF222595F14002FA5C6 /* OnboardingWelcomeViewController.swift in Sources */, 3548C45F26BEEFB400AFB533 /* UpcomingEventsDataSource.swift in Sources */, 35C36F732259E1AA002FA5C6 /* FloatingWindowController.swift in Sources */, 35C36F6F2259E185002FA5C6 /* CustomSliderCell.swift in Sources */, 35C36EF122595F14002FA5C6 /* OnboardingPermissionsViewController.swift in Sources */, 35C36F13225961DA002FA5C6 /* Date+TimeAgo.swift in Sources */, 35C36F412259D892002FA5C6 /* Themer.swift in Sources */, 35C36F452259D892002FA5C6 /* Strings.swift in Sources */, 35C36EF722595F14002FA5C6 /* FinalOnboardingViewController.swift in Sources */, 35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */, 357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */, 35C36F782259E1D0002FA5C6 /* Foundation + Additions.swift in Sources */, 35C36F16225961DA002FA5C6 /* Date+Inits.swift in Sources */, 35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */, 35C36F5D2259DD96002FA5C6 /* TimezoneDataOperations.swift in Sources */, 3548C45D26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift in Sources */, 3508CC942599FFEC000E3530 /* MenubarTitleProvider.swift in Sources */, 35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */, 35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */, 35C36F622259DE67002FA5C6 /* NotesPopover.swift in Sources */, 35C36F692259DF55002FA5C6 /* TextViewWithPlaceholder.swift in Sources */, 35C36F982259EBB1002FA5C6 /* AppFeedbackWindowController.swift in Sources */, 35C36F792259E1D0002FA5C6 /* String + Additions.swift in Sources */, 35C36F422259D892002FA5C6 /* Timer.swift in Sources */, 35C36F772259E1D0002FA5C6 /* AppKit + Additions.swift in Sources */, 35C36EF422595F14002FA5C6 /* StartAtLoginViewController.swift in Sources */, 35C36F702259E185002FA5C6 /* BackgroundPanelView.swift in Sources */, 9AB6F1582259CFFC00A44663 /* AboutViewController.swift in Sources */, 35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */, 35C36F1B225961DA002FA5C6 /* Date+Format.swift in Sources */, 35C36F372259D7C3002FA5C6 /* AddTableViewCell.swift in Sources */, 35C36F12225961DA002FA5C6 /* Date+Components.swift in Sources */, 35C36F2C2259D6FA002FA5C6 /* PanelController.swift in Sources */, 35C36F10225961DA002FA5C6 /* Constants.swift in Sources */, 35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */, 35DFBCEF26A8468900D6648B /* ConfigExport.swift in Sources */, 35C36F19225961DA002FA5C6 /* Enums.swift in Sources */, 35C36F5A2259DD8A002FA5C6 /* Panelr.swift in Sources */, 35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */, 35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */, 35C36F582259DD8A002FA5C6 /* PanelTableView.swift in Sources */, 35C36F18225961DA002FA5C6 /* Date+Comparators.swift in Sources */, 353B5BC52698B78A0023858D /* UpcomingEventStatusItemView.swift in Sources */, 35C36FA02259ED6D002FA5C6 /* CalendarHandler.swift in Sources */, 35C36F1A225961DA002FA5C6 /* Date+Manipulations.swift in Sources */, 35C36F572259DD8A002FA5C6 /* TimezoneDataSource.swift in Sources */, 35C36F462259D892002FA5C6 /* DataStore.swift in Sources */, 9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */, 3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */, 35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */, C2CCCD8220619C4C00F2DFC2 /* LocationController.swift in Sources */, 35C36F4B2259D971002FA5C6 /* PointingHandCursorButton.swift in Sources */, 9AB6F1562259CF3900A44663 /* CalendarViewController.swift in Sources */, 3595FAD0227F88BC0044A12A /* UserDefaults + KVOExtensions.swift in Sources */, 9AB6F1612259D1B000A44663 /* PreferencesViewController.swift in Sources */, 35C36F2022596253002FA5C6 /* OneWindowController.swift in Sources */, 35C36F0E225961DA002FA5C6 /* Date+Bundle.swift in Sources */, 9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */, 3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */, 9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */, 3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */, 35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */, 35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */, 35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */, 35C36F662259DF4C002FA5C6 /* UpcomingEventView.swift in Sources */, 35C36EF522595F14002FA5C6 /* OnboardingSearchController.swift in Sources */, 35C36F592259DD8A002FA5C6 /* TimezoneCellView.swift in Sources */, 35C36F15225961DA002FA5C6 /* TimeChunk.swift in Sources */, 35C36F482259D892002FA5C6 /* NetworkManager.swift in Sources */, 9A8605AE1BEC148400A810A4 /* main.m in Sources */, 3579765E2680208C009DDA6E /* ParentPanelController+ModernSlider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 9A5E75E7204CC39700119939 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ShortcutRecorder.framework; targetProxy = 9A5E75E6204CC39700119939 /* PBXContainerItemProxy */; }; 9A5E75EB204CC39700119939 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = PTHotKey.framework; targetProxy = 9A5E75EA204CC39700119939 /* PBXContainerItemProxy */; }; C20839CD21515C1F00C86589 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DD4F7C0313C30F9F00825C6E /* Clocker */; targetProxy = C20839CC21515C1F00C86589 /* PBXContainerItemProxy */; }; C2BFE3E92049F82300825BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DD4F7C0313C30F9F00825C6E /* Clocker */; targetProxy = C2BFE3E82049F82300825BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 9A13BAD81CA87F08007C6CBE /* Panel.xib */ = { isa = PBXVariantGroup; children = ( 9A13BAD71CA87F08007C6CBE /* en */, ); name = Panel.xib; path = Clocker; sourceTree = ""; }; 9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 9A13BAE11CA882FA007C6CBE /* en */, 352AF497232E07B400D96FA7 /* hi */, 9A4DC4DF2337F2EB00F03FA4 /* ru */, 9A4DC4E22337F31200F03FA4 /* zh-Hans */, 9A4DC4E42337F5BC00F03FA4 /* de */, 9A4DC4E62337F5C800F03FA4 /* ja */, 9A4DC4E82337F5D600F03FA4 /* ko */, 9AA522CC234169E400C9E005 /* fr */, 9AA522CE234169F200C9E005 /* ca */, 9AA522D023416A0E00C9E005 /* es */, 9AA522D323416A1B00C9E005 /* pt-PT */, 9AA522D423416A6000C9E005 /* nl */, 9AA522D623416E6000C9E005 /* it */, 3552066227AF63DC000EF08F /* ar */, 3552066427AF6488000EF08F /* pl */, ); name = InfoPlist.strings; path = Clocker; sourceTree = ""; }; 9A13BAEC1CA88A76007C6CBE /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 9A13BAEB1CA88A76007C6CBE /* en */, 352AF499232E07B400D96FA7 /* hi */, 9A4DC4E12337F2EB00F03FA4 /* ru */, 9A4DC4E32337F31200F03FA4 /* zh-Hans */, 9A4DC4E52337F5BC00F03FA4 /* de */, 9A4DC4E72337F5C800F03FA4 /* ja */, 9A4DC4E92337F5D600F03FA4 /* ko */, 9AA522CD234169E400C9E005 /* fr */, 9AA522CF234169F200C9E005 /* ca */, 9AA522D123416A0E00C9E005 /* es */, 9AA522D523416A6000C9E005 /* nl */, 9AA522D723416E6000C9E005 /* it */, 3569A44E25441F320087E254 /* pt-BR */, 35A6A4B925C5DEF300356073 /* zh-Hant */, 353B5BC72698D4BB0023858D /* hr */, 3552066027AF6277000EF08F /* tr */, 3552066327AF63DC000EF08F /* ar */, 3552066527AF6489000EF08F /* pl */, 3567F7DB288DC6520049C7A9 /* uk */, ); name = Localizable.strings; path = Clocker; sourceTree = ""; }; 9A7547DD1F184DC3004705EF /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 9A7547DE1F184DC3004705EF /* Base */, 9AA522C423415BDD00C9E005 /* de */, 9AA522C823415BF600C9E005 /* ru */, 9AA522CB23415C4F00C9E005 /* zh-Hans */, ); name = Main.storyboard; sourceTree = ""; }; 9AA522BE23415BDD00C9E005 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 9AA522BF23415BDD00C9E005 /* de */, 9AA522C723415BF600C9E005 /* ru */, 9AA522C923415C4F00C9E005 /* zh-Hans */, ); name = InfoPlist.strings; sourceTree = ""; }; 9AA522C123415BDD00C9E005 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 9AA522C223415BDD00C9E005 /* de */, 9AA522C623415BF600C9E005 /* ru */, 9AA522CA23415C4F00C9E005 /* zh-Hans */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 9A5B1A8B1BECDB5B00A77C68 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = AJS5SNW8EY; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; ONLY_ACTIVE_ARCH = YES; PROVISIONING_PROFILE = ""; SDKROOT = macosx; SWIFT_VERSION = 4.2; }; name = Distribution; }; 9A5B1A8C1BECDB5B00A77C68 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color"; CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_ASSIGN_ENUM = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_INTERFACE_IVARS = YES; CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CODE_SIGN_ENTITLEMENTS = Clocker/Clocker.entitlements; CODE_SIGN_IDENTITY = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 103; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clocker/Clocker-Prefix.pch"; "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ""; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = NO; INFOPLIST_FILE = "Clocker/Clocker-Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.Clocker; PRODUCT_NAME = Clocker; PROVISIONING_PROFILE = "71701bee-fee7-4927-b6e9-20d9a78ef29c"; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Clocker-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; WRAPPER_EXTENSION = app; }; name = Distribution; }; 9A7547E21F184DC3004705EF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CODE_SIGN_ENTITLEMENTS = ClockerHelper/ClockerHelper.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerHelper; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Debug; }; 9A7547E31F184DC3004705EF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CODE_SIGN_ENTITLEMENTS = ClockerHelper/ClockerHelper.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerHelper; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Release; }; 9A7547E41F184DC3004705EF /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CODE_SIGN_ENTITLEMENTS = ClockerHelper/ClockerHelper.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerHelper; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Distribution; }; C20839CE21515C1F00C86589 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)", "$(inherited)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUnitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clocker.app/Contents/MacOS/Clocker"; }; name = Debug; }; C20839CF21515C1F00C86589 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; FRAMEWORK_SEARCH_PATHS = ( "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)", "$(inherited)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUnitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clocker.app/Contents/MacOS/Clocker"; }; name = Release; }; C20839D021515C1F00C86589 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; FRAMEWORK_SEARCH_PATHS = ( "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)", "$(inherited)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUnitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clocker.app/Contents/MacOS/Clocker"; }; name = Distribution; }; C2BFE3EB2049F82300825BE5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "ClockerUITests/ClockerUITests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = Clocker; }; name = Debug; }; C2BFE3EC2049F82300825BE5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "ClockerUITests/ClockerUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = Clocker; }; name = Release; }; C2BFE3ED2049F82300825BE5 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = ClockerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.ClockerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "ClockerUITests/ClockerUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = Clocker; }; name = Distribution; }; DD4F7C2013C30F9F00825C6E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = AJS5SNW8EY; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; ONLY_ACTIVE_ARCH = YES; PROVISIONING_PROFILE = ""; SDKROOT = macosx; SWIFT_VERSION = 4.2; }; name = Debug; }; DD4F7C2113C30F9F00825C6E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = AJS5SNW8EY; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; ONLY_ACTIVE_ARCH = YES; PROVISIONING_PROFILE = ""; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.2; }; name = Release; }; DD4F7C2313C30F9F00825C6E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color"; CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_ASSIGN_ENUM = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_INTERFACE_IVARS = YES; CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CODE_SIGN_ENTITLEMENTS = Clocker/Clocker.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 103; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clocker/Clocker-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = DEBUG; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; INFOPLIST_FILE = "Clocker/Clocker-Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.Clocker; PRODUCT_NAME = Clocker; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Clocker-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; "TEST_HOST[sdk=*]" = Clocker; VERSIONING_SYSTEM = "apple-generic"; WRAPPER_EXTENSION = app; }; name = Debug; }; DD4F7C2413C30F9F00825C6E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "Accent Color"; CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_ASSIGN_ENUM = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_INTERFACE_IVARS = YES; CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CODE_SIGN_ENTITLEMENTS = Clocker/Clocker.entitlements; CODE_SIGN_IDENTITY = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 103; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/Frameworks", "$(PROJECT_DIR)/Frameworks/Firebase", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clocker/Clocker-Prefix.pch"; "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = RELEASE; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_SHADOW = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = NO; INFOPLIST_FILE = "Clocker/Clocker-Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; "OTHER_SWIFT_FLAGS[arch=*]" = "-D RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.Clocker; PRODUCT_NAME = Clocker; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Clocker-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9A7547E11F184DC3004705EF /* Build configuration list for PBXNativeTarget "ClockerHelper" */ = { isa = XCConfigurationList; buildConfigurations = ( 9A7547E21F184DC3004705EF /* Debug */, 9A7547E31F184DC3004705EF /* Release */, 9A7547E41F184DC3004705EF /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; C20839D221515C1F00C86589 /* Build configuration list for PBXNativeTarget "ClockerUnitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( C20839CE21515C1F00C86589 /* Debug */, C20839CF21515C1F00C86589 /* Release */, C20839D021515C1F00C86589 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; C2BFE3EA2049F82300825BE5 /* Build configuration list for PBXNativeTarget "ClockerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( C2BFE3EB2049F82300825BE5 /* Debug */, C2BFE3EC2049F82300825BE5 /* Release */, C2BFE3ED2049F82300825BE5 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; DD4F7BFE13C30F9F00825C6E /* Build configuration list for PBXProject "Clocker" */ = { isa = XCConfigurationList; buildConfigurations = ( DD4F7C2013C30F9F00825C6E /* Debug */, DD4F7C2113C30F9F00825C6E /* Release */, 9A5B1A8B1BECDB5B00A77C68 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; DD4F7C2213C30F9F00825C6E /* Build configuration list for PBXNativeTarget "Clocker" */ = { isa = XCConfigurationList; buildConfigurations = ( DD4F7C2313C30F9F00825C6E /* Debug */, DD4F7C2413C30F9F00825C6E /* Release */, 9A5B1A8C1BECDB5B00A77C68 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ 35B2FEBF259A186F005DA84D /* StartupKit */ = { isa = XCSwiftPackageProductDependency; productName = StartupKit; }; 35B2FEDC259A2291005DA84D /* CoreLoggerKit */ = { isa = XCSwiftPackageProductDependency; productName = CoreLoggerKit; }; 35B2FEF0259A2DB1005DA84D /* CoreModelKit */ = { isa = XCSwiftPackageProductDependency; productName = CoreModelKit; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = DD4F7BFB13C30F9F00825C6E /* Project object */; } ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcshareddata/Clocker.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" : 0, "F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" : 0 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "FE3C46F0-59C9-4F38-8281-63F46BD16224", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" : "Clocker\/", "F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" : "Clocker\/Clocker\/pop\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "Clocker", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Clocker.xcodeproj", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Abhishaker17\/Clocker.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/facebook\/pop.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" } ] } ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ BuildSystemType Latest ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcuserdata/abhishek_banthia.xcuserdatad/WorkspaceSettings.xcsettings ================================================ BuildLocationStyle UseAppPreferences CustomBuildLocationType RelativeToDerivedData DerivedDataLocationStyle Default EnabledFullIndexStoreVisibility IssueFilterStyle ShowActiveSchemeOnly LiveSourceIssuesEnabled ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcuserdata/abhishekbanthia.xcuserdatad/IDEFindNavigatorScopes.plist ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/project.xcworkspace/xcuserdata/ban.xcuserdatad/IDEFindNavigatorScopes.plist ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcshareddata/IDETemplateMacros.plist ================================================ FILEHEADER Copyright © 2015 Abhishek Banthia ================================================ FILE: Clocker/Clocker.xcodeproj/xcshareddata/xcschemes/Clocker.xcscheme ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhi.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Clocker.xcscheme_^#shared#^_ orderHint 0 ClockerHelper.xcscheme_^#shared#^_ orderHint 5 Tests.xcscheme_^#shared#^_ orderHint 3 ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishek_banthia.xcuserdatad/xcschemes/Clocker.xcscheme ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishek_banthia.xcuserdatad/xcschemes/ClockerHelper.xcscheme ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishek_banthia.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Clocker.xcscheme orderHint 0 Clocker.xcscheme_^#shared#^_ orderHint 4 ClockerHelper.xcscheme orderHint 2 ClockerUnitTest.xcscheme orderHint 6 SuppressBuildableAutocreation 9A7547CF1F184DC3004705EF primary 9AC5EE2C1EDA17E400B4CE7B primary DD4F7C0313C30F9F00825C6E primary ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishekbanthia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishekbanthia.xcuserdatad/xcschemes/ClockerHelper.xcscheme ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/abhishekbanthia.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Clocker.xcscheme_^#shared#^_ orderHint 0 ClockerHelper.xcscheme orderHint 1 Tests.xcscheme_^#shared#^_ orderHint 2 SuppressBuildableAutocreation 9A7547951F1834D9004705EF primary 9A7547AA1F1834D9004705EF primary 9A7547CF1F184DC3004705EF primary 9AC5EE2C1EDA17E400B4CE7B primary DD4F7C0313C30F9F00825C6E primary ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/ban.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: Clocker/Clocker.xcodeproj/xcuserdata/ban.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Clocker.xcscheme_^#shared#^_ orderHint 0 ClockerHelper.xcscheme_^#shared#^_ orderHint 4 Tests.xcscheme_^#shared#^_ orderHint 2 SuppressBuildableAutocreation DD4F7C0313C30F9F00825C6E primary ================================================ FILE: Clocker/ClockerHelper/AppDelegate.h ================================================ // Copyright © 2015 Abhishek Banthia #import @interface AppDelegate : NSObject @end ================================================ FILE: Clocker/ClockerHelper/AppDelegate.m ================================================ // Copyright © 2015 Abhishek Banthia #import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents]; pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)]; NSString *path = [NSString pathWithComponents:pathComponents]; [[NSWorkspace sharedWorkspace] launchApplication:path]; [NSApp terminate:nil]; } @end ================================================ FILE: Clocker/ClockerHelper/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/ClockerHelper/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Clocker/ClockerHelper/ClockerHelper.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: Clocker/ClockerHelper/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSBackgroundOnly LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSMainStoryboardFile Main NSPrincipalClass NSApplication ================================================ FILE: Clocker/ClockerHelper/de.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerHelper"; ================================================ FILE: Clocker/ClockerHelper/de.lproj/Main.strings ================================================ /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "ClockerHelper"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit ClockerHelper"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit ClockerHelper"; /* Class = "NSMenuItem"; title = "About ClockerHelper"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About ClockerHelper"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Edit"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "ClockerHelper Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "ClockerHelper Help"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Hide ClockerHelper"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide ClockerHelper"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenu"; title = "ClockerHelper"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; ================================================ FILE: Clocker/ClockerHelper/main.m ================================================ // Copyright © 2015 Abhishek Banthia #import int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); } ================================================ FILE: Clocker/ClockerHelper/ru.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerHelper"; ================================================ FILE: Clocker/ClockerHelper/ru.lproj/Main.strings ================================================ /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Найти"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "Customize Toolbar…"; /* Class = "NSMenuItem"; title = "ClockerHelper"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Найти"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit ClockerHelper"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit ClockerHelper"; /* Class = "NSMenuItem"; title = "About ClockerHelper"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About ClockerHelper"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "Изменить"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Использовать значение по умолчанию"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Окно"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Шрифт"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Показать цвета"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Сохранить как…"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Не использовать"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Текст"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Отменить"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Закрыть"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Текст"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "ClockerHelper Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "ClockerHelper Help"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Жирный"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Формат"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Шрифт"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Использовать значение по умолчанию"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Вставить"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Открыть…"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Не использовать"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Показать всё"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tПо умолчанию"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Hide ClockerHelper"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide ClockerHelper"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Удалить"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Сохранить…"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Показать шрифты"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Масштабирование"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tСправа налево"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Выбрать всё"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Окно"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenu"; title = "ClockerHelper"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Вырезать"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "По центру"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Курсив"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Изменить"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Справка"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Копировать"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Использовать всё"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Найти…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tПо умолчанию"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; ================================================ FILE: Clocker/ClockerHelper/zh-Hans.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerHelper"; ================================================ FILE: Clocker/ClockerHelper/zh-Hans.lproj/Main.strings ================================================ /* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */ "1b7-l0-nxx.title" = "Find"; /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw"; */ "1tx-W0-xDw.title" = "Lower"; /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ "1UK-8n-QPP.title" = "自定义工具栏…"; /* Class = "NSMenuItem"; title = "ClockerHelper"; ObjectID = "1Xt-HY-uBw"; */ "1Xt-HY-uBw.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG"; */ "2h7-ER-AoG.title" = "Raise"; /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ "2oI-Rn-ZJC.title" = "Transformations"; /* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ "3IN-sU-3Bg.title" = "Spelling"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK"; */ "3Om-Ey-2VK.title" = "Use Default"; /* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */ "3rS-ZA-NoH.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */ "4EN-yA-p0u.title" = "Find"; /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ "4J7-dP-txa.title" = "Enter Full Screen"; /* Class = "NSMenuItem"; title = "Quit ClockerHelper"; ObjectID = "4sb-4s-VLi"; */ "4sb-4s-VLi.title" = "Quit ClockerHelper"; /* Class = "NSMenuItem"; title = "About ClockerHelper"; ObjectID = "5kV-Vb-QxS"; */ "5kV-Vb-QxS.title" = "About ClockerHelper"; /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ "5QF-Oa-p0T.title" = "编辑"; /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD"; */ "5Vv-lz-BsD.title" = "Copy Style"; /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ "6dh-zS-Vam.title" = "Redo"; /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd"; */ "8mr-sm-Yjd.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ "9ic-FL-obx.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ "9yt-4B-nSM.title" = "Smart Copy/Paste"; /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj"; */ "46P-cB-AYj.title" = "Tighten"; /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ "78Y-hA-62v.title" = "Correct Spelling Automatically"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3"; */ "agt-UL-0e3.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS"; */ "aTl-1u-JFS.title" = "Print…"; /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ "aUF-d1-5bR.title" = "Window"; /* Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq"; */ "aXa-aM-Jaq.title" = "Font"; /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ "AYu-sK-qS6.title" = "Main Menu"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93"; */ "BgM-ve-c93.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk"; */ "bgn-CT-cEk.title" = "Show Colors"; /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ "bib-Uj-vzu.title" = "File"; /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ "BOF-NM-1cW.title" = "Preferences…"; /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */ "buJ-ug-pKt.title" = "Use Selection for Find"; /* Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A"; */ "Bw7-FT-i3A.title" = "Save As…"; /* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ "c8a-y6-VQd.title" = "Transformations"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR"; */ "cDB-IK-hbR.title" = "Use None"; /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA"; */ "cqv-fj-IhA.title" = "Selection"; /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ "cwL-P1-jid.title" = "Smart Links"; /* Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H"; */ "d9c-me-L2H.title" = "Text"; /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ "d9M-CD-aMd.title" = "Make Lower Case"; /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ "dMs-cI-mzQ.title" = "File"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ "dRJ-4n-Yzg.title" = "Undo"; /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ "Dv1-io-Yv7.title" = "Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG"; */ "DVo-aG-piG.title" = "Close"; /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ "F2S-fz-NVQ.title" = "Help"; /* Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk"; */ "Fal-I4-PZk.title" = "Text"; /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ "FeM-D8-WVr.title" = "Substitutions"; /* Class = "NSMenuItem"; title = "ClockerHelper Help"; ObjectID = "FKE-Sm-Kum"; */ "FKE-Sm-Kum.title" = "ClockerHelper Help"; /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27"; */ "GB9-OM-e27.title" = "Bold"; /* Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr"; */ "GEO-Iw-cKr.title" = "Format"; /* Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB"; */ "Gi5-1S-RQB.title" = "Font"; /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY"; */ "GUa-eO-cwY.title" = "Use Default"; /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ "gVA-U4-sdL.title" = "Paste"; /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J"; */ "H1b-Si-o9J.title" = "Writing Direction"; /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ "H8h-7b-M4v.title" = "View"; /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ "HFo-cy-zxI.title" = "Show Spelling and Grammar"; /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ "HFQ-gK-NFA.title" = "Text Replacement"; /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ "hQb-2v-fYv.title" = "Smart Quotes"; /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ "HyV-fh-RgO.title" = "View"; /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ "hz2-CU-CR7.title" = "Check Document Now"; /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ "hz9-B4-Xy5.title" = "Services"; /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l"; */ "I0S-gh-46l.title" = "Subscript"; /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST"; */ "i1d-Er-qST.title" = "Smaller"; /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9"; */ "IAo-SY-fd9.title" = "Open…"; /* Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga"; */ "ijk-EB-dga.title" = "Baseline"; /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23"; */ "J5U-5w-g23.title" = "Justify"; /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV"; */ "J7y-lM-qPV.title" = "Use None"; /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2"; */ "jBQ-r6-VK2.title" = "Kern"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx"; */ "jFq-tB-4Kx.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS"; */ "jxT-CU-nIS.title" = "Format"; /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H"; */ "KaW-ft-85H.title" = "Revert to Saved"; /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ "Kd2-mp-pUS.title" = "Show All"; /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ "kIP-vf-haE.title" = "Show Sidebar"; /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU"; */ "Lbh-J2-qVU.title" = "\tLeft to Right"; /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ "LE2-aR-0XJ.title" = "Bring All to Front"; /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI"; */ "LVM-kO-fVI.title" = "Paste Ruler"; /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ "mK6-2p-4JG.title" = "Check Grammar With Spelling"; /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5"; */ "MkV-Pr-PK5.title" = "Copy Ruler"; /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ "NMo-om-nkz.title" = "Services"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q"; */ "Nop-cj-93Q.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq"; */ "o6e-r0-MWq.title" = "Ligatures"; /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso"; */ "OaQ-X3-Vso.title" = "Baseline"; /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ"; */ "oas-Oc-fiZ.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1"; */ "ogc-rX-tC1.title" = "Loosen"; /* Class = "NSMenuItem"; title = "Hide ClockerHelper"; ObjectID = "Olw-nP-bQN"; */ "Olw-nP-bQN.title" = "Hide ClockerHelper"; /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */ "OwM-mh-QMV.title" = "Find Previous"; /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ "OY7-WF-poV.title" = "Minimize"; /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */ "Oyz-dy-DGm.title" = "Stop Speaking"; /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ "pa3-QI-u2k.title" = "Delete"; /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL"; */ "Ptp-SP-VEL.title" = "Bigger"; /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV"; */ "pxx-59-PXV.title" = "Save…"; /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq"; */ "Q5e-8K-NDq.title" = "Show Fonts"; /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */ "q09-fT-Sye.title" = "Find Next"; /* Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK"; */ "qIS-W8-SiK.title" = "Page Setup…"; /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ "R4o-n2-Eq4.title" = "Zoom"; /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC"; */ "RB4-Sm-HuC.title" = "\tRight to Left"; /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ "rbD-Rh-wIN.title" = "Check Spelling While Typing"; /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ "rgM-f4-ycn.title" = "Smart Dashes"; /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF"; */ "Rqc-34-cIF.title" = "Superscript"; /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ "Ruw-6m-B2m.title" = "Select All"; /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */ "S0p-oC-mLd.title" = "Jump to Selection"; /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ "snW-S8-Cw5.title" = "Show Toolbar"; /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ "Td7-aD-5lo.title" = "Window"; /* Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM"; */ "tlD-Oa-oAM.title" = "Kern"; /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ "tRr-pd-1PS.title" = "Data Detectors"; /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws"; */ "tXI-mr-wws.title" = "Open Recent"; /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ "UEZ-Bs-lqG.title" = "Capitalize"; /* Class = "NSMenu"; title = "ClockerHelper"; ObjectID = "uQy-DD-JDr"; */ "uQy-DD-JDr.title" = "ClockerHelper"; /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ "uRl-iY-unG.title" = "Cut"; /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ "Vdr-fp-XzO.title" = "Hide Others"; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb"; */ "VIY-Ag-zcb.title" = "Center"; /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq"; */ "Vjx-xi-njq.title" = "Italic"; /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH"; */ "vKC-jM-MkH.title" = "Paste Style"; /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL"; */ "vLm-3I-IUL.title" = "Show Ruler"; /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ "vmV-6d-7jI.title" = "Make Upper Case"; /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42"; */ "vNY-rz-j42.title" = "Clear Menu"; /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9"; */ "w0m-vy-SC9.title" = "Ligatures"; /* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ "W48-6f-4Dl.title" = "Edit"; /* Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl"; */ "Was-JA-tGl.title" = "New"; /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4"; */ "wb2-vD-lq4.title" = "Align Right"; /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ "WeT-3V-zwk.title" = "Paste and Match Style"; /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ "wpr-3q-Mcd.title" = "Help"; /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S"; */ "WRG-CD-K1S.title" = "Underline"; /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ "x3v-GG-iWU.title" = "Copy"; /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t"; */ "xQD-1f-W4t.title" = "Use All"; /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */ "xrE-MZ-jX0.title" = "Speech"; /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */ "Xz5-n4-O0W.title" = "Find…"; /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz"; */ "YEy-JH-Tfz.title" = "Find and Replace…"; /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR"; */ "YGs-j5-SAR.title" = "\tDefault"; /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */ "Ynk-f8-cLZ.title" = "Start Speaking"; /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ "z6F-FW-3nz.title" = "Show Substitutions"; /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1"; */ "ZM1-6Q-yy1.title" = "Align Left"; /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH"; */ "ZvO-Gk-QUH.title" = "Paragraph"; ================================================ FILE: Clocker/ClockerUITests/AboutUsTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class AboutUsTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(CLUITestingLaunchArgument) // To configure Firebase in AppDelegate app.launch() if app.tables["FloatingTableView"].exists { app.tapMenubarIcon() app.buttons["FloatingPin"].click() } } private func tapAboutTab() { let aboutTab = app.toolbars.buttons.element(boundBy: 4) aboutTab.click() } func testMockingFeedback() { app.tapMenubarIcon() app.buttons["Preferences"].click() tapAboutTab() app.checkBoxes["ClockerPrivateFeedback"].click() app.buttons["Send Feedback"].click() XCTAssertFalse(app.progressIndicators["ProgressIndicator"].exists) sleep(2) // Wait for Toast to disappear // Close window app.windows["Clocker Feedback"].buttons["Cancel"].click() } func testSendingDataToFirebase() { app.tapMenubarIcon() app.buttons["Preferences"].click() tapAboutTab() app.checkBoxes["ClockerPrivateFeedback"].click() let textView = app.textViews["FeedbackTextView"] textView.click() textView.typeText("This feedback was generated by UI Tests") let nameField = app.textFields["NameField"] nameField.click() nameField.typeText("Random Name") let emailField = app.textFields["EmailField"] emailField.click() emailField.typeText("randomemail@uitests.com") app.buttons["Send Feedback"].click() inverseWaiterFor(element: app.progressIndicators["ProgressIndicator"]) XCTAssertTrue(app.sheets.staticTexts["Thank you for helping make Clocker even better!"].exists) XCTAssertTrue(app.sheets.staticTexts["We owe you a candy. 😇"].exists) app.windows["Clocker Feedback"].sheets.buttons["Close"].click() } } ================================================ FILE: Clocker/ClockerUITests/ClockerUITests-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // ================================================ FILE: Clocker/ClockerUITests/ClockerUITests.m ================================================ // Copyright © 2015 Abhishek Banthia #import @interface ClockerUITests : XCTestCase @property (strong) XCUIApplication *app; @end @implementation ClockerUITests - (void)setUp { [super setUp]; self.continueAfterFailure = YES; self.app = [[XCUIApplication alloc] init]; [self.app launch]; if (self.app.tables[@"FloatingTableView"].exists) { XCUIElement *floatingPinButton = self.app.buttons[@"FloatingPin"]; [floatingPinButton click]; } } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } /* - (void)testChangingLabelFromPopover { XCUIElement *menuElement = [[self.app menuBars] elementBoundByIndex:1]; [menuElement click]; XCUIElement *cell = [self.app.tables[@"mainTableView"] cells].firstMatch; XCUIElement *originalField = cell.staticTexts[@"CustomNameLabelForCell"]; NSString *originalFieldValue = originalField.value; [cell hover]; NSLog(@"%@", cell.buttons.count); XCUIElement *extraOptionButton = cell.buttons[@"extraOptionButton"]; [extraOptionButton click]; XCUIElement *textField = self.app.textFields[@"CustomLabel"]; [textField typeText:@"My Precious"]; sleep(2); XCUIElement *verifyCell = self.app.tables[@"mainTableView"].cells.firstMatch; XCUIElement *newField = verifyCell.staticTexts[@"CustomNameLabelForCell"]; NSString *newFieldValue = (NSString *)newField.value; XCTAssertTrue([newFieldValue isEqualToString:@"My Precious"]); [self reset:textField withText:originalFieldValue]; sleep(2); } */ - (void)testSettingAFavourite { XCUIElement *menuElement = [[self.app statusItems] firstMatch]; [menuElement click]; [self.app/*@START_MENU_TOKEN@*/.tables[@"mainTableView"]/*[[".dialogs",".scrollViews.tables[@\"mainTableView\"]",".tables[@\"mainTableView\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/ typeKey:@"," modifierFlags:XCUIKeyModifierCommand]; XCUIElement *clockerWindow = self.app.windows[@"Clocker"]; if (clockerWindow.tables.count == 0) { XCTFail("We don't have any timezones added"); return; } NSInteger rowQueryCount = [[clockerWindow.tables[@"TimezoneTableView"] tableRows] count]; if (rowQueryCount == 0) { XCTFail("We don't have any timezones added"); return; } XCUIElement *currentElement = [[clockerWindow.tables[@"TimezoneTableView"] tableRows] elementBoundByIndex:0]; XCUIElement *favoriteCheckbox = [currentElement.checkBoxes elementBoundByIndex:0]; [favoriteCheckbox click]; sleep(2); } - (void)testChangingTo12Hour { XCUIElement *menuElement = [[self.app statusItems] firstMatch]; [menuElement click]; [self.app/*@START_MENU_TOKEN@*/.tables[@"mainTableView"]/*[[".dialogs",".scrollViews.tables[@\"mainTableView\"]",".tables[@\"mainTableView\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/ typeKey:@"," modifierFlags:XCUIKeyModifierCommand]; XCUIElement *appearance = [[self.app.toolbars buttons] elementBoundByIndex: 1]; [appearance click]; XCUIElement *timeFormat = [self.app.popUpButtons[@"TimeFormatPopover"] firstMatch]; [timeFormat click]; // Open Time Format Popover XCUIElementQuery *const query = [[[timeFormat childrenMatchingType:XCUIElementTypeMenu] firstMatch] childrenMatchingType:0]; [[query elementBoundByIndex:0] click]; // 0 is 12-Hour XCUIElementQuery *mainTableView = [[self.app.tables[@"mainTableView"] cells] staticTexts]; NSPredicate *timeCells = [NSPredicate predicateWithFormat:@"identifier like 'ActualTime'"]; XCUIElementQuery *elements = [mainTableView matchingPredicate:timeCells]; for (NSInteger i = 0; i < elements.count; i++) { XCUIElement *currentElement = [elements elementBoundByIndex:i]; NSString *currentTime = (NSString *)currentElement.value; XCTAssertTrue([currentTime containsString:@"AM"] || [currentTime containsString:@"PM"]); } } - (void)testChangingTo24Hour { XCUIElement *menuElement = [[self.app statusItems] firstMatch]; [menuElement click]; [self.app/*@START_MENU_TOKEN@*/.tables[@"mainTableView"]/*[[".dialogs",".scrollViews.tables[@\"mainTableView\"]",".tables[@\"mainTableView\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/ typeKey:@"," modifierFlags:XCUIKeyModifierCommand]; XCUIElement *appearance = [[self.app.toolbars buttons] elementBoundByIndex: 1]; [appearance click]; XCUIElement *timeFormat = self.app.popUpButtons[@"TimeFormatPopover"]; [timeFormat click]; XCUIElementQuery *const query = [[[timeFormat childrenMatchingType:XCUIElementTypeMenu] firstMatch] childrenMatchingType:0]; [[query elementBoundByIndex:1] click]; // 1 is 24-Hour XCUIElementQuery *mainTableView = [[self.app.tables[@"mainTableView"] cells] staticTexts]; NSPredicate *timeCells = [NSPredicate predicateWithFormat:@"identifier like 'ActualTime'"]; XCUIElementQuery *elements = [mainTableView matchingPredicate:timeCells]; for (NSInteger i = 0; i < elements.count; i++) { XCUIElement *currentElement = [elements elementBoundByIndex:i]; NSString *currentTime = (NSString *)currentElement.value; XCTAssertFalse([currentTime containsString:@"AM"] || [currentTime containsString:@"PM"]); } } - (void)reset:(XCUIElement *)field withText:(NSString *)text { NSString *currentValue = (NSString *)field.value; for (NSInteger i = 0; i < currentValue.length; i++) { [field typeKey:XCUIKeyboardKeyDelete modifierFlags:XCUIKeyModifierNone]; } [field typeText:text]; } @end ================================================ FILE: Clocker/ClockerUITests/CopyToClipboardTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class CopyToClipboardTests: XCTestCase { var app: XCUIApplication! override func setUp() { continueAfterFailure = false app = XCUIApplication() app.launch() if app.tables["FloatingTableView"].exists == false { app.tapMenubarIcon() app.buttons["Pin"].click() } } override func tearDownWithError() throws { app = nil } func testFullCopy() throws { let cell = app.tables["FloatingTableView"].cells.firstMatch let customLabel = cell.staticTexts["CustomNameLabelForCell"] guard let value = customLabel.value else { return } let time = cell.staticTexts["ActualTime"].value ?? "Nil Value" let expectedValue = "\(value) - \(time)" // Tap to copy! cell.click() let actualValue = NSPasteboard.general.string(forType: .string) ?? "Empty Pasteboard" XCTAssert(expectedValue == actualValue, "Clipboard value (\(actualValue)) doesn't match expected result: \(expectedValue)") // Test full copy let cellCount = app.tables["FloatingTableView"].cells.count var clipboardValue: [String] = [] for cellIndex in 0 ..< cellCount { let cell = app.tables["FloatingTableView"].cells.element(boundBy: cellIndex) let time = cell.staticTexts["ActualTime"].value ?? "Nil Value" clipboardValue.append("\(time)") } app.buttons["Share"].click() } func testModernSlider() { if app.buttons["FloatingPin"].exists { app.buttons["FloatingPin"].click() } app.tapMenubarIcon() let modernSliderExists = app.collectionViews["ModernSlider"].exists app.buttons["Preferences"].click() let appearanceTab = app.toolbars.buttons.element(boundBy: 1) appearanceTab.click() let miscTab = app.tabs.element(boundBy: 1) miscTab.click() if modernSliderExists { app.radioGroups["FutureSlider"].radioButtons["Hide"].click() } else { app.radioGroups["FutureSlider"].radioButtons["Show"].click() } app.tapMenubarIcon() let newFloatingSliderExists = app.collectionViews["ModernSlider"].exists XCTAssertNotEqual(newFloatingSliderExists, modernSliderExists) } } ================================================ FILE: Clocker/ClockerUITests/FloatingWindowTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest extension String { func localizedString() -> String { let bundle = Bundle(for: FloatingWindowTests.self) return NSLocalizedString(self, bundle: bundle, comment: "") } } class FloatingWindowTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(CLUITestingLaunchArgument) app.launch() if !app.tables["FloatingTableView"].exists { app.tapMenubarIcon() app.buttons["Pin"].click() } addUIInterruptionMonitor(withDescription: "Reminders Access") { alert -> Bool in let alertButton = alert.buttons["OK"] if alertButton.exists { alertButton.tap() return true } return false } } override func tearDown() { super.tearDown() } func testFloatingWindow() { let cell = app.tables["FloatingTableView"].cells.firstMatch let extraOptionButton = cell.buttons.firstMatch extraOptionButton.click() let remindersCheckbox = app.checkBoxes["ReminderCheckbox"] remindersCheckbox.click() sleep(1) XCTAssertTrue(app.popovers.datePickers.firstMatch.isEnabled) remindersCheckbox.click() sleep(1) XCTAssertFalse(app.popovers.datePickers.firstMatch.isEnabled) } func testAddingANote() { let expectedText = "This is a really important note to me and my friends" if app.buttons["Pin"].exists { app.buttons["Pin"].click() } let cell = app.tables["FloatingTableView"].cells.firstMatch let extraOptionButton = cell.buttons.firstMatch extraOptionButton.click() let notesTextView = app.textViews["NotesTextView"] notesTextView.click() app.textViews["NotesTextView"].click(forDuration: 2, thenDragTo: notesTextView) notesTextView.reset(text: "This is a really important note to me and my friends") let saveButton = app.buttons["SaveButton"] saveButton.click() if let noteLabelInCell = cell.staticTexts["This is a really important note to me and my friends"].value as? String { XCTAssert(noteLabelInCell == expectedText) } } func testSettingAReminder() { if app.buttons["Pin"].exists { app.buttons["Pin"].click() } let cell = app.tables["FloatingTableView"].cells.firstMatch let extraOptionButton = cell.buttons.firstMatch extraOptionButton.click() let remindersCheckbox = app.checkBoxes["ReminderCheckbox"] remindersCheckbox.click() app.buttons["SaveButton"].click() app.tapMenubarIcon() } func testMarkingSlider() { if app.buttons["Pin"].exists { app.buttons["Pin"].click() } let floatingSlider = app.sliders["FloatingSlider"].exists app.buttons["FloatingPreferences"].click() let appearanceTab = app.toolbars.buttons.element(boundBy: 1) appearanceTab.click() let miscTab = app.tabs.element(boundBy: 1) miscTab.click() if floatingSlider { app.radioGroups["FutureSlider"].radioButtons["Hide"].click() } else { app.radioGroups["FutureSlider"].radioButtons["Show"].click() } // Floating window does not support modern slider. let newFloatingSliderExists = app.sliders["FloatingSlider"].exists XCTAssertFalse(newFloatingSliderExists) } func testHidingMenubarOptions() { if app.buttons["Pin"].exists { app.buttons["Pin"].click() } app.buttons["FloatingPreferences"].click() app.windows["Clocker"].toolbars.buttons["Preferences Tab".localizedString()].click() let menubarDisplayQuery = app.tables.checkBoxes.matching(NSPredicate(format: "value == 1", "")) let menubarDisplayQueryCount = menubarDisplayQuery.count for index in 0 ..< menubarDisplayQueryCount where index < menubarDisplayQueryCount { menubarDisplayQuery.element(boundBy: 0).click() sleep(1) } let appearanceTab = app.toolbars.buttons.element(boundBy: 1) appearanceTab.click() // Select Misc tab let miscTab = app.tabs.element(boundBy: 1) miscTab.click() XCTAssertTrue(app.staticTexts["InformationLabel"].exists) let generalTab = app.toolbars.buttons.element(boundBy: 0) generalTab.click() app.tables["TimezoneTableView"].checkBoxes.firstMatch.click() appearanceTab.click() XCTAssertFalse(app.staticTexts["InformationLabel"].exists) } /// Make sure to ensure supplementary/relative date label is turned on! func testMovingSlider() { if app.buttons["Pin"].exists { app.buttons["Pin"].click() } let floatingSlider = app.sliders["FloatingSlider"].exists if floatingSlider { let tomorrowPredicate = NSPredicate(format: "identifier like %@", "RelativeDate") let tomorrow = app.tables.tableRows.staticTexts.matching(tomorrowPredicate) var previousValues: [String] = [] for index in 0 ..< tomorrow.count { let element = tomorrow.element(boundBy: index) guard let supplementaryText = element.value as? String else { continue } previousValues.append(supplementaryText) } app.sliders["FloatingSlider"].adjust(toNormalizedSliderPosition: 0.7) sleep(1) app.sliders["FloatingSlider"].adjust(toNormalizedSliderPosition: 1) let newTomorrow = app.tables.tableRows.staticTexts.matching(tomorrowPredicate) var newValues: [String] = [] for index in 0 ..< newTomorrow.count { let element = newTomorrow.element(boundBy: index) guard let supplementaryText = element.value as? String else { continue } newValues.append(supplementaryText) } XCTAssertNotEqual(newValues, previousValues) } } } extension XCUIElement { func reset(text: String) { guard let stringValue = value as? String else { XCTFail("Tried to clear and enter text into a non string value") return } if let hasKeyboardFocus = value(forKey: "hasKeyboardFocus") as? Bool, hasKeyboardFocus == false { click() } for _ in 0 ..< stringValue.count { typeKey(XCUIKeyboardKey.delete, modifierFlags: XCUIElement.KeyModifierFlags()) } guard let newStringValue = value as? String else { XCTFail("Tried to clear and enter text into a non string value") return } for _ in 0 ..< newStringValue.count { typeKey(XCUIKeyboardKey.forwardDelete, modifierFlags: XCUIElement.KeyModifierFlags()) } typeText(text) } } ================================================ FILE: Clocker/ClockerUITests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Clocker/ClockerUITests/NetworkDisconnectionTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class NetworkDisconnectionTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() } func precondition() { app.launch() if !app.tables["FloatingTableView"].exists { app.tapMenubarIcon() app.buttons["Pin"].click() } } func testAddingACity() { app.launchArguments.append("mockNetworkDown") precondition() app.buttons["FloatingPreferences"].click() if app.sheets.count == 0 { app.windows["Clocker"].checkBoxes["AddTimezone"].click() } XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists) let searchField = app.searchFields["AvailableSearchField"] searchField.reset(text: "Uganda") sleep(1) XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists) app.sheets.buttons["Close Button Title".localizedString()].click() } func testFetchingATimezone() { app.launchArguments.append("mockTimezoneDown") precondition() app.buttons["FloatingPreferences"].click() if app.sheets.count == 0 { app.windows["Clocker"].checkBoxes["AddTimezone"].click() } XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists) let searchField = app.searchFields["AvailableSearchField"] searchField.reset(text: "Uganda") let firstResult = app.tables["AvailableTimezoneTableView"].tableRows.firstMatch let waiter = XCTWaiter() let isHittable = NSPredicate(format: "exists == true", "") let addExpectation = expectation(for: isHittable, evaluatedWith: firstResult, handler: nil) waiter.wait(for: [addExpectation], timeout: 5) app.tables["AvailableTimezoneTableView"].click() app.buttons["AddAvailableTimezone"].click() sleep(1) XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists) app.sheets.buttons["Close Button Title".localizedString()].click() } } ================================================ FILE: Clocker/ClockerUITests/OnboardingSearchTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class OnboardingSearchTests: XCTestCase { var app: XCUIApplication! static let kOnboardingArgument = "isTestingTheOnboardingFlow" override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(Self.kOnboardingArgument) app.launch() // Let's go to the Search View moveForward() moveForward() moveForward() } func testRegularSearch() throws { let searchField = app.searchFields["MainSearchField"] searchField.reset(text: "Paris") searchField.typeKey(XCUIKeyboardKey.return, modifierFlags: XCUIElement.KeyModifierFlags()) sleep(2) // Wait for the query to return let results = app.tables["ResultsTableView"] let firstResult = results.cells.firstMatch XCTAssertTrue(results.cells.count > 0) let resultsPredicate = NSPredicate(format: "value CONTAINS 'Paris'", "") XCTAssertTrue(firstResult.staticTexts.matching(resultsPredicate).count > 0) // Let's retrieve the tap and add it! firstResult.doubleClick() sleep(2) // Wait for the Undo button to appear // Ensure Added Text is shown properly! let predicate = NSPredicate(format: "value BEGINSWITH 'Added'", "") let successTextShown = app.staticTexts.containing(predicate) XCTAssertTrue(successTextShown.count > 0) } func testUndoSearch() throws { let searchField = app.searchFields["MainSearchField"] searchField.reset(text: "Seoul") searchField.typeKey(XCUIKeyboardKey.return, modifierFlags: XCUIElement.KeyModifierFlags()) sleep(2) // Wait for the query to return let results = app.tables["ResultsTableView"] let firstResult = results.cells.firstMatch XCTAssertTrue(results.cells.count > 0) let resultsPredicate = NSPredicate(format: "value CONTAINS 'Seoul'", "") XCTAssertTrue(firstResult.staticTexts.containing(resultsPredicate).count > 0) // Let's retrieve the tap and add it! firstResult.doubleClick() sleep(2) // Wait for the Undo button to appear // Ensure Added Text is shown properly! let predicate = NSPredicate(format: "value BEGINSWITH 'Added'", "") let successTextShown = app.staticTexts.containing(predicate) XCTAssertTrue(successTextShown.count > 0) let undoButton = app.buttons.matching(identifier: "UndoButton").firstMatch undoButton.click() // Ensure Removed Text is shown! let removedPredicate = NSPredicate(format: "value BEGINSWITH 'Removed.'", "") let removedText = app.staticTexts.containing(removedPredicate) XCTAssertTrue(removedText.count > 0) } func testMispelledCityNameSearch() throws { let searchField = app.searchFields["MainSearchField"] searchField.reset(text: "ajsdkjasdkjhasdkashkjdazasdasdas") searchField.typeKey(XCUIKeyboardKey.return, modifierFlags: XCUIElement.KeyModifierFlags()) sleep(2) // Wait for the query to return let results = app.tables["ResultsTableView"] let firstResult = results.cells.firstMatch XCTAssertTrue(results.cells.count == 0) XCTAssertFalse(firstResult.staticTexts["Paris, France"].exists) sleep(2) // Wait for the Undo button to appear // Ensure Added Text is shown properly! let noErrorTextPredicate = NSPredicate(format: "value CONTAINS 'No results! 😔 Try entering the exact name.'", "") let noErrorText = app.staticTexts.containing(noErrorTextPredicate) XCTAssertTrue(noErrorText.count > 0) } private func moveForward() { let onboardingWindow = app.windows["OnboardingWindow"] onboardingWindow.buttons["Forward"].click() sleep(1) } } ================================================ FILE: Clocker/ClockerUITests/OnboardingTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class OnboardingTests: XCTestCase { var app: XCUIApplication! static let kOnboardingTestsLaunchArgument = "isTestingTheOnboardingFlow" override func setUp() { continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(Self.kOnboardingTestsLaunchArgument) app.launch() } // We test a couple of things in the Onboarding Process // 1. The flow (forward button and back button take the user to the correct screen) // 2. Static texts and button title's are appropriate func testForwardButton() { welcomeControllerTests() // Let's go to the Permissions View moveForward() permissionsControllerTests() // Time to test the launchAtLoginView moveForward() startupControllerTests() // Let's go to OnboardingSearchController moveForward() searchControllerTests() // Let's go to the FinalOnboardingController moveForward() finalOnboardingControllerTests() backButtonTests() } func backButtonTests() { moveBackward() searchControllerTests() moveBackward() startupControllerTests() moveBackward() permissionsControllerTests() moveBackward() welcomeControllerTests() alternateStartupFlowTests() } func alternateStartupFlowTests() { // Let's go to the Permissions View moveForward() permissionsControllerTests() // Time to test the launchAtLoginView moveForward() startupControllerTests() // Let's go to OnboardingSearchController alternateMoveForward() searchControllerTests() // Let's go to the FinalOnboardingController moveForward() finalOnboardingControllerTests() moveForward() XCTAssertTrue(app.statusItems.count > 0, "Status item was not installed in the menubar") } private func moveForward() { let onboardingWindow = app.windows["OnboardingWindow"] onboardingWindow.buttons["Forward"].click() sleep(1) } private func alternateMoveForward() { let onboardingWindow = app.windows["OnboardingWindow"] onboardingWindow.buttons["Alternate"].click() sleep(1) } private func moveBackward() { let onboardingWindow = app.windows["OnboardingWindow"] onboardingWindow.buttons["Backward"].click() sleep(1) } private func welcomeControllerTests() { let onboardingWindow = app.windows["OnboardingWindow"] // Tests static texts XCTAssertTrue(onboardingWindow.staticTexts["CFBundleDisplayName".localizedString()].exists, "Static text Clocker was unexpectedly missing") XCTAssertTrue(onboardingWindow.staticTexts["It only takes 3 steps to set up Clocker.".localizedString()].exists, "Accessory label's static text was unexpectedly wrong.") let button = onboardingWindow.buttons["Forward"] // Test the button title XCTAssertTrue(button.exists, "Button title was unexpectedly wrong. Expected \"Get Started\", Actual: \"\(onboardingWindow.buttons.firstMatch.title)\" ") XCTAssertTrue(onboardingWindow.buttons.count == 1, "More than 1 button on Welcome screen!") } private func permissionsControllerTests() { let onboardingWindow = app.windows["OnboardingWindow"] XCTAssertTrue(onboardingWindow.staticTexts["Permissions".localizedString()].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Your data doesn't leave your device 🔐"].exists, "Onboarding Info label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue".localizedString(), "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Backward"].exists, "Back button was unexpectedly missing") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") } private func startupControllerTests() { let onboardingWindow = app.windows["OnboardingWindow"] XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Open Clocker At Login".localizedString(), "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Alternate"].title == "Don't Open".localizedString(), "Alternate button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.staticTexts["Launch at Login".localizedString()].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Should Clocker open automatically on startup?".localizedString()].exists, "Accessory label's static text was unexpectedly wrong.") } private func searchControllerTests() { let onboardingWindow = app.windows["OnboardingWindow"] XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue".localizedString(), "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.staticTexts["Quick Add Locations".localizedString()].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["More search options in Clocker Preferences.".localizedString()].exists, "Accessory label's static text was unexpectedly wrong.") } private func finalOnboardingControllerTests() { let onboardingWindow = app.windows["OnboardingWindow"] // Let's test the buttons XCTAssertTrue(onboardingWindow.staticTexts["You're all set!".localizedString()].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Thank you for the details.".localizedString()].exists, "Accessory label's static text was unexpectedly wrong.") XCTAssertFalse(onboardingWindow.buttons["Alternate".localizedString()].exists, "Alternate button was unexpectedly present.") XCTAssertTrue(onboardingWindow.buttons["Forward".localizedString()].title == "Launch Clocker".localizedString(), "Forward button's title was unexpectedly wrong.") } } ================================================ FILE: Clocker/ClockerUITests/PanelTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest let CLUITestingLaunchArgument = "isUITesting" class PanelTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launch() if app.tables["FloatingTableView"].exists { app.buttons["FloatingPin"].click() } } override func tearDown() { super.tearDown() } func testPinningPanelAndBack() { app.tapMenubarIcon() app.buttons["Pin"].click() XCTAssertTrue(app.tables["FloatingTableView"].exists, "Floating Table unexpectedly doesn't exist.") app.buttons["FloatingPin"].click() app.tapMenubarIcon() XCTAssertTrue(app.tables["mainTableView"].exists, "Main Table unexpectedly doesn't exist") } func testChangingLabelFromPopover() { app.tapMenubarIcon() let cell = app.tables["mainTableView"].cells.firstMatch let originalField = cell.staticTexts["CustomNameLabelForCell"] guard let originalValue = originalField.value as? String else { XCTFail("Original Field's value was unexpectedly nil") return } cell.buttons["extraOptionButton"].click() XCTAssertTrue(app.textFields["CustomLabel"].exists, "Custom Label doesn't exist.") app.textFields["CustomLabel"].reset(text: "My Precious") app.buttons["SaveButton"].click() let verifyCell = app.tables["mainTableView"].cells.firstMatch let newField = verifyCell.staticTexts["CustomNameLabelForCell"] if let newFieldValue = newField.value as? String { XCTAssertTrue(newFieldValue == "My Precious", "Labels don't match") } cell.buttons["extraOptionButton"].click() app.textFields["CustomLabel"].reset(text: originalValue) app.buttons["SaveButton"].click() } func testEnablingUpcomingEventView() { app.tapMenubarIcon() let upcomingView = app.collectionViews["UpcomingEventCollectionView"] let beforeUpcomingEventViewExist = upcomingView.exists app.buttons["Preferences"].click() let clockerWindow = app.windows["Clocker"] let toolbarsQuery = clockerWindow.toolbars.buttons toolbarsQuery.element(boundBy: 2).click() if app.windows["Clocker"].staticTexts["InfoField"].exists { /* We haven't provided calendar access to the app*/ return } let yesPredicate = NSPredicate(format: "title like %@", "Yes") let noPredicate = NSPredicate(format: "title like %@", "No") let elementsMatching = clockerWindow.radioGroups.firstMatch.radioButtons let yesBar = elementsMatching.element(matching: yesPredicate) if let selection = yesBar.value as? Int, selection == 1 { let noBar = elementsMatching.element(matching: noPredicate) noBar.click() } else { yesBar.click() } clockerWindow.buttons[XCUIIdentifierCloseWindow].click() app.tapMenubarIcon() let newUpcomingEventView = app.collectionViews["UpcomingEventCollectionView"] let afterUpcomingEventViewExists = newUpcomingEventView.exists XCTAssertNotEqual(afterUpcomingEventViewExists, beforeUpcomingEventViewExist) } func testRightMouseDownToShowPopover() { app.tapMenubarIcon() let cell = app.tables["mainTableView"].cells.firstMatch cell.rightClick() XCTAssert(app.popovers.count > 0) } // Ensure that once main panel is closed, the time in the menubar doesn't stop and stays up-to-date func testTimeIsUpToDate() { // Ensure that we have seconds selected for the timezone format // Open Panel; before closing panel note the time // Close Panel; // Start timer; // Check time increments or is not equal for all those five seconds? } } ================================================ FILE: Clocker/ClockerUITests/PermissionsTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class PermissionsTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launch() } func testAcceptingCalendarPermissions() { if app.tables["FloatingTableView"].exists { app.buttons["FloatingPin"].click() } app.tapMenubarIcon() app/*@START_MENU_TOKEN@*/ .buttons["Preferences"]/*[[".dialogs[\"Clocker Panel\"].buttons[\"Preferences\"]",".buttons[\"Preferences\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ .click() let clockerWindow = app.windows["Clocker"] // Check Permissions first let permissionsTab = clockerWindow.toolbars.buttons["Permissions"] permissionsTab.click() let grantButton = clockerWindow.buttons["CalendarGrantAccessButton"].firstMatch if grantButton.title == "Granted" || grantButton.title == "Denied" { return } let calendarButton = clockerWindow.toolbars.buttons["Calendar"] calendarButton.click() let showUpcomingEventView = clockerWindow.staticTexts["UpcomingEventView"] XCTAssertFalse(showUpcomingEventView.isHittable) clockerWindow.buttons["Grant Access"].click() clockerWindow.buttons["CalendarGrantAccessButton"].firstMatch.click() addUIInterruptionMonitor(withDescription: "Calendars Access") { alert -> Bool in let alertButton = alert.buttons["OK"] if alertButton.exists { alertButton.tap() return true } return false } calendarButton.click() XCTAssertTrue(showUpcomingEventView.isHittable) } func testAcceptingRemindersPermissions() { if app.tables["FloatingTableView"].exists { app.tapMenubarIcon() app.buttons["FloatingPin"].click() } app.tapMenubarIcon() app/*@START_MENU_TOKEN@*/ .buttons["Preferences"]/*[[".dialogs[\"Clocker Panel\"].buttons[\"Preferences\"]",".buttons[\"Preferences\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ .click() let clockerWindow = app.windows["Clocker"] // Check Permissions first let permissionsTab = clockerWindow.toolbars.buttons["Permissions"] permissionsTab.click() let grantButton = clockerWindow.buttons["RemindersGrantAccessButton"].firstMatch if grantButton.title == "Granted" || grantButton.title == "Denied" { return } clockerWindow.buttons["RemindersGrantAccessButton"].firstMatch.click() addUIInterruptionMonitor(withDescription: "Reminders Access") { alert -> Bool in let alertButton = alert.buttons["OK"] if alertButton.exists { alertButton.tap() return true } return false } } } ================================================ FILE: Clocker/ClockerUITests/PreferencesTest.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class PreferencesTest: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(CLUITestingLaunchArgument) app.launch() if app.tables["FloatingTableView"].exists { app.tapMenubarIcon() app.buttons["FloatingPin"].click() } } func testRemovingButtonVisibility() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) let predicate = NSPredicate(format: "identifier BEGINSWITH 'DeleteTimezone'", "") let beforeTimezoneSelected = app.windows["Clocker"].checkBoxes.matching(predicate).firstMatch XCTAssertFalse(beforeTimezoneSelected.isEnabled) if app.tables["TimezoneTableView"].tableRows.count <= 0 { XCTFail("There are no timezones.") return } app.windows["Clocker"].tables["TimezoneTableView"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)).click() XCTAssertTrue(app.checkBoxes["DeleteTimezone"].isEnabled) } func testAddingATimezone() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) if app.sheets.count == 0 { app.windows["Clocker"].checkBoxes["AddTimezone"].click() } addAPlace(place: "UTC", to: app) let matchPredicate = NSPredicate(format: "value contains %@", "UTC") let matchingFields = app.tables["TimezoneTableView"].textFields.matching(matchPredicate) XCTAssertTrue(matchingFields.count > 0, "Matching Fields count was zero") deleteAPlace(place: "UTC", for: app) } func testSortingCitiesByTimezoneDifference() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) deleteAllPlaces(app: app) addAPlace(place: "New Zealand", to: app) addAPlace(place: "San Francisco", to: app) addAPlace(place: "Florida", to: app, shouldSleep: false) // Last elements don't need to sleep XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Time Difference".localizedString()].exists) app.windows["Clocker"].checkBoxes["Sort by Time Difference".localizedString()].click() var actualLabels: [String] = [] let newFormattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< newFormattedAddressQuery.count { if let currentValue = newFormattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 0 { actualLabels.append(currentValue) } } XCTAssertEqual(actualLabels, ["New Zealand".localizedString(), "Florida".localizedString(), "San Francisco".localizedString()]) app.windows["Clocker"].checkBoxes["Sort by Time Difference".localizedString()].click() var actualReversedLabels: [String] = [] let newReversedQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< newReversedQuery.count { if let currentValue = newReversedQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 0 { actualReversedLabels.append(currentValue) } } XCTAssertEqual(actualReversedLabels, ["San Francisco".localizedString(), "Florida".localizedString(), "New Zealand".localizedString()]) addAPlace(place: "Omaha", to: app) addAPlace(place: "Mumbai", to: app) } func testSortingCitiesByTimezoneName() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Time Difference".localizedString()].exists) XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].exists) XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Name".localizedString()].exists) var formattedAddress: [String] = [] let formattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< formattedAddressQuery.count { if let currentValue = formattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 0 { formattedAddress.append(currentValue) } } formattedAddress.sort() if let value = app.windows["Clocker"].checkBoxes["Sort by Name".localizedString()].value as? Int, value == 0 { app.windows["Clocker"].checkBoxes["Sort by Name".localizedString()].click() } var newformattedAddress: [String] = [] let newFormattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< newFormattedAddressQuery.count { if let currentValue = newFormattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 0 { newformattedAddress.append(currentValue) } } XCTAssertEqual(newformattedAddress, formattedAddress) } func testSortingCitiesByCustomLabel() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) addAPlace(place: "Aurangabad", to: app) addAPlace(place: "Zimbabwe", to: app) addAPlace(place: "Portland", to: app, shouldSleep: false) addAPlace(place: "Asia/Calcutta", to: app) addAPlace(place: "Anywhere on Earth", to: app, shouldSleep: false) XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].exists) var expectedLabels: [String] = [] let formattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< formattedAddressQuery.count { if let currentValue = formattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 1 { expectedLabels.append(currentValue) } } expectedLabels.sort() if let value = app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].value as? Int, value == 0 { app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].click() } var actualLabels: [String] = [] let newFormattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< newFormattedAddressQuery.count { if let currentValue = newFormattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 1 { actualLabels.append(currentValue) } } XCTAssertEqual(actualLabels, expectedLabels) deleteAPlace(place: "Aurangabad", for: app) deleteAPlace(place: "Zimbabwe", for: app) deleteAPlace(place: "Portland", for: app) deleteAPlace(place: "Asia/Calcutta", for: app) deleteAPlace(place: "Anywhere on Earth", for: app, shouldSleep: false) } func testSortingTimezonesByCustomLabel() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) addAPlace(place: "Europe/Lisbon", to: app) addAPlace(place: "Asia/Calcutta", to: app) addAPlace(place: "Anywhere on Earth", to: app, shouldSleep: false) XCTAssertTrue(app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].exists) var expectedLabels: [String] = [] let formattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< formattedAddressQuery.count { if let currentValue = formattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 1 { expectedLabels.append(currentValue) } } expectedLabels.sort() if let value = app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].value as? Int, value == 0 { app.windows["Clocker"].checkBoxes["Sort by Label".localizedString()].click() } var actualLabels: [String] = [] let newFormattedAddressQuery = app.windows["Clocker"].textFields for elementIndex in 0 ..< newFormattedAddressQuery.count { if let currentValue = newFormattedAddressQuery.element(boundBy: elementIndex).value as? String, elementIndex % 2 == 1 { actualLabels.append(currentValue) } } XCTAssertEqual(actualLabels, expectedLabels) deleteAPlace(place: "Europe/Lisbon", for: app) deleteAPlace(place: "Asia/Calcutta", for: app) deleteAPlace(place: "Anywhere on Earth", for: app, shouldSleep: false) } func searchingWithMisspelledName() { app.tapMenubarIcon() app.tables["mainTableView"].typeKey(",", modifierFlags: .command) if app.sheets.count == 0 { app.windows["Clocker"].checkBoxes["AddTimezone"].click() } let searchField = app.searchFields["AvailableSearchField"] searchField.reset(text: "StuJjlqh7AcJFnBuOdgNa2dQ4WrIajP9Mo8R83FV7fIZ3B8zE2n") sleep(2) let maxCharacterCountPredicate = NSPredicate(format: "value like %@", "Max Search Characters".localizedString()) let currentSheets = app.sheets.firstMatch.staticTexts let maxCharacterQuery = currentSheets.matching(maxCharacterCountPredicate) XCTAssertTrue(maxCharacterQuery.count > 0) addAPlace(place: "asdakjhdasdahsdasd", to: app, shouldSleep: false) XCTAssertTrue(app.sheets.staticTexts["No Timezone Selected".localizedString()].exists) let informativeLabelPredicate = NSPredicate(format: "placeholderValue like %@", "No results! 😔 Try entering the exact name.") let sheets = app.sheets.firstMatch.staticTexts let query = sheets.matching(informativeLabelPredicate) XCTAssertTrue(query.count > 0) addAPlace(place: "Cambodia", to: app) let newInformativeLabelPredicate = NSPredicate(format: "placeholderValue like %@", "No results! 😔 Try entering the exact name.") let newSheets = app.sheets.firstMatch.staticTexts let newQuery = newSheets.matching(newInformativeLabelPredicate) XCTAssertTrue(newQuery.count == 0, "New Query returned \(newQuery.count)") XCTAssertFalse(app.sheets.staticTexts["Please select a timezone!"].exists) deleteAPlace(place: "Cambodia", for: app, shouldSleep: false) } func testNoTimezone() { app.tapMenubarIcon() app.buttons["Preferences"].click() deleteAllTimezones() XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists) app.tapMenubarIcon() XCTAssertTrue(app.buttons["EmptyAddTimezone"].exists) addAPlace(place: "Omaha", to: app) addAPlace(place: "Mumbai", to: app) deleteAllTimezones() XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists) addAPlace(place: "Omaha", to: app) addAPlace(place: "Mumbai", to: app) } func testWarningIfMoreThanOneMenubarIsSelected() { app.tapMenubarIcon() app.buttons["Preferences"].click() let preferencesTable = app.tables["TimezoneTableView"] XCTAssertTrue(preferencesTable.exists) // Let's reset all checkboxes let favouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 1", "")) if favouritedMenubarsQuery.count > 1 { for _ in 0 ..< favouritedMenubarsQuery.count { let checkbox = favouritedMenubarsQuery.element(boundBy: 0) checkbox.click() } } // Let's make sure we have > 1 timezones first let favourites = preferencesTable.tableRows if favourites.count < 2 { addAPlace(place: "UTC", to: app) } XCTAssertTrue(favourites.count > 1) sleep(2) // Select two timezones let unfavouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 0", "")) if unfavouritedMenubarsQuery.count > 1 { for _ in 0 ..< unfavouritedMenubarsQuery.count { let checkbox = unfavouritedMenubarsQuery.element(boundBy: 0) checkbox.click() sleep(2) } } XCTAssertTrue(app.dialogs.count > 0) let compactModeButton = app.dialogs.buttons["Enable Compact Mode"] if compactModeButton.isHittable { compactModeButton.click() XCTAssertTrue(app.dialogs.count == 0) } } private func deleteAllTimezones() { let clockerWindow = app.windows["Clocker"] let rowQueryCount = clockerWindow.tables["TimezoneTableView"].tableRows.count if rowQueryCount > 0 { // Table Rows aren't hittable in Xcode 12.0 (10/7/20) and so we need to find a closer co-ordinate and perform click() let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)) currentElement.click() for _ in 0 ..< rowQueryCount { clockerWindow.typeKey(XCUIKeyboardKey.delete, modifierFlags: XCUIElement.KeyModifierFlags()) } } } } extension XCUIApplication { func tapMenubarIcon() { if menuBars.count < 2 { XCTFail("Unable to find menubar options") return } statusItems.firstMatch.click() } } extension XCTestCase { func inverseWaiterFor(element: XCUIElement, time: TimeInterval = 25) { let spinnerPredicate = NSPredicate(format: "exists == false") let spinnerExpectation = expectation(for: spinnerPredicate, evaluatedWith: element, handler: nil) let spinnerResult = XCTWaiter().wait(for: [spinnerExpectation], timeout: time) if spinnerResult != .completed { XCTFail("Still seeing Spinner after 25 seconds. Something's wrong") return } } func addAPlace(place: String, to app: XCUIApplication, shouldSleep: Bool = true) { // Let's first check if the place is already present in the list let matchPredicate = NSPredicate(format: "value contains %@", place) let matchingFields = app.windows["Clocker"].tables["TimezoneTableView"].textFields.matching(matchPredicate) if matchingFields.count > 0 { return } if app.sheets.count == 0 { app.windows["Clocker"].checkBoxes["AddTimezone"].click() } let searchField = app.searchFields["AvailableSearchField"] if searchField.isHittable { searchField.reset(text: place) } let results = app.tables["AvailableTimezoneTableView"].cells.staticTexts.matching(matchPredicate) let waiter = XCTWaiter() let isHittable = NSPredicate(format: "exists == true", "") let addExpectation = expectation(for: isHittable, evaluatedWith: results.firstMatch) { () -> Bool in true } waiter.wait(for: [addExpectation], timeout: 5) if results.count > 0 { results.firstMatch.click() } if app.buttons["AddAvailableTimezone"].exists { app.buttons["AddAvailableTimezone"].click() } if shouldSleep { sleep(2) } } func deleteAllPlaces(app: XCUIApplication) { var rowQueryCount = app.windows["Clocker"].tables["TimezoneTableView"].tableRows.count if rowQueryCount == 0 { return } let currentElement = app.windows["Clocker"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)) currentElement.click() while rowQueryCount > 0 { app.windows["Clocker"].typeKey(XCUIKeyboardKey.delete, modifierFlags: XCUIElement.KeyModifierFlags()) rowQueryCount -= 1 } } func deleteAPlace(place: String, for app: XCUIApplication, shouldSleep: Bool = true) { let userPrefferedLanguage = Locale.preferredLanguages.first ?? "en-US" if !userPrefferedLanguage.lowercased().contains("en") { // We're testing in a different user language. We can't do string matching here. // Delete the last row let rowCount = app.tables["TimezoneTableView"].tableRows.count let rowToDelete = app.tables["TimezoneTableView"].tableRows.element(boundBy: rowCount - 1) deleteAtRow(rowToDelete, for: app, shouldSleep: shouldSleep) return } let matchPredicate = NSPredicate(format: "value contains %@", place) let row = app.tables["TimezoneTableView"].textFields.matching(matchPredicate).firstMatch deleteAtRow(row, for: app, shouldSleep: shouldSleep) } private func deleteAtRow(_ rowToDelete: XCUIElement, for _: XCUIApplication, shouldSleep: Bool) { rowToDelete.click() rowToDelete.typeKey(XCUIKeyboardKey.delete, modifierFlags: XCUIElement.KeyModifierFlags()) if shouldSleep { sleep(2) } } } ================================================ FILE: Clocker/ClockerUITests/ReviewTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class ReviewTests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments.append(CLUITestingLaunchArgument) app.launch() app.tapMenubarIcon() app.tapMenubarIcon() app.tapMenubarIcon() } func testIfReviewIsNegativeAndUserWantsToProvideFeedback() { guard app.buttons["Not Really"].exists else { return } XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) app.buttons["Not Really"].click() sleep(2) app.buttons["Yes?"].click() XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.windows["Clocker Feedback"].exists) } func testIfReviewIsNegativeAndNoFeedback() { guard app.buttons["Not Really"].exists else { return } XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) app.buttons["Not Really"].click() sleep(2) app.buttons["No, thanks"].click() XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) } func testOnPositiveReviewAndNoAction() { guard app.buttons["Yes!"].exists else { return } XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) app.buttons["Yes!"].click() sleep(2) app.buttons["No, thanks"].click() XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) } func testOnPositiveReviewAndAction() { guard app.buttons["Yes!"].exists else { return } XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) app.buttons["Yes!"].click() sleep(2) app.buttons["Yes"].click() XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) } } ================================================ FILE: Clocker/ClockerUITests/ShortcutTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest class ShortcutTests: XCTestCase { var app: XCUIApplication! let randomIndex = Int(arc4random_uniform(26)) override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launch() app.tapMenubarIcon() if !app.tables["mainTableView"].exists { app.buttons["FloatingPin"].click() app.tapMenubarIcon() } } func testShortcuts() { app.tables["mainTableView"].typeKey(",", modifierFlags: .command) XCTAssertFalse(app.tables["mainTableView"].exists) let randomAlphabet = randomLetter() app.windows["Clocker"].otherElements["ShortcutControl"].click() app.windows["Clocker"].otherElements["ShortcutControl"].typeKey(randomAlphabet, modifierFlags: [.shift, .command]) // Close the window to really test app.windows["Clocker"].buttons["_XCUI:CloseWindow"].click() app.typeKey(randomAlphabet, modifierFlags: [.shift, .command]) XCTAssertTrue(app.tables["mainTableView"].exists) app.terminate() app.launch() app.typeKey(randomAlphabet, modifierFlags: [.shift, .command]) XCTAssertTrue(app.tables["mainTableView"].exists) // Reset the shortcut app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.windows["Clocker"].otherElements["ShortcutControl"].click() app.windows["Clocker"].typeKey(XCUIKeyboardKey.delete, modifierFlags: []) app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command]) XCTAssertFalse(app.tables["mainTableView"].exists) } private func randomLetter() -> String { let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] return alphabet[randomIndex] } } ================================================ FILE: Clocker/ClockerUITests/de.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerUITests"; ================================================ FILE: Clocker/ClockerUITests/ru.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerUITests"; ================================================ FILE: Clocker/ClockerUITests/zh-Hans.lproj/InfoPlist.strings ================================================ /* Bundle name */ "CFBundleName" = "ClockerUITests"; ================================================ FILE: Clocker/ClockerUnitTests/AppDelegateTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreModelKit import XCTest @testable import Clocker class AppDelegateTests: XCTestCase { func testStatusItemIsInitialized() throws { let subject = NSApplication.shared.delegate as? AppDelegate let statusHandler = subject?.statusItemForPanel() XCTAssertNotNil(EventCenter.sharedCenter) XCTAssertNotNil(statusHandler) } func testDockMenu() throws { let subject = NSApplication.shared.delegate as? AppDelegate let dockMenu = subject?.applicationDockMenu(NSApplication.shared) let items = dockMenu?.items ?? [] XCTAssertEqual(dockMenu?.title, "Quick Access") XCTAssertEqual(items.first?.title, "Toggle Panel") XCTAssertEqual(items[1].title, "Settings") XCTAssertEqual(items[1].keyEquivalent, ",") XCTAssertEqual(items[2].title, "Hide from Dock") // Test selections XCTAssertEqual(items.first?.action, #selector(AppDelegate.togglePanel(_:))) XCTAssertEqual(items[2].action, #selector(AppDelegate.hideFromDock)) items.forEach { menuItem in XCTAssertTrue(menuItem.isEnabled) } } func testSetupMenubarTimer() { let subject = NSApplication.shared.delegate as? AppDelegate let statusItemHandler = subject?.statusItemForPanel() XCTAssertEqual(statusItemHandler?.statusItem.autosaveName, NSStatusItem.AutosaveName("ClockerStatusItem")) } func testFloatingWindow() { let subject = NSApplication.shared.delegate as? AppDelegate let previousWindows = NSApplication.shared.windows XCTAssertTrue(previousWindows.count >= 1) // Only the status bar window should be present subject?.setupFloatingWindow(true) let floatingWindow = NSApplication.shared.windows.first { window in if (window.windowController as? FloatingWindowController) != nil { return true } return false } XCTAssertNotNil(floatingWindow) XCTAssertEqual(floatingWindow?.windowController?.windowFrameAutosaveName, NSWindow.FrameAutosaveName("FloatingWindowAutoSave")) subject?.setupFloatingWindow(false) let closedFloatingWindow = NSApplication.shared.windows.first { window in if (window.windowController as? FloatingWindowController) != nil { return true } return false } XCTAssertNotNil(closedFloatingWindow) } func testActivationPolicy() { let subject = NSApplication.shared.delegate as? AppDelegate let previousOption = UserDefaults.standard.integer(forKey: UserDefaultKeys.appDisplayOptions) if previousOption == 0 { XCTAssertEqual(NSApp.activationPolicy(), .accessory) } else { XCTAssertEqual(NSApp.activationPolicy(), .regular) } subject?.hideFromDock() XCTAssertEqual(NSApp.activationPolicy(), .accessory) } func testMenubarInvalidationToIcon() { let subject = NSApplication.shared.delegate as? AppDelegate subject?.invalidateMenubarTimer(true) let statusItemHandler = subject?.statusItemForPanel() XCTAssertEqual(statusItemHandler?.statusItem.button?.subviews, []) XCTAssertEqual(statusItemHandler?.statusItem.button?.title, UserDefaultKeys.emptyString) XCTAssertEqual(statusItemHandler?.statusItem.button?.image?.name(), "LightModeIcon") XCTAssertEqual(statusItemHandler?.statusItem.button?.imagePosition, .imageOnly) XCTAssertEqual(statusItemHandler?.statusItem.button?.toolTip, "Clocker") } func testCompactModeMenubarSetup() { let subject = NSApplication.shared.delegate as? AppDelegate let olderTimezones = DataStore.shared().timezones() let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.formattedAddress = "MenubarTimezone" timezone1.isFavourite = 1 // Encode it in UserDefaults guard let encodedTimezone = NSKeyedArchiver.clocker_archive(with: timezone1) else { return } DataStore.shared().setTimezones([encodedTimezone]) subject?.setupMenubarTimer() let statusItemHandler = subject?.statusItemForPanel() XCTAssertNotNil(statusItemHandler?.statusItem.button) // This won't be nil for compact mode DataStore.shared().setTimezones(olderTimezones) } func testStandardModeMenubarSetup() { let olderTimezones = DataStore.shared().timezones() UserDefaults.standard.set(1, forKey: UserDefaultKeys.menubarCompactMode) // Set the menubar mode to standard let subject = NSApplication.shared.delegate as? AppDelegate let statusItemHandler = subject?.statusItemForPanel() subject?.setupMenubarTimer() if olderTimezones.isEmpty { XCTAssertEqual(statusItemHandler?.statusItem.button?.image?.name(), "LightModeIcon") } else { XCTAssertTrue(statusItemHandler?.statusItem.button?.title != nil) } let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.formattedAddress = "MenubarTimezone" timezone1.isFavourite = 1 // Encode it in UserDefaults guard let encodedTimezone = NSKeyedArchiver.clocker_archive(with: timezone1) else { return } DataStore.shared().setTimezones([encodedTimezone]) subject?.setupMenubarTimer() XCTAssertEqual(subject?.statusItemForPanel().statusItem.button?.subviews.isEmpty, true) // This will be nil for standard mode UserDefaults.standard.set(0, forKey: UserDefaultKeys.menubarCompactMode) // Set the menubar mode back to compact } } ================================================ FILE: Clocker/ClockerUnitTests/ClockerUnitTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreModelKit @testable import Clocker import XCTest class ClockerUnitTests: XCTestCase { private let california = ["customLabel": "Test", "formattedAddress": "San Francisco", "place_id": "TestIdentifier", "timezoneID": "America/Los_Angeles", "nextUpdate": "", "latitude": "37.7749295", "longitude": "-122.4194155"] private let mumbai = ["customLabel": "Ghar", "formattedAddress": "Mumbai", "place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0", "timezoneID": "Asia/Calcutta", "nextUpdate": "", "latitude": "19.0759837", "longitude": "72.8776559"] private let auckland = ["customLabel": "Auckland", "formattedAddress": "New Zealand", "place_id": "ChIJh5Z3Fw4gLG0RM0dqdeIY1rE", "timezoneID": "Pacific/Auckland", "nextUpdate": "", "latitude": "-40.900557", "longitude": "174.885971"] private let florida = ["customLabel": "Gainesville", "formattedAddress": "Florida", "place_id": "ChIJvypWkWV2wYgR0E7HW9MTLvc", "timezoneID": "America/New_York", "nextUpdate": "", "latitude": "27.664827", "longitude": "-81.5157535"] private let onlyTimezone: [String: Any] = ["timezoneID": "Africa/Algiers", "formattedAddress": "Africa/Algiers", "place_id": "", "customLabel": "", "latitude": "", "longitude": ""] private let omaha: [String: Any] = ["timezoneID": "America/Chicago", "formattedAddress": "Omaha", "place_id": "ChIJ7fwMtciNk4cRBLY3rk9NQkY", "customLabel": "", "latitude": "41.2565369", "longitude": "-95.9345034"] private var operations: TimezoneDataOperations { return TimezoneDataOperations(with: TimezoneData(with: mumbai), store: DataStore.shared()) } private var californiaOperations: TimezoneDataOperations { return TimezoneDataOperations(with: TimezoneData(with: california), store: DataStore.shared()) } private var floridaOperations: TimezoneDataOperations { return TimezoneDataOperations(with: TimezoneData(with: florida), store: DataStore.shared()) } private var aucklandOperations: TimezoneDataOperations { return TimezoneDataOperations(with: TimezoneData(with: auckland), store: DataStore.shared()) } private var omahaOperations: TimezoneDataOperations { return TimezoneDataOperations(with: TimezoneData(with: omaha), store: DataStore.shared()) } func testOverridingSecondsComponent_shouldHideSeconds() { let dummyDefaults = UserDefaults.standard dummyDefaults.set(NSNumber(value: 4), forKey: UserDefaultKeys.selectedTimeZoneFormatKey) // 4 is 12 hour with seconds let timezoneObjects = [TimezoneData(with: mumbai), TimezoneData(with: auckland), TimezoneData(with: california)] timezoneObjects.forEach { let operationsObject = TimezoneDataOperations(with: $0, store: DataStore.shared()) let currentTime = operationsObject.time(with: 0) XCTAssert(currentTime.count == 8) // 8 includes 2 colons $0.setShouldOverrideGlobalTimeFormat(1) let newTime = operationsObject.time(with: 0) XCTAssert(newTime.count >= 7) // 5 includes colon } } func testAddingATimezoneToDefaults() { let timezoneData = TimezoneData(with: california) let currentFavourites = DataStore.shared().timezones() let oldCount = currentFavourites.count let operationsObject = TimezoneDataOperations(with: timezoneData, store: DataStore.shared()) operationsObject.saveObject() let newDefaults = DataStore.shared().timezones() XCTAssert(newDefaults.isEmpty == false) XCTAssert(newDefaults.count == oldCount + 1) } func testDecoding() { let timezone1 = TimezoneData.customObject(from: nil) XCTAssertNotNil(timezone1) let data = Data() let timezone2 = TimezoneData.customObject(from: data) XCTAssertNil(timezone2) } func testDescription() { let timezoneData = TimezoneData(with: california) XCTAssertFalse(timezoneData.description.isEmpty) XCTAssertFalse(timezoneData.debugDescription.isEmpty) } func testHashing() { let timezoneData = TimezoneData(with: california) XCTAssert(timezoneData.hash != -1) timezoneData.placeID = nil timezoneData.timezoneID = nil XCTAssert(timezoneData.hash == -1) } func testBadInputDictionaryForInitialization() { let badInput: [String: Any] = ["customLabel": "", "latitude": "41.2565369", "longitude": "-95.9345034"] let badTimezoneData = TimezoneData(with: badInput) XCTAssertEqual(badTimezoneData.placeID, "Error") XCTAssertEqual(badTimezoneData.timezoneID, "Error") XCTAssertEqual(badTimezoneData.formattedAddress, "Error") } func testDeletingATimezone() { var currentFavourites = DataStore.shared().timezones() // Check if timezone with test identifier is present. let filteredCount = currentFavourites.filter { let timezone = TimezoneData.customObject(from: $0) return timezone?.placeID == "TestIdentifier" } // California is absent. Add it! if filteredCount.count == 0 { let timezoneData = TimezoneData(with: california) let operationsObject = TimezoneDataOperations(with: timezoneData, store: DataStore.shared()) operationsObject.saveObject() } let oldCount = DataStore.shared().timezones().count currentFavourites = currentFavourites.filter { let timezone = TimezoneData.customObject(from: $0) return timezone?.placeID != "TestIdentifier" } DataStore.shared().setTimezones(currentFavourites) XCTAssertTrue(currentFavourites.count == oldCount - 1, "Current Favourites Count \(currentFavourites.count) and Old Count \(oldCount - 1) don't line up.") } // The below test might fail outside California or if DST is active! // CI is calibrated to be on LA timezone! func testTimeDifference() { let observingDaylightSavings = TimeZone.autoupdatingCurrent.isDaylightSavingTime(for: Date()) let expectedDifference = observingDaylightSavings ? ", +9h 30m" : ", +10h 30m" let expectedDifferenceForAuckland = observingDaylightSavings ? ", +17h " : ", +18h " XCTAssertTrue(operations.timeDifference() == expectedDifference, "Difference was unexpectedly: \(operations.timeDifference())") XCTAssertTrue(californiaOperations.timeDifference() == ", -3h ", "Difference was unexpectedly: \(californiaOperations.timeDifference())") XCTAssertTrue(floridaOperations.timeDifference() == "", "Difference was unexpectedly: \(floridaOperations.timeDifference())") XCTAssertTrue(aucklandOperations.timeDifference() == expectedDifferenceForAuckland, "Difference was unexpectedly: \(aucklandOperations.timeDifference())") XCTAssertTrue(omahaOperations.timeDifference() == ", -1h ", "Difference was unexpectedly: \(omahaOperations.timeDifference())") } func testSunriseSunset() { let dataObject = TimezoneData(with: mumbai) let operations = TimezoneDataOperations(with: dataObject, store: DataStore.shared()) XCTAssertNotNil(operations.formattedSunriseTime(with: 0)) XCTAssertNotNil(dataObject.sunriseTime) XCTAssertNotNil(dataObject.sunriseTime) let timezoneObject = TimezoneData(with: onlyTimezone) timezoneObject.selectionType = .timezone let timezoneOperations = TimezoneDataOperations(with: timezoneObject, store: DataStore.shared()) XCTAssertTrue(timezoneOperations.formattedSunriseTime(with: 0) == "") XCTAssertNil(timezoneObject.sunriseTime) XCTAssertNil(timezoneObject.sunsetTime) } func testDateWithSliderValue() { let dataObject = TimezoneData(with: mumbai) let operations = TimezoneDataOperations(with: dataObject, store: DataStore.shared()) XCTAssertNotNil(operations.date(with: 0, displayType: .menu)) } func testTimezoneFormat() { let dataObject = TimezoneData(with: mumbai) UserDefaults.standard.set(NSNumber(value: 0), forKey: UserDefaultKeys.selectedTimeZoneFormatKey) // Set to 12 hour format dataObject.setShouldOverrideGlobalTimeFormat(0) // Respect Global Preference XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm a") dataObject.setShouldOverrideGlobalTimeFormat(1) // 12-Hour Format XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm a") dataObject.setShouldOverrideGlobalTimeFormat(2) // 24-Hour format XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm") // Skip 3 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(4) // 12-Hour with seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm:ss a") dataObject.setShouldOverrideGlobalTimeFormat(5) // 24-Hour format with seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm:ss") // Skip 6 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(7) // 12-hour with preceding zero and no seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm a") dataObject.setShouldOverrideGlobalTimeFormat(8) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss a") // Skip 9 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(10) // 12-hour without am/pm and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm") dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss") // Wrong input dataObject.setShouldOverrideGlobalTimeFormat(0) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(88) == "h:mm a") } func testTimezoneFormatWithDefaultSetAs24HourFormat() { let dataObject = TimezoneData(with: california) UserDefaults.standard.set(NSNumber(value: 1), forKey: UserDefaultKeys.selectedTimeZoneFormatKey) // Set to 24-Hour Format dataObject.setShouldOverrideGlobalTimeFormat(0) XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm", "Unexpected format returned: \(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()))") dataObject.setShouldOverrideGlobalTimeFormat(1) // 12-Hour Format XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm a") dataObject.setShouldOverrideGlobalTimeFormat(2) // 24-Hour format XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm") // Skip 3 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(4) // 12-Hour with seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm:ss a") dataObject.setShouldOverrideGlobalTimeFormat(5) // 24-Hour format with seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm:ss") // Skip 6 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(7) // 12-hour with preceding zero and no seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm a") dataObject.setShouldOverrideGlobalTimeFormat(8) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss a") // Skip 9 since it's a placeholder dataObject.setShouldOverrideGlobalTimeFormat(10) // 12-hour without am/pm and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm") dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss") dataObject.setShouldOverrideGlobalTimeFormat(12) // 12-hour with preceding zero and seconds XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "epoch") } func testSecondsDisplayForOverridenTimezone() { let dataObject = TimezoneData(with: california) UserDefaults.standard.set(NSNumber(value: 1), forKey: UserDefaultKeys.selectedTimeZoneFormatKey) // Set to 24-Hour Format // Test default behaviour let timezoneWithSecondsKeys = [4, 5, 8, 11] for timezoneKey in timezoneWithSecondsKeys { dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey) XCTAssertTrue(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) } let timezoneWithoutSecondsKeys = [1, 2, 7, 10] for timezoneKey in timezoneWithoutSecondsKeys { dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey) XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) } // Test wrong override timezone key let wrongTimezoneKey = 88 dataObject.setShouldOverrideGlobalTimeFormat(wrongTimezoneKey) XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) // Test wrong global preference key dataObject.setShouldOverrideGlobalTimeFormat(0) XCTAssertFalse(dataObject.shouldShowSeconds(88)) } func testTimezoneRetrieval() { let dataObject = TimezoneData(with: mumbai) let autoupdatingTimezone = TimeZone.autoupdatingCurrent.identifier XCTAssertEqual(dataObject.timezone(), "Asia/Calcutta") // Unlikely dataObject.timezoneID = nil XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone) dataObject.isSystemTimezone = true XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone) } func testFormattedLabel() { let dataObject = TimezoneData(with: mumbai) XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") dataObject.setLabel("") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Mumbai", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") dataObject.formattedAddress = nil XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") dataObject.setLabel("Jogeshwari") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Jogeshwari", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") // Unlikely scenario dataObject.setLabel("") dataObject.timezoneID = "GMT" XCTAssertTrue(dataObject.formattedTimezoneLabel() == "GMT", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") // Another unlikely scenario dataObject.setLabel("") dataObject.timezoneID = nil XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Error", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") } func testEquality() { let dataObject1 = TimezoneData(with: mumbai) let dataObject2 = TimezoneData(with: auckland) XCTAssertFalse(dataObject1 == dataObject2) XCTAssertFalse(dataObject1.isEqual(dataObject2)) let dataObject3 = TimezoneData(with: mumbai) XCTAssertTrue(dataObject1 == dataObject3) XCTAssertTrue(dataObject1.isEqual(dataObject3)) XCTAssertFalse(dataObject1.isEqual(nil)) } func testWithAllLocales() { let dataObject1 = TimezoneData(with: mumbai) let operations = TimezoneDataOperations(with: dataObject1, store: DataStore.shared()) for locale in Locale.availableIdentifiers { let currentLocale = Locale(identifier: locale) let localizedDate = operations.todaysDate(with: 0, locale: currentLocale) XCTAssertNotNil(localizedDate) } } func testTimeWithAllLocales() { let dataObject = TimezoneData(with: mumbai) let cal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) guard let newDate = cal?.date(byAdding: .minute, value: 0, to: Date(), options: .matchFirst) else { XCTFail("Unable to add dates!") return } for locale in Locale.availableIdentifiers { let currentLocale = Locale(identifier: locale) let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none, format: dataObject.timezoneFormat(DataStore.shared().timezoneFormat()), timezoneIdentifier: dataObject.timezone(), locale: currentLocale) let convertedDate = dateFormatter.string(from: newDate) XCTAssertNotNil(convertedDate) } } func testStringFiltering() { let stringWithComma = "Mumbai, Maharashtra" let stringWithoutComma = "Mumbai" let emptyString = "" XCTAssertEqual(stringWithComma.filteredName(), "Mumbai") XCTAssertEqual(stringWithoutComma.filteredName(), "Mumbai") XCTAssertEqual(emptyString.filteredName(), "") } func testToasty() { let view = NSView(frame: CGRect.zero) view.makeToast("Hello, this is a toast") XCTAssertEqual(view.subviews.first?.accessibilityIdentifier(), "ToastView") let toastExpectation = expectation(description: "Toast View should hide after 1 second") let result = XCTWaiter.wait(for: [toastExpectation], timeout: 1.5) // Set 1.5 seconds here for a little leeway if result == XCTWaiter.Result.timedOut { XCTAssertTrue(view.subviews.isEmpty) } } func testPointingHandButton() { let sampleRect = CGRect(x: 0, y: 0, width: 200, height: 200) let pointingHandCursorButton = PointingHandCursorButton(frame: CGRect.zero) pointingHandCursorButton.draw(sampleRect) pointingHandCursorButton.resetCursorRects() XCTAssertEqual(pointingHandCursorButton.pointingHandCursor, NSCursor.pointingHand) } func testNoTimezoneView() { let sampleRect = CGRect(x: 0, y: 0, width: 200, height: 200) let subject = NoTimezoneView(frame: sampleRect) // Perform a layout to add subviews subject.layout() XCTAssertEqual(subject.subviews.count, 2) // Two textfields XCTAssertEqual(subject.subviews.first?.layer?.animationKeys(), ["notimezone.emoji"]) } func testDefaultsWiping() { let defaultsDict: [String: Any] = ["test1": "testString", "test2": 24] let domainName = "com.test.clocker" let defaults = UserDefaults(suiteName: domainName) defaults?.setPersistentDomain(defaultsDict, forName: domainName) defaults?.wipe(for: domainName) XCTAssertNil(defaults?.object(forKey: "test1")) XCTAssertNil(defaults?.object(forKey: "test2")) } } ================================================ FILE: Clocker/ClockerUnitTests/DateFormatterManagerTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest @testable import Clocker class DateFormatterManagerTests: XCTestCase { func testRegularDateFormatter() throws { let subject = DateFormatterManager.dateFormatter(with: .medium, for: "UTC") XCTAssertEqual(subject.dateStyle, .medium) XCTAssertEqual(subject.timeStyle, .medium) XCTAssertEqual(subject.locale.identifier, "en_US") XCTAssertEqual(subject.timeZone.identifier, "GMT") } func testDateFormatterWithFormat() throws { let subject = DateFormatterManager.dateFormatterWithFormat(with: .none, format: "hh:mm a", timezoneIdentifier: "Asia/Calcutta") XCTAssertEqual(subject.dateStyle, .none) XCTAssertEqual(subject.timeStyle, .none) XCTAssertEqual(subject.locale.identifier, "en_US") XCTAssertEqual(subject.timeZone.identifier, "Asia/Calcutta") XCTAssertEqual(subject.locale.identifier, "en_US") XCTAssertEqual(subject.dateFormat, "hh:mm a") } func testLocalizedDateFormatter() throws { let subject = DateFormatterManager.localizedFormatter(with: "hh:mm:ss", for: "America/Los_Angeles") XCTAssertEqual(subject.dateStyle, .none) XCTAssertEqual(subject.timeStyle, .none) XCTAssertEqual(subject.locale.identifier, Locale.autoupdatingCurrent.identifier) XCTAssertEqual(subject.timeZone.identifier, "America/Los_Angeles") XCTAssertEqual(subject.dateFormat, "hh:mm:ss") } } ================================================ FILE: Clocker/ClockerUnitTests/EventInfoTests.swift ================================================ // 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() == "started.", "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") } func testMetadataForEventHappeningAfterAnHour() throws { let pastChunk = TimeChunk(seconds: 10, minutes: 10, hours: 1, 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 1h 10m", "Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation") } func testMetadataForEventHappeningAfterThreeHours() throws { let pastChunk = TimeChunk(seconds: 10, minutes: 4, hours: 3, 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 3h", "Metadata for meeting: \(mockEventInfo.metadataForMeeting()) doesn't match expectation") } } ================================================ FILE: Clocker/ClockerUnitTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Clocker/ClockerUnitTests/ReviewControllerTests.swift ================================================ // Copyright © 2015 Abhishek Banthia @testable import Clocker import XCTest class ReviewControllerTests: XCTestCase { func testDebuggingMode() throws { let mockSuite = "com.test.Clocker.\(randomLetter())" guard let mockDefaults = UserDefaults(suiteName: mockSuite) else { return } ReviewController.applicationDidLaunch(mockDefaults) // Call it again to ensure Keys.install ReviewController.applicationDidLaunch(mockDefaults) ReviewController.setPreviewMode(true) XCTAssertTrue(ReviewController.canPrompt()) mockDefaults.removeSuite(named: mockSuite) } func testPrompDisplayedAfterFirstWeekOfInstall() { let dateChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 8, weeks: 0, months: 0, years: 0) let oldDate = Date().subtract(dateChunk) let mockSuite = "com.test.Clocker2.\(randomLetter())" guard let mockDefaults = UserDefaults(suiteName: mockSuite) else { return } mockDefaults.set(oldDate, forKey: "install") ReviewController.applicationDidLaunch(mockDefaults) // Explicitly set preview mode to false ReviewController.setPreviewMode(false) XCTAssertNil(mockDefaults.object(forKey: "last-prompt")) XCTAssertNil(mockDefaults.object(forKey: "last-version")) XCTAssertTrue(ReviewController.canPrompt()) mockDefaults.removeSuite(named: mockSuite) } func testPromptDisplayAfterTwoMonths() { let dateChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 68, weeks: 0, months: 0, years: 0) let minInstall = Date().subtract(dateChunk) let promptChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 60, weeks: 0, months: 0, years: 0) let lastPromptDate = Date().subtract(promptChunk) let mockSuite = "com.test.Clocker3.\(randomLetter())" guard let mockDefaults = UserDefaults(suiteName: mockSuite) else { return } mockDefaults.set(minInstall, forKey: "install") mockDefaults.set("test-version", forKey: "last-version") mockDefaults.set(lastPromptDate, forKey: "last-prompt") ReviewController.applicationDidLaunch(mockDefaults) // Explicitly set preview mode to false ReviewController.setPreviewMode(false) XCTAssertFalse(ReviewController.canPrompt()) mockDefaults.removeSuite(named: mockSuite) } func testPrompDisplayedAfterThreeMonths() { let dateChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 98, weeks: 0, months: 0, years: 0) let minInstall = Date().subtract(dateChunk) let promptChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 91, weeks: 0, months: 0, years: 0) let lastPromptDate = Date().subtract(promptChunk) let mockSuite = "com.test.Clocker4.\(randomLetter())" guard let mockDefaults = UserDefaults(suiteName: mockSuite) else { return } mockDefaults.set(minInstall, forKey: "install") mockDefaults.set("test-version", forKey: "last-version") mockDefaults.set(lastPromptDate, forKey: "last-prompt") ReviewController.applicationDidLaunch(mockDefaults) // Explicitly set preview mode to false ReviewController.setPreviewMode(false) XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt")) XCTAssertNotNil(mockDefaults.object(forKey: "last-version")) XCTAssertTrue(ReviewController.canPrompt()) mockDefaults.removeSuite(named: mockSuite) } func testPrompted() { let mockSuite = "com.test.Clocker5.\(randomLetter())" guard let mockDefaults = UserDefaults(suiteName: mockSuite) else { return } ReviewController.applicationDidLaunch(mockDefaults) ReviewController.prompt() XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt")) XCTAssertNotNil(mockDefaults.object(forKey: "last-version")) mockDefaults.removeSuite(named: mockSuite) } private func randomLetter() -> String { let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] return alphabet[Int(arc4random_uniform(26))] } } ================================================ FILE: Clocker/ClockerUnitTests/SearchDataSourceTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreModelKit import XCTest @testable import Clocker class SearchDataSourceTests: XCTestCase { private var subject: SearchDataSource! private func setupSubject(searchText: String = "") { let mockSearchField = NSSearchField() mockSearchField.stringValue = searchText subject = SearchDataSource(with: mockSearchField, location: .preferences) } private func setupMockData() { subject.searchTimezones("los") XCTAssertTrue(subject.calculateChangesets()) let mockTimezone = TimezoneData() mockTimezone.timezoneID = "PST" mockTimezone.formattedAddress = "Los Angeles" subject.setFilteredArrayValue([mockTimezone]) subject.searchTimezones("los") XCTAssertTrue(subject.calculateChangesets()) } override func tearDownWithError() throws { subject = nil try super.tearDownWithError() } func testSearchTimezones() { setupSubject(searchText: "") // Test capitalized string subject.searchTimezones("MUMBAI") XCTAssert(subject.timezoneFilteredArray.isEmpty == false) // Test sentence-cased string subject.searchTimezones("Delhi") XCTAssert(subject.timezoneFilteredArray.isEmpty == false) // Test lower-cased string subject.searchTimezones("california") XCTAssert(subject.timezoneFilteredArray.isEmpty == false) } func testCalculateChangesets() { setupSubject(searchText: "los") setupMockData() subject.cleanupFilterArray() subject.searchTimezones("los") XCTAssertTrue(subject.calculateChangesets()) } func testRetrieveResult() throws { setupSubject(searchText: "los") setupMockData() // 0 will translate to a city search result let result1 = subject.retrieveResult(0) let unwrap = try XCTUnwrap(result1) if let metadata = unwrap as? CoreModelKit.TimezoneData { XCTAssert(metadata.timezoneID == "PST") } // 1 will translate to a timezone search result let result2 = subject.retrieveResult(1) let unwrap2 = try XCTUnwrap(result2) if let metadata = unwrap2 as? TimezoneMetadata { XCTAssert(metadata.timezone.name == "America/Santa_Isabel") } // Test placeForRow let rowType = subject.placeForRow(0) XCTAssert(rowType == .city) let rowType1 = subject.placeForRow(1) XCTAssert(rowType1 == .timezone) // Test count XCTAssertEqual(subject.resultsCount(), 5) // Test retrieveFilteredResultFromGoogleAPI let firstResult = try XCTUnwrap(subject.retrieveFilteredResultFromGoogleAPI(0)) XCTAssert(firstResult.timezoneID == "PST") // filteredArray should only have a count of 1 XCTAssertNil(subject.retrieveFilteredResultFromGoogleAPI(1)) } func testTableViewDataSourceMethods() { let mockTableView = NSTableView(frame: CGRect.zero) setupSubject(searchText: "los") setupMockData() let resultsCount = subject.numberOfRows(in: mockTableView) XCTAssertEqual(resultsCount, 5) XCTAssert(subject.tableView(mockTableView, heightOfRow: 0) == 30) } func testRetrieveSelectedTimezone() { setupSubject(searchText: "los") setupMockData() let result = subject.retrieveSelectedTimezone(0) let possibleOutcomes = Set(["PDT", "PST"]) XCTAssert(possibleOutcomes.contains(result.timezone.abbreviation ?? "NA"), "Result timezone is actually \(result.timezone.abbreviation ?? "NA")") } func testRetrieveSelectedTimezoneWithEmptySearchField() { // Setup subject with an empty search field setupSubject(searchText: UserDefaultKeys.emptyString) subject.searchTimezones("los") XCTAssertFalse(subject.calculateChangesets()) let mockTimezone = TimezoneData() mockTimezone.timezoneID = "PST" mockTimezone.formattedAddress = "Los Angeles" subject.setFilteredArrayValue([mockTimezone]) subject.searchTimezones("los") XCTAssertFalse(subject.calculateChangesets()) let result = subject.retrieveSelectedTimezone(1) XCTAssert(result.timezone.abbreviation == "GMT") } func testRetrieveSelectedTimezoneWithEmptySearchFieldWithoutSearchResults() { // Setup subject with an empty search field setupSubject(searchText: "los") setupMockData() subject.cleanupFilterArray() let result = subject.retrieveResult(0) XCTAssertNil(result) } } ================================================ FILE: Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreModelKit import EventKit import XCTest @testable import Clocker class StandardMenubarHandlerTests: XCTestCase { private let eventStore = EKEventStore() private let mumbai = ["customLabel": "Ghar", "formattedAddress": "Mumbai", "place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0", "timezoneID": "Asia/Calcutta", "nextUpdate": "", "latitude": "19.0759837", "longitude": "72.8776559"] private func makeMockStore(with menubarMode: Int = 1) -> DataStore { // Wipe all timezones from UserDefaults let defaults = UserDefaults(suiteName: "com.abhishek.Clocker.StandardMenubarHandlerTests")! defaults.set(menubarMode, forKey: UserDefaultKeys.menubarCompactMode) defaults.set(0, forKey: UserDefaultKeys.showMeetingInMenubar) XCTAssertNotEqual(defaults, UserDefaults.standard) return DataStore(with: defaults) } private func saveObject(object: TimezoneData, in store: DataStore, at index: Int = -1) { var defaults = store.timezones() guard let encodedObject = NSKeyedArchiver.clocker_archive(with: object as Any) else { return } index == -1 ? defaults.append(encodedObject) : defaults.insert(encodedObject, at: index) store.setTimezones(defaults) } func testValidStandardMenubarHandler_returnMenubarTitle() { let store = makeMockStore() store.setTimezones(nil) // Save a menubar selected timezone let dataObject = TimezoneData(with: mumbai) dataObject.isFavourite = 1 saveObject(object: dataObject, in: store) let menubarTimezones = store.menubarTimezones() XCTAssertTrue(menubarTimezones?.count == 1, "Count is \(String(describing: menubarTimezones?.count))") } func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() { let store = makeMockStore() // Wipe all timezones from UserDefaults store.setTimezones(nil) // Save a menubar selected timezone let dataObject = TimezoneData(with: mumbai) dataObject.isFavourite = 0 saveObject(object: dataObject, in: store) let menubarTimezones = store.menubarTimezones() XCTAssertTrue(menubarTimezones?.count == 0) } func testUnfavouritedTimezone_returnNilMenubarString() { let store = makeMockStore() // Wipe all timezones from UserDefaults store.setTimezones(nil) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) let emptyMenubarString = menubarHandler.titleForMenubar() // Returns early because DataStore.menubarTimezones is nil XCTAssertNil(emptyMenubarString) // Save a menubar selected timezone let dataObject = TimezoneData(with: mumbai) dataObject.isFavourite = 0 saveObject(object: dataObject, in: store) let menubarString = menubarHandler.titleForMenubar() ?? "" // Test menubar string is absent XCTAssertTrue(menubarString.count == 0) } func testWithEmptyMenubarTimezones() { let store = makeMockStore() store.setTimezones(nil) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) XCTAssertNil(menubarHandler.titleForMenubar()) } func testWithStandardMenubarMode() { // Set mode to standard mode let store = makeMockStore(with: 0) // Save a menubar selected timezone let dataObject = TimezoneData(with: mumbai) dataObject.isFavourite = 1 saveObject(object: dataObject, in: store) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) XCTAssertNil(menubarHandler.titleForMenubar()) } func testProviderPassingAllConditions() { // Set mode to standard mode let store = makeMockStore() // Save a menubar selected timezone let dataObject = TimezoneData(with: mumbai) dataObject.isFavourite = 1 saveObject(object: dataObject, in: store) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) 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, eventStore: EventCenter.sharedCenter()) 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, eventStore: EventCenter.sharedCenter()) 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, eventStore: EventCenter.sharedCenter()) 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, eventStore: EventCenter.sharedCenter()) XCTAssert(menubarHandler.format(event: mockEvent) == UserDefaultKeys.emptyString, "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, eventStore: EventCenter.sharedCenter()) XCTAssert(menubarHandler.format(event: mockEvent) == "Really long calendar event tit... starts now.", "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation") } func testUpcomingEventHappeningInFiveMinutes() throws { let store = makeMockStore() let futureChunk = TimeChunk(seconds: 10, minutes: 5, hours: 0, days: 0, weeks: 0, months: 0, years: 0) let mockEvent = EKEvent(eventStore: eventStore) mockEvent.title = "Event happening" mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore) mockEvent.startDate = Date().add(futureChunk) let eventInfo = EventInfo(event: mockEvent, isAllDay: false, meetingURL: nil, attendeStatus: .accepted) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) let calendar = Calendar.autoupdatingCurrent let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]] let actualResult = try XCTUnwrap(menubarHandler.checkForUpcomingEvents(events, calendar: calendar)) let expectedResult = "Event happening in 5m" XCTAssert(actualResult == expectedResult, "Actual Result \(actualResult)") } func testUpcomingEventHappeningIn29Minutes() throws { let store = makeMockStore() let futureChunk = TimeChunk(seconds: 10, minutes: 29, hours: 0, days: 0, weeks: 0, months: 0, years: 0) let mockEvent = EKEvent(eventStore: eventStore) mockEvent.title = "Event happening" mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore) mockEvent.startDate = Date().add(futureChunk) let eventInfo = EventInfo(event: mockEvent, isAllDay: false, meetingURL: nil, attendeStatus: .accepted) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) let calendar = Calendar.autoupdatingCurrent let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]] let actualResult = try XCTUnwrap(menubarHandler.checkForUpcomingEvents(events, calendar: calendar)) let expectedResult = "Event happening in 29m" XCTAssert(actualResult == expectedResult, "Actual Result \(actualResult)") } func testUpcomingEventHappeningIn31Minutes() throws { let store = makeMockStore() let futureChunk = TimeChunk(seconds: 10, minutes: 31, hours: 0, days: 0, weeks: 0, months: 0, years: 0) let mockEvent = EKEvent(eventStore: eventStore) mockEvent.title = "Event happening" mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore) mockEvent.startDate = Date().add(futureChunk) let eventInfo = EventInfo(event: mockEvent, isAllDay: false, meetingURL: nil, attendeStatus: .accepted) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) let calendar = Calendar.autoupdatingCurrent let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]] XCTAssertNil(menubarHandler.checkForUpcomingEvents(events, calendar: calendar)) } func testUpcomingEventHappeningIn31MinutesWithEmptyEvent() throws { let store = makeMockStore() let futureChunk = TimeChunk(seconds: 10, minutes: 31, hours: 0, days: 0, weeks: 0, months: 0, years: 0) let mockEvent = EKEvent(eventStore: eventStore) mockEvent.startDate = Date().add(futureChunk) mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore) let eventInfo = EventInfo(event: mockEvent, isAllDay: false, meetingURL: nil, attendeStatus: .accepted) let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter()) let calendar = Calendar.autoupdatingCurrent let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]] XCTAssertNil(menubarHandler.checkForUpcomingEvents(events, calendar: calendar)) } } ================================================ FILE: Clocker/ClockerUnitTests/ThemerTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import XCTest @testable import Clocker class ThemerTests: XCTestCase { private struct ThemeExpectations { // Colors let expectedSliderKnobColor: NSColor let expectedSliderRightColor: NSColor let expectedBackgroundColor: NSColor let expectedTextColor: NSColor let expectedTextBackgroundColor: NSColor // Popover Appearance let expectedPopoverApperarance: NSAppearance // Images let expectedShutdownImageName: String let expectedPreferenceImageName: String let expectedPinImageName: String let expectedSunriseImageName: String let expectedSunsetImageName: String let expectedRemoveImageName: String let expectedExtraOptionsImage: String let expectedMenubarOnboardingImage: String let expectedExtraOptionsHighlightedImage: String let expectedSharingImage: String let expectedCurrentLocationImage: String let expectedAddImage: String let expectedPrivacyTabImage: String let expectedAppearanceTabImage: String let expectedCalendarTabImage: String let expectedGeneralTabImage: String let expectedAboutTabImage: String let expectedVideoCallImage: String let expectedFilledTrashImage: String let expectedBackwardsImage: String let expectedForwardsImage: String let expectedResetSliderImage: String } @available(macOS 10.14, *) func testSettingTheme() { // Set to some random number should set to 0 let subject = Themer(index: 124) XCTAssertEqual(NSAppearance(named: .aqua), NSAppearance(named: .aqua)) // Set the same theme; this should return early subject.set(theme: 0) // Set the theme to dark theme subject.set(theme: 1) let expectedApperance = NSAppearance(named: .darkAqua) XCTAssertEqual(expectedApperance, NSApp.appearance) } func testLightTheme() throws { let subject = Themer(index: 0) // 0 is for light theme let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9), expectedSliderRightColor: NSColor.gray, expectedBackgroundColor: NSColor.white, expectedTextColor: NSColor.black, expectedTextBackgroundColor: NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0), expectedPopoverApperarance: NSAppearance(named: NSAppearance.Name.vibrantLight)!, expectedShutdownImageName: "ellipsis.circle", expectedPreferenceImageName: "plus", expectedPinImageName: "macwindow.on.rectangle", expectedSunriseImageName: "sunrise.fill", expectedSunsetImageName: "sunset.fill", expectedRemoveImageName: "xmark.circle", expectedExtraOptionsImage: "Extra", expectedMenubarOnboardingImage: "Light Menubar", expectedExtraOptionsHighlightedImage: "ExtraHighlighted", expectedSharingImage: "doc.on.doc", expectedCurrentLocationImage: "location.fill", expectedAddImage: "plus", expectedPrivacyTabImage: "lock", expectedAppearanceTabImage: "eye", expectedCalendarTabImage: "calendar", expectedGeneralTabImage: "gearshape", expectedAboutTabImage: "info.circle", expectedVideoCallImage: "video.circle.fill", expectedFilledTrashImage: "trash.fill", expectedBackwardsImage: "gobackward.15", expectedForwardsImage: "goforward.15", expectedResetSliderImage: "xmark.circle.fill") testSubject(subject: subject, withExpectatations: expectedThemeElements) } func testDarkTheme() throws { let subject = Themer(index: 1) // 1 is for dark theme let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9), expectedSliderRightColor: NSColor.white, expectedBackgroundColor: NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0), expectedTextColor: NSColor.white, expectedTextBackgroundColor: NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0), expectedPopoverApperarance: NSAppearance(named: NSAppearance.Name.vibrantDark)!, expectedShutdownImageName: "ellipsis.circle", expectedPreferenceImageName: "plus", expectedPinImageName: "macwindow.on.rectangle", expectedSunriseImageName: "sunrise.fill", expectedSunsetImageName: "sunset.fill", expectedRemoveImageName: "xmark.circle", expectedExtraOptionsImage: "ExtraWhite", expectedMenubarOnboardingImage: "Dark Menubar", expectedExtraOptionsHighlightedImage: "ExtraWhiteHighlighted", expectedSharingImage: "doc.on.doc", expectedCurrentLocationImage: "location.fill", expectedAddImage: "plus", expectedPrivacyTabImage: "lock", expectedAppearanceTabImage: "eye", expectedCalendarTabImage: "calendar", expectedGeneralTabImage: "gearshape", expectedAboutTabImage: "info.circle", expectedVideoCallImage: "video.circle.fill", expectedFilledTrashImage: "trash.fill", expectedBackwardsImage: "gobackward.15", expectedForwardsImage: "goforward.15", expectedResetSliderImage: "xmark.circle.fill") testSubject(subject: subject, withExpectatations: expectedThemeElements) XCTAssertEqual(subject.description, "Current Theme is \(Themer.Theme.dark)") } func testSystemTheme() throws { let currentSystemTheme = UserDefaults.standard.string(forKey: UserDefaultKeys.appleInterfaceStyleKey)?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light let subject = Themer(index: 2) // 2 is for system theme let expectedSliderKnobColor = currentSystemTheme == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderRightColor = currentSystemTheme == .dark ? NSColor.white : NSColor.gray let expectedBackgroundColor = currentSystemTheme == .dark ? NSColor.windowBackgroundColor : NSColor.white let expectedTextColor = NSColor.textColor let expectedTextBackgroundColor = currentSystemTheme == .light ? NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) : NSColor.controlBackgroundColor let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: expectedSliderKnobColor, expectedSliderRightColor: expectedSliderRightColor, expectedBackgroundColor: expectedBackgroundColor, expectedTextColor: expectedTextColor, expectedTextBackgroundColor: expectedTextBackgroundColor, expectedPopoverApperarance: NSAppearance.current!, expectedShutdownImageName: "ellipsis.circle", expectedPreferenceImageName: "plus", expectedPinImageName: "macwindow.on.rectangle", expectedSunriseImageName: "sunrise.fill", expectedSunsetImageName: "sunset.fill", expectedRemoveImageName: "xmark.circle", expectedExtraOptionsImage: "Extra Dynamic", expectedMenubarOnboardingImage: "Dynamic Menubar", expectedExtraOptionsHighlightedImage: "ExtraHighlighted Dynamic", expectedSharingImage: "doc.on.doc", expectedCurrentLocationImage: "location.fill", expectedAddImage: "plus", expectedPrivacyTabImage: "lock", expectedAppearanceTabImage: "eye", expectedCalendarTabImage: "calendar", expectedGeneralTabImage: "gearshape", expectedAboutTabImage: "info.circle", expectedVideoCallImage: "video.circle.fill", expectedFilledTrashImage: "trash.fill", expectedBackwardsImage: "gobackward.15", expectedForwardsImage: "goforward.15", expectedResetSliderImage: "xmark.circle.fill") testSubject(subject: subject, withExpectatations: expectedThemeElements) XCTAssertEqual(subject.description, "System Theme is \(currentSystemTheme == .dark ? Themer.Theme.dark : Themer.Theme.light)") } func testSolarizedLightTheme() throws { let subject = Themer(index: 3) // 3 is for solarized light theme let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9), expectedSliderRightColor: NSColor.gray, expectedBackgroundColor: NSColor(deviceRed: 253.0 / 255.0, green: 246.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0), expectedTextColor: NSColor.black, expectedTextBackgroundColor: NSColor(deviceRed: 238.0 / 255.0, green: 232.0 / 255.0, blue: 213.0 / 255.0, alpha: 1.0), expectedPopoverApperarance: NSAppearance(named: NSAppearance.Name.vibrantLight)!, expectedShutdownImageName: "ellipsis.circle", expectedPreferenceImageName: "plus", expectedPinImageName: "macwindow.on.rectangle", expectedSunriseImageName: "sunrise.fill", expectedSunsetImageName: "sunset.fill", expectedRemoveImageName: "xmark.circle", expectedExtraOptionsImage: "Extra", expectedMenubarOnboardingImage: "Light Menubar", expectedExtraOptionsHighlightedImage: "ExtraHighlighted", expectedSharingImage: "doc.on.doc", expectedCurrentLocationImage: "location.fill", expectedAddImage: "plus", expectedPrivacyTabImage: "lock", expectedAppearanceTabImage: "eye", expectedCalendarTabImage: "calendar", expectedGeneralTabImage: "gearshape", expectedAboutTabImage: "info.circle", expectedVideoCallImage: "video.circle.fill", expectedFilledTrashImage: "trash.fill", expectedBackwardsImage: "gobackward.15", expectedForwardsImage: "goforward.15", expectedResetSliderImage: "xmark.circle.fill") testSubject(subject: subject, withExpectatations: expectedThemeElements) } func testSolarizedDarkTheme() throws { let subject = Themer(index: 4) // 4 is for solarized dark theme let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9), expectedSliderRightColor: NSColor.gray, expectedBackgroundColor: NSColor(deviceRed: 7.0 / 255.0, green: 54.0 / 255.0, blue: 66.0 / 255.0, alpha: 1.0), expectedTextColor: NSColor.white, expectedTextBackgroundColor: NSColor(deviceRed: 0.0 / 255.0, green: 43.0 / 255.0, blue: 54.0 / 255.0, alpha: 1.0), expectedPopoverApperarance: NSAppearance(named: NSAppearance.Name.vibrantDark)!, expectedShutdownImageName: "ellipsis.circle", expectedPreferenceImageName: "plus", expectedPinImageName: "macwindow.on.rectangle", expectedSunriseImageName: "sunrise.fill", expectedSunsetImageName: "sunset.fill", expectedRemoveImageName: "xmark.circle", expectedExtraOptionsImage: "ExtraWhite", expectedMenubarOnboardingImage: "Dark Menubar", expectedExtraOptionsHighlightedImage: "ExtraWhiteHighlighted", expectedSharingImage: "doc.on.doc", expectedCurrentLocationImage: "location.fill", expectedAddImage: "plus", expectedPrivacyTabImage: "lock", expectedAppearanceTabImage: "eye", expectedCalendarTabImage: "calendar", expectedGeneralTabImage: "gearshape", expectedAboutTabImage: "info.circle", expectedVideoCallImage: "video.circle.fill", expectedFilledTrashImage: "trash.fill", expectedBackwardsImage: "gobackward.15", expectedForwardsImage: "goforward.15", expectedResetSliderImage: "xmark.circle.fill") testSubject(subject: subject, withExpectatations: expectedThemeElements) } private func testSubject(subject: Themer, withExpectatations expectations: ThemeExpectations) { // Symbol images were introduced in 11.0; Clocker still supports 10.13+ so few asserts below that rely on symbol images will fail. let eligibleOSVersion = ProcessInfo.processInfo.isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 11, minorVersion: 0, patchVersion: 0)) if eligibleOSVersion { XCTAssertEqual(subject.shutdownImage().accessibilityDescription, expectations.expectedShutdownImageName) XCTAssertEqual(subject.preferenceImage().accessibilityDescription, expectations.expectedPreferenceImageName) XCTAssertEqual(subject.pinImage().accessibilityDescription, expectations.expectedPinImageName) XCTAssertEqual(subject.sunriseImage().accessibilityDescription, expectations.expectedSunriseImageName) XCTAssertEqual(subject.sunsetImage().accessibilityDescription, expectations.expectedSunsetImageName) XCTAssertEqual(subject.removeImage().accessibilityDescription, expectations.expectedRemoveImageName) XCTAssertEqual(subject.addImage().accessibilityDescription, expectations.expectedAddImage) XCTAssertEqual(subject.privacyTabImage().accessibilityDescription, expectations.expectedPrivacyTabImage) XCTAssertEqual(subject.appearanceTabImage().accessibilityDescription, expectations.expectedAppearanceTabImage) XCTAssertEqual(subject.calendarTabImage().accessibilityDescription, expectations.expectedCalendarTabImage) XCTAssertEqual(subject.generalTabImage()?.accessibilityDescription, expectations.expectedGeneralTabImage) XCTAssertEqual(subject.aboutTabImage()?.accessibilityDescription, expectations.expectedAboutTabImage) XCTAssertEqual(subject.videoCallImage()?.accessibilityDescription, expectations.expectedVideoCallImage) XCTAssertEqual(subject.filledTrashImage()?.accessibilityDescription, expectations.expectedFilledTrashImage) XCTAssertEqual(subject.goBackwardsImage()?.accessibilityDescription, expectations.expectedBackwardsImage) XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectations.expectedForwardsImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectations.expectedResetSliderImage) XCTAssertEqual(subject.sharingImage().accessibilityDescription, expectations.expectedSharingImage) XCTAssertEqual(subject.currentLocationImage().accessibilityDescription, expectations.expectedCurrentLocationImage) } XCTAssertEqual(subject.sliderKnobColor(), expectations.expectedSliderKnobColor) XCTAssertEqual(subject.sliderRightColor(), expectations.expectedSliderRightColor) XCTAssertEqual(subject.mainBackgroundColor(), expectations.expectedBackgroundColor) XCTAssertEqual(subject.mainTextColor(), expectations.expectedTextColor) XCTAssertEqual(subject.textBackgroundColor(), expectations.expectedTextBackgroundColor) XCTAssertEqual(subject.menubarOnboardingImage().name(), expectations.expectedMenubarOnboardingImage) XCTAssertEqual(subject.popoverAppearance(), expectations.expectedPopoverApperarance) } } ================================================ FILE: Clocker/CoreLoggerKit/.gitignore ================================================ .DS_Store /.build /Packages /*.xcodeproj xcuserdata/ ================================================ FILE: Clocker/CoreLoggerKit/Package.swift ================================================ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CoreLoggerKit", platforms: [ .macOS(.v10_12), ], products: [ .library( name: "CoreLoggerKit", targets: ["CoreLoggerKit"] ), ], dependencies: [], targets: [ .target( name: "CoreLoggerKit", dependencies: [] ), .testTarget( name: "CoreLoggerKitTests", dependencies: ["CoreLoggerKit"] ), ] ) ================================================ FILE: Clocker/CoreLoggerKit/README.md ================================================ # CoreLoggerKit Encompasses two main logging classes. 1. Logger: Essentially logs to console 2. PerfLogger: Uses `signpost` APIs to track time taken to open panel ================================================ FILE: Clocker/CoreLoggerKit/Sources/CoreLoggerKit/Logger.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import os import os.log import os.signpost public class Logger: NSObject { let logObjc = OSLog(subsystem: "com.abhishek.Clocker", category: "app") public class func log(object annotations: [String: Any]?, for event: NSString) { #if DEBUG if #available(OSX 10.14, *) { os_log(.default, "[%@] - [%@]", event, annotations ?? [:]) } #endif } public class func info(_ message: String) { #if DEBUG if #available(OSX 10.14, *) { os_log(.info, "%@", message) } #endif } } @available(OSX 10.14, *) public class PerfLogger: NSObject { static var panelLog = OSLog(subsystem: "com.abhishek.Clocker", category: "Open Panel") static let signpostID = OSSignpostID(log: panelLog) public class func disable() { panelLog = .disabled } public class func startMarker(_ name: StaticString) { os_signpost(.begin, log: panelLog, name: name, signpostID: signpostID) } public class func endMarker(_ name: StaticString) { os_signpost(.end, log: panelLog, name: name, signpostID: signpostID) } } ================================================ FILE: Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/CoreLoggerKitTests.swift ================================================ @testable import CoreLoggerKit import XCTest final class CoreLoggerKitTests: XCTestCase { static var allTests = [ ("testExample", testExample), ] } ================================================ FILE: Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/XCTestManifests.swift ================================================ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ testCase(CoreLoggerKitTests.allTests), ] } #endif ================================================ FILE: Clocker/CoreLoggerKit/Tests/LinuxMain.swift ================================================ import XCTest import CoreLoggerKitTests var tests = [XCTestCaseEntry]() tests += CoreLoggerKitTests.allTests() XCTMain(tests) ================================================ FILE: Clocker/CoreModelKit/.gitignore ================================================ .DS_Store /.build /Packages /*.xcodeproj xcuserdata/ ================================================ FILE: Clocker/CoreModelKit/Package.swift ================================================ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CoreModelKit", platforms: [ .macOS(.v10_12), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "CoreModelKit", targets: ["CoreModelKit"] ), ], dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), .package(path: "../CoreLoggerKit/"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "CoreModelKit", dependencies: ["CoreLoggerKit"] ), .testTarget( name: "CoreModelKitTests", dependencies: ["CoreModelKit", "CoreLoggerKit"] ), ] ) ================================================ FILE: Clocker/CoreModelKit/README.md ================================================ # CoreModelKit A description of this package. ================================================ FILE: Clocker/CoreModelKit/Sources/CoreModelKit/SearchResults.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa public enum ResultStatus { public static let okay = "OK" public static let zeroResults = "ZERO_RESULTS" public static let requestDenied = "REQUEST_DENIED" } public struct SearchResult: Codable { public let results: [Result] public let status: String public let errorMessage: String? public struct Result: Codable { public let addressComponents: [AddressComponent] public let formattedAddress: String public let geometry: Geometry public let placeId: String public let types: [String] private enum CodingKeys: String, CodingKey { case addressComponents = "address_components" case formattedAddress = "formatted_address" case geometry case placeId = "place_id" case types } } public struct Geometry: Codable { public let location: Location public let locationType: String public struct Location: Codable { public let lat: Double public let lng: Double } private enum CodingKeys: String, CodingKey { case locationType = "location_type" case location } } public struct AddressComponent: Codable { public let longName: String public let shortName: String public let types: [String] private enum CodingKeys: String, CodingKey { case longName = "long_name" case shortName = "short_name" case types } } private enum CodingKeys: String, CodingKey { case results case status case errorMessage = "error_message" } } public struct Timezone: Codable { public let dstOffset: Int public let rawOffset: Int public let status: String public let timeZoneId: String public let timeZoneName: String } ================================================ FILE: Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit struct ModelConstants { static let customLabel = "customLabel" static let timezoneName = "formattedAddress" static let placeIdentifier = "place_id" static let timezoneID = "timezoneID" static let emptyString = "" static let latitude = "latitude" static let longitude = "longitude" static let note = "note" } public enum DateFormat { public static let twelveHour = "h:mm a" public static let twelveHourWithSeconds = "h:mm:ss a" public static let twentyFourHour = "HH:mm" public static let twentyFourHourWithSeconds = "HH:mm:ss" public static let twelveHourWithZero = "hh:mm a" public static let twelveHourWithZeroSeconds = "hh:mm:ss a" public static let twelveHourWithoutSuffix = "hh:mm" public static let twelveHourWithoutSuffixAndSeconds = "hh:mm:ss" public static let epochTime = "epoch" } // Non-class type cannot conform to NSCoding! public class TimezoneData: NSObject, NSCoding, NSSecureCoding { public static var supportsSecureCoding: Bool { return true } public enum SelectionType: Int { case city case timezone } public enum DateDisplayType: Int { case panel case menu } public enum TimezoneOverride: Int { case globalFormat = 0 case twelveHourFormat = 1 case twentyFourFormat = 2 case twelveHourWithSeconds = 4 case twentyHourWithSeconds = 5 case twelveHourPrecedingZero = 7 case twelveHourPrecedingZeroSeconds = 8 case twelveHourWithoutSuffix = 10 case twelveHourWithoutSuffixAndSeconds = 11 case epochTime = 12 } static let values = [ NSNumber(integerLiteral: 0): DateFormat.twelveHour, NSNumber(integerLiteral: 1): DateFormat.twentyFourHour, // Seconds NSNumber(integerLiteral: 3): DateFormat.twelveHourWithSeconds, NSNumber(integerLiteral: 4): DateFormat.twentyFourHourWithSeconds, // Preceding Zero NSNumber(integerLiteral: 6): DateFormat.twelveHourWithZero, NSNumber(integerLiteral: 7): DateFormat.twelveHourWithZeroSeconds, // Suffix NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix, NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds, NSNumber(integerLiteral: 11): DateFormat.epochTime, ] public var customLabel: String? public var formattedAddress: String? public var placeID: String? public var timezoneID: String? = ModelConstants.emptyString public var latitude: Double? public var longitude: Double? public var note: String? = ModelConstants.emptyString public var nextUpdate: Date? = Date() public var sunriseTime: Date? public var sunsetTime: Date? public var isFavourite: Int = 0 public var isSunriseOrSunset = false public var selectionType: SelectionType = .city public var isSystemTimezone = false public var overrideFormat: TimezoneOverride = .globalFormat override public init() { selectionType = .timezone isFavourite = 0 note = ModelConstants.emptyString isSystemTimezone = false overrideFormat = .globalFormat placeID = UUID().uuidString } public init(with dictionary: [String: Any]) { customLabel = dictionary[ModelConstants.customLabel] as? String timezoneID = (dictionary[ModelConstants.timezoneID] as? String) ?? "Error" latitude = dictionary[ModelConstants.latitude] as? Double ?? -0.0 longitude = dictionary[ModelConstants.longitude] as? Double ?? -0.0 placeID = (dictionary[ModelConstants.placeIdentifier] as? String) ?? "Error" formattedAddress = (dictionary[ModelConstants.timezoneName] as? String) ?? "Error" isFavourite = 0 selectionType = .city note = (dictionary[ModelConstants.note] as? String) ?? ModelConstants.emptyString isSystemTimezone = false overrideFormat = .globalFormat } public required init?(coder aDecoder: NSCoder) { customLabel = aDecoder.decodeObject(forKey: "customLabel") as? String formattedAddress = aDecoder.decodeObject(forKey: "formattedAddress") as? String placeID = aDecoder.decodeObject(forKey: "place_id") as? String timezoneID = aDecoder.decodeObject(forKey: "timezoneID") as? String latitude = aDecoder.decodeObject(forKey: "latitude") as? Double longitude = aDecoder.decodeObject(forKey: "longitude") as? Double note = aDecoder.decodeObject(forKey: "note") as? String nextUpdate = aDecoder.decodeObject(forKey: "nextUpdate") as? Date sunriseTime = aDecoder.decodeObject(forKey: "sunriseTime") as? Date sunsetTime = aDecoder.decodeObject(forKey: "sunsetTime") as? Date isFavourite = aDecoder.decodeInteger(forKey: "isFavourite") let selection = aDecoder.decodeInteger(forKey: "selectionType") selectionType = SelectionType(rawValue: selection)! isSystemTimezone = aDecoder.decodeBool(forKey: "isSystemTimezone") let override = aDecoder.decodeInteger(forKey: "overrideFormat") overrideFormat = TimezoneOverride(rawValue: override)! } public class func customObject(from encodedData: Data?) -> TimezoneData? { guard let dataObject = encodedData else { return TimezoneData() } if let timezoneObject = NSKeyedUnarchiver.unarchiveObject(with: dataObject) as? TimezoneData { return timezoneObject } return nil } public func encode(with aCoder: NSCoder) { aCoder.encode(placeID, forKey: "place_id") aCoder.encode(formattedAddress, forKey: "formattedAddress") aCoder.encode(customLabel, forKey: "customLabel") aCoder.encode(timezoneID, forKey: "timezoneID") aCoder.encode(nextUpdate, forKey: "nextUpdate") aCoder.encode(latitude, forKey: "latitude") aCoder.encode(longitude, forKey: "longitude") aCoder.encode(isFavourite, forKey: "isFavourite") aCoder.encode(sunriseTime, forKey: "sunriseTime") aCoder.encode(sunsetTime, forKey: "sunsetTime") aCoder.encode(selectionType.rawValue, forKey: "selectionType") aCoder.encode(note, forKey: "note") aCoder.encode(isSystemTimezone, forKey: "isSystemTimezone") aCoder.encode(overrideFormat.rawValue, forKey: "overrideFormat") } public func formattedTimezoneLabel() -> String { // First check if there's an user preferred custom label set if let label = customLabel, !label.isEmpty { return label } // No custom label, return the formatted address/timezone if let address = formattedAddress, !address.isEmpty { return address } // No formatted address, return the timezoneID if let timezone = timezoneID, !timezone.isEmpty { let hashSeperatedString = timezone.components(separatedBy: "/") // Return the second component! if let first = hashSeperatedString.first { return first } // Second component not available, return the whole thing! return timezone } // Return error return "Error" } public func setLabel(_ label: String) { customLabel = !label.isEmpty ? label : ModelConstants.emptyString } public func setShouldOverrideGlobalTimeFormat(_ shouldOverride: Int) { if shouldOverride == 0 { overrideFormat = .globalFormat } else if shouldOverride == 1 { overrideFormat = .twelveHourFormat } else if shouldOverride == 2 { overrideFormat = .twentyFourFormat } else if shouldOverride == 4 { overrideFormat = .twelveHourWithSeconds } else if shouldOverride == 5 { overrideFormat = .twentyHourWithSeconds } else if shouldOverride == 7 { overrideFormat = .twelveHourPrecedingZero } else if shouldOverride == 8 { overrideFormat = .twelveHourPrecedingZeroSeconds } else if shouldOverride == 10 { overrideFormat = .twelveHourWithoutSuffix } else if shouldOverride == 11 { overrideFormat = .twelveHourWithoutSuffixAndSeconds } else if shouldOverride == 12 { overrideFormat = .epochTime } else { Logger.info("Chosen a wrong timezone format: \(shouldOverride)") } } public func timezone() -> String { if isSystemTimezone { timezoneID = TimeZone.autoupdatingCurrent.identifier formattedAddress = TimeZone.autoupdatingCurrent.identifier return TimeZone.autoupdatingCurrent.identifier } if let timezone = timezoneID { return timezone } return TimeZone.autoupdatingCurrent.identifier } public func timezoneFormat(_ currentFormat: NSNumber) -> String { let chosenDefault = currentFormat let timeFormat = TimezoneData.values[chosenDefault] ?? DateFormat.twelveHour switch overrideFormat { case .globalFormat: return timeFormat case .twelveHourFormat: return DateFormat.twelveHour case .twentyFourFormat: return DateFormat.twentyFourHour case .twelveHourWithSeconds: return DateFormat.twelveHourWithSeconds case .twentyHourWithSeconds: return DateFormat.twentyFourHourWithSeconds case .twelveHourPrecedingZero: return DateFormat.twelveHourWithZero case .twelveHourPrecedingZeroSeconds: return DateFormat.twelveHourWithZeroSeconds case .twelveHourWithoutSuffix: return DateFormat.twelveHourWithoutSuffix case .twelveHourWithoutSuffixAndSeconds: return DateFormat.twelveHourWithoutSuffixAndSeconds case .epochTime: return DateFormat.epochTime } } public func shouldShowSeconds(_ currentFormat: NSNumber) -> Bool { if overrideFormat == .globalFormat { let formatInString = TimezoneData.values[currentFormat] ?? DateFormat.twelveHour return formatInString.contains("ss") } // We subtract 1 because the timezone format in the dropdown contains 1 extra row for "Respecting global preferences" let key = NSNumber(integerLiteral: overrideFormat.rawValue - 1) let formatInString = TimezoneData.values[key] ?? DateFormat.twelveHour return formatInString.contains("ss") } public func isDaylightSavings() -> Bool { guard let timezone = TimeZone(abbreviation: timezone()) else { return false } return timezone.isDaylightSavingTime(for: Date()) } override public var hash: Int { guard let placeIdentifier = placeID, let timezone = timezoneID else { return -1 } return placeIdentifier.hashValue ^ timezone.hashValue } override public func isEqual(_ object: Any?) -> Bool { guard let compared = object as? TimezoneData else { return false } // Plain timezones might have similar placeID. Adding another check for timezone identifier. return placeID == compared.placeID && timezoneID == compared.timezoneID } } public extension TimezoneData { override var description: String { return objectDescription() } override var debugDescription: String { return objectDescription() } private func objectDescription() -> String { let customString = """ TimezoneID: \(String(describing: timezoneID)) Formatted Address: \(formattedAddress ?? "Error") Custom Label: \(customLabel ?? "Error") Latitude: \(latitude ?? -0.0) Longitude: \(longitude ?? -0.0) Place Identifier: \(String(describing: placeID)) Is Favourite: \(isFavourite) Sunrise Time: \(String(describing: sunriseTime)) Sunset Time: \(String(describing: sunsetTime)) Selection Type: \(selectionType.rawValue) Note: \(String(describing: note)) Is System Timezone: \(isSystemTimezone) Override: \(overrideFormat) """ return customString } } ================================================ FILE: Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreModelKit import Foundation import XCTest class TimezoneDataEqualityTests: XCTestCase { func testEqualityWhenTimezoneIdentifiersDiffer() { let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.formattedAddress = "SameLabel" let timezone2 = TimezoneData() timezone2.timezoneID = "Africa/Banjul" timezone2.formattedAddress = "SameLabel" XCTAssertFalse(timezone1 == timezone2) // Test == XCTAssertNotEqual(timezone1, timezone2) // Test isEqual } func testEqualityWhenTimezonesLabelsDiffer() { let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.formattedAddress = "SameLabel" let timezone2 = TimezoneData() timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone2.formattedAddress = "DifferentLabel" XCTAssertFalse(timezone1 == timezone2) XCTAssertNotEqual(timezone1, timezone2) } func testEqualityWhenTimezonesPlaceIDsAreSame() { let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.placeID = "SamplePlaceID" timezone1.formattedAddress = "SameLabel" let timezone2 = TimezoneData() timezone2.placeID = "SamplePlaceID" timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone2.formattedAddress = "DifferentLabel" XCTAssertTrue(timezone1 == timezone2) XCTAssertEqual(timezone1, timezone2) } func testEqualityWhenTimezonesPlaceIDsDiffer() { let timezone1 = TimezoneData() timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone1.placeID = "SamplePlaceID1" timezone1.formattedAddress = "SameLabel" let timezone2 = TimezoneData() timezone2.placeID = "SamplePlaceID2" timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier timezone2.formattedAddress = "DifferentLabel" XCTAssertFalse(timezone1 == timezone2) XCTAssertNotEqual(timezone1, timezone2) } } ================================================ FILE: Clocker/CoreModelKit/Tests/CoreModelKitTests/XCTestManifests.swift ================================================ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ testCase(CoreModelKitTests.allTests), ] } #endif ================================================ FILE: Clocker/CoreModelKit/Tests/LinuxMain.swift ================================================ import XCTest import CoreModelKitTests var tests = [XCTestCaseEntry]() tests += CoreModelKitTests.allTests() XCTMain(tests) ================================================ FILE: Clocker/Dependencies/Date Additions/Constants.swift ================================================ // // Constants.swift // DateTools // // Created by Grayson Webster on 8/17/16. // Copyright © 2016 Grayson Webster. All rights reserved. // import Foundation /** * Time conversions used across DateTools */ public class Constants { public static let SecondsInYear: TimeInterval = 31_536_000 public static let SecondsInLeapYear: TimeInterval = 31_622_400 public static let SecondsInMonth28: TimeInterval = 2_419_200 public static let SecondsInMonth29: TimeInterval = 2_505_600 public static let SecondsInMonth30: TimeInterval = 2_592_000 public static let SecondsInMonth31: TimeInterval = 2_678_400 public static let SecondsInWeek: TimeInterval = 604_800 public static let SecondsInDay: TimeInterval = 86400 public static let SecondsInHour: TimeInterval = 3600 public static let SecondsInMinute: TimeInterval = 60 public static let MillisecondsInDay: TimeInterval = 86_400_000 public static let AllCalendarUnitFlags: Set = [.year, .quarter, .month, .weekOfYear, .weekOfMonth, .day, .hour, .minute, .second, .era, .weekday, .weekdayOrdinal, .weekOfYear] } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Bundle.swift ================================================ // // Date+Bundle.swift // DateTools // // Created by Matthew York on 8/26/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation public extension Bundle { class func dateToolsBundle() -> Bundle { let assetPath = Bundle(for: Constants.self).resourcePath! return Bundle(path: NSString(string: assetPath).appendingPathComponent("DateTools.bundle"))! } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Comparators.swift ================================================ // // Date+Comparators.swift // DateToolsTests // // Created by Matthew York on 8/26/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding methods for calculating the chunk * of time between two dates and providing many variables and functions * that compare the ordinality of two dates and the space between two dates * for a given unit of time. */ public extension Date { // MARK: - Comparisons /** * Given a date, returns a `TimeChunk` with components in their most natural form. Example: * * ``` * let formatter = DateFormatter() * formatter.dateFormat = "yyyy MM dd HH:mm:ss.SSS" * let birthday = formatter.date(from: "2015 11 24 14:50:12.000")! * let age = birthday.chunkBetween(date: formatter.date(from: "2016 10 07 15:27:12.000")!) * ``` * * The age variable will have a chunk of time with year, month, day, hour, minute, * and second components (note that we do not use weeks since they are not components * of `Calendar`). So if you just wanted the age in years, you could then say: age.years. * * The chunk is calculated exactly as you'd say it in real life, always converting up * when a lower unit equals 1 of the unit above it. The above example returns * `TimeChunk(seconds: 0, minutes: 37, hours: 0, days: 13, weeks: 0, months: 10, years: 0)`. * * Passing a future date returns a TimeChunk with all positive components and passing * a date in the past returns one with all negative components. * * - parameter date: The date of reference from the date called on * * - returns: A TimeChunk representing the time between the dates, in natural form */ func chunkBetween(date: Date) -> TimeChunk { let components: Set = [.year, .month, .day, .hour, .minute, .second] let compenentsBetween = Calendar.autoupdatingCurrent.dateComponents(components, from: self, to: date) return TimeChunk(seconds: compenentsBetween.second!, minutes: compenentsBetween.minute!, hours: compenentsBetween.hour!, days: compenentsBetween.day!, weeks: 0, months: compenentsBetween.month!, years: compenentsBetween.year!) // TimeChunk(seconds: secondDelta, minutes: minuteDelta, hours: hourDelta, days: dayDelta, weeks: 0, months: monthDelta, years: yearDelta) } /** * Returns a true if receiver is equal to provided comparison date, otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func equals(_ date: Date) -> Bool { return compare(date) == .orderedSame } /** * Returns a true if receiver is later than provided comparison date, otherwise * returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isLater(than date: Date) -> Bool { return compare(date) == .orderedDescending } /** * Returns a true if receiver is later than or equal to provided comparison date, * otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isLaterThanOrEqual(to date: Date) -> Bool { return compare(date) == .orderedDescending || compare(date) == .orderedSame } /** * Returns a true if receiver is earlier than provided comparison date, otherwise * returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isEarlier(than date: Date) -> Bool { return compare(date) == .orderedAscending } /** * Returns a true if receiver is earlier than or equal to the provided comparison date, * otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isEarlierThanOrEqual(to date: Date) -> Bool { return compare(date) == .orderedAscending || compare(date) == .orderedSame } /** * Returns whether two dates fall on the same day. * * - parameter date: Date to compare with sender * * - returns: True if both paramter dates fall on the same day, false otherwise */ func isSameDay(date: Date) -> Bool { return Date.isSameDay(date: self, as: date) } /** * Returns whether two dates fall on the same day. * * - parameter date: First date to compare * - parameter compareDate: Second date to compare * * - returns: True if both paramter dates fall on the same day, false otherwise */ static func isSameDay(date: Date, as compareDate: Date) -> Bool { let calendar = Calendar.autoupdatingCurrent var components = calendar.dateComponents([.era, .year, .month, .day], from: date) let dateOne = calendar.date(from: components) components = calendar.dateComponents([.era, .year, .month, .day], from: compareDate) let dateTwo = calendar.date(from: components) return (dateOne?.equals(dateTwo!))! } // MARK: - Date Comparison // MARK: Time From /** * Returns an Int representing the amount of time in years between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The years between receiver and provided date */ func years(from date: Date) -> Int { return years(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in months between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The years between receiver and provided date */ func months(from date: Date) -> Int { return months(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in weeks between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The weeks between receiver and provided date */ func weeks(from date: Date) -> Int { return weeks(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in days between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The days between receiver and provided date */ func days(from date: Date) -> Int { return days(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in hours between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The hours between receiver and provided date */ func hours(from date: Date) -> Int { return Int(timeIntervalSince(date) / Constants.SecondsInHour) } /** * Returns an Int representing the amount of time in minutes between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The minutes between receiver and provided date */ func minutes(from date: Date) -> Int { return Int(timeIntervalSince(date) / Constants.SecondsInMinute) } /** * Returns an Int representing the amount of time in seconds between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The seconds between receiver and provided date */ func seconds(from date: Date) -> Int { return Int(timeIntervalSince(date)) } // MARK: Time From With Calendar /** * Returns an Int representing the amount of time in years between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The years between receiver and provided date */ func years(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.year], from: earliest, to: latest) return multiplier * components.year! } /** * Returns an Int representing the amount of time in months between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The months between receiver and provided date */ func months(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest) return multiplier * (components.month! + 12 * components.year!) } /** * Returns an Int representing the amount of time in weeks between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The weeks between receiver and provided date */ func weeks(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest) return multiplier * components.weekOfYear! } /** * Returns an Int representing the amount of time in days between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The days between receiver and provided date */ func days(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest) return multiplier * components.day! } // MARK: Time Until /** * The number of years until the receiver's date (0 if the receiver is the same or * earlier than now). */ var yearsUntil: Int { return yearsLater(than: Date()) } /** * The number of months until the receiver's date (0 if the receiver is the same or * earlier than now). */ var monthsUntil: Int { return monthsLater(than: Date()) } /** * The number of weeks until the receiver's date (0 if the receiver is the same or * earlier than now). */ var weeksUntil: Int { return weeksLater(than: Date()) } /** * The number of days until the receiver's date (0 if the receiver is the same or * earlier than now). */ var daysUntil: Int { return daysLater(than: Date()) } /** * The number of hours until the receiver's date (0 if the receiver is the same or * earlier than now). */ var hoursUntil: Int { return hoursLater(than: Date()) } /** * The number of minutes until the receiver's date (0 if the receiver is the same or * earlier than now). */ var minutesUntil: Int { return minutesLater(than: Date()) } /** * The number of seconds until the receiver's date (0 if the receiver is the same or * earlier than now). */ var secondsUntil: Int { return secondsLater(than: Date()) } // MARK: Time Ago /** * The number of years the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var yearsAgo: Int { return yearsEarlier(than: Date()) } /** * The number of months the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var monthsAgo: Int { return monthsEarlier(than: Date()) } /** * The number of weeks the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var weeksAgo: Int { return weeksEarlier(than: Date()) } /** * The number of days the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var daysAgo: Int { return daysEarlier(than: Date()) } /** * The number of hours the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var hoursAgo: Int { return hoursEarlier(than: Date()) } /** * The number of minutes the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var minutesAgo: Int { return minutesEarlier(than: Date()) } /** * The number of seconds the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var secondsAgo: Int { return secondsEarlier(than: Date()) } // MARK: Earlier Than /** * Returns the number of years the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of years */ func yearsEarlier(than date: Date) -> Int { return abs(min(years(from: date), 0)) } /** * Returns the number of months the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of months */ func monthsEarlier(than date: Date) -> Int { return abs(min(months(from: date), 0)) } /** * Returns the number of weeks the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of weeks */ func weeksEarlier(than date: Date) -> Int { return abs(min(weeks(from: date), 0)) } /** * Returns the number of days the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of days */ func daysEarlier(than date: Date) -> Int { return abs(min(days(from: date), 0)) } /** * Returns the number of hours the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of hours */ func hoursEarlier(than date: Date) -> Int { return abs(min(hours(from: date), 0)) } /** * Returns the number of minutes the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of minutes */ func minutesEarlier(than date: Date) -> Int { return abs(min(minutes(from: date), 0)) } /** * Returns the number of seconds the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of seconds */ func secondsEarlier(than date: Date) -> Int { return abs(min(seconds(from: date), 0)) } // MARK: Later Than /** * Returns the number of years the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of years */ func yearsLater(than date: Date) -> Int { return max(years(from: date), 0) } /** * Returns the number of months the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of months */ func monthsLater(than date: Date) -> Int { return max(months(from: date), 0) } /** * Returns the number of weeks the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of weeks */ func weeksLater(than date: Date) -> Int { return max(weeks(from: date), 0) } /** * Returns the number of days the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of days */ func daysLater(than date: Date) -> Int { return max(days(from: date), 0) } /** * Returns the number of hours the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of hours */ func hoursLater(than date: Date) -> Int { return max(hours(from: date), 0) } /** * Returns the number of minutes the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of minutes */ func minutesLater(than date: Date) -> Int { return max(minutes(from: date), 0) } /** * Returns the number of seconds the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of seconds */ func secondsLater(than date: Date) -> Int { return max(seconds(from: date), 0) } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Components.swift ================================================ // // Date+Components.swift // DateToolsTests // // Created by Matthew York on 8/26/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding convenient accessors of calendar * components. Meta information about the date is also accessible via * several computed Bools. */ public extension Date { /** * Convenient accessor of the date's `Calendar` components. * * - parameter component: The calendar component to access from the date * * - returns: The value of the component * */ func component(_ component: Calendar.Component) -> Int { let calendar = Calendar.autoupdatingCurrent return calendar.component(component, from: self) } /** * Convenient accessor of the date's `Calendar` components ordinality. * * - parameter smaller: The smaller calendar component to access from the date * - parameter larger: The larger calendar component to access from the date * * - returns: The ordinal number of a smaller calendar component within a specified larger calendar component * */ func ordinality(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? { let calendar = Calendar.autoupdatingCurrent return calendar.ordinality(of: smaller, in: larger, for: self) } /** * Use calendar components to determine how many units of a smaller component are inside 1 larger unit. * * Ex. If used on a date in the month of February in a leap year (regardless of the day), the method would * return 29 days. * * - parameter smaller: The smaller calendar component to access from the date * - parameter larger: The larger calendar component to access from the date * * - returns: The number of smaller units required to equal in 1 larger unit, given the date called on * */ func unit(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? { let calendar = Calendar.autoupdatingCurrent var units = 1 var unitRange: Range? if larger.hashValue < smaller.hashValue { for x in larger.hashValue ..< smaller.hashValue { var stepLarger: Calendar.Component var stepSmaller: Calendar.Component switch x { case 0: stepLarger = Calendar.Component.era stepSmaller = Calendar.Component.year unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) case 1: if smaller.hashValue > 2 { break } else { stepLarger = Calendar.Component.year stepSmaller = Calendar.Component.month unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) } case 2: if larger.hashValue < 2 { if isInLeapYear { unitRange = Range(uncheckedBounds: (lower: 0, upper: 366)) } else { unitRange = Range(uncheckedBounds: (lower: 0, upper: 365)) } } else { stepLarger = Calendar.Component.month stepSmaller = Calendar.Component.day unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) } case 3: stepLarger = Calendar.Component.day stepSmaller = Calendar.Component.hour unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) case 4: stepLarger = Calendar.Component.hour stepSmaller = Calendar.Component.minute unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) case 5: stepLarger = Calendar.Component.minute stepSmaller = Calendar.Component.second unitRange = calendar.range(of: stepSmaller, in: stepLarger, for: self) default: return nil } if unitRange?.count != nil { units *= (unitRange?.count)! } } return units } return nil } // MARK: - Components /** * Convenience getter for the date's `era` component */ var era: Int { return component(.era) } /** * Convenience getter for the date's `year` component */ var year: Int { return component(.year) } /** * Convenience getter for the date's `month` component */ var month: Int { return component(.month) } /** * Convenience getter for the date's `week` component */ var week: Int { return component(.weekday) } /** * Convenience getter for the date's `day` component */ var day: Int { return component(.day) } /** * Convenience getter for the date's `hour` component */ var hour: Int { return component(.hour) } /** * Convenience getter for the date's `minute` component */ var minute: Int { return component(.minute) } /** * Convenience getter for the date's `second` component */ var second: Int { return component(.second) } /** * Convenience getter for the date's `weekday` component */ var weekday: Int { return component(.weekday) } /** * Convenience getter for the date's `weekdayOrdinal` component */ var weekdayOrdinal: Int { return component(.weekdayOrdinal) } /** * Convenience getter for the date's `quarter` component */ var quarter: Int { return component(.quarter) } /** * Convenience getter for the date's `weekOfYear` component */ var weekOfMonth: Int { return component(.weekOfMonth) } /** * Convenience getter for the date's `weekOfYear` component */ var weekOfYear: Int { return component(.weekOfYear) } /** * Convenience getter for the date's `yearForWeekOfYear` component */ var yearForWeekOfYear: Int { return component(.yearForWeekOfYear) } /** * Convenience getter for the date's `daysInMonth` component */ var daysInMonth: Int { let calendar = Calendar.autoupdatingCurrent let days = calendar.range(of: .day, in: .month, for: self) return days!.count } // MARK: - Set Components /** * Convenience setter for the date's `year` component */ mutating func year(_ year: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } /** * Convenience setter for the date's `month` component */ mutating func month(_ month: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } /** * Convenience setter for the date's `day` component */ mutating func day(_ day: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } /** * Convenience setter for the date's `hour` component */ mutating func hour(_ hour: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } /** * Convenience setter for the date's `minute` component */ mutating func minute(_ minute: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } /** * Convenience setter for the date's `second` component */ mutating func second(_ second: Int) { self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second) } // MARK: - Bools /** * Determine if date is in a leap year */ var isInLeapYear: Bool { let yearComponent = component(.year) if yearComponent % 400 == 0 { return true } if yearComponent % 100 == 0 { return false } if yearComponent % 4 == 0 { return true } return false } /** * Determine if date is within the current day */ var isToday: Bool { let calendar = Calendar.autoupdatingCurrent return calendar.isDateInToday(self) } /** * Determine if date is within the day tomorrow */ var isTomorrow: Bool { let calendar = Calendar.autoupdatingCurrent return calendar.isDateInTomorrow(self) } /** * Determine if date is within yesterday */ var isYesterday: Bool { let calendar = Calendar.autoupdatingCurrent return calendar.isDateInYesterday(self) } /** * Determine if date is in a weekend */ var isWeekend: Bool { if weekday == 7 || weekday == 1 { return true } return false } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Format.swift ================================================ // // Date+Format.swift // DateToolsTests // // Created by Matthew York on 8/23/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding convenience methods for formatting dates. */ public extension Date { // MARK: - Formatted Date - Style /** * Get string representation of date. * * - parameter dateStyle: The date style in which to represent the date * - parameter timeZone: The time zone of the date * - parameter locale: Encapsulates information about linguistic, cultural, and technological conventions and standards * * - returns: Represenation of the date (self) in the specified format */ func format(with dateStyle: DateFormatter.Style, timeZone: TimeZone, locale: Locale) -> String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = dateStyle dateFormatter.timeZone = timeZone dateFormatter.locale = locale return dateFormatter.string(from: self) } /** * Get string representation of date. Locale is automatically selected as the current locale of the system. * * - parameter dateStyle: The date style in which to represent the date * - parameter timeZone: The time zone of the date * * - returns String? - Represenation of the date (self) in the specified format */ func format(with dateStyle: DateFormatter.Style, timeZone: TimeZone) -> String { #if os(Linux) return format(with: dateStyle, timeZone: timeZone, locale: Locale.current) #else return format(with: dateStyle, timeZone: timeZone, locale: Locale.autoupdatingCurrent) #endif } /** * Get string representation of date. * Time zone is automatically selected as the current time zone of the system. * * - parameter dateStyle: The date style in which to represent the date * - parameter locale: Encapsulates information about linguistic, cultural, and technological conventions and standards. * * - returns: Represenation of the date (self) in the specified format */ func format(with dateStyle: DateFormatter.Style, locale: Locale) -> String { return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: locale) } /** * Get string representation of date. * Locale and time zone are automatically selected as the current locale and time zone of the system. * * - parameter dateStyle: The date style in which to represent the date * * - returns: Represenation of the date (self) in the specified format */ func format(with dateStyle: DateFormatter.Style) -> String { #if os(Linux) return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.current) #else return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent) #endif } // MARK: - Formatted Date - String /** * Get string representation of date. * * - parameter dateFormat: The date format string, according to Apple's date formatting guide in which to represent the date * - parameter timeZone: The time zone of the date * - parameter locale: Encapsulates information about linguistic, cultural, and technological conventions and standards * * - returns: Represenation of the date (self) in the specified format */ func format(with dateFormat: String, timeZone: TimeZone, locale: Locale) -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = dateFormat dateFormatter.timeZone = timeZone dateFormatter.locale = locale return dateFormatter.string(from: self) } /** * Get string representation of date. * Locale is automatically selected as the current locale of the system. * * - parameter dateFormat: The date format string, according to Apple's date formatting guide in which to represent the date * - parameter timeZone: The time zone of the date * * - returns: Representation of the date (self) in the specified format */ func format(with dateFormat: String, timeZone: TimeZone) -> String { #if os(Linux) return format(with: dateFormat, timeZone: timeZone, locale: Locale.current) #else return format(with: dateFormat, timeZone: timeZone, locale: Locale.autoupdatingCurrent) #endif } /** * Get string representation of date. * Time zone is automatically selected as the current time zone of the system. * * - parameter dateFormat: The date format string, according to Apple's date formatting guide in which to represent the date * - parameter locale: Encapsulates information about linguistic, cultural, and technological conventions and standards * * - returns: Represenation of the date (self) in the specified format */ func format(with dateFormat: String, locale: Locale) -> String { return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: locale) } /** * Get string representation of date. * Locale and time zone are automatically selected as the current locale and time zone of the system. * * - parameter dateFormat: The date format string, according to Apple's date formatting guide in which to represent the date * * - returns: Represenation of the date (self) in the specified format */ func format(with dateFormat: String) -> String { #if os(Linux) return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.current) #else return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent) #endif } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Inits.swift ================================================ // // Date+DateTools.swift // DateTools // // Created by Grayson Webster on 8/17/16. // Copyright © 2016 Grayson Webster. All rights reserved. // import Foundation /** * Extends the Date class by adding convenient initializers based on components * and format strings. */ public extension Date { // MARK: - Initializers /** * Init date with components. * * - parameter year: Year component of new date * - parameter month: Month component of new date * - parameter day: Day component of new date * - parameter hour: Hour component of new date * - parameter minute: Minute component of new date * - parameter second: Second component of new date */ init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) { var dateComponents = DateComponents() dateComponents.year = year dateComponents.month = month dateComponents.day = day dateComponents.hour = hour dateComponents.minute = minute dateComponents.second = second guard let date = Calendar.current.date(from: dateComponents) else { self = Date() return } self = date } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+Manipulations.swift ================================================ // // Date+Manipulations.swift // DateToolsTests // // Created by Grayson Webster on 9/28/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding manipulation methods for transforming dates */ public extension Date { // MARK: - StartOf /** * Return a date set to the start of a given component. * * - parameter component: The date component (second, minute, hour, day, month, or year) * * - returns: A date retaining the value of the given component and all larger components, * with all smaller components set to their minimum */ func start(of component: Component) -> Date { var newDate = self if component == .second { newDate.second(second) } else if component == .minute { newDate.second(0) } else if component == .hour { newDate.second(0) newDate.minute(0) } else if component == .day { newDate.second(0) newDate.minute(0) newDate.hour(0) } else if component == .month { newDate.second(0) newDate.minute(0) newDate.hour(0) newDate.day(1) } else if component == .year { newDate.second(0) newDate.minute(0) newDate.hour(0) newDate.day(1) newDate.month(1) } return newDate } /** * Return a date set to the end of a given component. * * - parameter component: The date component (second, minute, hour, day, month, or year) * * - returns: A date retaining the value of the given component and all larger components, * with all smaller components set to their maximum */ func end(of component: Component) -> Date { var newDate = self if component == .second { newDate.second(newDate.second + 1) newDate -= 0.001 } else if component == .minute { newDate.second(60) newDate -= 0.001 } else if component == .hour { newDate.second(60) newDate -= 0.001 newDate.minute(59) } else if component == .day { newDate.second(60) newDate -= 0.001 newDate.minute(59) newDate.hour(23) } else if component == .month { newDate.second(60) newDate -= 0.001 newDate.minute(59) newDate.hour(23) newDate.day(daysInMonth(date: newDate)) } else if component == .year { newDate.second(60) newDate -= 0.001 newDate.minute(59) newDate.hour(23) newDate.month(12) newDate.day(31) } return newDate } func daysInMonth(date: Date) -> Int { let month = date.month if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 { // 31 day month return 31 } else if month == 2, date.isInLeapYear { // February with leap year return 29 } else if month == 2, !date.isInLeapYear { // February without leap year return 28 } else { // 30 day month return 30 } } // MARK: - Addition / Subtractions /** * # Add (TimeChunk to Date) * Increase a date by the value of a given `TimeChunk`. * * - parameter chunk: The amount to increase the date by (ex. 2.days, 4.years, etc.) * * - returns: A date with components increased by the values of the * corresponding `TimeChunk` variables */ func add(_ chunk: TimeChunk) -> Date { let calendar = Calendar.autoupdatingCurrent var components = DateComponents() components.year = chunk.years components.month = chunk.months components.day = chunk.days + (chunk.weeks * 7) components.hour = chunk.hours components.minute = chunk.minutes components.second = chunk.seconds return calendar.date(byAdding: components, to: self)! } /** * # Subtract (TimeChunk from Date) * Decrease a date by the value of a given `TimeChunk`. * * - parameter chunk: The amount to decrease the date by (ex. 2.days, 4.years, etc.) * * - returns: A date with components decreased by the values of the * corresponding `TimeChunk` variables */ func subtract(_ chunk: TimeChunk) -> Date { let calendar = Calendar.autoupdatingCurrent var components = DateComponents() components.year = -chunk.years components.month = -chunk.months components.day = -(chunk.days + (chunk.weeks * 7)) components.hour = -chunk.hours components.minute = -chunk.minutes components.second = -chunk.seconds return calendar.date(byAdding: components, to: self)! } // MARK: - Operator Overloads /** * Operator overload for adding a `TimeChunk` to a date. */ static func + (leftAddend: Date, rightAddend: TimeChunk) -> Date { return leftAddend.add(rightAddend) } /** * Operator overload for subtracting a `TimeChunk` from a date. */ static func - (minuend: Date, subtrahend: TimeChunk) -> Date { return minuend.subtract(subtrahend) } /** * Operator overload for adding a `TimeInterval` to a date. */ static func + (leftAddend: Date, rightAddend: Int) -> Date { return leftAddend.addingTimeInterval(TimeInterval(rightAddend)) } /** * Operator overload for subtracting a `TimeInterval` from a date. */ static func - (minuend: Date, subtrahend: Int) -> Date { return minuend.addingTimeInterval(-TimeInterval(subtrahend)) } } ================================================ FILE: Clocker/Dependencies/Date Additions/Date+TimeAgo.swift ================================================ // // Date+TimeAgo.swift // DateToolsTests // // Created by Matthew York on 8/23/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding convenient methods to display the passage of * time in String format. */ public extension Date { // MARK: - Time Ago /** * Takes in a date and returns a string with the most convenient unit of time representing * how far in the past that date is from now. * * - parameter date: Date to be measured from now * * - returns String - Formatted return string */ static func timeAgo(since date: Date) -> String { return date.timeAgo(since: Date(), numericDates: false, numericTimes: false) } /** * Takes in a date and returns a shortened string with the most convenient unit of time representing * how far in the past that date is from now. * * - parameter date: Date to be measured from now * * - returns String - Formatted return string */ static func shortTimeAgo(since date: Date) -> String { return date.shortTimeAgo(since: Date()) } /** * Returns a string with the most convenient unit of time representing * how far in the past that date is from now. * * - returns String - Formatted return string */ var timeAgoSinceNow: String { return timeAgo(since: Date()) } /** * Returns a shortened string with the most convenient unit of time representing * how far in the past that date is from now. * * - returns String - Formatted return string */ var shortTimeAgoSinceNow: String { return shortTimeAgo(since: Date()) } func timeAgo(since date: Date, numericDates: Bool = false, numericTimes: Bool = false) -> String { let calendar = NSCalendar.current let unitFlags = Set([.second, .minute, .hour, .day, .weekOfYear, .month, .year]) let earliest = earlierDate(date) let latest = (earliest == self) ? date : self // Should be triple equals, but not extended to Date at this time let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let yesterday = date.subtract(1.days) let isYesterday = yesterday.day == day // Not Yet Implemented/Optional // The following strings are present in the translation files but lack logic as of 2014.04.05 // @"Today", @"This week", @"This month", @"This year" // and @"This morning", @"This afternoon" if components.year! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!) } else if components.year! >= 1 { if numericDates { return dateToolsLocalizedStrings("1 year ago") } return dateToolsLocalizedStrings("Last year") } else if components.month! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!) } else if components.month! >= 1 { if numericDates { return dateToolsLocalizedStrings("1 month ago") } return dateToolsLocalizedStrings("Last month") } else if components.weekOfYear! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!) } else if components.weekOfYear! >= 1 { if numericDates { return dateToolsLocalizedStrings("1 week ago") } return dateToolsLocalizedStrings("Last week") } else if components.day! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!) } else if isYesterday { if numericDates { return dateToolsLocalizedStrings("1 day ago") } return dateToolsLocalizedStrings("Yesterday") } else if components.hour! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!) } else if components.hour! >= 1 { if numericTimes { return dateToolsLocalizedStrings("1 hour ago") } return dateToolsLocalizedStrings("1h ago") } else if components.minute! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d%@m ago", value: components.minute!) } else if components.minute! >= 1 { if numericTimes { return dateToolsLocalizedStrings("1m ago") } return dateToolsLocalizedStrings("A minute ago") } else if components.second! >= 3 { return logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!) } else { if numericTimes { return dateToolsLocalizedStrings("1 second ago") } // Instead of returning "Just now" or the equivalent localized version; let's return an empty string // Previously, we returned DateToolsLocalizedStrings("Just now") return UserDefaultKeys.emptyString } } func shortTimeAgo(since date: Date) -> String { let calendar = NSCalendar.current let unitFlags = Set([.second, .minute, .hour, .day, .weekOfYear, .month, .year]) let earliest = earlierDate(date) let latest = (earliest == self) ? date : self // Should pbe triple equals, but not extended to Date at this time let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let yesterday = date.subtract(1.days) let isYesterday = yesterday.day == day if components.year! >= 1 { return logicalLocalizedStringFromFormat(format: "%%d%@y", value: components.year!) } else if components.month! >= 1 { return logicalLocalizedStringFromFormat(format: "%%d%@M", value: components.month!) } else if components.weekOfYear! >= 1 { return logicalLocalizedStringFromFormat(format: "%%d%@w", value: components.weekOfYear!) } else if components.day! >= 2 { return logicalLocalizedStringFromFormat(format: "%%d%@d", value: components.day!) } else if isYesterday { return logicalLocalizedStringFromFormat(format: "%%d%@d", value: 1) } else if components.hour! >= 1 { return logicalLocalizedStringFromFormat(format: "%%d%@h", value: components.hour!) } else if components.minute! >= 1 { return logicalLocalizedStringFromFormat(format: "%%d%@m", value: components.minute!) } else if components.second! >= 3 { return logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) } else { return logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) // return DateToolsLocalizedStrings(@"Now"); //string not yet translated 2014.04.05 } } private func logicalLocalizedStringFromFormat(format: String, value: Int) -> String { let localeFormat = String(format: format, getLocaleFormatUnderscoresWithValue(Double(value))) return String(format: dateToolsLocalizedStrings(localeFormat), value) } private func getLocaleFormatUnderscoresWithValue(_ value: Double) -> String { let localCode = Bundle.main.preferredLocalizations[0] if localCode == "ru" || localCode == "uk" { let xy = Int(floor(value).truncatingRemainder(dividingBy: 100)) let y = Int(floor(value).truncatingRemainder(dividingBy: 10)) if y == 0 || y > 4 || (xy > 10 && xy < 15) { return "" } if y > 1, y < 5, xy < 10 || xy > 20 { return "_" } if y == 1, xy != 11 { return "__" } } return "" } // MARK: - Localization private func dateToolsLocalizedStrings(_ string: String) -> String { // let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle") // let bundelPath = Bundle(path:classBundle)! #if os(Linux) // NSLocalizedString() is not available yet, see: https://github.com/apple/swift-corelibs-foundation/blob/16f83ddcd311b768e30a93637af161676b0a5f2f/Foundation/NSData.swift // However, a seemingly-equivalent method from NSBundle is: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSBundle.swift return Bundle.main.localizedString(forKey: string, value: "", table: "DateTools") #else return NSLocalizedString(string, tableName: "DateTools", bundle: Bundle.dateToolsBundle(), value: "", comment: "") #endif } // MARK: - Date Earlier/Later /** * Return the earlier of two dates, between self and a given date. * * - parameter date: The date to compare to self * * - returns: The date that is earlier */ func earlierDate(_ date: Date) -> Date { return (timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : date } /** * Return the later of two dates, between self and a given date. * * - parameter date: The date to compare to self * * - returns: The date that is later */ func laterDate(_ date: Date) -> Date { return (timeIntervalSince1970 >= date.timeIntervalSince1970) ? self : date } } ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/am.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d ቀናት በፊት"; /* No comment provided by engineer. */ "%d hours ago" = "%d ሰዓታት በፊት"; /* No comment provided by engineer. */ "%d minutes ago" = "%d ደቂቃዎች በፊት"; /* No comment provided by engineer. */ "%d months ago" = "%d ወሮች በፊት"; /* No comment provided by engineer. */ "%d seconds ago" = "%d ሰከንዶች በፊት"; /* No comment provided by engineer. */ "%d weeks ago" = "%d ሳምንታት በፊት"; /* No comment provided by engineer. */ "%d years ago" = "%d አመታት በፊት"; /* No comment provided by engineer. */ "A minute ago" = "አንድ ደቂቃ በፊት"; /* No comment provided by engineer. */ "An hour ago" = "አንድ ሰዓት በፊት"; /* No comment provided by engineer. */ "Just now" = "ልክ አሁን"; /* No comment provided by engineer. */ "Last month" = "ያለፈው ወር"; /* No comment provided by engineer. */ "Last week" = "ያለፈው ሳምንት"; /* No comment provided by engineer. */ "Last year" = "ያለፈው አመት"; /* No comment provided by engineer. */ "Yesterday" = "ትናንትና"; /* No comment provided by engineer. */ "1 year ago" = "አንድ አመት በፊት"; /* No comment provided by engineer. */ "1 month ago" = "አንድ ወር በፊት"; /* No comment provided by engineer. */ "1 week ago" = "አንድ ሳምንት በፊት"; /* No comment provided by engineer. */ "1 day ago" = "አንድ ቀን በፊት"; /* No comment provided by engineer. */ "1 hour ago" = "አንድ ሰዓት በፊት"; /* No comment provided by engineer. */ "1 minute ago" = "አንድ ደቂቃ በፊት"; /* No comment provided by engineer. */ "1 second ago" = "አንድ ሰከንድ በፊት"; /* No comment provided by engineer. */ "This morning" = "ዛሬ ጠዋት"; /* No comment provided by engineer. */ "This afternoon" = "ዛሬ ከሰዓት"; /* No comment provided by engineer. */ "Today" = "ዛሬ"; /* No comment provided by engineer. */ "This week" = "በዚህ ሳምንት"; /* No comment provided by engineer. */ "This month" = "በዚህ ወር"; /* No comment provided by engineer. */ "This year" = "በዚህ አመት"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%dM"; // month "%dw" = "%dw"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/bg.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "преди %d дена"; /* No comment provided by engineer. */ "%d hours ago" = "преди %d часа"; /* No comment provided by engineer. */ "%d minutes ago" = "преди %d минути"; /* No comment provided by engineer. */ "%d months ago" = "преди %d месеца"; /* No comment provided by engineer. */ "%d seconds ago" = "преди %d секунди"; /* No comment provided by engineer. */ "%d weeks ago" = "преди %d седмици"; /* No comment provided by engineer. */ "%d years ago" = "преди %d години"; /* No comment provided by engineer. */ "A minute ago" = "преди минута"; /* No comment provided by engineer. */ "An hour ago" = "преди час"; /* No comment provided by engineer. */ "Just now" = "току що"; /* No comment provided by engineer. */ "Last month" = "през последния месец"; /* No comment provided by engineer. */ "Last week" = "през последната седмица"; /* No comment provided by engineer. */ "Last year" = "през последната година"; /* No comment provided by engineer. */ "Yesterday" = "вчера"; /* No comment provided by engineer. */ "1 year ago" = "преди 1 година"; /* No comment provided by engineer. */ "1 month ago" = "преди 1 месец"; /* No comment provided by engineer. */ "1 week ago" = "преди 1 седмица"; /* No comment provided by engineer. */ "1 day ago" = "преди 1 ден"; /* No comment provided by engineer. */ "This morning" = "тази сутрин"; /* No comment provided by engineer. */ "This afternoon" = "тази вечер"; /* No comment provided by engineer. */ "Today" = "днес"; /* No comment provided by engineer. */ "This week" = "тази седмица"; /* No comment provided by engineer. */ "This month" = "този месец"; /* No comment provided by engineer. */ "This year" = "тази година"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ca.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Fa %d dies"; /* No comment provided by engineer. */ "%d hours ago" = "Fa %d hores"; /* No comment provided by engineer. */ "%d minutes ago" = "Fa %d minuts"; /* No comment provided by engineer. */ "%d months ago" = "Fa %d mesos"; /* No comment provided by engineer. */ "%d seconds ago" = "Fa %d segons"; /* No comment provided by engineer. */ "%d weeks ago" = "Fa %d setmanes"; /* No comment provided by engineer. */ "%d years ago" = "Fa %d anys"; /* No comment provided by engineer. */ "A minute ago" = "Fa un minut"; /* No comment provided by engineer. */ "An hour ago" = "Fa una hora"; /* No comment provided by engineer. */ "Just now" = "Fa un moment"; /* No comment provided by engineer. */ "Last month" = "El mes passat"; /* No comment provided by engineer. */ "Last week" = "La setmana passada"; /* No comment provided by engineer. */ "Last year" = "L'any passat"; /* No comment provided by engineer. */ "Yesterday" = "Ahir"; /* No comment provided by engineer. */ "1 year ago" = "Fa un any"; /* No comment provided by engineer. */ "1 month ago" = "Fa un mes"; /* No comment provided by engineer. */ "1 week ago" = "Fa una setmana"; /* No comment provided by engineer. */ "1 day ago" = "Fa un dia"; /* No comment provided by engineer. */ "This morning" = "Aquest matí"; /* No comment provided by engineer. */ "This afternoon" = "Aquesta tarda"; /* No comment provided by engineer. */ "Today" = "Avui"; /* No comment provided by engineer. */ "This week" = "Aquesta setmana"; /* No comment provided by engineer. */ "This month" = "Aquest mes"; /* No comment provided by engineer. */ "This year" = "Aquest any"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/cs.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Před %d dny"; /* No comment provided by engineer. */ "%d hours ago" = "Před %d hodinami"; /* No comment provided by engineer. */ "%d minutes ago" = "Před %d minutami"; /* No comment provided by engineer. */ "%d months ago" = "Před %d měsíci"; /* No comment provided by engineer. */ "%d seconds ago" = "Před %d sekundami"; /* No comment provided by engineer. */ "%d weeks ago" = "Před %d týdny"; /* No comment provided by engineer. */ "%d years ago" = "Před %d lety"; /* No comment provided by engineer. */ "A minute ago" = "Před minutou"; /* No comment provided by engineer. */ "An hour ago" = "Před hodinou"; /* No comment provided by engineer. */ "Just now" = "Právě teď"; /* No comment provided by engineer. */ "Last month" = "Minulý měsíc"; /* No comment provided by engineer. */ "Last week" = "Minulý týden"; /* No comment provided by engineer. */ "Last year" = "Minulý rok"; /* No comment provided by engineer. */ "Yesterday" = "Včera"; /* No comment provided by engineer. */ "1 year ago" = "Před rokem"; /* No comment provided by engineer. */ "1 month ago" = "Před měsícem"; /* No comment provided by engineer. */ "1 week ago" = "Před týdnem"; /* No comment provided by engineer. */ "1 day ago" = "Předevčírem"; /* No comment provided by engineer. */ "This morning" = "Dnes dopoledne"; /* No comment provided by engineer. */ "This afternoon" = "Dnes odpoledne"; /* No comment provided by engineer. */ "Today" = "Dnes"; /* No comment provided by engineer. */ "This week" = "Tento týden"; /* No comment provided by engineer. */ "This month" = "Tento měsíc"; /* No comment provided by engineer. */ "This year" = "Letos"; /* Short format for */ "%dy" = "%dr"; // year "%dM" = "%dM"; // month "%dw" = "%dt"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/cy.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d diwrnod yn ôl"; /* No comment provided by engineer. */ "%d hours ago" = "%d awr yn ôl"; /* No comment provided by engineer. */ "%d minutes ago" = "%d munud yn ôl"; /* No comment provided by engineer. */ "%d months ago" = "%d mis yn ôl"; /* No comment provided by engineer. */ "%d seconds ago" = "%d eiliad yn ôl"; /* No comment provided by engineer. */ "%d weeks ago" = "%d wythnos yn ôl"; /* No comment provided by engineer. */ "%d years ago" = "%d mlynydd yn ôl"; /* No comment provided by engineer. */ "A minute ago" = "Un munud yn ôl"; /* No comment provided by engineer. */ "An hour ago" = "Un awr yn ôl"; /* No comment provided by engineer. */ "Just now" = "Nawr"; /* No comment provided by engineer. */ "Last month" = "Mis diwethaf"; /* No comment provided by engineer. */ "Last week" = "Wythnos diwethaf"; /* No comment provided by engineer. */ "Last year" = "Llynedd"; /* No comment provided by engineer. */ "Yesterday" = "Ddoe"; /* No comment provided by engineer. */ "1 year ago" = "1 flynydd yn ôl"; /* No comment provided by engineer. */ "1 month ago" = "1 mis yn ôl"; /* No comment provided by engineer. */ "1 week ago" = "1 wythnos yn ôl"; /* No comment provided by engineer. */ "1 day ago" = "1 diwrnod yn ôl"; /* No comment provided by engineer. */ "This morning" = "Y bore ma"; /* No comment provided by engineer. */ "This afternoon" = "Y penwythnos hon"; /* No comment provided by engineer. */ "Today" = "Heddiw"; /* No comment provided by engineer. */ "This week" = "Yr wythnos hon"; /* No comment provided by engineer. */ "This month" = "Y mis hon"; /* No comment provided by engineer. */ "This year" = "Eleni"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/da.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dage siden"; /* No comment provided by engineer. */ "%d hours ago" = "%d timer siden"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minutter siden"; /* No comment provided by engineer. */ "%d months ago" = "%d måneder siden"; /* No comment provided by engineer. */ "%d seconds ago" = "%d sekunder siden"; /* No comment provided by engineer. */ "%d weeks ago" = "%d uger siden"; /* No comment provided by engineer. */ "%d years ago" = "%d år siden"; /* No comment provided by engineer. */ "A minute ago" = "Et minut siden"; /* No comment provided by engineer. */ "An hour ago" = "En time siden"; /* No comment provided by engineer. */ "Just now" = "Lige nu"; /* No comment provided by engineer. */ "Last month" = "Sidste måned"; /* No comment provided by engineer. */ "Last week" = "Sidste uge"; /* No comment provided by engineer. */ "Last year" = "Sidste år"; /* No comment provided by engineer. */ "Yesterday" = "I går"; /* No comment provided by engineer. */ "1 year ago" = "1 år siden"; /* No comment provided by engineer. */ "1 month ago" = "1 måned siden"; /* No comment provided by engineer. */ "1 week ago" = "1 uge siden"; /* No comment provided by engineer. */ "1 day ago" = "1 dag siden"; /* No comment provided by engineer. */ "This morning" = "Her til morgen"; /* No comment provided by engineer. */ "This afternoon" = "Her til eftermiddag"; /* No comment provided by engineer. */ "Today" = "I dag"; /* No comment provided by engineer. */ "This week" = "Denne uge"; /* No comment provided by engineer. */ "This month" = "Denne måned"; /* No comment provided by engineer. */ "This year" = "Dette år"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/de.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Vor %d Tagen"; /* No comment provided by engineer. */ "%d hours ago" = "Vor %d Stunden"; /* No comment provided by engineer. */ "%d minutes ago" = "Vor %d Minuten"; /* No comment provided by engineer. */ "%d months ago" = "Vor %d Monaten"; /* No comment provided by engineer. */ "%d seconds ago" = "Vor %d Sekunden"; /* No comment provided by engineer. */ "%d weeks ago" = "Vor %d Wochen"; /* No comment provided by engineer. */ "%d years ago" = "Vor %d Jahren"; /* No comment provided by engineer. */ "A minute ago" = "Vor einer Minute"; /* No comment provided by engineer. */ "An hour ago" = "Vor einer Stunde"; /* No comment provided by engineer. */ "Just now" = "Gerade eben"; /* No comment provided by engineer. */ "Last month" = "Letzten Monat"; /* No comment provided by engineer. */ "Last week" = "Letzte Woche"; /* No comment provided by engineer. */ "Last year" = "Letztes Jahr"; /* No comment provided by engineer. */ "Yesterday" = "Gestern"; /* No comment provided by engineer. */ "1 year ago" = "Vor 1 Jahr"; /* No comment provided by engineer. */ "1 month ago" = "Vor 1 Monat"; /* No comment provided by engineer. */ "1 week ago" = "Vor 1 Woche"; /* No comment provided by engineer. */ "1 day ago" = "Vor 1 Tag"; /* No comment provided by engineer. */ "This morning" = "Heute Morgen"; /* No comment provided by engineer. */ "This afternoon" = "Heute Nachmittag"; /* No comment provided by engineer. */ "Today" = "Heute"; /* No comment provided by engineer. */ "This week" = "Diese Woche"; /* No comment provided by engineer. */ "This month" = "Diesen Monat"; /* No comment provided by engineer. */ "This year" = "Dieses Jahr"; /* Short format for */ "%dy" = "%dJ"; // year "%dM" = "%dM"; // month "%dw" = "%dW"; // week "%dd" = "%dT"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/en.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d days ago"; /* No comment provided by engineer. */ "%d hours ago" = "%dh ago"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minutes ago"; /* No comment provided by engineer. */ "%d months ago" = "%d months ago"; /* No comment provided by engineer. */ "%d seconds ago" = "%d seconds ago"; /* No comment provided by engineer. */ "%d weeks ago" = "%d weeks ago"; /* No comment provided by engineer. */ "%d years ago" = "%d years ago"; /* No comment provided by engineer. */ "A minute ago" = "A minute ago"; /* No comment provided by engineer. */ "An hour ago" = "An hour ago"; /* No comment provided by engineer. */ "Just now" = "Just now"; /* No comment provided by engineer. */ "Last month" = "Last month"; /* No comment provided by engineer. */ "Last week" = "Last week"; /* No comment provided by engineer. */ "Last year" = "Last year"; /* No comment provided by engineer. */ "Yesterday" = "Yesterday"; /* No comment provided by engineer. */ "1 year ago" = "1 year ago"; /* No comment provided by engineer. */ "1 month ago" = "1 month ago"; /* No comment provided by engineer. */ "1 week ago" = "1 week ago"; /* No comment provided by engineer. */ "1 day ago" = "1 day ago"; /* No comment provided by engineer. */ "1 hour ago" = "1 hour ago"; /* No comment provided by engineer. */ "1 minute ago" = "1 minute ago"; /* No comment provided by engineer. */ "1 second ago" = "1 second ago"; /* No comment provided by engineer. */ "This morning" = "This morning"; /* No comment provided by engineer. */ "This afternoon" = "This afternoon"; /* No comment provided by engineer. */ "Today" = "Today"; /* No comment provided by engineer. */ "This week" = "This week"; /* No comment provided by engineer. */ "This month" = "This month"; /* No comment provided by engineer. */ "This year" = "This year"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%dM"; // month "%dw" = "%dw"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second /* Week format for */ "Mon" = "Mon"; "Tue" = "Tue"; "Wed" = "Wed"; "Thu" = "Thu"; "Fri" = "Fri"; "Sat" = "Sat"; "Sun" = "Sun"; "周一" = "星期一"; "周二" = "星期二"; "周三" = "星期三"; "周四" = "星期四"; "周五" = "星期五"; "周六" = "星期六"; "周日" = "星期日"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/es.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Hace %d días"; /* No comment provided by engineer. */ "%d hours ago" = "Hace %d horas"; /* No comment provided by engineer. */ "%d minutes ago" = "Hace %d minutos"; /* No comment provided by engineer. */ "%d months ago" = "Hace %d meses"; /* No comment provided by engineer. */ "%d seconds ago" = "Hace %d segundos"; /* No comment provided by engineer. */ "%d weeks ago" = "Hace %d semanas"; /* No comment provided by engineer. */ "%d years ago" = "Hace %d años"; /* No comment provided by engineer. */ "A minute ago" = "Hace un minuto"; /* No comment provided by engineer. */ "An hour ago" = "Hace una hora"; /* No comment provided by engineer. */ "Just now" = "Ahora mismo"; /* No comment provided by engineer. */ "Last month" = "El mes pasado"; /* No comment provided by engineer. */ "Last week" = "La semana pasada"; /* No comment provided by engineer. */ "Last year" = "El año pasado"; /* No comment provided by engineer. */ "Yesterday" = "Ayer"; /* No comment provided by engineer. */ "1 year ago" = "Hace un año"; /* No comment provided by engineer. */ "1 month ago" = "Hace un mes"; /* No comment provided by engineer. */ "1 week ago" = "Hace una semana"; /* No comment provided by engineer. */ "1 day ago" = "Hace un día"; /* No comment provided by engineer. */ "1 hour ago" = "Hace 1 hora"; /* No comment provided by engineer. */ "This morning" = "Esta mañana"; /* No comment provided by engineer. */ "This afternoon" = "Esta tarde"; /* No comment provided by engineer. */ "Today" = "Hoy"; /* No comment provided by engineer. */ "This week" = "Esta semana"; /* No comment provided by engineer. */ "This month" = "Este mes"; /* No comment provided by engineer. */ "This year" = "Este año"; /* Short format for */ "%dy" = "%da"; // year "%dM" = "%dM"; // month "%dw" = "%dS"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/eu.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Orain dela %d egun"; /* No comment provided by engineer. */ "%d hours ago" = "Orain dela %d ordu"; /* No comment provided by engineer. */ "%d minutes ago" = "Orain dela %d minutu"; /* No comment provided by engineer. */ "%d months ago" = "Orain dela %d hile"; /* No comment provided by engineer. */ "%d seconds ago" = "Orain dela %d segundu"; /* No comment provided by engineer. */ "%d weeks ago" = "Orain dela %d aste"; /* No comment provided by engineer. */ "%d years ago" = "Orain dela %d urte"; /* No comment provided by engineer. */ "A minute ago" = "Orain dela minutu bat"; /* No comment provided by engineer. */ "An hour ago" = "Orain dela ordu bat"; /* No comment provided by engineer. */ "Just now" = "Oraintxe bertan"; /* No comment provided by engineer. */ "Last month" = "Pasa den hilean"; /* No comment provided by engineer. */ "Last week" = "Pasa den astean"; /* No comment provided by engineer. */ "Last year" = "Pasa den urtean"; /* No comment provided by engineer. */ "Yesterday" = "Atzo"; /* No comment provided by engineer. */ "1 year ago" = "Orain dela urte bat"; /* No comment provided by engineer. */ "1 month ago" = "Orain dela hile bat"; /* No comment provided by engineer. */ "1 week ago" = "Orain dela aste bat"; /* No comment provided by engineer. */ "1 day ago" = "Orain dela egun bat"; /* No comment provided by engineer. */ "This morning" = "Gaur goizean"; /* No comment provided by engineer. */ "This afternoon" = "Gaur arratsaldean"; /* No comment provided by engineer. */ "Today" = "Gaur"; /* No comment provided by engineer. */ "This week" = "Aste honetan"; /* No comment provided by engineer. */ "This month" = "Hile honetan"; /* No comment provided by engineer. */ "This year" = "Urte honetan"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/fi.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d päivää sitten"; /* No comment provided by engineer. */ "%d hours ago" = "%d tuntia sitten"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minuuttia sitten"; /* No comment provided by engineer. */ "%d months ago" = "%d kuukautta sitten"; /* No comment provided by engineer. */ "%d seconds ago" = "%d sekuntia sitten"; /* No comment provided by engineer. */ "%d weeks ago" = "%d viikkoa sitten"; /* No comment provided by engineer. */ "%d years ago" = "%d vuotta sitten"; /* No comment provided by engineer. */ "A minute ago" = "Minuutti sitten"; /* No comment provided by engineer. */ "An hour ago" = "Tunti sitten"; /* No comment provided by engineer. */ "Just now" = "Juuri äsken"; /* No comment provided by engineer. */ "Last month" = "Viime kuussa"; /* No comment provided by engineer. */ "Last week" = "Viime viikolla"; /* No comment provided by engineer. */ "Last year" = "Viime vuonna"; /* No comment provided by engineer. */ "Yesterday" = "Eilen"; /* No comment provided by engineer. */ "1 year ago" = "Vuosi sitten"; /* No comment provided by engineer. */ "1 month ago" = "Kuukausi sitten"; /* No comment provided by engineer. */ "1 week ago" = "Viikko sitten"; /* No comment provided by engineer. */ "1 day ago" = "Vuorokausi sitten"; /* No comment provided by engineer. */ "This morning" = "Tänä aamuna"; /* No comment provided by engineer. */ "This afternoon" = "Tänä iltapäivänä"; /* No comment provided by engineer. */ "Today" = "Tänään"; /* No comment provided by engineer. */ "This week" = "Tällä viikolla"; /* No comment provided by engineer. */ "This month" = "Tässä kuussa"; /* No comment provided by engineer. */ "This year" = "Tänä vuonna"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/fr.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Il y a %d jours"; /* No comment provided by engineer. */ "%d hours ago" = "Il y a %d heures"; /* No comment provided by engineer. */ "%d minutes ago" = "Il y a %d minutes"; /* No comment provided by engineer. */ "%d months ago" = "Il y a %d mois"; /* No comment provided by engineer. */ "%d seconds ago" = "Il y a %d secondes"; /* No comment provided by engineer. */ "%d weeks ago" = "Il y a %d semaines"; /* No comment provided by engineer. */ "%d years ago" = "Il y a %d ans"; /* No comment provided by engineer. */ "A minute ago" = "Il y a une minute"; /* No comment provided by engineer. */ "An hour ago" = "Il y a une heure"; /* No comment provided by engineer. */ "Just now" = "A l'instant"; /* No comment provided by engineer. */ "Last month" = "Le mois dernier"; /* No comment provided by engineer. */ "Last week" = "La semaine dernière"; /* No comment provided by engineer. */ "Last year" = "L'année dernière"; /* No comment provided by engineer. */ "Yesterday" = "Hier"; /* No comment provided by engineer. */ "1 year ago" = "Il y a 1 an"; /* No comment provided by engineer. */ "1 month ago" = "Il y a 1 mois"; /* No comment provided by engineer. */ "1 week ago" = "Il y a 1 semaine"; /* No comment provided by engineer. */ "1 day ago" = "Il y a 1 jour"; /* No comment provided by engineer. */ "1 hour ago" = "Il y a 1 heure"; /* No comment provided by engineer. */ "1 minute ago" = "Il y a 1 minute"; /* No comment provided by engineer. */ "1 second ago" = "Il y a 1 seconde"; /* No comment provided by engineer. */ "This morning" = "Ce matin"; /* No comment provided by engineer. */ "This afternoon" = "Cet après-midi"; /* No comment provided by engineer. */ "Today" = "Aujourd'hui"; /* No comment provided by engineer. */ "This week" = "Cette semaine"; /* No comment provided by engineer. */ "This month" = "Ce mois-ci"; /* No comment provided by engineer. */ "This year" = "Cette année"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/gu.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d દિવસ પહેલા"; /* No comment provided by engineer. */ "%d hours ago" = "%d કલાક પહેલા"; /* No comment provided by engineer. */ "%d minutes ago" = "%d મિનિટ પહેલા"; /* No comment provided by engineer. */ "%d months ago" = "%d મહિના પહેલા"; /* No comment provided by engineer. */ "%d seconds ago" = "%d સેકન્ડ પહેલા"; /* No comment provided by engineer. */ "%d weeks ago" = "%d અઠવાડિયા પહેલા"; /* No comment provided by engineer. */ "%d years ago" = "%d વર્ષ પહેલા"; /* No comment provided by engineer. */ "A minute ago" = "એક મિનિટ પહેલા"; /* No comment provided by engineer. */ "An hour ago" = "એક કલાક પહેલા"; /* No comment provided by engineer. */ "Just now" = "હમણાં"; /* No comment provided by engineer. */ "Last month" = "ગયા મહિને"; /* No comment provided by engineer. */ "Last week" = "ગયા અઠવાડિયે"; /* No comment provided by engineer. */ "Last year" = "ગયા વર્ષે"; /* No comment provided by engineer. */ "Yesterday" = "ગઈ કાલે"; /* No comment provided by engineer. */ "1 year ago" = "1 વર્ષ પહેલાં"; /* No comment provided by engineer. */ "1 month ago" = "1 મહિનો પહેલા"; /* No comment provided by engineer. */ "1 week ago" = "1 અઠવાડિયું પહેલા"; /* No comment provided by engineer. */ "1 day ago" = "1 દિવસ પહેલાં"; /* No comment provided by engineer. */ "1 hour ago" = "1 કલાક પહેલા"; /* No comment provided by engineer. */ "1 minute ago" = "1 મિનિટ પહેલા"; /* No comment provided by engineer. */ "1 second ago" = "1 સેકન્ડ પહેલા"; /* No comment provided by engineer. */ "This morning" = "આ સવારે"; /* No comment provided by engineer. */ "This afternoon" = "આજે બપોરે"; /* No comment provided by engineer. */ "Today" = "આજે"; /* No comment provided by engineer. */ "This week" = "આ અઠવાડિયેું"; /* No comment provided by engineer. */ "This month" = "આ મહિને"; /* No comment provided by engineer. */ "This year" = "આ વર્ષે"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%dM"; // month "%dw" = "%dw"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/he.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "לפני %d ימים"; /* No comment provided by engineer. */ "%d hours ago" = "לפני %d שעות"; /* No comment provided by engineer. */ "%d minutes ago" = "לפני %d דקות"; /* No comment provided by engineer. */ "%d months ago" = "לפני %d חודשים"; /* No comment provided by engineer. */ "%d seconds ago" = "לפני %d שניות"; /* No comment provided by engineer. */ "%d weeks ago" = "לפני %d שבועות"; /* No comment provided by engineer. */ "%d years ago" = "לפני %d שנים"; /* No comment provided by engineer. */ "A minute ago" = "לפני דקה"; /* No comment provided by engineer. */ "An hour ago" = "לפני שעה"; /* No comment provided by engineer. */ "Just now" = "ממש עכשיו"; /* No comment provided by engineer. */ "Last month" = "בחודש שעבר"; /* No comment provided by engineer. */ "Last week" = "בשבוע שעבר"; /* No comment provided by engineer. */ "Last year" = "בשנה שעברה"; /* No comment provided by engineer. */ "Yesterday" = "אתמול"; /* No comment provided by engineer. */ "1 year ago" = "לפני שנה"; /* No comment provided by engineer. */ "1 month ago" = "לפני חודש"; /* No comment provided by engineer. */ "1 week ago" = "לפני שבוע"; /* No comment provided by engineer. */ "1 day ago" = "לפני יום"; /* No comment provided by engineer. */ "This morning" = "הבוקר"; /* No comment provided by engineer. */ "This afternoon" = "בצהריים"; /* No comment provided by engineer. */ "Today" = "היום"; /* No comment provided by engineer. */ "This week" = "השבוע"; /* No comment provided by engineer. */ "This month" = "החודש"; /* No comment provided by engineer. */ "This year" = "השנה"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/hi.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d दिन पहले"; /* No comment provided by engineer. */ "%d hours ago" = "%d घंटे पहले"; /* No comment provided by engineer. */ "%d minutes ago" = "%d मिनट पहले"; /* No comment provided by engineer. */ "%d months ago" = "%d महीन पहले"; /* No comment provided by engineer. */ "%d seconds ago" = "%d सेकंड पहले"; /* No comment provided by engineer. */ "%d weeks ago" = "%d हफ्ते पहले"; /* No comment provided by engineer. */ "%d years ago" = "%d साल पहले"; /* No comment provided by engineer. */ "A minute ago" = "एक मिनट पहले"; /* No comment provided by engineer. */ "An hour ago" = "एक घंटे पहले"; /* No comment provided by engineer. */ "Just now" = "बस अभी"; /* No comment provided by engineer. */ "Last month" = "पिछले महीने"; /* No comment provided by engineer. */ "Last week" = "पिछले हफ्ते"; /* No comment provided by engineer. */ "Last year" = "पिछले साल"; /* No comment provided by engineer. */ "Yesterday" = "कल"; /* No comment provided by engineer. */ "1 year ago" = "1 साल पहले"; /* No comment provided by engineer. */ "1 month ago" = "1 महीने पहले"; /* No comment provided by engineer. */ "1 week ago" = "1 हफ्ते पहले"; /* No comment provided by engineer. */ "1 day ago" = "1 दिन पहले"; /* No comment provided by engineer. */ "1 hour ago" = "1 घंटे पहले"; /* No comment provided by engineer. */ "1 minute ago" = "1 मिनट पहले"; /* No comment provided by engineer. */ "1 second ago" = "1 सेकंड पहले"; /* No comment provided by engineer. */ "This morning" = "आज सुबह"; /* No comment provided by engineer. */ "This afternoon" = "यह दोपहर"; /* No comment provided by engineer. */ "Today" = "आज"; /* No comment provided by engineer. */ "This week" = "इस सप्ताह"; /* No comment provided by engineer. */ "This month" = "इस महीने"; /* No comment provided by engineer. */ "This year" = "इस साल"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%dM"; // month "%dw" = "%dw"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/hr.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dana"; /* No comment provided by engineer. */ "%d hours ago" = "%d prime sati"; /* No comment provided by engineer. */ "%d minutes ago" = "%d prije minuta"; /* No comment provided by engineer. */ "%d months ago" = "%d prije nekoliko mjeseci"; /* No comment provided by engineer. */ "%d seconds ago" = "%d sekunde prije"; /* No comment provided by engineer. */ "%d weeks ago" = "%d prije nekoliko tjedana"; /* No comment provided by engineer. */ "%d years ago" = "%d prije nekoliko godina"; /* No comment provided by engineer. */ "A minute ago" = "prije minute"; /* No comment provided by engineer. */ "An hour ago" = "prije sat vremena"; /* No comment provided by engineer. */ "Just now" = "upravo sada"; /* No comment provided by engineer. */ "Last month" = "prosli mjesec"; /* No comment provided by engineer. */ "Last week" = "prosli tjedan"; /* No comment provided by engineer. */ "Last year" = "prosle godine"; /* No comment provided by engineer. */ "Yesterday" = "jucer"; /* No comment provided by engineer. */ "1 year ago" = "Prije 1 godina"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/hu.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d napja"; /* No comment provided by engineer. */ "%d hours ago" = "%d órája"; /* No comment provided by engineer. */ "%d minutes ago" = "%d perce"; /* No comment provided by engineer. */ "%d months ago" = "%d hónapja"; /* No comment provided by engineer. */ "%d seconds ago" = "%d másodperce"; /* No comment provided by engineer. */ "%d weeks ago" = "%d hete"; /* No comment provided by engineer. */ "%d years ago" = "%d éve"; /* No comment provided by engineer. */ "A minute ago" = "Egy perccel ezelőtt"; /* No comment provided by engineer. */ "An hour ago" = "Egy órával ezelőtt"; /* No comment provided by engineer. */ "Just now" = "Az imént"; /* No comment provided by engineer. */ "Last month" = "Az előző hónapban"; /* No comment provided by engineer. */ "Last week" = "Az előző héten"; /* No comment provided by engineer. */ "Last year" = "Tavaly"; /* No comment provided by engineer. */ "Yesterday" = "Tegnap"; /* No comment provided by engineer. */ "1 year ago" = "Tavaly"; /* No comment provided by engineer. */ "1 month ago" = "Egy hónapja"; /* No comment provided by engineer. */ "1 week ago" = "Egy hete"; /* No comment provided by engineer. */ "1 day ago" = "Tegnap"; /* No comment provided by engineer. */ "This morning" = "Ma reggel"; /* No comment provided by engineer. */ "This afternoon" = "Ma délután"; /* No comment provided by engineer. */ "Today" = "Ma"; /* No comment provided by engineer. */ "This week" = "Ezen a héten"; /* No comment provided by engineer. */ "This month" = "Ebben a hónapban"; /* No comment provided by engineer. */ "This year" = "Idén"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/id.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d hari yang lalu"; /* No comment provided by engineer. */ "%d hours ago" = "%d jam yang lalu"; /* No comment provided by engineer. */ "%d minutes ago" = "%d menit yang lalu"; /* No comment provided by engineer. */ "%d months ago" = "%d bulan yang lalu"; /* No comment provided by engineer. */ "%d seconds ago" = "%d detik yang lalu"; /* No comment provided by engineer. */ "%d weeks ago" = "%d minggu yang lalu"; /* No comment provided by engineer. */ "%d years ago" = "%d tahun yang lalu"; /* No comment provided by engineer. */ "A minute ago" = "Semenit yang lalu"; /* No comment provided by engineer. */ "An hour ago" = "Sejam yang lalu"; /* No comment provided by engineer. */ "Just now" = "Sekarang"; /* No comment provided by engineer. */ "Last month" = "Bulan lalu"; /* No comment provided by engineer. */ "Last week" = "Minggu lalu"; /* No comment provided by engineer. */ "Last year" = "Tahun lalu"; /* No comment provided by engineer. */ "Yesterday" = "Kemarin"; /* No comment provided by engineer. */ "1 year ago" = "1 tahun yang lalu"; /* No comment provided by engineer. */ "1 month ago" = "1 bulan yang lalu"; /* No comment provided by engineer. */ "1 week ago" = "1 minggu yang lalu"; /* No comment provided by engineer. */ "1 day ago" = "1 hari yang lalu"; /* No comment provided by engineer. */ "This morning" = "Pagi ini"; /* No comment provided by engineer. */ "This afternoon" = "Sore ini"; /* No comment provided by engineer. */ "Today" = "Hari ini"; /* No comment provided by engineer. */ "This week" = "Minggu ini"; /* No comment provided by engineer. */ "This month" = "Bulan ini"; /* No comment provided by engineer. */ "This year" = "Tahun ini"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/is.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dögum síðan"; /* No comment provided by engineer. */ "%d hours ago" = "%d klst. síðan"; /* No comment provided by engineer. */ "%d minutes ago" = "%d mínútum síðan"; /* No comment provided by engineer. */ "%d months ago" = "%d mánuðum síðan"; /* No comment provided by engineer. */ "%d seconds ago" = "%d sekúndum síðan"; /* No comment provided by engineer. */ "%d weeks ago" = "%d vikum síðan"; /* No comment provided by engineer. */ "%d years ago" = "%d árum síðan"; /* No comment provided by engineer. */ "A minute ago" = "Einni mínútu síðan"; /* No comment provided by engineer. */ "An hour ago" = "Einni klst. síðan"; /* No comment provided by engineer. */ "Just now" = "Rétt í þessu"; /* No comment provided by engineer. */ "Last month" = "Í síðasta mánuði"; /* No comment provided by engineer. */ "Last week" = "Í síðustu viku"; /* No comment provided by engineer. */ "Last year" = "Á síðasta ári"; /* No comment provided by engineer. */ "Yesterday" = "Í gær"; /* No comment provided by engineer. */ "1 year ago" = "1 ári síðan"; /* No comment provided by engineer. */ "1 month ago" = "1 mánuði síðan"; /* No comment provided by engineer. */ "1 week ago" = "1 viku síðan"; /* No comment provided by engineer. */ "1 day ago" = "1 degi síðan"; /* No comment provided by engineer. */ "This morning" = "Í morgun"; /* No comment provided by engineer. */ "This afternoon" = "Síðdegis"; /* No comment provided by engineer. */ "Today" = "Í dag"; /* No comment provided by engineer. */ "This week" = "Í þessari viku"; /* No comment provided by engineer. */ "This month" = "Í þessum mánuði"; /* No comment provided by engineer. */ "This year" = "Á þessu ári"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/it.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d giorni fa"; /* No comment provided by engineer. */ "%d hours ago" = "%d ore fa"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minuti fa"; /* No comment provided by engineer. */ "%d months ago" = "%d mesi fa"; /* No comment provided by engineer. */ "%d seconds ago" = "%d secondi fa"; /* No comment provided by engineer. */ "%d weeks ago" = "%d settimane fa"; /* No comment provided by engineer. */ "%d years ago" = "%d anni fa"; /* No comment provided by engineer. */ "A minute ago" = "Un minuto fa"; /* No comment provided by engineer. */ "An hour ago" = "Un'ora fa"; /* No comment provided by engineer. */ "Just now" = "Ora"; /* No comment provided by engineer. */ "Last month" = "Il mese scorso"; /* No comment provided by engineer. */ "Last week" = "La settimana scorsa"; /* No comment provided by engineer. */ "Last year" = "L'anno scorso"; /* No comment provided by engineer. */ "Yesterday" = "Ieri"; /* No comment provided by engineer. */ "1 year ago" = "Un anno fa"; /* No comment provided by engineer. */ "1 month ago" = "Un mese fa"; /* No comment provided by engineer. */ "1 week ago" = "Una settimana fa"; /* No comment provided by engineer. */ "1 day ago" = "Un giorno fa"; /* No comment provided by engineer. */ "This morning" = "Questa mattina"; /* No comment provided by engineer. */ "This afternoon" = "Questo pomeriggio"; /* No comment provided by engineer. */ "Today" = "Oggi"; /* No comment provided by engineer. */ "This week" = "Questa settimana"; /* No comment provided by engineer. */ "This month" = "Questo mese"; /* No comment provided by engineer. */ "This year" = "Quest'anno"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ja.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d日前"; /* No comment provided by engineer. */ "%d hours ago" = "%d時間前"; /* No comment provided by engineer. */ "%d minutes ago" = "%d分前"; /* No comment provided by engineer. */ "%d months ago" = "%dヶ月前"; /* No comment provided by engineer. */ "%d seconds ago" = "%d秒前"; /* No comment provided by engineer. */ "%d weeks ago" = "%d週間前"; /* No comment provided by engineer. */ "%d years ago" = "%d年前"; /* No comment provided by engineer. */ "A minute ago" = "1分前"; /* No comment provided by engineer. */ "An hour ago" = "1時間前"; /* No comment provided by engineer. */ "Just now" = "たった今"; /* No comment provided by engineer. */ "Last month" = "先月"; /* No comment provided by engineer. */ "Last week" = "先週"; /* No comment provided by engineer. */ "Last year" = "去年"; /* No comment provided by engineer. */ "Yesterday" = "昨日"; /* No comment provided by engineer. */ "1 year ago" = "1年前"; /* No comment provided by engineer. */ "1 month ago" = "1ヶ月前"; /* No comment provided by engineer. */ "1 week ago" = "1週間前"; /* No comment provided by engineer. */ "1 day ago" = "1日前"; /* No comment provided by engineer. */ "1 hour ago" = "1時間前"; /* No comment provided by engineer. */ "1 minute ago" = "1分前"; /* No comment provided by engineer. */ "1 second ago" = "1秒前"; /* No comment provided by engineer. */ "This morning" = "午前"; /* No comment provided by engineer. */ "This afternoon" = "午後"; /* No comment provided by engineer. */ "Today" = "今日"; /* No comment provided by engineer. */ "This week" = "今週"; /* No comment provided by engineer. */ "This month" = "今月"; /* No comment provided by engineer. */ "This year" = "今年"; /* Short format for */ "%dy" = "%d年"; // year "%dM" = "%d月"; // month "%dw" = "%d週"; // week "%dd" = "%d日"; // day "%dh" = "%d時間"; // hour "%dm" = "%d分"; // minute "%ds" = "%d秒"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ko.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d일 전"; /* No comment provided by engineer. */ "%d hours ago" = "%d시간 전"; /* No comment provided by engineer. */ "%d minutes ago" = "%d분 전"; /* No comment provided by engineer. */ "%d months ago" = "%d개월 전"; /* No comment provided by engineer. */ "%d seconds ago" = "%d초 전"; /* No comment provided by engineer. */ "%d weeks ago" = "%d주 전"; /* No comment provided by engineer. */ "%d years ago" = "%d년 전"; /* No comment provided by engineer. */ "A minute ago" = "1분 전"; /* No comment provided by engineer. */ "An hour ago" = "1시간 전"; /* No comment provided by engineer. */ "Just now" = "방금 전"; /* No comment provided by engineer. */ "Last month" = "지난 달"; /* No comment provided by engineer. */ "Last week" = "지난 주"; /* No comment provided by engineer. */ "Last year" = "지난 해"; /* No comment provided by engineer. */ "Yesterday" = "어제"; /* No comment provided by engineer. */ "1 year ago" = "1년 전"; /* No comment provided by engineer. */ "1 month ago" = "1개월 전"; /* No comment provided by engineer. */ "1 week ago" = "1주 전"; /* No comment provided by engineer. */ "1 day ago" = "1일 전"; /* No comment provided by engineer. */ "This morning" = "오늘 아침"; /* No comment provided by engineer. */ "This afternoon" = "오늘 오후"; /* No comment provided by engineer. */ "Today" = "오늘"; /* No comment provided by engineer. */ "This week" = "이번 주"; /* No comment provided by engineer. */ "This month" = "이번 달"; /* No comment provided by engineer. */ "This year" = "올해"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/lv.lproj/DateTools.strings ================================================ "1 year ago" = "Pirms gada"; "1 month ago" = "Pirms mēneša"; "1 week ago" = "Pirms nedēļas"; "1 day ago" = "Pirms dienas"; "A minute ago" = "Pirms minūtes"; "An hour ago" = "Pirms stundas"; "Last month" = "Pagājušajā mēnesī"; "Last week" = "Pagājušajā nedēļā"; "Last year" = "Pagājušajā gadā"; "Just now" = "Tikko"; "Today" = "Šodien"; "Yesterday" = "Vakar"; "This morning" = "Šorīt"; "This afternoon" = "Pēcpusdienā"; "This week" = "Šonedēļ"; "This month" = "Šomēnes"; "This year" = "Šogad"; "%d seconds ago" = "Pirms %d sekundēm"; "%d minutes ago" = "Pirms %d minūtēm"; "%d hours ago" = "Pirms %d stundām"; "%d days ago" = "Pirms %d dienām"; "%d weeks ago" = "Pirms %d nedēļām"; "%d months ago" = "Pirms %d mēnešiem"; "%d years ago" = "Pirms %d gadiem"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ms.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d hari yang lepas"; /* No comment provided by engineer. */ "%d hours ago" = "%d jam yang lepas"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minit yang lepas"; /* No comment provided by engineer. */ "%d months ago" = "%d bulan yang lepas"; /* No comment provided by engineer. */ "%d seconds ago" = "%d saat yang lepas"; /* No comment provided by engineer. */ "%d weeks ago" = "%d minggu yang lepas"; /* No comment provided by engineer. */ "%d years ago" = "%d tahun yang lepas"; /* No comment provided by engineer. */ "A minute ago" = "Seminit yang lepas"; /* No comment provided by engineer. */ "An hour ago" = "Sejam yang lepas"; /* No comment provided by engineer. */ "Just now" = "Sebentar tadi"; /* No comment provided by engineer. */ "Last month" = "Bulan lepas"; /* No comment provided by engineer. */ "Last week" = "Minggu lepas"; /* No comment provided by engineer. */ "Last year" = "Tahun lepas"; /* No comment provided by engineer. */ "Yesterday" = "Semalam"; /* No comment provided by engineer. */ "1 year ago" = "1 tahun lepas"; /* No comment provided by engineer. */ "1 month ago" = "1 bulan lepas"; /* No comment provided by engineer. */ "1 week ago" = "1 minggu lepas"; /* No comment provided by engineer. */ "1 day ago" = "1 hari lepas"; /* No comment provided by engineer. */ "1 hour ago" = "1 jam lepas"; /* No comment provided by engineer. */ "1 minute ago" = "1 minit lepas"; /* No comment provided by engineer. */ "1 second ago" = "1 saat lepas"; /* No comment provided by engineer. */ "This morning" = "Pagi ini"; /* No comment provided by engineer. */ "This afternoon" = "Petang ini"; /* No comment provided by engineer. */ "Today" = "Hari ini"; /* No comment provided by engineer. */ "This week" = "Minggu ini"; /* No comment provided by engineer. */ "This month" = "Bulan ini"; /* No comment provided by engineer. */ "This year" = "Tahun ini"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%dM"; // month "%dw" = "%dw"; // week "%dd" = "%dd"; // day "%dh" = "%dh"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/nb.lproj/DateTools.strings ================================================ /* RULES: Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is last two digits; */ /* Y ==0 OR Y > 4 OR XY == 11; */ "%d days ago" = "%d dager siden"; /* If Y != 1 AND Y < 5; */ "%d _days ago" = "%d dager siden"; /* If Y == 1; */ "%d __days ago" = "%d dag siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d hours ago" = "%d timer siden"; /* If Y != 1 AND Y < 5; */ "%d _hours ago" = "%d timer siden"; /* If Y == 1; */ "%d __hours ago" = "%d time siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d minutes ago" = "%d minutter siden"; /* If Y != 1 AND Y < 5; */ "%d _minutes ago" = "%d minutter siden"; /* If Y == 1; */ "%d __minutes ago" = "%d minutt siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d months ago" = "%d måneder siden"; /* If Y != 1 AND Y < 5; */ "%d _months ago" = "%d måneder siden"; /* If Y == 1; */ "%d __months ago" = "%d måned siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d seconds ago" = "%d sekunder siden"; /* If Y != 1 AND Y < 5; */ "%d _seconds ago" = "%d sekunder siden"; /* If Y == 1; */ "%d __seconds ago" = "%d sekund siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d weeks ago" = "%d uker siden"; /* If Y != 1 AND Y < 5; */ "%d _weeks ago" = "%d uker siden"; /* If Y == 1; */ "%d __weeks ago" = "%d uke siden"; /* Y ==0 OR Y > 4 OR XY == 11; */ "%d years ago" = "%d år siden"; /* If Y != 1 AND Y < 5; */ "%d _years ago" = "%d år siden"; /* If Y == 1; */ "%d __years ago" = "%d år siden"; /* No comment provided by engineer. */ "A minute ago" = "Et minutt siden"; /* No comment provided by engineer. */ "An hour ago" = "En time siden"; /* No comment provided by engineer. */ "Just now" = "Nå"; /* No comment provided by engineer. */ "Last month" = "For en måned siden"; /* No comment provided by engineer. */ "Last week" = "For en uke siden"; /* No comment provided by engineer. */ "Last year" = "For et år siden"; /* No comment provided by engineer. */ "Yesterday" = "I går"; /* No comment provided by engineer. */ "1 year ago" = "1 år siden"; /* No comment provided by engineer. */ "1 month ago" = "1 måned siden"; /* No comment provided by engineer. */ "1 week ago" = "1 uke siden"; /* No comment provided by engineer. */ "1 day ago" = "1 dag siden"; /* No comment provided by engineer. */ "This morning" = "Denne morgenen"; /* No comment provided by engineer. */ "This afternoon" = "I ettermiddag"; /* No comment provided by engineer. */ "Today" = "I dag"; /* No comment provided by engineer. */ "This week" = "Denne uken"; /* No comment provided by engineer. */ "This month" = "Denne måneden"; /* No comment provided by engineer. */ "This year" = "Dette året"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/nl.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dagen geleden"; /* No comment provided by engineer. */ "%d hours ago" = "%d uur geleden"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minuten geleden"; /* No comment provided by engineer. */ "%d months ago" = "%d maanden geleden"; /* No comment provided by engineer. */ "%d seconds ago" = "%d seconden geleden"; /* No comment provided by engineer. */ "%d weeks ago" = "%d weken geleden"; /* No comment provided by engineer. */ "%d years ago" = "%d jaar geleden"; /* No comment provided by engineer. */ "A minute ago" = "Een minuut geleden"; /* No comment provided by engineer. */ "An hour ago" = "Een uur geleden"; /* No comment provided by engineer. */ "Just now" = "Zojuist"; /* No comment provided by engineer. */ "Last month" = "Vorige maand"; /* No comment provided by engineer. */ "Last week" = "Vorige week"; /* No comment provided by engineer. */ "Last year" = "Vorig jaar"; /* No comment provided by engineer. */ "Yesterday" = "Gisteren"; /* No comment provided by engineer. */ "1 year ago" = "1 jaar geleden"; /* No comment provided by engineer. */ "1 month ago" = "1 maand geleden"; /* No comment provided by engineer. */ "1 week ago" = "1 week geleden"; /* No comment provided by engineer. */ "1 day ago" = "1 dag geleden"; /* No comment provided by engineer. */ "This morning" = "Vanmorgen"; /* No comment provided by engineer. */ "This afternoon" = "Vanmiddag"; /* No comment provided by engineer. */ "Today" = "Vandaag"; /* No comment provided by engineer. */ "This week" = "Deze week"; /* No comment provided by engineer. */ "This month" = "Deze maand"; /* No comment provided by engineer. */ "This year" = "Dit jaar"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/pl.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dni temu"; /* No comment provided by engineer. */ "%d hours ago" = "%d godzin(y) temu"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minut(y) temu"; /* No comment provided by engineer. */ "%d months ago" = "%d miesiące/-y temu"; /* No comment provided by engineer. */ "%d seconds ago" = "%d sekund(y) temu"; /* No comment provided by engineer. */ "%d weeks ago" = "%d tygodni(e) temu"; /* No comment provided by engineer. */ "%d years ago" = "%d lat(a) temu"; /* No comment provided by engineer. */ "A minute ago" = "Minutę temu"; /* No comment provided by engineer. */ "An hour ago" = "Godzinę temu"; /* No comment provided by engineer. */ "Just now" = "W tej chwili"; /* No comment provided by engineer. */ "Last month" = "W zeszłym miesiącu"; /* No comment provided by engineer. */ "Last week" = "W zeszłym tygodniu"; /* No comment provided by engineer. */ "Last year" = "W zeszłym roku"; /* No comment provided by engineer. */ "Yesterday" = "Wczoraj"; /* No comment provided by engineer. */ "1 year ago" = "1 rok temu"; /* No comment provided by engineer. */ "1 month ago" = "1 miesiąc temu"; /* No comment provided by engineer. */ "1 week ago" = "1 tydzień temu"; /* No comment provided by engineer. */ "1 day ago" = "1 dzień temu"; /* No comment provided by engineer. */ "This morning" = "Dziś rano"; /* No comment provided by engineer. */ "This afternoon" = "Dziś po południu"; /* No comment provided by engineer. */ "Today" = "Dzisiaj"; /* No comment provided by engineer. */ "This week" = "W tym tygodniu"; /* No comment provided by engineer. */ "This month" = "W tym miesiącu"; /* No comment provided by engineer. */ "This year" = "W tym roku"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/pt-PT.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dias atrás"; /* No comment provided by engineer. */ "%d hours ago" = "%d horas atrás"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minutos atrás"; /* No comment provided by engineer. */ "%d months ago" = "%d meses atrás"; /* No comment provided by engineer. */ "%d seconds ago" = "%d segundos atrás"; /* No comment provided by engineer. */ "%d weeks ago" = "%d semanas atrás"; /* No comment provided by engineer. */ "%d years ago" = "%d anos atrás"; /* No comment provided by engineer. */ "A minute ago" = "Um minuto atrás"; /* No comment provided by engineer. */ "An hour ago" = "Uma hora atrás"; /* No comment provided by engineer. */ "Just now" = "Agora mesmo"; /* No comment provided by engineer. */ "Last month" = "Mês passado"; /* No comment provided by engineer. */ "Last week" = "Semana passada"; /* No comment provided by engineer. */ "Last year" = "Ano passado"; /* No comment provided by engineer. */ "Yesterday" = "Ontem"; /* No comment provided by engineer. */ "1 year ago" = "1 ano passado"; /* No comment provided by engineer. */ "1 month ago" = "1 mês atrás"; /* No comment provided by engineer. */ "1 week ago" = "1 semana atrás"; /* No comment provided by engineer. */ "1 day ago" = "1 dia atrás"; /* No comment provided by engineer. */ "This morning" = "Esta manhã"; /* No comment provided by engineer. */ "This afternoon" = "Esta tarde"; /* No comment provided by engineer. */ "Today" = "Hoje"; /* No comment provided by engineer. */ "This week" = "Esta semana"; /* No comment provided by engineer. */ "This month" = "Este mês"; /* No comment provided by engineer. */ "This year" = "Este ano"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/pt.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d dias atrás"; /* No comment provided by engineer. */ "%d hours ago" = "%d horas atrás"; /* No comment provided by engineer. */ "%d minutes ago" = "%d minutos atrás"; /* No comment provided by engineer. */ "%d months ago" = "%d meses atrás"; /* No comment provided by engineer. */ "%d seconds ago" = "%d segundos atrás"; /* No comment provided by engineer. */ "%d weeks ago" = "%d semanas atrás"; /* No comment provided by engineer. */ "%d years ago" = "%d anos atrás"; /* No comment provided by engineer. */ "A minute ago" = "Há um minuto"; /* No comment provided by engineer. */ "An hour ago" = "Há uma hora"; /* No comment provided by engineer. */ "Just now" = "Agora"; /* No comment provided by engineer. */ "Last month" = "Mês passado"; /* No comment provided by engineer. */ "Last week" = "Semana passada"; /* No comment provided by engineer. */ "Last year" = "Ano passado"; /* No comment provided by engineer. */ "Yesterday" = "Ontem"; /* No comment provided by engineer. */ "1 year ago" = "1 ano atrás"; /* No comment provided by engineer. */ "1 month ago" = "1 mês atrás"; /* No comment provided by engineer. */ "1 week ago" = "1 semana atrás"; /* No comment provided by engineer. */ "1 day ago" = "1 dia atrás"; /* No comment provided by engineer. */ "This morning" = "Esta manhã"; /* No comment provided by engineer. */ "This afternoon" = "Esta tarde"; /* No comment provided by engineer. */ "Today" = "Hoje"; /* No comment provided by engineer. */ "This week" = "Esta semana"; /* No comment provided by engineer. */ "This month" = "Este mês"; /* No comment provided by engineer. */ "This year" = "Este ano"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ro.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "În urmă cu %d zile"; /* No comment provided by engineer. */ "%d hours ago" = "În urmă cu %d ore"; /* No comment provided by engineer. */ "%d minutes ago" = "În urmă cu %d minute"; /* No comment provided by engineer. */ "%d months ago" = "În urmă cu %d luni"; /* No comment provided by engineer. */ "%d seconds ago" = "În urmă cu %d secunde"; /* No comment provided by engineer. */ "%d weeks ago" = "În urmă cu %d săptămâni"; /* No comment provided by engineer. */ "%d years ago" = "În urmă cu %d ani"; /* No comment provided by engineer. */ "A minute ago" = "În urmă cu 1 minut"; /* No comment provided by engineer. */ "An hour ago" = "În urmă cu 1 oră"; /* No comment provided by engineer. */ "Just now" = "Acum câteva momente"; /* No comment provided by engineer. */ "Last month" = "Luna trecută"; /* No comment provided by engineer. */ "Last week" = "Săptămâna trecută"; /* No comment provided by engineer. */ "Last year" = "Anul trecut"; /* No comment provided by engineer. */ "Yesterday" = "Ieri"; /* No comment provided by engineer. */ "1 year ago" = "În urmă cu 1 an"; /* No comment provided by engineer. */ "1 month ago" = "În urmă cu 1 lună"; /* No comment provided by engineer. */ "1 week ago" = "În urmă cu 1 săptămână"; /* No comment provided by engineer. */ "1 day ago" = "În urmă cu 1 zi"; /* No comment provided by engineer. */ "This morning" = "Azi dimineață"; /* No comment provided by engineer. */ "This afternoon" = "În această seară"; /* No comment provided by engineer. */ "Today" = "Astăzi"; /* No comment provided by engineer. */ "This week" = "Săptămâna aceasta"; /* No comment provided by engineer. */ "This month" = "Luna aceasta"; /* No comment provided by engineer. */ "This year" = "Anul acesta"; /* Short format for */ "%dy" = "%da"; // year (an) "%dM" = "%dl"; // month (luna) "%dw" = "%dS"; // week (saptamana) "%dd" = "%dz"; // day (ziua) "%dh" = "%do"; // hour (ora) "%dm" = "%dm"; // minute (minut) "%ds" = "%ds"; // second (secunda) ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/ru.lproj/DateTools.strings ================================================ /* RULES: Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is last two digits; */ /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d days ago" = "%d дней назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _days ago" = "%d дня назад"; /* Y == 1 AND XY != 11; */ "%d __days ago" = "%d день назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d hours ago" = "%d часов назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _hours ago" = "%d часа назад"; /* Y == 1 AND XY != 11; */ "%d __hours ago" = "%d час назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d minutes ago" = "%d минут назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _minutes ago" = "%d минуты назад"; /* Y == 1 AND XY != 11; */ "%d __minutes ago" = "%d минуту назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d months ago" = "%d месяцев назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _months ago" = "%d месяца назад"; /* Y == 1 AND XY != 11; */ "%d __months ago" = "%d месяц назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d seconds ago" = "%d секунд назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _seconds ago" = "%d секунды назад"; /* Y == 1 AND XY != 11; */ "%d __seconds ago" = "%d секунду назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d weeks ago" = "%d недель назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _weeks ago" = "%d недели назад"; /* Y == 1 AND XY != 11; */ "%d __weeks ago" = "%d неделю назад"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d years ago" = "%d лет назад"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _years ago" = "%d года назад"; /* Y == 1 AND XY != 11; */ "%d __years ago" = "%d год назад"; /* No comment provided by engineer. */ "A minute ago" = "Минуту назад"; /* No comment provided by engineer. */ "An hour ago" = "Час назад"; /* No comment provided by engineer. */ "Just now" = "Только что"; /* No comment provided by engineer. */ "Last month" = "Месяц назад"; /* No comment provided by engineer. */ "Last week" = "Неделю назад"; /* No comment provided by engineer. */ "Last year" = "Год назад"; /* No comment provided by engineer. */ "Yesterday" = "Вчера"; /* No comment provided by engineer. */ "1 year ago" = "1 год назад"; /* No comment provided by engineer. */ "1 month ago" = "1 месяц назад"; /* No comment provided by engineer. */ "1 week ago" = "1 неделю назад"; /* No comment provided by engineer. */ "1 day ago" = "1 день назад"; /* No comment provided by engineer. */ "This morning" = "Этим утром"; /* No comment provided by engineer. */ "This afternoon" = "Этим днём"; /* No comment provided by engineer. */ "Today" = "Сегодня"; /* No comment provided by engineer. */ "This week" = "На этой неделе"; /* No comment provided by engineer. */ "This month" = "В этом месяце"; /* No comment provided by engineer. */ "This year" = "В этом году"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/sk.lproj/NSDateTimeAgo.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "Pred %d dňami"; /* No comment provided by engineer. */ "%d hours ago" = "Pred %d hodinami"; /* No comment provided by engineer. */ "%d minutes ago" = "Pred %d minútami"; /* No comment provided by engineer. */ "%d months ago" = "Pred %d mesiaci"; /* No comment provided by engineer. */ "%d seconds ago" = "Pred %d sekundami"; /* No comment provided by engineer. */ "%d weeks ago" = "Pred %d týždňami"; /* No comment provided by engineer. */ "%d years ago" = "Pred %d rokmi"; /* No comment provided by engineer. */ "A minute ago" = "Pred minútou"; /* No comment provided by engineer. */ "An hour ago" = "Pred hodinou"; /* No comment provided by engineer. */ "Just now" = "Práve teraz"; /* No comment provided by engineer. */ "Last month" = "Minulý mesiac"; /* No comment provided by engineer. */ "Last week" = "Minulý týždeň"; /* No comment provided by engineer. */ "Last year" = "Minulý rok"; /* No comment provided by engineer. */ "Yesterday" = "Včera"; /* No comment provided by engineer. */ "1 year ago" = "Pred rokom"; /* No comment provided by engineer. */ "1 month ago" = "Pred mesiacom"; /* No comment provided by engineer. */ "1 week ago" = "Pred týždňom"; /* No comment provided by engineer. */ "1 day ago" = "Predvčerom"; /* No comment provided by engineer. */ "This morning" = "Dnes dopoludnia"; /* No comment provided by engineer. */ "This afternoon" = "Dnes popoludní"; /* No comment provided by engineer. */ "Today" = "Dnes"; /* No comment provided by engineer. */ "This week" = "Tento týždeň"; /* No comment provided by engineer. */ "This month" = "Tento mesiac"; /* No comment provided by engineer. */ "This year" = "Tento rok"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/sl.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "pred %d dnevi"; /* No comment provided by engineer. */ "%d hours ago" = "pred %d urami"; /* No comment provided by engineer. */ "%d minutes ago" = "pred %d minutami"; /* No comment provided by engineer. */ "%d months ago" = "pred %d meseci"; /* No comment provided by engineer. */ "%d seconds ago" = "pred %d sekundami"; /* No comment provided by engineer. */ "%d weeks ago" = "pred %d tedni"; /* No comment provided by engineer. */ "%d years ago" = "pred %d leti"; /* No comment provided by engineer. */ "A minute ago" = "pred eno minuto"; /* No comment provided by engineer. */ "An hour ago" = "pred eno uro"; /* No comment provided by engineer. */ "Just now" = "ravnokar"; /* No comment provided by engineer. */ "Last month" = "prejšnji mesec"; /* No comment provided by engineer. */ "Last week" = "prejšnji teden"; /* No comment provided by engineer. */ "Last year" = "prejšnje leto"; /* No comment provided by engineer. */ "Yesterday" = "včeraj"; /* No comment provided by engineer. */ "1 year ago" = "pred 1 letom"; /* No comment provided by engineer. */ "1 month ago" = "pred 1 mesecem"; /* No comment provided by engineer. */ "1 week ago" = "pred 1 tednom"; /* No comment provided by engineer. */ "1 day ago" = "pred 1 dnevom"; /* No comment provided by engineer. */ "1 hour ago" = "pred 1 uro"; /* No comment provided by engineer. */ "1 minute ago" = "pred 1 minuto"; /* No comment provided by engineer. */ "1 second ago" = "pred 1 sekundo"; /* No comment provided by engineer. */ "This morning" = "zjutraj"; /* No comment provided by engineer. */ "This afternoon" = "zvečer"; /* No comment provided by engineer. */ "Today" = "danes"; /* No comment provided by engineer. */ "This week" = "ta teden"; /* No comment provided by engineer. */ "This month" = "ta mesec"; /* No comment provided by engineer. */ "This year" = "to leto"; /* Short format for */ "%dy" = "%dl"; // year "%dM" = "%dM"; // month "%dw" = "%dt"; // week "%dd" = "%dd"; // day "%dh" = "%du"; // hour "%dm" = "%dm"; // minute "%ds" = "%ds"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/tr.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d gün önce"; /* No comment provided by engineer. */ "%d hours ago" = "%d saat önce"; /* No comment provided by engineer. */ "%d minutes ago" = "%d dakika önce"; /* No comment provided by engineer. */ "%d months ago" = "%d ay önce"; /* No comment provided by engineer. */ "%d seconds ago" = "%d saniye önce"; /* No comment provided by engineer. */ "%d weeks ago" = "%d hafta önce"; /* No comment provided by engineer. */ "%d years ago" = "%d yıl önce"; /* No comment provided by engineer. */ "A minute ago" = "Bir dakika önce"; /* No comment provided by engineer. */ "An hour ago" = "Bir saat önce"; /* No comment provided by engineer. */ "Just now" = "Şimdi"; /* No comment provided by engineer. */ "Last month" = "Geçen ay"; /* No comment provided by engineer. */ "Last week" = "Geçen hafta"; /* No comment provided by engineer. */ "Last year" = "Geçen yıl"; /* No comment provided by engineer. */ "Yesterday" = "Dün"; /* No comment provided by engineer. */ "1 year ago" = "1 yıl önce"; /* No comment provided by engineer. */ "1 month ago" = "1 ay önce"; /* No comment provided by engineer. */ "1 week ago" = "1 hafta önce"; /* No comment provided by engineer. */ "1 day ago" = "1 gün önce"; /* No comment provided by engineer. */ "This morning" = "Bu sabah"; /* No comment provided by engineer. */ "This afternoon" = "Öğleden sonra"; /* No comment provided by engineer. */ "Today" = "Bugün"; /* No comment provided by engineer. */ "This week" = "Bu hafta"; /* No comment provided by engineer. */ "This month" = "Bu ay"; /* No comment provided by engineer. */ "This year" = "Bu yıl"; /* Short format for */ "%dy" = "%dy"; // year "%dM" = "%day"; // month "%dw" = "%dh"; // week "%dd" = "%dg"; // day "%dh" = "%dsa"; // hour "%dm" = "%ddk"; // minute "%ds" = "%dsn"; // second ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/uk.lproj/DateTools.strings ================================================ /* RULES: Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is last two digits; */ /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d days ago" = "%d днів тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _days ago" = "%d дні тому"; /* Y == 1 AND XY != 11; */ "%d __days ago" = "%d день тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d hours ago" = "%d годин тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _hours ago" = "%d години тому"; /* Y == 1 AND XY != 11; */ "%d __hours ago" = "%d годину тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d minutes ago" = "%d хвилин тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _minutes ago" = "%d хвилини тому"; /* Y == 1 AND XY != 11; */ "%d __minutes ago" = "%d хвилину тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d months ago" = "%d місяців тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _months ago" = "%d місяці тому"; /* Y == 1 AND XY != 11; */ "%d __months ago" = "%d місяць тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d seconds ago" = "%d секунд тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _seconds ago" = "%d секунди тому"; /* Y == 1 AND XY != 11; */ "%d __seconds ago" = "%d секунду тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d weeks ago" = "%d тижнів тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _weeks ago" = "%d тижні тому"; /* Y == 1 AND XY != 11; */ "%d __weeks ago" = "%d тиждень тому"; /* Y == 0 OR Y > 4 OR (XY > 10 AND XY < 15); */ "%d years ago" = "%d років тому"; /* Y > 1 AND Y < 5 AND (XY < 10 OR XY > 20); */ "%d _years ago" = "%d роки тому"; /* Y == 1 AND XY != 11; */ "%d __years ago" = "%d рік тому"; /* No comment provided by engineer. */ "A minute ago" = "Хвилину тому"; /* No comment provided by engineer. */ "An hour ago" = "Годину тому"; /* No comment provided by engineer. */ "Just now" = "Щойно"; /* No comment provided by engineer. */ "Last month" = "Місяць тому"; /* No comment provided by engineer. */ "Last week" = "Тиждень тому"; /* No comment provided by engineer. */ "Last year" = "Рік тому"; /* No comment provided by engineer. */ "Yesterday" = "Вчора"; /* No comment provided by engineer. */ "1 year ago" = "1 рік тому"; /* No comment provided by engineer. */ "1 month ago" = "1 місяць тому"; /* No comment provided by engineer. */ "1 week ago" = "1 тиждень тому"; /* No comment provided by engineer. */ "1 day ago" = "1 день тому"; /* No comment provided by engineer. */ "This morning" = "Цього ранку"; /* No comment provided by engineer. */ "This afternoon" = "Сьогодні вдень"; /* No comment provided by engineer. */ "Today" = "Сьогодні"; /* No comment provided by engineer. */ "This week" = "Цього тижня"; /* No comment provided by engineer. */ "This month" = "Цього місяця"; /* No comment provided by engineer. */ "This year" = "Цього року"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/vi.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d ngày trước"; /* No comment provided by engineer. */ "%d hours ago" = "%d giờ trước"; /* No comment provided by engineer. */ "%d minutes ago" = "%d phút trước"; /* No comment provided by engineer. */ "%d months ago" = "%d tháng trước"; /* No comment provided by engineer. */ "%d seconds ago" = "%d giây trước"; /* No comment provided by engineer. */ "%d weeks ago" = "%d tuần trước"; /* No comment provided by engineer. */ "%d years ago" = "%d năm trước"; /* No comment provided by engineer. */ "A minute ago" = "Một phút trước"; /* No comment provided by engineer. */ "An hour ago" = "Một giờ trước"; /* No comment provided by engineer. */ "Just now" = "Vừa mới đây"; /* No comment provided by engineer. */ "Last month" = "Tháng trước"; /* No comment provided by engineer. */ "Last week" = "Tuần trước"; /* No comment provided by engineer. */ "Last year" = "Năm vừa rồi"; /* No comment provided by engineer. */ "Yesterday" = "Hôm qua"; /* No comment provided by engineer. */ "1 year ago" = "1 năm trước"; /* No comment provided by engineer. */ "1 month ago" = "1 tháng trước"; /* No comment provided by engineer. */ "1 week ago" = "1 tuần trước"; /* No comment provided by engineer. */ "1 day ago" = "1 ngày trước"; /* No comment provided by engineer. */ "This morning" = "Sáng nay"; /* No comment provided by engineer. */ "This afternoon" = "Trưa nay"; /* No comment provided by engineer. */ "Today" = "Hôm nay"; /* No comment provided by engineer. */ "This week" = "Tuần này"; /* No comment provided by engineer. */ "This month" = "Tháng này"; /* No comment provided by engineer. */ "This year" = "Năm nay"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/zh-Hans.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d天前"; /* No comment provided by engineer. */ "%d hours ago" = "%d小时前"; /* No comment provided by engineer. */ "%d minutes ago" = "%d分钟前"; /* No comment provided by engineer. */ "%d months ago" = "%d个月前"; /* No comment provided by engineer. */ "%d seconds ago" = "%d秒钟前"; /* No comment provided by engineer. */ "%d weeks ago" = "%d星期前"; /* No comment provided by engineer. */ "%d years ago" = "%d年前"; /* No comment provided by engineer. */ "A minute ago" = "1分钟前"; /* No comment provided by engineer. */ "An hour ago" = "1小时前"; /* No comment provided by engineer. */ "Just now" = "刚刚"; /* No comment provided by engineer. */ "Last month" = "上个月"; /* No comment provided by engineer. */ "Last week" = "上星期"; /* No comment provided by engineer. */ "Last year" = "去年"; /* No comment provided by engineer. */ "Yesterday" = "昨天"; /* You can add a space between the number and the characters. */ "1 year ago" = "1年前"; /* You can add a space between the number and the characters. */ "1 month ago" = "1个月前"; /* You can add a space between the number and the characters. */ "1 week ago" = "1星期前"; /* You can add a space between the number and the characters. */ "1 day ago" = "1天前"; /* No comment provided by engineer. */ "This morning" = "今天上午"; /* No comment provided by engineer. */ "This afternoon" = "今天下午"; /* No comment provided by engineer. */ "Today" = "今天"; /* No comment provided by engineer. */ "This week" = "本周"; /* No comment provided by engineer. */ "This month" = "本月"; /* No comment provided by engineer. */ "This year" = "今年"; /* Short format for */ "%dy" = "%d年"; // year "%dM" = "%d月"; // month "%dw" = "%d周"; // week "%dd" = "%d天"; // day "%dh" = "%d小时"; // hour "%dm" = "%d分"; // minute "%ds" = "%d秒"; // second /* Week format for */ "Mon" = "星期一"; "Tue" = "星期二"; "Wed" = "星期三"; "Thu" = "星期四"; "Fri" = "星期五"; "Sat" = "星期六"; "Sun" = "星期日"; "周一" = "星期一"; "周二" = "星期二"; "周三" = "星期三"; "周四" = "星期四"; "周五" = "星期五"; "周六" = "星期六"; "周日" = "星期日"; ================================================ FILE: Clocker/Dependencies/Date Additions/DateTools.bundle/zh-Hant.lproj/DateTools.strings ================================================ /* No comment provided by engineer. */ "%d days ago" = "%d天前"; /* No comment provided by engineer. */ "%d hours ago" = "%d小時前"; /* No comment provided by engineer. */ "%d minutes ago" = "%d分鐘前"; /* No comment provided by engineer. */ "%d months ago" = "%d個月前"; /* No comment provided by engineer. */ "%d seconds ago" = "%d秒鐘前"; /* No comment provided by engineer. */ "%d weeks ago" = "%d星期前"; /* No comment provided by engineer. */ "%d years ago" = "%d年前"; /* No comment provided by engineer. */ "A minute ago" = "1分鐘前"; /* No comment provided by engineer. */ "An hour ago" = "1小時前"; /* No comment provided by engineer. */ "Just now" = "剛剛"; /* No comment provided by engineer. */ "Last month" = "上個月"; /* No comment provided by engineer. */ "Last week" = "上星期"; /* No comment provided by engineer. */ "Last year" = "去年"; /* No comment provided by engineer. */ "Yesterday" = "昨天"; /* No comment provided by engineer. */ "1 year ago" = "1年前"; /* No comment provided by engineer. */ "1 month ago" = "1個月前"; /* No comment provided by engineer. */ "1 week ago" = "1星期前"; /* No comment provided by engineer. */ "1 day ago" = "1天前"; /* No comment provided by engineer. */ "This morning" = "今天上午"; /* No comment provided by engineer. */ "This afternoon" = "今天下午"; /* No comment provided by engineer. */ "Today" = "今天"; /* No comment provided by engineer. */ "This week" = "本周"; /* No comment provided by engineer. */ "This month" = "本月"; /* No comment provided by engineer. */ "This year" = "今年"; /* Short format for */ "%dy" = "%d年"; // year "%dM" = "%d月"; // month "%dw" = "%d週"; // week "%dd" = "%d天"; // day "%dh" = "%d小時"; // hour "%dm" = "%d分"; // minute "%ds" = "%d秒"; // second /* Week format for */ "Mon" = "星期壹"; "Tue" = "星期二"; "Wed" = "星期三"; "Thu" = "星期四"; "Fri" = "星期五"; "Sat" = "星期六"; "Sun" = "星期日"; "周壹" = "星期壹"; "周二" = "星期二"; "周三" = "星期三"; "周四" = "星期四"; "周五" = "星期五"; "周六" = "星期六"; "周日" = "星期日"; ================================================ FILE: Clocker/Dependencies/Date Additions/Enums.swift ================================================ // // Enums.swift // DateToolsTests // // Created by Matthew York on 8/26/16. // Copyright © 2016 Matthew York. All rights reserved. // // MARK: - Enums /** * There may come a need, say when you are making a scheduling app, when * it might be good to know how two time periods relate to one another. * Are they the same? Is one inside of another? All these questions may be * asked using the relationship methods of DTTimePeriod. * * Further reading: [GitHub](https://github.com/MatthewYork/DateTools#relationships), * [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET) */ public enum Relation { case after case startTouching case startInside case insideStartTouching case enclosingStartTouching case enclosing case enclosingEndTouching case exactMatch case inside case insideEndTouching case endInside case endTouching case before case none // One or more of the dates does not exist } /** * Whether the time period is Open or Closed * * Closed: The boundary moment of time is included in calculations. * * Open: The boundary moment of time represents a boundary value which is excluded in regard to calculations. */ public enum Interval { case open case closed } /** * When a time periods is lengthened or shortened, it does so anchoring one date * of the time period and then changing the other one. There is also an option to * anchor the centerpoint of the time period, changing both the start and end dates. */ public enum Anchor { case beginning case center case end } /** * When a time periods is lengthened or shortened, it does so anchoring one date * of the time period and then changing the other one. There is also an option to * anchor the centerpoint of the time period, changing both the start and end dates. */ public enum Component { case year case month case day case hour case minute case second } /** * Time units that include weeks, but not months since their exact size is dependent * on the date. Used for TimeChunk conversions. */ public enum TimeUnits { case years case weeks case days case hours case minutes case seconds } ================================================ FILE: Clocker/Dependencies/Date Additions/Integer+DateTools.swift ================================================ // // Integer+DateTools.swift // DateTools // // Created by Grayson Webster on 8/17/16. // Copyright © 2016 Grayson Webster. All rights reserved. // import Foundation public extension Int { // MARK: TimePeriod /** * A `TimeChunk` with its seconds component set to the value of self */ var seconds: TimeChunk { return TimeChunk(seconds: self, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0) } /** * A `TimeChunk` with its minutes component set to the value of self */ var minutes: TimeChunk { return TimeChunk(seconds: 0, minutes: self, hours: 0, days: 0, weeks: 0, months: 0, years: 0) } /** * A `TimeChunk` with its hours component set to the value of self */ var hours: TimeChunk { return TimeChunk(seconds: 0, minutes: 0, hours: self, days: 0, weeks: 0, months: 0, years: 0) } /** * A `TimeChunk` with its days component set to the value of self */ var days: TimeChunk { return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: self, weeks: 0, months: 0, years: 0) } /** * A `TimeChunk` with its weeks component set to the value of self */ var weeks: TimeChunk { return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: self, months: 0, years: 0) } /** * A `TimeChunk` with its months component set to the value of self */ var months: TimeChunk { return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: self, years: 0) } /** * A `TimeChunk` with its years component set to the value of self */ var years: TimeChunk { return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: self) } } ================================================ FILE: Clocker/Dependencies/Date Additions/TimeChunk.swift ================================================ // // TimeChunk.swift // DateTools // // Created by Grayson Webster on 8/19/16. // Copyright © 2016 Grayson Webster. All rights reserved. // import Foundation /** * TimeChunk represents an abstract collection of `DateComponent`s intended for use in date manipulation. A `TimeChunk` differs from a * TimeInterval in that the former depends on the `Calendar` class (and takes into account daylight savings, leap year, etc.) while the * latter depends on hard, second based adjustments that are independent from calendar constructs. * * In essence, TimeChunk is meant to be a thin, more flexible layer over the `Calender` and `DateComponent` classes for ease of use. * `TimeChunk`s may be created either by calling the provided initializer or shorthand like `2.days`. TimeChunks are manipulable and combine * using basic arithmetic operators like + and -. * * For more information about the utility of TimeChunks in relation to Dates, see the `Date+Manipulations` class. */ public struct TimeChunk { // MARK: - Variables public var seconds = 0 public var minutes = 0 public var hours = 0 public var days = 0 public var weeks = 0 public var months = 0 public var years = 0 public init() {} public init(seconds: Int, minutes: Int, hours: Int, days: Int, weeks: Int, months: Int, years: Int) { self.seconds = seconds self.minutes = minutes self.hours = hours self.days = days self.weeks = weeks self.months = months self.years = years } // MARK: - Comparisons /** * Check if two `TimeChunk`s are equal. * * - parameter chunk: `TimeChunk` to compare with self * * - returns: If all components in both `TimeChunk`s are equal */ public func equals(chunk: TimeChunk) -> Bool { return (seconds == chunk.seconds && minutes == chunk.minutes && hours == chunk.hours && days == chunk.days && weeks == chunk.weeks && months == chunk.months && years == chunk.years) } // MARK: - Conversion /** * Generic conversion method. Years are taken to mean * 365 days. This method should not be used for accurate * date operations. Ex. 456.days.to(.years) will return 1. * * ! Months are not supported for conversions. They are not a * well defined unit of time without the context of a calendar. ! */ public func to(_ unit: TimeUnits) -> Int { if months != 0 { return 0 } if unit == .seconds { var total = seconds total += minutes * 60 total += hours * 60 * 60 total += days * 24 * 60 * 60 total += weeks * 7 * 24 * 60 * 60 total += years * 365 * 24 * 60 * 60 return total } else if unit == .minutes { var total = minutes total += seconds / 60 total += hours * 60 total += days * 24 * 60 total += weeks * 7 * 24 * 60 total += years * 365 * 24 * 60 return total } else if unit == .hours { var total = hours let secondsToMinutes = seconds / 60 total += (minutes + secondsToMinutes) / 60 total += days * 24 total += weeks * 7 * 24 total += years * 365 * 24 return total } else if unit == .days { var total = days let secondsToMinutes = seconds / 60 let minutesToHours = (minutes + secondsToMinutes) / 60 total += (hours + minutesToHours) / 24 total += weeks * 7 total += years * 365 return total } else if unit == .weeks { var total = weeks let secondsToMinutes = seconds / 60 let minutesToHours = (minutes + secondsToMinutes) / 60 let hoursToDays = (hours + minutesToHours) / 24 total += (days + hoursToDays) / 7 total += years * 52 return total } else if unit == .years { var total = years let secondsToMinutes = seconds / 60 let minutesToHours = (minutes + secondsToMinutes) / 60 let hoursToDays = (hours + minutesToHours) / 24 let weeksToDays = weeks * 7 total += (days + hoursToDays + weeksToDays) / 365 return total } return 0 } // MARK: - Date Creation /** * Returns the current date decreased by the amount in self */ public var earlier: Date { return earlier(than: Date()) } /** * Returns the current date increased by the amount in self */ public var later: Date { return later(than: Date()) } /** * Returns the given date decreased by the amount in self. * * - parameter date: The date to decrease * * - returns: A new date with components decreased according to the variables of self */ public func earlier(than date: Date) -> Date { return date.subtract(self) } /** * Returns the given date increased by the amount in self. * * - parameter date: The date to increase * * - returns: A new date with components increased according to the variables of self */ public func later(than date: Date) -> Date { return date.add(self) } // MARK: - Lengthen / Shorten // MARK: In Place /** * Increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * * - parameter chunk: The `TimeChunk` to increase self by * * - returns: The `TimeChunk` with variables increased */ public func lengthened(by chunk: TimeChunk) -> TimeChunk { var newChunk = TimeChunk() newChunk.seconds = seconds + chunk.seconds newChunk.minutes = minutes + chunk.minutes newChunk.hours = hours + chunk.hours newChunk.days = days + chunk.days newChunk.weeks = weeks + chunk.weeks newChunk.months = months + chunk.months newChunk.years = years + chunk.years return newChunk } /** * Decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * * - parameter chunk: The `TimeChunk` to decrease self by * * - returns: The `TimeChunk` with variables decreased */ public func shortened(by chunk: TimeChunk) -> TimeChunk { var newChunk = TimeChunk() newChunk.seconds = seconds - chunk.seconds newChunk.minutes = minutes - chunk.minutes newChunk.hours = hours - chunk.hours newChunk.days = days - chunk.days newChunk.weeks = weeks - chunk.weeks newChunk.months = months - chunk.months newChunk.years = years - chunk.years return newChunk } // MARK: Mutation /** * In place, increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * * - parameter chunk: The `TimeChunk` to increase self by */ public mutating func lengthen(by chunk: TimeChunk) { seconds += chunk.seconds minutes += chunk.minutes hours += chunk.hours days += chunk.days weeks += chunk.weeks months += chunk.months years += chunk.years } /** * In place, decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * * - parameter chunk: The `TimeChunk` to decrease self by */ public mutating func shorten(by chunk: TimeChunk) { seconds -= chunk.seconds minutes -= chunk.minutes hours -= chunk.hours days -= chunk.days weeks -= chunk.weeks months -= chunk.months years -= chunk.years } // MARK: - Operator Overloads /** * Operator overload for adding two `TimeChunk`s */ public static func + (leftAddend: TimeChunk, rightAddend: TimeChunk) -> TimeChunk { return leftAddend.lengthened(by: rightAddend) } /** * Operator overload for subtracting two `TimeChunk`s */ public static func - (minuend: TimeChunk, subtrahend: TimeChunk) -> TimeChunk { return minuend.shortened(by: subtrahend) } /** * Operator overload for checking if two `TimeChunk`s are equal */ public static func == (left: TimeChunk, right: TimeChunk) -> Bool { return left.equals(chunk: right) } /** * Operator overload for inverting (negating all variables) a `TimeChunk` */ public static prefix func - (chunk: TimeChunk) -> TimeChunk { var invertedChunk = chunk invertedChunk.seconds = -chunk.seconds invertedChunk.minutes = -chunk.minutes invertedChunk.hours = -chunk.hours invertedChunk.days = -chunk.days invertedChunk.weeks = -chunk.weeks invertedChunk.months = -chunk.months invertedChunk.years = -chunk.years return invertedChunk } } ================================================ FILE: Clocker/Dependencies/Solar.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLocation public struct Solar { /// The coordinate that is used for the calculation public let coordinate: CLLocationCoordinate2D /// The date to generate sunrise / sunset times for public private(set) var date: Date public private(set) var sunrise: Date? public private(set) var sunset: Date? public private(set) var civilSunrise: Date? public private(set) var civilSunset: Date? public private(set) var nauticalSunrise: Date? public private(set) var nauticalSunset: Date? public private(set) var astronomicalSunrise: Date? public private(set) var astronomicalSunset: Date? // MARK: Init public init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) { self.date = date guard CLLocationCoordinate2DIsValid(coordinate) else { return nil } self.coordinate = coordinate // Fill this Solar object with relevant data calculate() } // MARK: - Public functions /// Sets all of the Solar object's sunrise / sunset variables, if possible. /// - Note: Can return `nil` objects if sunrise / sunset does not occur on that day. public mutating func calculate() { sunrise = calculate(.sunrise, for: date, and: .official) sunset = calculate(.sunset, for: date, and: .official) civilSunrise = calculate(.sunrise, for: date, and: .civil) civilSunset = calculate(.sunset, for: date, and: .civil) nauticalSunrise = calculate(.sunrise, for: date, and: .nautical) nauticalSunset = calculate(.sunset, for: date, and: .nautical) astronomicalSunrise = calculate(.sunrise, for: date, and: .astronimical) astronomicalSunset = calculate(.sunset, for: date, and: .astronimical) } // MARK: - Private functions private enum SunriseSunset { case sunrise case sunset } /// Used for generating several of the possible sunrise / sunset times private enum Zenith: Double { case official = 90.83 case civil = 96 case nautical = 102 case astronimical = 108 } private func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? { guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil } // Get the day of the year var calendar = Calendar(identifier: .gregorian) calendar.timeZone = utcTimezone guard let dayInt = calendar.ordinality(of: .day, in: .year, for: date) else { return nil } let day = Double(dayInt) // Convert longitude to hour value and calculate an approx. time let lngHour = coordinate.longitude / 15 let hourTime: Double = sunriseSunset == .sunrise ? 6 : 18 let time = day + ((hourTime - lngHour) / 24) // Calculate the suns mean anomaly let meanAnomaly = (0.9856 * time) - 3.289 // Calculate the sun's true longitude let subexpression1 = 1.916 * sin(meanAnomaly.degreesToRadians) let subexpression2 = 0.020 * sin(2 * meanAnomaly.degreesToRadians) var longitude = meanAnomaly + subexpression1 + subexpression2 + 282.634 // Normalise L into [0, 360] range longitude = normalise(longitude, withMaximum: 360) // Calculate the Sun's right ascension var rightAscenscion = atan(0.91764 * tan(longitude.degreesToRadians)).radiansToDegrees // Normalise RA into [0, 360] range rightAscenscion = normalise(rightAscenscion, withMaximum: 360) // Right ascension value needs to be in the same quadrant as L... let leftQuadrant = floor(longitude / 90) * 90 let rightQuadrant = floor(rightAscenscion / 90) * 90 rightAscenscion += (leftQuadrant - rightQuadrant) // Convert RA into hours rightAscenscion /= 15 // Calculate Sun's declination let sinDec = 0.39782 * sin(longitude.degreesToRadians) let cosDec = cos(asin(sinDec)) // Calculate the Sun's local hour angle let cosH = (cos(zenith.rawValue.degreesToRadians) - (sinDec * sin(coordinate.latitude.degreesToRadians))) / (cosDec * cos(coordinate.latitude.degreesToRadians)) // No sunrise guard cosH < 1 else { return nil } // No sunset guard cosH > -1 else { return nil } // Finish calculating H and convert into hours let tempH = sunriseSunset == .sunrise ? 360 - acos(cosH).radiansToDegrees : acos(cosH).radiansToDegrees let hours = tempH / 15.0 // Calculate local mean time of rising let localMeanRisingTime = hours + rightAscenscion - (0.06571 * time) - 6.622 // Adjust time back to UTC var utcCompatibleTime = localMeanRisingTime - lngHour // Normalise UT into [0, 24] range utcCompatibleTime = normalise(utcCompatibleTime, withMaximum: 24) // Calculate all of the sunrise's / sunset's date components let hour = floor(utcCompatibleTime) let minute = floor((utcCompatibleTime - hour) * 60.0) let second = (((utcCompatibleTime - hour) * 60) - minute) * 60.0 let shouldBeYesterday = lngHour > 0 && utcCompatibleTime > 12 && sunriseSunset == .sunrise let shouldBeTomorrow = lngHour < 0 && utcCompatibleTime < 12 && sunriseSunset == .sunset let setDate: Date if shouldBeYesterday { setDate = Date(timeInterval: -(60 * 60 * 24), since: date) } else if shouldBeTomorrow { setDate = Date(timeInterval: 60 * 60 * 24, since: date) } else { setDate = date } var components = calendar.dateComponents([.day, .month, .year], from: setDate) components.hour = Int(hour) components.minute = Int(minute) components.second = Int(second) calendar.timeZone = utcTimezone return calendar.date(from: components) } /// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum` private func normalise(_ value: Double, withMaximum maximum: Double) -> Double { var value = value if value < 0 { value += maximum } if value > maximum { value -= maximum } return value } } public extension Solar { /// Whether the location specified by the `latitude` and `longitude` is in daytime on `date` /// - Complexity: O(1) var isDaytime: Bool { guard let sunrise = sunrise, let sunset = sunset else { return false } let beginningOfDay = sunrise.timeIntervalSince1970 let endOfDay = sunset.timeIntervalSince1970 let currentTime = date.timeIntervalSince1970 let isSunriseOrLater = currentTime >= beginningOfDay let isBeforeSunset = currentTime < endOfDay return isSunriseOrLater && isBeforeSunset } /// Whether the location specified by the `latitude` and `longitude` is in nighttime on `date` /// - Complexity: O(1) var isNighttime: Bool { return !isDaytime } } // MARK: - Helper extensions private extension Double { var degreesToRadians: Double { return Double(self) * (Double.pi / 180.0) } var radiansToDegrees: Double { return (Double(self) * 180.0) / Double.pi } } ================================================ FILE: Clocker/Dependencies/iVersion/iVersion.bundle/da.lproj/Localizable.strings ================================================ "iVersionInThisVersionTitle" = "Nyheder i denne version"; "iVersionUpdateAvailableTitle" = "Ny version tilgængelig"; "iVersionVersionLabelFormat" = "Version %@"; "iVersionIgnoreButton" = "Ignorér"; "iVersionDownloadButton" = "Hent"; "iVersionRemindButton" = "Påmind mig senere"; "iVersionOKButton" = "OK"; ================================================ FILE: Clocker/Dependencies/iVersion/iVersion.h ================================================ // // iVersion.h // // Version 1.11.4 // // Created by Nick Lockwood on 26/01/2011. // Copyright 2011 Charcoal Design // // Distributed under the permissive zlib license // Get the latest version from here: // // https://github.com/nicklockwood/iVersion // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis" #import #undef weak_delegate #if __has_feature(objc_arc_weak) && \ (TARGET_OS_IPHONE || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8) #define weak_delegate weak #else #define weak_delegate unsafe_unretained #endif #import #if TARGET_OS_IPHONE #import #define IVERSION_EXTERN UIKIT_EXTERN #else #import #define IVERSION_EXTERN APPKIT_EXTERN #endif #if defined(IVERSION_USE_STOREKIT) && IVERSION_USE_STOREKIT #import #endif extern NSString *const iVersionErrorDomain; //localisation string keys IVERSION_EXTERN NSString *const iVersionInThisVersionTitleKey; //iVersionInThisVersionTitle IVERSION_EXTERN NSString *const iVersionUpdateAvailableTitleKey; //iVersionUpdateAvailableTitle IVERSION_EXTERN NSString *const iVersionVersionLabelFormatKey; //iVersionVersionLabelFormat IVERSION_EXTERN NSString *const iVersionOKButtonKey; //iVersionOKButton IVERSION_EXTERN NSString *const iVersionIgnoreButtonKey; //iVersionIgnoreButton IVERSION_EXTERN NSString *const iVersionRemindButtonKey; //iVersionRemindButton IVERSION_EXTERN NSString *const iVersionDownloadButtonKey; //iVersionDownloadButton typedef NS_ENUM(NSUInteger, iVersionErrorCode) { iVersionErrorBundleIdDoesNotMatchAppStore = 1, iVersionErrorApplicationNotFoundOnAppStore, iVersionErrorOSVersionNotSupported }; typedef NS_ENUM(NSInteger, iVersionUpdatePriority) { iVersionUpdatePriorityDefault = 0, iVersionUpdatePriorityLow = 1, iVersionUpdatePriorityMedium = 2, iVersionUpdatePriorityHigh = 3 }; @interface NSString(iVersion) - (NSComparisonResult)compareVersion:(NSString *)version; - (NSComparisonResult)compareVersionDescending:(NSString *)version; @end @interface iVersion : NSObject + (iVersion *)sharedInstance; //app store ID - this is only needed if your //bundle ID is not unique between iOS and Mac app stores @property (nonatomic, assign) NSUInteger appStoreID; //application details - these are set automatically @property (nonatomic, copy) NSString *applicationVersion; @property (nonatomic, copy) NSString *applicationBundleID; @property (nonatomic, copy) NSString *appStoreCountry; //usage settings - these have sensible defaults @property (nonatomic, assign) BOOL showOnFirstLaunch; @property (nonatomic, assign) BOOL groupNotesByVersion; @property (nonatomic, assign) float checkPeriod; @property (nonatomic, assign) float remindPeriod; //message text - you may wish to customise these @property (nonatomic, copy) NSString *inThisVersionTitle; @property (nonatomic, copy) NSString *updateAvailableTitle; @property (nonatomic, copy) NSString *versionLabelFormat; @property (nonatomic, copy) NSString *okButtonLabel; @property (nonatomic, copy) NSString *ignoreButtonLabel; @property (nonatomic, copy) NSString *remindButtonLabel; @property (nonatomic, copy) NSString *downloadButtonLabel; //debugging and prompt overrides @property (nonatomic, assign) iVersionUpdatePriority updatePriority; @property (nonatomic, assign) BOOL useUIAlertControllerIfAvailable; @property (nonatomic, assign) BOOL useAllAvailableLanguages; @property (nonatomic, assign) BOOL onlyPromptIfMainWindowIsAvailable; @property (nonatomic, assign) BOOL useAppStoreDetailsIfNoPlistEntryFound; @property (nonatomic, assign) BOOL checkAtLaunch; @property (nonatomic, assign) BOOL verboseLogging; @property (nonatomic, assign) BOOL previewMode; //advanced properties for implementing custom behaviour @property (nonatomic, copy) NSString *remoteVersionsPlistURL; @property (nonatomic, copy) NSString *localVersionsPlistPath; @property (nonatomic, copy) NSString *ignoredVersion; @property (nonatomic, strong) NSDate *lastChecked; @property (nonatomic, strong) NSDate *lastReminded; @property (nonatomic, strong) NSURL *updateURL; @property (nonatomic, assign) BOOL viewedVersionDetails; @property (nonatomic, copy) NSDictionary *remoteVersionsDict; - (NSString *)versionDetailsSince:(NSString *)lastVersion inDict:(NSDictionary *)dict; //manually control behaviour @property (NS_NONATOMIC_IOSONLY, readonly) BOOL openAppPageInAppStore; - (void)checkIfNewVersion; @property (readonly, copy) NSString *versionDetails; @property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldCheckForNewVersion; - (void)checkForNewVersion; @end #pragma clang diagnostic pop ================================================ FILE: Clocker/Dependencies/iVersion/iVersion.m ================================================ // // iVersion.m // // Version 1.11.4 // // Created by Nick Lockwood on 26/01/2011. // Copyright 2011 Charcoal Design // // Distributed under the permissive zlib license // Get the latest version from here: // // https://github.com/nicklockwood/iVersion // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #import "iVersion.h" #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis" #pragma clang diagnostic ignored "-Wundeclared-selector" #pragma clang diagnostic ignored "-Wdirect-ivar-access" #pragma clang diagnostic ignored "-Wunused-macros" #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wselector" #pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wgnu" #import #if !__has_feature(objc_arc) #error This class requires automatic reference counting #endif NSString *const iVersionErrorDomain = @"iVersionErrorDomain"; NSString *const iVersionInThisVersionTitleKey = @"iVersionInThisVersionTitle"; NSString *const iVersionUpdateAvailableTitleKey = @"iVersionUpdateAvailableTitle"; NSString *const iVersionVersionLabelFormatKey = @"iVersionVersionLabelFormat"; NSString *const iVersionOKButtonKey = @"iVersionOKButton"; NSString *const iVersionIgnoreButtonKey = @"iVersionIgnoreButton"; NSString *const iVersionRemindButtonKey = @"iVersionRemindButton"; NSString *const iVersionDownloadButtonKey = @"iVersionDownloadButton"; static NSString *const iVersionAppStoreIDKey = @"iVersionAppStoreID"; static NSString *const iVersionLastVersionKey = @"iVersionLastVersionChecked"; static NSString *const iVersionIgnoreVersionKey = @"iVersionIgnoreVersion"; static NSString *const iVersionLastCheckedKey = @"iVersionLastChecked"; static NSString *const iVersionLastRemindedKey = @"iVersionLastReminded"; static NSString *const iVersionMacAppStoreBundleID = @"com.apple.appstore"; static NSString *const iVersionAppLookupURLFormat = @"http://itunes.apple.com/%@/lookup"; static NSString *const iVersionMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%@"; #define SECONDS_IN_A_DAY 86400.0 #define MAC_APP_STORE_REFRESH_DELAY 5.0 #define REQUEST_TIMEOUT 60.0 @implementation NSString(iVersion) - (NSComparisonResult)compareVersion:(NSString *)version { return [self compare:version options:NSNumericSearch]; } - (NSComparisonResult)compareVersionDescending:(NSString *)version { return (NSComparisonResult)(0 - [self compareVersion:version]); } @end static NSString *mostRecentVersionInDict(NSDictionary *dictionary) { return [dictionary.allKeys sortedArrayUsingSelector:@selector(compareVersion:)].lastObject; } @interface iVersion () @property (nonatomic, strong) NSError *downloadError; @property (nonatomic, copy) NSString *versionDetails; @property (nonatomic, strong) id visibleLocalAlert; @property (nonatomic, strong) id visibleRemoteAlert; @property (nonatomic, assign) BOOL checkingForNewVersion; @end @implementation iVersion + (void)load { [self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO]; } + (iVersion *)sharedInstance { static iVersion *sharedInstance = nil; if (sharedInstance == nil) { sharedInstance = [[iVersion alloc] init]; } return sharedInstance; } - (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString { static NSBundle *bundle = nil; if (bundle == nil) { NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"iVersion" ofType:@"bundle"]; if (self.useAllAvailableLanguages) { bundle = [NSBundle bundleWithPath:bundlePath]; NSString *language = [NSLocale preferredLanguages].count? [NSLocale preferredLanguages][0]: @"en"; if (![bundle.localizations containsObject:language]) { language = [language componentsSeparatedByString:@"-"][0]; } if ([bundle.localizations containsObject:language]) { bundlePath = [bundle pathForResource:language ofType:@"lproj"]; } } bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle]; } defaultString = [bundle localizedStringForKey:key value:defaultString table:nil]; return defaultString ?: [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil]; } - (iVersion *)init { if ((self = [super init])) { //get country self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; if ([self.appStoreCountry isEqualToString:@"150"]) { self.appStoreCountry = @"eu"; } else if ([self.appStoreCountry stringByReplacingOccurrencesOfString:@"[A-Za-z]{2}" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)].length) { self.appStoreCountry = @"us"; } //application version (use short version preferentially) self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; if ((self.applicationVersion).length == 0) { self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; } //bundle id self.applicationBundleID = [NSBundle mainBundle].bundleIdentifier; //default settings self.updatePriority = iVersionUpdatePriorityDefault; self.useAllAvailableLanguages = YES; self.onlyPromptIfMainWindowIsAvailable = YES; self.checkAtLaunch = YES; self.checkPeriod = 0.0f; self.remindPeriod = 1.0f; self.verboseLogging = YES; #ifdef DEBUG //enable verbose logging in debug mode self.verboseLogging = YES; #endif //app launched [self performSelectorOnMainThread:@selector(applicationLaunched) withObject:nil waitUntilDone:NO]; } return self; } - (NSString *)inThisVersionTitle { return _inThisVersionTitle ?: [self localizedStringForKey:iVersionInThisVersionTitleKey withDefault:@"New in this version"]; } - (NSString *)updateAvailableTitle { return _updateAvailableTitle ?: [self localizedStringForKey:iVersionUpdateAvailableTitleKey withDefault:@"New version available"]; } - (NSString *)versionLabelFormat { return _versionLabelFormat ?: [self localizedStringForKey:iVersionVersionLabelFormatKey withDefault:@"Version %@"]; } - (NSString *)okButtonLabel { return _okButtonLabel ?: [self localizedStringForKey:iVersionOKButtonKey withDefault:@"OK"]; } - (NSString *)ignoreButtonLabel { return _ignoreButtonLabel ?: [self localizedStringForKey:iVersionIgnoreButtonKey withDefault:@"Ignore"]; } - (NSString *)downloadButtonLabel { return _downloadButtonLabel ?: [self localizedStringForKey:iVersionDownloadButtonKey withDefault:@"Download"]; } - (NSString *)remindButtonLabel { return _remindButtonLabel ?: [self localizedStringForKey:iVersionRemindButtonKey withDefault:@"Remind Me Later"]; } - (NSURL *)updateURL { if (_updateURL) { return _updateURL; } if (!self.appStoreID) { NSLog(@"iVersion error: No App Store ID was found for this application. If the application is not intended for App Store release then you must specify a custom updateURL."); } return [NSURL URLWithString:[NSString stringWithFormat:iVersionMacAppStoreURLFormat, @(self.appStoreID)]]; } - (NSUInteger)appStoreID { return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionAppStoreIDKey] unsignedIntegerValue]; } - (void)setAppStoreID:(NSUInteger)appStoreID { [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)appStoreID forKey:iVersionAppStoreIDKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSDate *)lastChecked { return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastCheckedKey]; } - (void)setLastChecked:(NSDate *)date { [[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastCheckedKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSDate *)lastReminded { return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastRemindedKey]; } - (void)setLastReminded:(NSDate *)date { [[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastRemindedKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSString *)ignoredVersion { return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionIgnoreVersionKey]; } - (void)setIgnoredVersion:(NSString *)version { [[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionIgnoreVersionKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (BOOL)viewedVersionDetails { return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey] isEqualToString:self.applicationVersion]; } - (void)setViewedVersionDetails:(BOOL)viewed { [[NSUserDefaults standardUserDefaults] setObject:(viewed? self.applicationVersion: nil) forKey:iVersionLastVersionKey]; } - (NSString *)lastVersion { return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey]; } - (void)setLastVersion:(NSString *)version { [[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionLastVersionKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSDictionary *)localVersionsDict { static NSDictionary *versionsDict = nil; if (versionsDict == nil) { if (self.localVersionsPlistPath == nil) { versionsDict = [[NSDictionary alloc] init]; //empty dictionary } else { NSString *versionsFile = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:self.localVersionsPlistPath]; versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile]; if (!versionsDict) { // Get the path to versions plist in localized directory NSArray *pathComponents = [self.localVersionsPlistPath componentsSeparatedByString:@"."]; versionsFile = (pathComponents.count == 2) ? [[NSBundle mainBundle] pathForResource:pathComponents[0] ofType:pathComponents[1]] : nil; versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile]; } } } return versionsDict; } - (NSString *)versionDetails:(NSString *)version inDict:(NSDictionary *)dict { id versionData = dict[version]; if ([versionData isKindOfClass:[NSString class]]) { return versionData; } else if ([versionData isKindOfClass:[NSArray class]]) { return [versionData componentsJoinedByString:@"\n"]; } return nil; } - (NSString *)versionDetailsSince:(NSString *)lastVersion inDict:(NSDictionary *)dict { if (self.previewMode) { lastVersion = @"0"; } BOOL newVersionFound = NO; NSMutableString *details = [NSMutableString string]; NSArray *versions = [dict.allKeys sortedArrayUsingSelector:@selector(compareVersionDescending:)]; for (NSString *version in versions) { if ([version compareVersion:lastVersion] == NSOrderedDescending) { newVersionFound = YES; if (self.groupNotesByVersion) { [details appendString:[self.versionLabelFormat stringByReplacingOccurrencesOfString:@"%@" withString:version]]; [details appendString:@"\n\n"]; } [details appendString:[self versionDetails:version inDict:dict] ?: @""]; [details appendString:@"\n"]; if (self.groupNotesByVersion) { [details appendString:@"\n"]; } } } return newVersionFound? [details stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]: nil; } - (NSString *)versionDetails { if (!_versionDetails) { if (self.viewedVersionDetails) { self.versionDetails = [self versionDetails:self.applicationVersion inDict:[self localVersionsDict]]; } else { self.versionDetails = [self versionDetailsSince:self.lastVersion inDict:[self localVersionsDict]]; } } return _versionDetails; } - (void)downloadedVersionsData { //only show when main window is available if (self.onlyPromptIfMainWindowIsAvailable && ![NSApplication sharedApplication].mainWindow) { [self performSelector:@selector(downloadedVersionsData) withObject:nil afterDelay:0.5]; return; } if (self.checkingForNewVersion) { //no longer checking self.checkingForNewVersion = NO; //check if data downloaded if (!self.remoteVersionsDict) { //log the error if (self.downloadError) { NSLog(@"iVersion update check failed because: %@", (self.downloadError).localizedDescription); } else { NSLog(@"iVersion update check failed because an unknown error occured"); } return; } //get version details NSString *details = [self versionDetailsSince:self.applicationVersion inDict:self.remoteVersionsDict]; NSString *mostRecentVersion = mostRecentVersionInDict(self.remoteVersionsDict); if (details) { //check if ignored BOOL showDetails = ![self.ignoredVersion isEqualToString:mostRecentVersion] || self.previewMode; //show details if (showDetails && !self.visibleRemoteAlert) { NSString *title = self.updateAvailableTitle; if (!self.groupNotesByVersion) { title = [title stringByAppendingFormat:@" (%@)", mostRecentVersion]; } self.visibleRemoteAlert = [self showAlertWithTitle:title details:details defaultButton:self.downloadButtonLabel ignoreButton:[self showIgnoreButton]? self.ignoreButtonLabel: nil remindButton:[self showRemindButton]? self.remindButtonLabel: nil]; } } } } - (BOOL)shouldCheckForNewVersion { //debug mode? if (!self.previewMode) { //check if within the reminder period if (self.lastReminded != nil) { //reminder takes priority over check period if ([[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY) { if (self.verboseLogging) { NSLog(@"iVersion did not check for a new version because the user last asked to be reminded less than %g days ago", self.remindPeriod); } return NO; } } //check if within the check period else if (self.lastChecked != nil && [[NSDate date] timeIntervalSinceDate:self.lastChecked] < self.checkPeriod * SECONDS_IN_A_DAY) { if (self.verboseLogging) { NSLog(@"iVersion did not check for a new version because the last check was less than %g days ago", self.checkPeriod); } return NO; } } else if (self.verboseLogging) { NSLog(@"iVersion debug mode is enabled - make sure you disable this for release"); } //perform the check return YES; } - (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString { self.appStoreID = appStoreIDString.longLongValue; } - (void)checkForNewVersionInBackground { @synchronized (self) { @autoreleasepool { __block BOOL newerVersionAvailable = NO; __block BOOL osVersionSupported = NO; __block NSString *latestVersion = nil; __block NSDictionary *versions = nil; //first check iTunes NSString *iTunesServiceURL = [NSString stringWithFormat:iVersionAppLookupURLFormat, self.appStoreCountry]; if (self.appStoreID) { iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%@", @(self.appStoreID)]; } else { iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID]; } if (self.verboseLogging) { NSLog(@"iVersion is checking %@ for a new app version...", iTunesServiceURL); } __block NSError *jsonError = nil; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunesServiceURL] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:REQUEST_TIMEOUT]; __weak typeof(self) weakSelf = self; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; __strong typeof(self) strongSelf = weakSelf; if (!strongSelf) { return; } if (error != nil || data == nil) { return; } NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (!jsonError) { //check bundle ID matches NSArray *resultsArray = json[@"results"]; if (![resultsArray isKindOfClass:[NSArray class]]) { return; } NSDictionary *firstResult = [resultsArray firstObject]; if (!firstResult) { return; } NSString *bundleID = firstResult[@"bundleId"]; if (![bundleID isKindOfClass:[NSString class]]) { return; } if (bundleID) { if ([bundleID isEqualToString:strongSelf.applicationBundleID]) { //get supported OS version NSString *minimumSupportedOSVersion = firstResult[@"minimumOsVersion"]; if (!minimumSupportedOSVersion || ![minimumSupportedOSVersion isKindOfClass:[NSString class]]) { return; } NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion; NSString *systemVersion = [NSString stringWithFormat:@"%zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion]; osVersionSupported = ([systemVersion compare:minimumSupportedOSVersion options:NSNumericSearch] != NSOrderedAscending); if (!osVersionSupported) { error = [NSError errorWithDomain:iVersionErrorDomain code:iVersionErrorOSVersionNotSupported userInfo:@{NSLocalizedDescriptionKey: @"Current OS version is not supported."}]; } //get version details NSString *releaseNotes = firstResult[@"releaseNotes"]; latestVersion = firstResult[@"version"]; if (latestVersion && osVersionSupported) { versions = @{latestVersion: releaseNotes ?: @""}; } //get app id if (!strongSelf.appStoreID) { NSString *appStoreIDString = firstResult[@"trackId"]; [strongSelf performSelectorOnMainThread:@selector(setAppStoreIDOnMainThread:) withObject:appStoreIDString waitUntilDone:YES]; if (strongSelf.verboseLogging) { NSLog(@"iVersion found the app on iTunes. The App Store ID is %@", appStoreIDString); } } //check for new version newerVersionAvailable = ([latestVersion compareVersion:strongSelf.applicationVersion] == NSOrderedDescending); if (strongSelf.verboseLogging) { if (newerVersionAvailable) { NSLog(@"iVersion found a new version (%@) of the app on iTunes. Current version is %@", latestVersion, strongSelf.applicationVersion); } else { NSLog(@"iVersion did not find a new version of the app on iTunes. Current version is %@, latest version is %@", strongSelf.applicationVersion, latestVersion); } } } else { if (strongSelf.verboseLogging) { NSLog(@"iVersion found that the application bundle ID (%@) does not match the bundle ID of the app found on iTunes (%@) with the specified App Store ID (%@)", strongSelf.applicationBundleID, bundleID, @(strongSelf.appStoreID)); } error = [NSError errorWithDomain:iVersionErrorDomain code:iVersionErrorBundleIdDoesNotMatchAppStore userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}]; } } else if (strongSelf.appStoreID || !strongSelf.remoteVersionsPlistURL) { if (strongSelf.verboseLogging) { NSLog(@"iVersion could not find this application on iTunes. If your app is not intended for App Store release then you must specify a remoteVersionsPlistURL. If this is the first release of your application then it's not a problem that it cannot be found on the store yet"); } error = [NSError errorWithDomain:iVersionErrorDomain code:iVersionErrorApplicationNotFoundOnAppStore userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}]; } else if (!strongSelf.appStoreID && strongSelf.verboseLogging) { NSLog(@"iVersion could not find your app on iTunes. If your app is not yet on the store or is not intended for App Store release then don't worry about this"); } } else { //http error NSString *message = [NSString stringWithFormat:@"The server returned a %@ error", @([httpResponse statusCode])]; error = [NSError errorWithDomain:@"HTTPResponseErrorDomain" code:[httpResponse statusCode] userInfo:@{NSLocalizedDescriptionKey: message}]; } [strongSelf performSelectorOnMainThread:@selector(setDownloadError:) withObject:error waitUntilDone:YES]; [strongSelf performSelectorOnMainThread:@selector(setRemoteVersionsDict:) withObject:versions waitUntilDone:YES]; [strongSelf performSelectorOnMainThread:@selector(setLastChecked:) withObject:[NSDate date] waitUntilDone:YES]; [strongSelf performSelectorOnMainThread:@selector(downloadedVersionsData) withObject:nil waitUntilDone:YES]; }]; [dataTask resume]; } } } - (void)checkForNewVersion { if (!self.checkingForNewVersion) { self.checkingForNewVersion = YES; [self performSelectorInBackground:@selector(checkForNewVersionInBackground) withObject:nil]; } } - (void)checkIfNewVersion { //only show when main window is available if (self.onlyPromptIfMainWindowIsAvailable && ![NSApplication sharedApplication].mainWindow) { [self performSelector:@selector(checkIfNewVersion) withObject:nil afterDelay:0.5]; return; } if (self.lastVersion != nil || self.showOnFirstLaunch || self.previewMode) { if ([self.applicationVersion compareVersion:self.lastVersion] == NSOrderedDescending || self.previewMode) { //clear reminder self.lastReminded = nil; //get version details BOOL showDetails = !!self.versionDetails; //show details if (showDetails && !self.visibleLocalAlert && !self.visibleRemoteAlert) { self.visibleLocalAlert = [self showAlertWithTitle:self.inThisVersionTitle details:self.versionDetails defaultButton:self.okButtonLabel ignoreButton:nil remindButton:nil]; } } } else { //record this as last viewed release self.viewedVersionDetails = YES; } } - (BOOL)showIgnoreButton { return (self.ignoreButtonLabel).length && self.updatePriority < iVersionUpdatePriorityMedium; } - (BOOL)showRemindButton { return (self.remindButtonLabel).length && self.updatePriority < iVersionUpdatePriorityHigh; } - (id)showAlertWithTitle:(NSString *)title details:(NSString *)details defaultButton:(NSString *)defaultButton ignoreButton:(NSString *)ignoreButton remindButton:(NSString *)remindButton { NSAlert *alert = [[NSAlert alloc] init]; alert.messageText = title; alert.informativeText = self.inThisVersionTitle; [alert addButtonWithTitle:defaultButton]; NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 380.0, 15.0)]; NSSize contentSize = scrollview.contentSize; scrollview.borderType = NSBezelBorder; scrollview.hasVerticalScroller = YES; scrollview.hasHorizontalScroller = NO; scrollview.autoresizingMask = (NSAutoresizingMaskOptions)(NSViewWidthSizable|NSViewHeightSizable); NSTextView *textView = [[NSTextView alloc] initWithFrame:NSMakeRect(0.0, 0.0, contentSize.width, contentSize.height)]; textView.minSize = NSMakeSize(0.0, contentSize.height); textView.maxSize = NSMakeSize(FLT_MAX, FLT_MAX); textView.verticallyResizable = YES; textView.horizontallyResizable = NO; textView.editable = NO; textView.autoresizingMask = NSViewWidthSizable; textView.textContainer.containerSize = NSMakeSize(contentSize.width, FLT_MAX); textView.textContainer.widthTracksTextView = YES; textView.string = details; scrollview.documentView = textView; [textView sizeToFit]; CGFloat height = MIN(200.0, [[scrollview documentView] frame].size.height) + 3.0; scrollview.frame = NSMakeRect(0.0, 0.0, scrollview.frame.size.width, height); alert.accessoryView = scrollview; if (ignoreButton) { [alert addButtonWithTitle:ignoreButton]; } if (remindButton) { [alert addButtonWithTitle:remindButton]; NSModalResponse modalResponse = [alert runModal]; if (modalResponse == NSAlertFirstButtonReturn) { //right most button [self didDismissAlert:alert withButtonAtIndex:0]; } else if (modalResponse == NSAlertSecondButtonReturn) { [self didDismissAlert:alert withButtonAtIndex:1]; } else { [self didDismissAlert:alert withButtonAtIndex:2]; } } return alert; } - (void)didDismissAlert:(id)alertView withButtonAtIndex:(NSInteger)buttonIndex { //get button indices NSInteger downloadButtonIndex = 0; NSInteger ignoreButtonIndex = [self showIgnoreButton]? 1: 0; NSInteger remindButtonIndex = [self showRemindButton]? ignoreButtonIndex + 1: 0; //latest version NSString *latestVersion = mostRecentVersionInDict(self.remoteVersionsDict); if (alertView == self.visibleLocalAlert) { //record that details have been viewed self.viewedVersionDetails = YES; //release alert self.visibleLocalAlert = nil; return; } if (buttonIndex == downloadButtonIndex) { //clear reminder self.lastReminded = nil; [self openAppPageInAppStore]; } else if (buttonIndex == ignoreButtonIndex) { //ignore this version self.ignoredVersion = latestVersion; self.lastReminded = nil; } else if (buttonIndex == remindButtonIndex) { //remind later self.lastReminded = [NSDate date]; } //release alert self.visibleRemoteAlert = nil; } - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(__unused void *)contextInfo { [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn]; } - (void)openAppPageWhenAppStoreLaunched { //check if app store is running for (NSRunningApplication *app in [NSWorkspace sharedWorkspace].runningApplications) { if ([app.bundleIdentifier isEqualToString:iVersionMacAppStoreBundleID]) { //open app page [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.updateURL afterDelay:MAC_APP_STORE_REFRESH_DELAY]; return; } } //try again [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0]; } - (BOOL)openAppPageInAppStore { if (!_updateURL && !self.appStoreID) { if (self.verboseLogging) { NSLog(@"iVersion was unable to open the App Store because the app store ID is not set."); } return NO; } if (self.verboseLogging) { NSLog(@"iVersion will open the App Store using the following URL: %@", self.updateURL); } [[NSWorkspace sharedWorkspace] openURL:self.updateURL]; if (!_updateURL) [self openAppPageWhenAppStoreLaunched]; return YES; } - (void)applicationLaunched { if (self.checkAtLaunch) { [self checkIfNewVersion]; if ([self shouldCheckForNewVersion]) [self checkForNewVersion]; } else if (self.verboseLogging) { NSLog(@"iVersion will not check for updates because the checkAtLaunch option is disabled"); } } @end ================================================ FILE: Clocker/Events and Reminders/CalendarHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import EventKit extension EventCenter { func calendarAccessGranted() -> Bool { return EKEventStore.authorizationStatus(for: .event) == .fullAccess } func calendarAccessNotDetermined() -> Bool { return EKEventStore.authorizationStatus(for: .event) == .notDetermined } func calendarAccessDenied() -> Bool { return EKEventStore.authorizationStatus(for: .event) == .denied } func fetchSourcesAndCalendars() -> [Any] { var sourcesAndCalendars: [Any] = [] // Fetch array of user's calendars sorted first by source title and then by calendar title let calendars = eventStore.calendars(for: .event).sorted { cal1, cal2 -> Bool in if cal1.source.sourceIdentifier == cal2.source.sourceIdentifier { return cal1.title < cal2.title } return cal1.source.title < cal2.source.title } // Now time to fetch the events // Fetch the user-selected calendars. Initially, all the calendars will be selected var setOfCalendars: Set = Set() if let userCalendars = DataStore.shared().selectedCalendars(), !userCalendars.isEmpty { setOfCalendars = Set(userCalendars) } var currentSourceTitle = UserDefaultKeys.emptyString for calendar in calendars { if !(calendar.source.title == currentSourceTitle) { sourcesAndCalendars.append(calendar.source.title) currentSourceTitle = calendar.source.title } let isCalendarSelected = setOfCalendars.contains(calendar.calendarIdentifier) let calendarInfo = CalendarInfo(calendar: calendar, selected: isCalendarSelected) sourcesAndCalendars.append(calendarInfo) } return sourcesAndCalendars } func isThereAnUpcomingCalendarEvent() -> Bool { if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { let filteredDates = EventCenter.sharedCenter().eventsForDate let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else { return false } for event in events { if event.event.startDate.timeIntervalSinceNow > 0, !event.isAllDay { let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60 if timeForEventToStart > 30 { Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") continue } return true } } } return false } /* Used for the compact menubar mode. Returns a tuple with 0 as the header string and 1 as the subtitle string */ func separateFormat(event: EKEvent) -> (String, String)? { guard let truncateLength = DataStore.shared().retrieve(key: UserDefaultKeys.truncateTextLength) as? NSNumber, let eventTitle = event.title else { return nil } let seconds = event.startDate.timeIntervalSinceNow var formattedTitle: String = UserDefaultKeys.emptyString if eventTitle.count > truncateLength.intValue { let truncateIndex = eventTitle.index(eventTitle.startIndex, offsetBy: truncateLength.intValue) let truncatedTitle = String(eventTitle[.. 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 (formattedTitle, menubarText) } func nextOccuring(_: [EventInfo]) -> EventInfo? { if calendarAccessDenied() || calendarAccessNotDetermined() { return nil } let relevantEvents = filteredEvents[autoupdatingCalendar.startOfDay(for: Date())] ?? [] let filteredEvents = relevantEvents.filter { $0.event.isAllDay == false && $0.event.endDate.timeIntervalSinceNow > 0 && $0.event.startDate.timeIntervalSinceNow > -300 && $0.event.status != .canceled } if filteredEvents.count == 1 { return filteredEvents.first } // If there are multiple events coming up, prefer the ones the currentUser has accepted let acceptedEvents = filteredEvents.filter { $0.attendeStatus == .accepted } let optionalEvents = filteredEvents.filter { $0.attendeStatus == .tentative } if let firstAcceptedEvent = acceptedEvents.first { return firstAcceptedEvent } // If there are no accepted events, prefer the first optional event if acceptedEvents.isEmpty, !optionalEvents.isEmpty { return optionalEvents.first } // Otherwise check if there's a filtered event at all and return it if let first = filteredEvents.first { return first } let filteredAllDayEvent = relevantEvents.filter { $0.isAllDay }.first return filteredAllDayEvent } func upcomingEventsForDay(_: [EventInfo]) -> [EventInfo]? { if calendarAccessDenied() || calendarAccessNotDetermined() { return nil } let todayEvents = filteredEvents[autoupdatingCalendar.startOfDay(for: Date())] ?? [] let tomorrowEvents = filteredEvents[autoupdatingCalendar.startOfDay(for: Date().addingTimeInterval(86400))] ?? [] let relevantEvents = todayEvents + tomorrowEvents return relevantEvents.filter { $0.event.startDate.timeIntervalSinceNow > -300 } } func initializeStoreIfNeccesary() { if eventStore == nil { eventStore = EKEventStore() } } func requestAccess(to entity: EKEntityType, completionHandler: @escaping (_ granted: Bool) -> Void) { initializeStoreIfNeccesary() if entity == .event { eventStore.requestFullAccessToEvents { [weak self] granted, error in if let self = self, granted { self.saveDefaultIdentifiersList() } else if let requestError = error { Logger.info("Unable to request events access due to \(requestError.localizedDescription)") } else { Logger.info("Request events access failed silently") } completionHandler(granted) } } else { eventStore.requestFullAccessToReminders { granted, error in if let requestError = error { Logger.info("Unable to request reminders access due to \(requestError.localizedDescription)") } else if !granted { Logger.info("Request reminders access failed silently") } completionHandler(granted) } } } func filterEvents() { filteredEvents = [:] if let selectedCalendars = DataStore.shared().selectedCalendars() { for date in eventsForDate.keys { if let events = eventsForDate[date] { for event in events { if event.event.calendar != nil && selectedCalendars.contains(event.event.calendar.calendarIdentifier) { if filteredEvents[date] == nil { filteredEvents[date] = Array() } filteredEvents[date]?.append(event) } } } } Logger.info("Fetched filtered events for \(filteredEvents.count) days\n") return } Logger.info("Unable to filter events because user hasn't selected calendars") } func saveDefaultIdentifiersList() { OperationQueue.main.addOperation { [weak self] in guard let self = self else { return } let allCalendars = self.retrieveAllCalendarIdentifiers() if !allCalendars.isEmpty { UserDefaults.standard.set(allCalendars, forKey: UserDefaultKeys.selectedCalendars) Logger.info("Finished saving all calendar identifiers in default") self.filterEvents() } } } func retrieveAllCalendarIdentifiers() -> [String] { return eventStore.calendars(for: .event).map { calendar -> String in calendar.calendarIdentifier } } private func createDateComponents(with calendar: NSCalendar?, _ day: Int) -> Date { var dateComps = DateComponents() dateComps.day = day guard let convertedDate = calendar?.date(byAdding: dateComps, to: Date(), options: NSCalendar.Options.matchFirst) else { return Date() } return convertedDate } private func shouldSkipEvent(_ event: EKEvent) -> Bool { if event.hasAttendees, let attendes = event.attendees { for participant in attendes where participant.isCurrentUser && participant.participantStatus == .declined { return true } } return false } func fetchEvents(_ start: Int, _ end: Int) { if calendarAccessDenied() || calendarAccessNotDetermined() { Logger.info("Refetching aborted because we don't have permission!") return } initializeStoreIfNeccesary() let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) let startDate = createDateComponents(with: calendar, start) let endDate = createDateComponents(with: calendar, end) // Passing in nil for calendars to search all calendars let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil) var eventsForDateMapper: [Date: [EventInfo]] = [:] let events = eventStore.events(matching: predicate) // Populate our cache with events that match our startDate and endDate. // We map eachDate to array of events happening on that day for event in events where shouldSkipEvent(event) == false { // Iterate through the days this event spans. We only care about // days for this event that are between startDate and endDate let eventStartDate = event.startDate as NSDate let eventEndDate = event.endDate as NSDate var date = eventStartDate.laterDate(startDate) let final = eventEndDate.earlierDate(endDate) date = autoupdatingCalendar.startOfDay(for: date) while date.compare(final) == .orderedAscending { guard var nextDate = autoupdatingCalendar.date(byAdding: Calendar.Component.day, value: 1, to: date) else { Logger.info("Could not calculate end date") return } nextDate = autoupdatingCalendar.startOfDay(for: nextDate) if eventsForDateMapper[date] == nil { eventsForDateMapper[date] = [] } eventsForDateMapper[date]?.append(generateEventInfo(for: event, date, nextDate)) date = nextDate } } // We now sort the array so that AllDay Events are first, then sort by startTime for date in eventsForDateMapper.keys { let sortedEvents = eventsForDateMapper[date]?.sorted(by: { event1, event2 -> Bool in if event1.isAllDay { return true } else if event2.isAllDay { return false } else { return event1.event.startDate < event2.event.startDate } }) eventsForDateMapper[date] = sortedEvents } eventsForDate = eventsForDateMapper Logger.info("Fetched events for \(eventsForDate.count) days") filterEvents() } private func generateEventInfo(for event: EKEvent, _ date: Date, _ nextDate: Date) -> EventInfo { // Make a customized struct let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame) let eventParticipationStatus = attendingStatusForUser(event) let meetingURL = retrieveMeetingURL(event) let eventInfo = EventInfo(event: event, isAllDay: isAllDay, meetingURL: meetingURL, attendeStatus: eventParticipationStatus) return eventInfo } static var dataDetector: NSDataDetector? // Borrowing logic from Ityscal @discardableResult private func findAppropriateURLs(_ description: String) -> URL? { guard let results = EventCenter.dataDetector?.matches(in: description, options: .reportCompletion, range: NSRange(location: 0, length: description.count)), results.isEmpty == false else { return nil } for result in results { if result.resultType == .link, var actualLink = result.url?.absoluteString { // Check for Zoom links if actualLink.contains("zoom.us/j/") || actualLink.contains("zoom.us/s/") || actualLink.contains("zoom.us/w/") || actualLink.contains("zoom.us/my/") || actualLink.contains("zoomgov.com/j/") || actualLink.contains("zoomgov.com/s/") || actualLink.contains("zoomgov.com/w/") || actualLink.contains("zoomgov.com/my/") { // Create a Zoom App link let workspace = NSWorkspace.shared if workspace.urlForApplication(toOpen: URL(string: "zoommtg://")!) != nil { actualLink = actualLink.replacingOccurrences(of: "https://", with: "zoommtg://") actualLink = actualLink.replacingOccurrences(of: "?", with: "&") actualLink = actualLink.replacingOccurrences(of: "/j/", with: "/join?confno=") actualLink = actualLink.replacingOccurrences(of: "/s/", with: "/join?confno=") actualLink = actualLink.replacingOccurrences(of: "/w/", with: "/join?confno=") if let appLink = URL(string: actualLink) { return appLink } } } else if (actualLink.contains("teams.microsoft.com/l/meetup-join")) { let workSpace = NSWorkspace.shared if workSpace.urlForApplication(toOpen: URL(string:"msteams://")!) != nil { let sanitizedString = actualLink.replacingOccurrences(of: "https://", with: "msteams://") if let sanitizedURL = URL(string: sanitizedString) { return sanitizedURL } } } else if (actualLink.contains("chime.aws/")) { let workSpace = NSWorkspace.shared if workSpace.urlForApplication(toOpen: URL(string:"chime://")!) != nil { let sanitizedString = actualLink.replacingOccurrences(of: "https://chime.aws/", with: "chime://meeting?pin=") if let sanitizedURL = URL(string: sanitizedString) { return sanitizedURL } } } else if actualLink.contains("zoommtg://") || actualLink.contains("msteams://") || actualLink.contains("chime://") || actualLink.contains("meet.google.com/") || actualLink.contains("hangouts.google.com/") || actualLink.contains("webex.com/") || actualLink.contains("gotomeeting.com/join") || actualLink.contains("ringcentral.com/j") || actualLink.contains("bigbluebutton.org/gl") || actualLink.contains("https://bigbluebutton.") || actualLink.contains("https://bbb.") || actualLink.contains("https://meet.jit.si/") || actualLink.contains("indigo.collocall.de") || actualLink.contains("public.senfcall.de") || actualLink.contains("facetime.apple.com/join") || actualLink.contains("workplace.com/meet") || actualLink.contains("youcanbook.me/zoom/") || actualLink.contains("fb.workplace.com/groupcall") { if let meetingLink = result.url { return meetingLink } } } } return nil } private func retrieveMeetingURL(_ event: EKEvent) -> URL? { if EventCenter.dataDetector == nil { var dataDetector: NSDataDetector? do { dataDetector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) } catch { assertionFailure("Unable to create a link-type data detector") return nil } EventCenter.dataDetector = dataDetector } var meetingURL: URL? = nil if let url = event.url { meetingURL = findAppropriateURLs(url.absoluteString) } if let notes = event.notes, meetingURL == nil { meetingURL = findAppropriateURLs(notes) } if let location = event.location, meetingURL == nil { meetingURL = findAppropriateURLs(location) } return meetingURL } private func attendingStatusForUser(_ event: EKEvent) -> EKParticipantStatus { // First check if the current user is the organizer if event.organizer?.isCurrentUser == true { return event.organizer?.participantStatus ?? .unknown } guard let attendes = event.attendees else { return .unknown } for attende in attendes where attende.isCurrentUser { return attende.participantStatus } return .unknown } } struct CalendarInfo { let calendar: EKCalendar var selected: Bool } struct EventInfo { let event: EKEvent let isAllDay: Bool let meetingURL: URL? let attendeStatus: EKParticipantStatus private let nsCalendar = Calendar.autoupdatingCurrent func metadataForMeeting() -> String { let timeIntervalSinceNowForMeeting = event.startDate.timeIntervalSinceNow if timeIntervalSinceNowForMeeting == 0 || event.startDate.shortTimeAgoSinceNow == "0s" { return "started." } else if timeIntervalSinceNowForMeeting < 0, timeIntervalSinceNowForMeeting > -300 { return "started +\(event.startDate.shortTimeAgoSinceNow)." } else if event.startDate.isToday, timeIntervalSinceNowForMeeting > 0 { let timeSince = Date().timeAgo(since: event.startDate).lowercased() let withoutAn = timeSince.replacingOccurrences(of: "an", with: UserDefaultKeys.emptyString) var withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: UserDefaultKeys.emptyString) // If the user has not turned on seconds granularity for one of the timezones, // we return "in 12 seconds" which looks weird. let upToHours: Set = [.second, .minute, .hour] let difference = nsCalendar.dateComponents(upToHours, from: Date(), to: event.startDate as Date) let minuteDifference = difference.minute ?? 0 let hourDifference = difference.hour ?? 0 if hourDifference > 0, minuteDifference > 0 { withoutAgo.append(contentsOf: "\(minuteDifference)m") } return withoutAgo.contains("seconds") ? "in <1m" : "in \(withoutAgo.lowercased())".trimmingCharacters(in: .whitespaces) } else if event.startDate.isTomorrow { let hoursUntil = event.startDate.hoursUntil return "in \(hoursUntil)h" } return "started." } } ================================================ FILE: Clocker/Events and Reminders/EventCenter.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import EventKit class EventCenter: NSObject { private static var shared = EventCenter() var eventStore: EKEventStore! var calendar: EKCalendar? var autoupdatingCalendar = NSCalendar.autoupdatingCurrent var eventsForDate: [Date: [EventInfo]] = [:] var filteredEvents: [Date: [EventInfo]] = [:] private let fetchQueue = DispatchQueue(label: "com.abhishek.fetch") @discardableResult class func sharedCenter() -> EventCenter { return shared } override init() { super.init() refetchAll() NotificationCenter.default.addObserver(self, selector: #selector(EventCenter.eventStoreDidChange(_:)), name: .EKEventStoreChanged, object: nil) } @objc func eventStoreDidChange(_: Any) { refetchAll() } deinit { // Just to be super safe NotificationCenter.default.removeObserver(self) } private func refetchAll() { Logger.info("Refetching events from the store") eventsForDate = [:] filteredEvents = [:] autoreleasepool { fetchQueue.async { // We get events for a 120 day period. // If the user uses a calendar often, this will be called frequently self.fetchEvents(-40, 80) } } } } ================================================ FILE: Clocker/Events and Reminders/RemindersHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreLoggerKit import EventKit extension EventCenter { // MARK: Private helper methods private func retrieveCalendar() -> EKCalendar? { if calendar == nil { let calendars = eventStore.calendars(for: .reminder) let calendarTitle = "Clocker Reminders" let predicate = NSPredicate(format: "title matches %@", calendarTitle) let filtered = calendars.filter { predicate.evaluate(with: $0) } if !filtered.isEmpty { calendar = filtered.first } else { calendar = EKCalendar(for: .reminder, eventStore: eventStore) calendar?.title = "Clocker Reminders" calendar?.source = eventStore.defaultCalendarForNewReminders()?.source guard let calendar = calendar else { return nil } do { try eventStore.saveCalendar(calendar, commit: true) } catch { assertionFailure("Unable to store calendar") } } } return calendar } // MARK: Public func reminderAccessGranted() -> Bool { return EKEventStore.authorizationStatus(for: .reminder) == .fullAccess } func reminderAccessNotDetermined() -> Bool { return EKEventStore.authorizationStatus(for: .reminder) == .notDetermined } func reminderAccessDenied() -> Bool { return EKEventStore.authorizationStatus(for: .reminder) == .denied } func createReminder(with title: String, timezone: String, alertIndex: Int, reminderDate: Date, additionalNotes: String?) -> Bool { initializeStoreIfNeccesary() if reminderAccessNotDetermined() || reminderAccessDenied() { return false } let gregorian = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) let components: NSCalendar.Unit = [.day, .month, .year, .minute, .second, .hour] guard var reminderComponents: DateComponents = (gregorian?.components(components, from: reminderDate)) else { return false } reminderComponents.timeZone = TimeZone(identifier: timezone) let reminderEvent = EKReminder(eventStore: eventStore) reminderEvent.calendar = retrieveCalendar() reminderEvent.title = "\(title) - Clocker" reminderEvent.startDateComponents = reminderComponents reminderEvent.dueDateComponents = reminderComponents reminderEvent.notes = additionalNotes addAlarmIfNeccesary(for: reminderEvent, alertIndex) // Commit the event do { try eventStore.save(reminderEvent, commit: true) } catch { Logger.log(object: ["Error": error.localizedDescription], for: "Error saving reminder") return false } return true } private func addAlarmIfNeccesary(for event: EKReminder, _ selection: Int) { if selection != 0 { var offset: TimeInterval = 0 switch selection { case 2: offset = -300 case 3: offset = -600 case 4: offset = -900 case 5: offset = -1800 case 6: offset = -3600 case 7: offset = -7200 case 8: offset = -86400 case 9: offset = -172_800 default: offset = 0 } let alarm = EKAlarm(relativeOffset: offset) event.addAlarm(alarm) } } } ================================================ FILE: Clocker/Firebase.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import #if !defined(__has_include) #error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \ import the headers individually." #else #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #endif // defined(__has_include) ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FIRApp.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import @class FIROptions; NS_ASSUME_NONNULL_BEGIN /** A block that takes a BOOL and has no return value. */ typedef void (^FIRAppVoidBoolCallback)(BOOL success) NS_SWIFT_NAME(FirebaseAppVoidBoolCallback); /** * The entry point of Firebase SDKs. * * Initialize and configure FIRApp using +[FIRApp configure] * or other customized ways as shown below. * * The logging system has two modes: default mode and debug mode. In default mode, only logs with * log level Notice, Warning and Error will be sent to device. In debug mode, all logs will be sent * to device. The log levels that Firebase uses are consistent with the ASL log levels. * * Enable debug mode by passing the -FIRDebugEnabled argument to the application. You can add this * argument in the application's Xcode scheme. When debug mode is enabled via -FIRDebugEnabled, * further executions of the application will also be in debug mode. In order to return to default * mode, you must explicitly disable the debug mode with the application argument -FIRDebugDisabled. * * It is also possible to change the default logging level in code by calling setLoggerLevel: on * the FIRConfiguration interface. */ NS_SWIFT_NAME(FirebaseApp) @interface FIRApp : NSObject /** * Configures a default Firebase app. Raises an exception if any configuration step fails. The * default app is named "__FIRAPP_DEFAULT". This method should be called after the app is launched * and before using Firebase services. This method should be called from the main thread and * contains synchronous file I/O (reading GoogleService-Info.plist from disk). */ + (void)configure; /** * Configures the default Firebase app with the provided options. The default app is named * "__FIRAPP_DEFAULT". Raises an exception if any configuration step fails. This method should be * called from the main thread. * * @param options The Firebase application options used to configure the service. */ + (void)configureWithOptions:(FIROptions *)options NS_SWIFT_NAME(configure(options:)); /** * Configures a Firebase app with the given name and options. Raises an exception if any * configuration step fails. This method should be called from the main thread. * * @param name The application's name given by the developer. The name should should only contain Letters, Numbers and Underscore. * @param options The Firebase application options used to configure the services. */ // clang-format off + (void)configureWithName:(NSString *)name options:(FIROptions *)options NS_SWIFT_NAME(configure(name:options:)); // clang-format on /** * Returns the default app, or nil if the default app does not exist. */ + (nullable FIRApp *)defaultApp NS_SWIFT_NAME(app()); /** * Returns a previously created FIRApp instance with the given name, or nil if no such app exists. * This method is thread safe. */ + (nullable FIRApp *)appNamed:(NSString *)name NS_SWIFT_NAME(app(name:)); /** * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This * method is thread safe. */ @property(class, readonly, nullable) NSDictionary *allApps; /** * Cleans up the current FIRApp, freeing associated data and returning its name to the pool for * future use. This method is thread safe. */ - (void)deleteApp:(FIRAppVoidBoolCallback)completion; /** * FIRApp instances should not be initialized directly. Call +[FIRApp configure], * +[FIRApp configureWithOptions:], or +[FIRApp configureWithNames:options:] directly. */ - (instancetype)init NS_UNAVAILABLE; /** * Gets the name of this app. */ @property(nonatomic, copy, readonly) NSString *name; /** * Gets a copy of the options for this app. These are non-modifiable. */ @property(nonatomic, copy, readonly) FIROptions *options; /** * Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES` * unless `FirebaseDataCollectionDefaultEnabled` is set to `NO` in your app's Info.plist. This value * is persisted across runs of the app so that it can be set once when users have consented to * collection. */ @property(nonatomic, readwrite, getter=isDataCollectionDefaultEnabled) BOOL dataCollectionDefaultEnabled; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FIRConfiguration.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "FIRLoggerLevel.h" NS_ASSUME_NONNULL_BEGIN /** * This interface provides global level properties that the developer can tweak. */ NS_SWIFT_NAME(FirebaseConfiguration) @interface FIRConfiguration : NSObject /** Returns the shared configuration object. */ @property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared); /** * Sets the logging level for internal Firebase logging. Firebase will only log messages * that are logged at or below loggerLevel. The messages are logged both to the Xcode * console and to the device's log. Note that if an app is running from AppStore, it will * never log above FIRLoggerLevelNotice even if loggerLevel is set to a higher (more verbose) * setting. * * @param loggerLevel The maximum logging level. The default level is set to FIRLoggerLevelNotice. */ - (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FIRLoggerLevel.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Note that importing GULLoggerLevel.h will lead to a non-modular header // import error. /** * The log levels used by internal logging. */ typedef NS_ENUM(NSInteger, FIRLoggerLevel) { /** Error level, matches ASL_LEVEL_ERR. */ FIRLoggerLevelError = 3, /** Warning level, matches ASL_LEVEL_WARNING. */ FIRLoggerLevelWarning = 4, /** Notice level, matches ASL_LEVEL_NOTICE. */ FIRLoggerLevelNotice = 5, /** Info level, matches ASL_LEVEL_INFO. */ FIRLoggerLevelInfo = 6, /** Debug level, matches ASL_LEVEL_DEBUG. */ FIRLoggerLevelDebug = 7, /** Minimum log level. */ FIRLoggerLevelMin = FIRLoggerLevelError, /** Maximum log level. */ FIRLoggerLevelMax = FIRLoggerLevelDebug } NS_SWIFT_NAME(FirebaseLoggerLevel); ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FIROptions.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** * This class provides constant fields of Google APIs. */ NS_SWIFT_NAME(FirebaseOptions) @interface FIROptions : NSObject /** * Returns the default options. The first time this is called it synchronously reads * GoogleService-Info.plist from disk. */ + (nullable FIROptions *)defaultOptions NS_SWIFT_NAME(defaultOptions()); /** * An iOS API key used for authenticating requests from your app, e.g. * @"AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers. */ @property(nonatomic, copy, nullable) NSString *APIKey NS_SWIFT_NAME(apiKey); /** * The bundle ID for the application. Defaults to `[[NSBundle mainBundle] bundleID]` when not set * manually or in a plist. */ @property(nonatomic, copy) NSString *bundleID; /** * The OAuth2 client ID for iOS application used to authenticate Google users, for example * @"12345.apps.googleusercontent.com", used for signing in with Google. */ @property(nonatomic, copy, nullable) NSString *clientID; /** * The tracking ID for Google Analytics, e.g. @"UA-12345678-1", used to configure Google Analytics. */ @property(nonatomic, copy, nullable) NSString *trackingID; /** * The Project Number from the Google Developer's console, for example @"012345678901", used to * configure Google Cloud Messaging. */ @property(nonatomic, copy) NSString *GCMSenderID NS_SWIFT_NAME(gcmSenderID); /** * The Project ID from the Firebase console, for example @"abc-xyz-123". */ @property(nonatomic, copy, nullable) NSString *projectID; /** * The Android client ID used in Google AppInvite when an iOS app has its Android version, for * example @"12345.apps.googleusercontent.com". */ @property(nonatomic, copy, nullable) NSString *androidClientID; /** * The Google App ID that is used to uniquely identify an instance of an app. */ @property(nonatomic, copy) NSString *googleAppID; /** * The database root URL, e.g. @"http://abc-xyz-123.firebaseio.com". */ @property(nonatomic, copy, nullable) NSString *databaseURL; /** * The URL scheme used to set up Durable Deep Link service. */ @property(nonatomic, copy, nullable) NSString *deepLinkURLScheme; /** * The Google Cloud Storage bucket name, e.g. @"abc-xyz-123.storage.firebase.com". */ @property(nonatomic, copy, nullable) NSString *storageBucket; /** * The App Group identifier to share data between the application and the application extensions. * The App Group must be configured in the application and on the Apple Developer Portal. Default * value `nil`. */ @property(nonatomic, copy, nullable) NSString *appGroupID; /** * Initializes a customized instance of FIROptions from the file at the given plist file path. This * will read the file synchronously from disk. * For example, * NSString *filePath = * [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; * FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; * Returns nil if the plist file does not exist or is invalid. */ - (nullable instancetype)initWithContentsOfFile:(NSString *)plistPath NS_DESIGNATED_INITIALIZER; /** * Initializes a customized instance of FIROptions with required fields. Use the mutable properties * to modify fields for configuring specific services. */ // clang-format off - (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID NS_SWIFT_NAME(init(googleAppID:gcmSenderID:)) NS_DESIGNATED_INITIALIZER; // clang-format on /** Unavailable. Please use `init(contentsOfFile:)` or `init(googleAppID:gcmSenderID:)` instead. */ - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FIRVersion.h ================================================ /* * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** Returns the current version of Firebase. */ NS_SWIFT_NAME(FirebaseVersion()) NSString* FIRFirebaseVersion(void); NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Headers/FirebaseCore.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRApp.h" #import "FIRConfiguration.h" #import "FIRLoggerLevel.h" #import "FIROptions.h" #import "FIRVersion.h" ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Info.plist ================================================ CFBundleExecutable FirebaseCore CFBundleIdentifier com.firebase.Firebase-FirebaseCore CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseCore CFBundlePackageType FMWK CFBundleVersion 8.2.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCore.framework/Modules/module.modulemap ================================================ framework module FirebaseCore { umbrella header "FirebaseCore.h" export * module * { export * } link framework "AppKit" link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCoreDiagnostics.framework/Headers/FIRCoreDiagnostics.h ================================================ /* * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // There are no actual public headers in the lib. This is a dummy public header to prevent Cocoapods // from adding all internal headers as public. ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCoreDiagnostics.framework/Headers/FirebaseCoreDiagnostics-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FIRCoreDiagnostics.h" FOUNDATION_EXPORT double FirebaseCoreDiagnosticsVersionNumber; FOUNDATION_EXPORT const unsigned char FirebaseCoreDiagnosticsVersionString[]; ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCoreDiagnostics.framework/Info.plist ================================================ CFBundleExecutable FirebaseCoreDiagnostics CFBundleIdentifier com.firebase.Firebase-FirebaseCoreDiagnostics CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseCoreDiagnostics CFBundlePackageType FMWK CFBundleVersion 8.2.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCoreDiagnostics.framework/Modules/module.modulemap ================================================ framework module FirebaseCoreDiagnostics { umbrella header "FirebaseCoreDiagnostics-umbrella.h" export * module * { export * } link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Headers/FIRCrashlytics.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import #import "FIRCrashlyticsReport.h" #import "FIRExceptionModel.h" #if __has_include() #warning "FirebaseCrashlytics and Crashlytics are not compatible \ in the same app because including multiple crash reporters can \ cause problems when registering exception handlers." #endif NS_ASSUME_NONNULL_BEGIN /** * The Firebase Crashlytics API provides methods to annotate and manage fatal and * non-fatal reports captured and reported to Firebase Crashlytics. * * By default, Firebase Crashlytics is initialized with `[FIRApp configure]`. * * Note: The Crashlytics class cannot be subclassed. If this makes testing difficult, * we suggest using a wrapper class or a protocol extension. */ NS_SWIFT_NAME(Crashlytics) @interface FIRCrashlytics : NSObject /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** * Accesses the singleton Crashlytics instance. * * @return The singleton Crashlytics instance. */ + (instancetype)crashlytics NS_SWIFT_NAME(crashlytics()); /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param msg Message to log */ - (void)log:(NSString *)msg; /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param format Format of string * @param ... A comma-separated list of arguments to substitute into format */ - (void)logWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param format Format of string * @param args Arguments to substitute into format */ - (void)logWithFormat:(NSString *)format arguments:(va_list)args NS_SWIFT_NAME(log(format:arguments:)); /** * Sets a custom key and value to be associated with subsequent fatal and non-fatal reports. * When setting an object value, the object is converted to a string. This is * typically done by calling "-[NSObject description]". * * @param value The value to be associated with the key * @param key A unique key */ - (void)setCustomValue:(id)value forKey:(NSString *)key; /** * Sets custom keys and values to be associated with subsequent fatal and non-fatal reports. * The objects in the dictionary are converted to strings. This is * typically done by calling "-[NSObject description]". * * @param keysAndValues The values to be associated with the corresponding keys */ - (void)setCustomKeysAndValues:(NSDictionary *)keysAndValues; /** * Records a user ID (identifier) that's associated with subsequent fatal and non-fatal reports. * * If you want to associate a crash with a specific user, we recommend specifying an arbitrary * string (e.g., a database, ID, hash, or other value that you can index and query, but is * meaningless to a third-party observer). This allows you to facilitate responses for support * requests and reach out to users for more information. * * @param userID An arbitrary user identifier string that associates a user to a record in your * system. */ - (void)setUserID:(NSString *)userID; /** * Records a non-fatal event described by an NSError object. The events are * grouped and displayed similarly to crashes. Keep in mind that this method can be expensive. * The total number of NSErrors that can be recorded during your app's life-cycle is limited by a * fixed-size circular buffer. If the buffer is overrun, the oldest data is dropped. Errors are * relayed to Crashlytics on a subsequent launch of your application. * * @param error Non-fatal error to be recorded */ - (void)recordError:(NSError *)error NS_SWIFT_NAME(record(error:)); /** * Records an Exception Model described by an FIRExceptionModel object. The events are * grouped and displayed similarly to crashes. Keep in mind that this method can be expensive. * The total number of FIRExceptionModels that can be recorded during your app's life-cycle is * limited by a fixed-size circular buffer. If the buffer is overrun, the oldest data is dropped. * Exception Models are relayed to Crashlytics on a subsequent launch of your application. * * @param exceptionModel Instance of the FIRExceptionModel to be recorded */ - (void)recordExceptionModel:(FIRExceptionModel *)exceptionModel NS_SWIFT_NAME(record(exceptionModel:)); /** * Returns whether the app crashed during the previous execution. */ - (BOOL)didCrashDuringPreviousExecution; /** * Enables/disables automatic data collection. * * Calling this method overrides both the FirebaseCrashlyticsCollectionEnabled flag in your * App's Info.plist and FIRApp's isDataCollectionDefaultEnabled flag. * * When you set a value for this method, it persists across runs of the app. * * The value does not apply until the next run of the app. If you want to disable data * collection without rebooting, add the FirebaseCrashlyticsCollectionEnabled flag to your app's * Info.plist. * * * @param enabled Determines whether automatic data collection is enabled */ - (void)setCrashlyticsCollectionEnabled:(BOOL)enabled; /** * Indicates whether or not automatic data collection is enabled * * This method uses three ways to decide whether automatic data collection is enabled, * in order of priority: * - If setCrashlyticsCollectionEnabled is called with a value, use it * - If the FirebaseCrashlyticsCollectionEnabled key is in your app's Info.plist, use it * - Otherwise, use the default isDataCollectionDefaultEnabled in FIRApp */ - (BOOL)isCrashlyticsCollectionEnabled; /** * Determines whether there are any unsent crash reports cached on the device, then calls the given * callback. * * The callback only executes if automatic data collection is disabled. You can use * the callback to get one-time consent from a user upon a crash, and then call * sendUnsentReports or deleteUnsentReports, depending on whether or not the user gives consent. * * Disable automatic collection by: * - Adding the FirebaseCrashlyticsCollectionEnabled: NO key to your App's Info.plist * - Calling [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:NO] in your app * - Setting FIRApp's isDataCollectionDefaultEnabled to NO * * @param completion The callback that's executed once Crashlytics finishes checking for unsent * reports. The callback is set to YES if there are unsent reports on disk. */ - (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL))completion NS_SWIFT_NAME(checkForUnsentReports(completion:)); /** * Determines whether there are any unsent crash reports cached on the device, then calls the given * callback with a CrashlyticsReport object that you can use to update the unsent report. * CrashlyticsReports have a lot of the familiar Crashlytics methods like setting custom keys and * logs. * * The callback only executes if automatic data collection is disabled. You can use * the callback to get one-time consent from a user upon a crash, and then call * sendUnsentReports or deleteUnsentReports, depending on whether or not the user gives consent. * * Disable automatic collection by: * - Adding the FirebaseCrashlyticsCollectionEnabled: NO key to your App's Info.plist * - Calling [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:NO] in your app * - Setting FIRApp's isDataCollectionDefaultEnabled to NO * * Not calling send/deleteUnsentReports will result in the report staying on disk, which means the * same CrashlyticsReport can show up in multiple runs of the app. If you want avoid duplicates, * ensure there was a crash on the last run of the app by checking the value of * didCrashDuringPreviousExecution. * * @param completion The callback that's executed once Crashlytics finishes checking for unsent * reports. The callback is called with the newest unsent Crashlytics Report, or nil if there are * none cached on disk. */ - (void)checkAndUpdateUnsentReportsWithCompletion: (void (^)(FIRCrashlyticsReport *_Nullable))completion NS_SWIFT_NAME(checkAndUpdateUnsentReports(completion:)); /** * Enqueues any unsent reports on the device to upload to Crashlytics. * * This method only applies if automatic data collection is disabled. * * When automatic data collection is enabled, Crashlytics automatically uploads and deletes reports * at startup, so this method is ignored. */ - (void)sendUnsentReports; /** * Deletes any unsent reports on the device. * * This method only applies if automatic data collection is disabled. */ - (void)deleteUnsentReports; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Headers/FIRCrashlyticsReport.h ================================================ // Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import NS_ASSUME_NONNULL_BEGIN /** * The Firebase Crashlytics Report provides a way to read and write information * to a past Crashlytics reports. A common use case is gathering end-user feedback * on the next run of the app. * * The CrashlyticsReport should be modified before calling send/deleteUnsentReports. */ NS_SWIFT_NAME(CrashlyticsReport) @interface FIRCrashlyticsReport : NSObject /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** * Returns the unique ID for the Crashlytics report. */ @property(nonatomic, readonly) NSString *reportID; /** * Returns the date that the report was created. */ @property(nonatomic, readonly) NSDate *dateCreated; /** * Returns true when one of the events in the Crashlytics report is a crash. */ @property(nonatomic, readonly) BOOL hasCrash; /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param msg Message to log */ - (void)log:(NSString *)msg; /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param format Format of string * @param ... A comma-separated list of arguments to substitute into format */ - (void)logWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); /** * Adds logging that is sent with your crash data. The logging does not appear in the * system.log and is only visible in the Crashlytics dashboard. * * @param format Format of string * @param args Arguments to substitute into format */ - (void)logWithFormat:(NSString *)format arguments:(va_list)args NS_SWIFT_NAME(log(format:arguments:)); /** * Sets a custom key and value to be associated with subsequent fatal and non-fatal reports. * When setting an object value, the object is converted to a string. This is * typically done by calling "-[NSObject description]". * * @param value The value to be associated with the key * @param key A unique key */ - (void)setCustomValue:(id)value forKey:(NSString *)key; /** * Sets custom keys and values to be associated with subsequent fatal and non-fatal reports. * The objects in the dictionary are converted to strings. This is * typically done by calling "-[NSObject description]". * * @param keysAndValues The values to be associated with the corresponding keys */ - (void)setCustomKeysAndValues:(NSDictionary *)keysAndValues; /** * Records a user ID (identifier) that's associated with subsequent fatal and non-fatal reports. * * If you want to associate a crash with a specific user, we recommend specifying an arbitrary * string (e.g., a database, ID, hash, or other value that you can index and query, but is * meaningless to a third-party observer). This allows you to facilitate responses for support * requests and reach out to users for more information. * * @param userID An arbitrary user identifier string that associates a user to a record in your * system. */ - (void)setUserID:(NSString *)userID; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Headers/FIRExceptionModel.h ================================================ // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import #import "FIRStackFrame.h" NS_ASSUME_NONNULL_BEGIN /** * The Firebase Crashlytics Exception Model provides a way to report custom exceptions * to Crashlytics that came from a runtime environment outside of the native * platform Crashlytics is running in. */ NS_SWIFT_NAME(ExceptionModel) @interface FIRExceptionModel : NSObject /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** * Initializes an Exception Model model with the given required fields. * * @param name - typically the type of the Exception class * @param reason - the human-readable reason the issue occurred */ - (instancetype)initWithName:(NSString *)name reason:(NSString *)reason; /** * Creates an Exception Model model with the given required fields. * * @param name - typically the type of the Exception class * @param reason - the human-readable reason the issue occurred */ + (instancetype)exceptionModelWithName:(NSString *)name reason:(NSString *)reason NS_SWIFT_UNAVAILABLE(""); /** * A list of Stack Frames that make up the stack trace. The order of the stack trace is top-first, * so typically the "main" function is the last element in this list. */ @property(nonatomic, copy) NSArray *stackTrace; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Headers/FIRStackFrame.h ================================================ // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import NS_ASSUME_NONNULL_BEGIN /** * The Firebase Crashlytics Stack Frame provides a way to construct the lines of * a stack trace for reporting along with a recorded Exception Model. */ NS_SWIFT_NAME(StackFrame) @interface FIRStackFrame : NSObject /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** * Initializes a symbolicated Stack Frame with the given required fields. Symbolicated * Stack Frames will appear in the Crashlytics dashboard as reported in these fields. * * @param symbol - The function or method name * @param file - the file where the exception occurred * @param line - the line number */ - (instancetype)initWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line; /** * Creates a symbolicated Stack Frame from an address. The address will be * symbolicated in the Crashlytics backend for the customer and reported in the * Crahslytics dashboard with the appropriate file name and line number. If an * invalid address is provided it will appear in the dashboard as missing. * * @param address - the address where the exception occurred */ + (instancetype)stackFrameWithAddress:(NSUInteger)address; /** * Creates a symbolicated Stack Frame with the given required fields. Symbolicated * Stack Frames will appear in the Crashlytics dashboard as reported in these fields. * * * @param symbol - The function or method name * @param file - the file where the exception occurred * @param line - the line number */ + (instancetype)stackFrameWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Headers/FirebaseCrashlytics.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRCrashlytics.h" #import "FIRCrashlyticsReport.h" #import "FIRExceptionModel.h" #import "FIRStackFrame.h" ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Info.plist ================================================ CFBundleExecutable FirebaseCrashlytics CFBundleIdentifier com.firebase.Firebase-FirebaseCrashlytics CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseCrashlytics CFBundlePackageType FMWK CFBundleVersion 8.2.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseCrashlytics.framework/Modules/module.modulemap ================================================ framework module FirebaseCrashlytics { umbrella header "FirebaseCrashlytics.h" export * module * { export * } link framework "AppKit" link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link "c++" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRDataEventType.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef Firebase_FIRDataEventType_h #define Firebase_FIRDataEventType_h #import /** * This enum is the set of events that you can observe at a Firebase Database * location. */ typedef NS_ENUM(NSInteger, FIRDataEventType) { /// A new child node is added to a location. FIRDataEventTypeChildAdded, /// A child node is removed from a location. FIRDataEventTypeChildRemoved, /// A child node at a location changes. FIRDataEventTypeChildChanged, /// A child node moves relative to the other child nodes at a location. FIRDataEventTypeChildMoved, /// Any data changes at a location or, recursively, at any child node. FIRDataEventTypeValue } NS_SWIFT_NAME(DataEventType); #endif ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRDataSnapshot.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN @class FIRDatabaseReference; /** * A FIRDataSnapshot contains data from a Firebase Database location. Any time * you read Firebase data, you receive the data as a FIRDataSnapshot. * * FIRDataSnapshots are passed to the blocks you attach with * observeEventType:withBlock: or observeSingleEvent:withBlock:. They are * efficiently-generated immutable copies of the data at a Firebase Database * location. They can't be modified and will never change. To modify data at a * location, use a FIRDatabaseReference (e.g. with setValue:). */ NS_SWIFT_NAME(DataSnapshot) @interface FIRDataSnapshot : NSObject #pragma mark - Navigating and inspecting a snapshot /** * Gets a FIRDataSnapshot for the location at the specified relative path. * The relative path can either be a simple child key (e.g. 'fred') * or a deeper slash-separated path (e.g. 'fred/name/first'). If the child * location has no data, an empty FIRDataSnapshot is returned. * * @param childPathString A relative path to the location of child data. * @return The FIRDataSnapshot for the child location. */ - (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString; /** * Return YES if the specified child exists. * * @param childPathString A relative path to the location of a potential child. * @return YES if data exists at the specified childPathString, else NO. */ - (BOOL)hasChild:(NSString *)childPathString; /** * Return YES if the DataSnapshot has any children. * * @return YES if this snapshot has any children, else NO. */ - (BOOL)hasChildren; /** * Return YES if the DataSnapshot contains a non-null value. * * @return YES if this snapshot contains a non-null value, else NO. */ - (BOOL)exists; #pragma mark - Data export /** * Returns the raw value at this location, coupled with any metadata, such as * priority. * * Priorities, where they exist, are accessible under the ".priority" key in * instances of NSDictionary. For leaf locations with priorities, the value will * be under the ".value" key. */ - (id __nullable)valueInExportFormat; #pragma mark - Properties /** * Returns the contents of this data snapshot as native types. * * Data types returned: * + NSDictionary * + NSArray * + NSNumber (also includes booleans) * + NSString * * @return The data as a native object. */ @property(strong, readonly, nonatomic, nullable) id value; /** * Gets the number of children for this DataSnapshot. * * @return An integer indicating the number of children. */ @property(readonly, nonatomic) NSUInteger childrenCount; /** * Gets a FIRDatabaseReference for the location that this data came from. * * @return A FIRDatabaseReference instance for the location of this data. */ @property(nonatomic, readonly, strong) FIRDatabaseReference *ref; /** * The key of the location that generated this FIRDataSnapshot. * * @return An NSString containing the key for the location of this * FIRDataSnapshot. */ @property(strong, readonly, nonatomic) NSString *key; /** * An iterator for snapshots of the child nodes in this snapshot. * You can use the native for..in syntax: * * for (FIRDataSnapshot* child in snapshot.children) { * ... * } * * @return An NSEnumerator of the children. */ @property(strong, readonly, nonatomic) NSEnumerator *children; /** * The priority of the data in this FIRDataSnapshot. * * @return The priority as a string, or nil if no priority was set. */ @property(strong, readonly, nonatomic, nullable) id priority; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRDatabase.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRDatabaseReference.h" #import @class FIRApp; NS_ASSUME_NONNULL_BEGIN /** * The entry point for accessing a Firebase Database. You can get an instance * by calling [FIRDatabase database]. To access a location in the database and * read or write data, use [FIRDatabase reference]. */ NS_SWIFT_NAME(Database) @interface FIRDatabase : NSObject /** * The NSObject initializer that has been marked as unavailable. Use the * `database` class method instead. */ - (instancetype)init __attribute__((unavailable("use the database method instead"))); /** * Gets the instance of FIRDatabase for the default FIRApp. * * @return A FIRDatabase instance. */ + (FIRDatabase *)database NS_SWIFT_NAME(database()); /** * Gets a FirebaseDatabase instance for the specified URL. * * @param url The URL to the Firebase Database instance you want to access. * @return A FIRDatabase instance. */ + (FIRDatabase *)databaseWithURL:(NSString *)url NS_SWIFT_NAME(database(url:)); /** * Gets a FirebaseDatabase instance for the specified URL, using the specified * FirebaseApp. * * @param app The FIRApp to get a FIRDatabase for. * @param url The URL to the Firebase Database instance you want to access. * @return A FIRDatabase instance. */ // clang-format off + (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url NS_SWIFT_NAME(database(app:url:)); // clang-format on /** * Gets an instance of FIRDatabase for a specific FIRApp. * * @param app The FIRApp to get a FIRDatabase for. * @return A FIRDatabase instance. */ + (FIRDatabase *)databaseForApp:(FIRApp *)app NS_SWIFT_NAME(database(app:)); /** The FIRApp instance to which this FIRDatabase belongs. */ @property(weak, readonly, nonatomic) FIRApp *app; /** * Gets a FIRDatabaseReference for the root of your Firebase Database. */ - (FIRDatabaseReference *)reference; /** * Gets a FIRDatabaseReference for the provided path. * * @param path Path to a location in your Firebase Database. * @return A FIRDatabaseReference pointing to the specified path. */ - (FIRDatabaseReference *)referenceWithPath:(NSString *)path; /** * Gets a FIRDatabaseReference for the provided URL. The URL must be a URL to a * path within this Firebase Database. To create a FIRDatabaseReference to a * different database, create a FIRApp with a FIROptions object configured with * the appropriate database URL. * * @param databaseUrl A URL to a path within your database. * @return A FIRDatabaseReference for the provided URL. */ - (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl; /** * The Firebase Database client automatically queues writes and sends them to * the server at the earliest opportunity, depending on network connectivity. In * some cases (e.g. offline usage) there may be a large number of writes waiting * to be sent. Calling this method will purge all outstanding writes so they are * abandoned. * * All writes will be purged, including transactions and onDisconnect writes. * The writes will be rolled back locally, perhaps triggering events for * affected event listeners, and the client will not (re-)send them to the * Firebase Database backend. */ - (void)purgeOutstandingWrites; /** * Shuts down our connection to the Firebase Database backend until goOnline is * called. */ - (void)goOffline; /** * Resumes our connection to the Firebase Database backend after a previous * goOffline call. */ - (void)goOnline; /** * The Firebase Database client will cache synchronized data and keep track of * all writes you've initiated while your application is running. It seamlessly * handles intermittent network connections and re-sends write operations when * the network connection is restored. * * However by default your write operations and cached data are only stored * in-memory and will be lost when your app restarts. By setting this value to * `YES`, the data will be persisted to on-device (disk) storage and will thus * be available again when the app is restarted (even when there is no network * connectivity at that time). Note that this property must be set before * creating your first Database reference and only needs to be called once per * application. * */ @property(nonatomic) BOOL persistenceEnabled NS_SWIFT_NAME(isPersistenceEnabled) ; /** * By default the Firebase Database client will use up to 10MB of disk space to * cache data. If the cache grows beyond this size, the client will start * removing data that hasn't been recently used. If you find that your * application caches too little or too much data, call this method to change * the cache size. This property must be set before creating your first * FIRDatabaseReference and only needs to be called once per application. * * Note that the specified cache size is only an approximation and the size on * disk may temporarily exceed it at times. Cache sizes smaller than 1 MB or * greater than 100 MB are not supported. */ @property(nonatomic) NSUInteger persistenceCacheSizeBytes; /** * Sets the dispatch queue on which all events are raised. The default queue is * the main queue. * * Note that this must be set before creating your first Database reference. */ @property(nonatomic, strong) dispatch_queue_t callbackQueue; /** * Enables verbose diagnostic logging. * * @param enabled YES to enable logging, NO to disable. */ + (void)setLoggingEnabled:(BOOL)enabled; /** Retrieve the Firebase Database SDK version. */ + (NSString *)sdkVersion; /** * Configures the database to use an emulated backend instead of the default * remote backend. */ - (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRDatabaseQuery.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRDataEventType.h" #import "FIRDataSnapshot.h" #import NS_ASSUME_NONNULL_BEGIN /** * A FIRDatabaseHandle is used to identify listeners of Firebase Database * events. These handles are returned by observeEventType: and can later be * passed to removeObserverWithHandle: to stop receiving updates. */ typedef NSUInteger FIRDatabaseHandle NS_SWIFT_NAME(DatabaseHandle); /** * A FIRDatabaseQuery instance represents a query over the data at a particular * location. * * You create one by calling one of the query methods (queryOrderedByChild:, * queryStartingAtValue:, etc.) on a FIRDatabaseReference. The query methods can * be chained to further specify the data you are interested in observing */ NS_SWIFT_NAME(DatabaseQuery) @interface FIRDatabaseQuery : NSObject #pragma mark - Attach observers to read data /** * observeEventType:withBlock: is used to listen for data changes at a * particular location. This is the primary way to read data from the Firebase * Database. Your block will be triggered for the initial data and again * whenever the data changes. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot. * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock: (void (^)(FIRDataSnapshot *snapshot))block; /** * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data * changes at a particular location. This is the primary way to read data from * the Firebase Database. Your block will be triggered for the initial data and * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your * block will be passed the key of the previous node by priority order. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot and the previous child's key. * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; /** * observeEventType:withBlock: is used to listen for data changes at a * particular location. This is the primary way to read data from the Firebase * Database. Your block will be triggered for the initial data and again * whenever the data changes. * * The cancelBlock will be called if you will no longer receive new events due * to no longer having permission. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot. * @param cancelBlock The block that should be called if this client no longer * has permission to receive these events * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; /** * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data * changes at a particular location. This is the primary way to read data from * the Firebase Database. Your block will be triggered for the initial data and * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your * block will be passed the key of the previous node by priority order. * * The cancelBlock will be called if you will no longer receive new events due * to no longer having permission. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot and the previous child's key. * @param cancelBlock The block that should be called if this client no longer * has permission to receive these events * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; /** * getDataWithCompletionBlock: is used to get the most up-to-date value for * this query. This method updates the cache and raises events if successful. If * not connected, falls back to a locally-cached value. * * @param block The block that should be called with the most up-to-date value * of this query, or an error if no such value could be retrieved. */ - (void)getDataWithCompletionBlock: (void (^_Nonnull)(NSError *__nullable error, FIRDataSnapshot *snapshot))block NS_SWIFT_NAME(getData(completion:)); /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot. */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. In addition, for * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and * FIRDataEventTypeChildChanged events, your block will be passed the key of the * previous node by priority order. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot and the previous child's key. */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. * * The cancelBlock will be called if you do not have permission to read data at * this location. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot. * @param cancelBlock The block that will be called if you don't have permission * to access this data */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock:(nullable void (^)(NSError *error))cancelBlock; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. In addition, for * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and * FIRDataEventTypeChildChanged events, your block will be passed the key of the * previous node by priority order. * * The cancelBlock will be called if you do not have permission to read data at * this location. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot and the previous child's key. * @param cancelBlock The block that will be called if you don't have permission * to access this data */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; #pragma mark - Detaching observers /** * Detach a block previously attached with observeEventType:withBlock:. * * @param handle The handle returned by the call to observeEventType:withBlock: * which we are trying to remove. */ - (void)removeObserverWithHandle:(FIRDatabaseHandle)handle; /** * Detach all blocks previously attached to this Firebase Database location with * observeEventType:withBlock: */ - (void)removeAllObservers; /** * By calling `keepSynced:YES` on a location, the data for that location will * automatically be downloaded and kept in sync, even when no listeners are * attached for that location. Additionally, while a location is kept synced, it * will not be evicted from the persistent disk cache. * * @param keepSynced Pass YES to keep this location synchronized, pass NO to * stop synchronization. */ - (void)keepSynced:(BOOL)keepSynced; #pragma mark - Querying and limiting /** * queryLimitedToFirst: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryLimitedToFirst: will respond to at most the first limit child nodes. * * @param limit The upper bound, inclusive, for the number of child nodes to * receive events for * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. */ - (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit; /** * queryLimitedToLast: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryLimitedToLast: will respond to at most the last limit child nodes. * * @param limit The upper bound, inclusive, for the number of child nodes to * receive events for * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. */ - (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit; /** * queryOrderBy: is used to generate a reference to a view of the data that's * been sorted by the values of a particular child key. This method is intended * to be used in combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @param key The child key to use in ordering data visible to the returned * FIRDatabaseQuery * @return A FIRDatabaseQuery instance, ordered by the values of the specified * child key. */ - (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key; /** * queryOrderedByKey: is used to generate a reference to a view of the data * that's been sorted by child key. This method is intended to be used in * combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @return A FIRDatabaseQuery instance, ordered by child keys. */ - (FIRDatabaseQuery *)queryOrderedByKey; /** * queryOrderedByValue: is used to generate a reference to a view of the data * that's been sorted by child value. This method is intended to be used in * combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @return A FIRDatabaseQuery instance, ordered by child value. */ - (FIRDatabaseQuery *)queryOrderedByValue; /** * queryOrderedByPriority: is used to generate a reference to a view of the data * that's been sorted by child priority. This method is intended to be used in * combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @return A FIRDatabaseQuery instance, ordered by child priorities. */ - (FIRDatabaseQuery *)queryOrderedByPriority; /** * queryStartingAtValue: is used to generate a reference to a limited view of * the data at this location. The FIRDatabaseQuery instance returned by * queryStartingAtValue: will respond to events at nodes with a value greater * than or equal to startValue. * * @param startValue The lower bound, inclusive, for the value of data visible * to the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value greater than * or equal to startValue */ - (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue; /** * queryStartingAtValue:childKey: is used to generate a reference to a limited * view of the data at this location. The FIRDatabaseQuery instance returned by * queryStartingAtValue:childKey will respond to events at nodes with a value * greater than startValue, or equal to startValue and with a key greater than * or equal to childKey. This is most useful when implementing pagination in a * case where multiple nodes can match the startValue. * * @param startValue The lower bound, inclusive, for the value of data visible * to the returned FIRDatabaseQuery * @param childKey The lower bound, inclusive, for the key of nodes with value * equal to startValue * @return A FIRDatabaseQuery instance, limited to data with value greater than * or equal to startValue */ - (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue childKey:(nullable NSString *)childKey; /** * queryStartingAfterValue: is used to generate a reference to a * limited view of the data at this location. The FIRDatabaseQuery instance * returned by queryStartingAfterValue: will respond to events at nodes * with a value greater than startAfterValue. * * @param startAfterValue The lower bound, exclusive, for the value of data * visible to the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value greater * startAfterValue */ - (FIRDatabaseQuery *)queryStartingAfterValue:(nullable id)startAfterValue; /** * queryStartingAfterValue:childKey: is used to generate a reference to a * limited view of the data at this location. The FIRDatabaseQuery instance * returned by queryStartingAfterValue:childKey will respond to events at nodes * with a value greater than startAfterValue, or equal to startAfterValue and * with a key greater than childKey. This is most useful when implementing * pagination in a case where multiple nodes can match the startAfterValue. * * @param startAfterValue The lower bound, inclusive, for the value of data * visible to the returned FIRDatabaseQuery * @param childKey The lower bound, exclusive, for the key of nodes with value * equal to startAfterValue * @return A FIRDatabaseQuery instance, limited to data with value greater than * startAfterValue, or equal to startAfterValue with a key greater than childKey */ - (FIRDatabaseQuery *)queryStartingAfterValue:(nullable id)startAfterValue childKey:(nullable NSString *)childKey; /** * queryEndingAtValue: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryEndingAtValue: will respond to events at nodes with a value less than or * equal to endValue. * * @param endValue The upper bound, inclusive, for the value of data visible to * the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value less than or * equal to endValue */ - (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue; /** * queryEndingAtValue:childKey: is used to generate a reference to a limited * view of the data at this location. The FIRDatabaseQuery instance returned by * queryEndingAtValue:childKey will respond to events at nodes with a value less * than endValue, or equal to endValue and with a key less than or equal to * childKey. This is most useful when implementing pagination in a case where * multiple nodes can match the endValue. * * @param endValue The upper bound, inclusive, for the value of data visible to * the returned FIRDatabaseQuery * @param childKey The upper bound, inclusive, for the key of nodes with value * equal to endValue * @return A FIRDatabaseQuery instance, limited to data with value less than or * equal to endValue */ - (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue childKey:(nullable NSString *)childKey; /** * queryEndingBeforeValue: is used to generate a reference to a limited view of * the data at this location. The FIRDatabaseQuery instance returned by * queryEndingBeforeValue: will respond to events at nodes with a value less * than endValue. * * @param endValue The upper bound, exclusive, for the value of data visible to * the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value less than * endValue */ - (FIRDatabaseQuery *)queryEndingBeforeValue:(nullable id)endValue; /** * queryEndingBeforeValue:childKey: is used to generate a reference to a limited * view of the data at this location. The FIRDatabaseQuery instance returned by * queryEndingBeforeValue:childKey will respond to events at nodes with a value * less than endValue, or equal to endValue and with a key less than childKey. * * @param endValue The upper bound, inclusive, for the value of data visible to * the returned FIRDatabaseQuery * @param childKey The upper bound, exclusive, for the key of nodes with value * equal to endValue * @return A FIRDatabaseQuery instance, limited to data with value less than or * equal to endValue */ - (FIRDatabaseQuery *)queryEndingBeforeValue:(nullable id)endValue childKey:(nullable NSString *)childKey; /** * queryEqualToValue: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryEqualToValue: will respond to events at nodes with a value equal to the * supplied argument. * * @param value The value that the data returned by this FIRDatabaseQuery will * have * @return A FIRDatabaseQuery instance, limited to data with the supplied value. */ - (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value; /** * queryEqualToValue:childKey: is used to generate a reference to a limited view * of the data at this location. The FIRDatabaseQuery instance returned by * queryEqualToValue:childKey will respond to events at nodes with a value equal * to the supplied argument and with their key equal to childKey. There will be * at most one node that matches because child keys are unique. * * @param value The value that the data returned by this FIRDatabaseQuery will * have * @param childKey The name of nodes with the right value * @return A FIRDatabaseQuery instance, limited to data with the supplied value * and the key. */ - (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value childKey:(nullable NSString *)childKey; #pragma mark - Properties /** * Gets a FIRDatabaseReference for the location of this query. * * @return A FIRDatabaseReference for the location of this query. */ @property(nonatomic, readonly, strong) FIRDatabaseReference *ref; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRDatabaseReference.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRDataSnapshot.h" #import "FIRDatabase.h" #import "FIRDatabaseQuery.h" #import "FIRMutableData.h" #import "FIRServerValue.h" #import "FIRTransactionResult.h" #import NS_ASSUME_NONNULL_BEGIN @class FIRDatabase; /** * A FIRDatabaseReference represents a particular location in your Firebase * Database and can be used for reading or writing data to that Firebase * Database location. * * This class is the starting point for all Firebase Database operations. After * you've obtained your first FIRDatabaseReference via [FIRDatabase reference], * you can use it to read data (ie. observeEventType:withBlock:), write data * (ie. setValue:), and to create new FIRDatabaseReferences (ie. child:). */ NS_SWIFT_NAME(DatabaseReference) @interface FIRDatabaseReference : FIRDatabaseQuery #pragma mark - Getting references to children locations /** * Gets a FIRDatabaseReference for the location at the specified relative path. * The relative path can either be a simple child key (e.g. 'fred') or a * deeper slash-separated path (e.g. 'fred/name/first'). * * @param pathString A relative path from this location to the desired child * location. * @return A FIRDatabaseReference for the specified relative path. */ - (FIRDatabaseReference *)child:(NSString *)pathString; /** * childByAutoId generates a new child location using a unique key and returns a * FIRDatabaseReference to it. This is useful when the children of a Firebase * Database location represent a list of items. * * The unique key generated by childByAutoId: is prefixed with a * client-generated timestamp so that the resulting list will be * chronologically-sorted. * * @return A FIRDatabaseReference for the generated location. */ - (FIRDatabaseReference *)childByAutoId; #pragma mark - Writing data /** Write data to this Firebase Database location. This will overwrite any data at this location and all child locations. Data types that can be set are: - NSString -- @"Hello World" - NSNumber (also includes boolean) -- @YES, @43, @4.333 - NSDictionary -- @{@"key": @"value", @"nested": @{@"another": @"value"} } - NSArray The effect of the write will be visible immediately and the corresponding events will be triggered. Synchronization of the data to the Firebase Database servers will also be started. Passing null for the new value is equivalent to calling remove:; all data at this location or any child location will be deleted. Note that setValue: will remove any priority stored at this location, so if priority is meant to be preserved, you should use setValue:andPriority: instead. @param value The value to be written. */ - (void)setValue:(nullable id)value; /** * The same as setValue: with a block that gets triggered after the write * operation has been committed to the Firebase Database servers. * * @param value The value to be written. * @param block The block to be called after the write has been committed to the * Firebase Database servers. */ - (void)setValue:(nullable id)value withCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * The same as setValue: with an additional priority to be attached to the data * being written. Priorities are used to order items. * * @param value The value to be written. * @param priority The priority to be attached to that data. */ - (void)setValue:(nullable id)value andPriority:(nullable id)priority; /** * The same as setValue:andPriority: with a block that gets triggered after the * write operation has been committed to the Firebase Database servers. * * @param value The value to be written. * @param priority The priority to be attached to that data. * @param block The block to be called after the write has been committed to the * Firebase Database servers. */ - (void)setValue:(nullable id)value andPriority:(nullable id)priority withCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Remove the data at this Firebase Database location. Any data at child * locations will also be deleted. * * The effect of the delete will be visible immediately and the corresponding * events will be triggered. Synchronization of the delete to the Firebase * Database servers will also be started. * * remove: is equivalent to calling setValue:nil */ - (void)removeValue; /** * The same as remove: with a block that gets triggered after the remove * operation has been committed to the Firebase Database servers. * * @param block The block to be called after the remove has been committed to * the Firebase Database servers. */ - (void)removeValueWithCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Sets a priority for the data at this Firebase Database location. * Priorities can be used to provide a custom ordering for the children at a * location (if no priorities are specified, the children are ordered by key). * * You cannot set a priority on an empty location. For this reason * setValue:andPriority: should be used when setting initial data with a * specific priority and setPriority: should be used when updating the priority * of existing data. * * Children are sorted based on this priority using the following rules: * * Children with no priority come first. * Children with a number as their priority come next. They are sorted * numerically by priority (small to large). Children with a string as their * priority come last. They are sorted lexicographically by priority. Whenever * two children have the same priority (including no priority), they are sorted * by key. Numeric keys come first (sorted numerically), followed by the * remaining keys (sorted lexicographically). * * Note that priorities are parsed and ordered as IEEE 754 double-precision * floating-point numbers. Keys are always stored as strings and are treated as * numbers only when they can be parsed as a 32-bit integer * * @param priority The priority to set at the specified location. */ - (void)setPriority:(nullable id)priority; /** * The same as setPriority: with a block that is called once the priority has * been committed to the Firebase Database servers. * * @param priority The priority to set at the specified location. * @param block The block that is triggered after the priority has been written * on the servers. */ - (void)setPriority:(nullable id)priority withCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Updates the values at the specified paths in the dictionary without * overwriting other keys at this location. * * @param values A dictionary of the keys to change and their new values */ - (void)updateChildValues:(NSDictionary *)values; /** * The same as update: with a block that is called once the update has been * committed to the Firebase Database servers * * @param values A dictionary of the keys to change and their new values * @param block The block that is triggered after the update has been written on * the Firebase Database servers */ - (void)updateChildValues:(NSDictionary *)values withCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; #pragma mark - Attaching observers to read data /** * observeEventType:withBlock: is used to listen for data changes at a * particular location. This is the primary way to read data from the Firebase * Database. Your block will be triggered for the initial data and again * whenever the data changes. * * Use removeObserverWithHandle: to stop receiving updates. * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot. * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock: (void (^)(FIRDataSnapshot *snapshot))block; /** * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data * changes at a particular location. This is the primary way to read data from * the Firebase Database. Your block will be triggered for the initial data and * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your * block will be passed the key of the previous node by priority order. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot and the previous child's key. * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; /** * observeEventType:withBlock: is used to listen for data changes at a * particular location. This is the primary way to read data from the Firebase * Database. Your block will be triggered for the initial data and again * whenever the data changes. * * The cancelBlock will be called if you will no longer receive new events due * to no longer having permission. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot. * @param cancelBlock The block that should be called if this client no longer * has permission to receive these events * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; /** * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data * changes at a particular location. This is the primary way to read data from * the Firebase Database. Your block will be triggered for the initial data and * again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, * FIRDataEventTypeChildMoved, and FIRDataEventTypeChildChanged events, your * block will be passed the key of the previous node by priority order. * * The cancelBlock will be called if you will no longer receive new events due * to no longer having permission. * * Use removeObserverWithHandle: to stop receiving updates. * * @param eventType The type of event to listen for. * @param block The block that should be called with initial data and updates. * It is passed the data as a FIRDataSnapshot and the previous child's key. * @param cancelBlock The block that should be called if this client no longer * has permission to receive these events * @return A handle used to unregister this block later using * removeObserverWithHandle: */ - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot. */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. In addition, for * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and * FIRDataEventTypeChildChanged events, your block will be passed the key of the * previous node by priority order. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot and the previous child's key. */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock: (void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. * * The cancelBlock will be called if you do not have permission to read data at * this location. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot. * @param cancelBlock The block that will be called if you don't have permission * to access this data */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock:(nullable void (^)(NSError *error))cancelBlock; /** * This is equivalent to observeEventType:withBlock:, except the block is * immediately canceled after the initial data is returned. In addition, for * FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and * FIRDataEventTypeChildChanged events, your block will be passed the key of the * previous node by priority order. * * The cancelBlock will be called if you do not have permission to read data at * this location. * * @param eventType The type of event to listen for. * @param block The block that should be called. It is passed the data as a * FIRDataSnapshot and the previous child's key. * @param cancelBlock The block that will be called if you don't have permission * to access this data */ - (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock: (nullable void (^)(NSError *error))cancelBlock; /** * getDataWithCompletionBlock: is used to get the most up-to-date value for * this query. This method updates the cache and raises events if successful. If * not connected, falls back to a locally-cached value. * * @param block The block that should be called with the most up-to-date value * of this query, or an error if no such value could be retrieved. */ - (void)getDataWithCompletionBlock: (void (^_Nonnull)(NSError *__nullable error, FIRDataSnapshot *snapshot))block NS_SWIFT_NAME(getData(completion:)); #pragma mark - Detaching observers /** * Detach a block previously attached with observeEventType:withBlock:. * * @param handle The handle returned by the call to observeEventType:withBlock: * which we are trying to remove. */ - (void)removeObserverWithHandle:(FIRDatabaseHandle)handle; /** * By calling `keepSynced:YES` on a location, the data for that location will * automatically be downloaded and kept in sync, even when no listeners are * attached for that location. Additionally, while a location is kept synced, it * will not be evicted from the persistent disk cache. * * @param keepSynced Pass YES to keep this location synchronized, pass NO to * stop synchronization. */ - (void)keepSynced:(BOOL)keepSynced; /** * Removes all observers at the current reference, but does not remove any * observers at child references. removeAllObservers must be called again for * each child reference where a listener was established to remove the * observers. */ - (void)removeAllObservers; #pragma mark - Querying and limiting /** * queryLimitedToFirst: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryLimitedToFirst: will respond to at most the first limit child nodes. * * @param limit The upper bound, inclusive, for the number of child nodes to * receive events for * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. */ - (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit; /** * queryLimitedToLast: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryLimitedToLast: will respond to at most the last limit child nodes. * * @param limit The upper bound, inclusive, for the number of child nodes to * receive events for * @return A FIRDatabaseQuery instance, limited to at most limit child nodes. */ - (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit; /** * queryOrderBy: is used to generate a reference to a view of the data that's * been sorted by the values of a particular child key. This method is intended * to be used in combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @param key The child key to use in ordering data visible to the returned * FIRDatabaseQuery * @return A FIRDatabaseQuery instance, ordered by the values of the specified * child key. */ - (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key; /** * queryOrderedByKey: is used to generate a reference to a view of the data * that's been sorted by child key. This method is intended to be used in * combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @return A FIRDatabaseQuery instance, ordered by child keys. */ - (FIRDatabaseQuery *)queryOrderedByKey; /** * queryOrderedByPriority: is used to generate a reference to a view of the data * that's been sorted by child priority. This method is intended to be used in * combination with queryStartingAtValue:, queryEndingAtValue:, or * queryEqualToValue:. * * @return A FIRDatabaseQuery instance, ordered by child priorities. */ - (FIRDatabaseQuery *)queryOrderedByPriority; /** * queryStartingAtValue: is used to generate a reference to a limited view of * the data at this location. The FIRDatabaseQuery instance returned by * queryStartingAtValue: will respond to events at nodes with a value greater * than or equal to startValue. * * @param startValue The lower bound, inclusive, for the value of data visible * to the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value greater than * or equal to startValue */ - (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue; /** * queryStartingAtValue:childKey: is used to generate a reference to a limited * view of the data at this location. The FIRDatabaseQuery instance returned by * queryStartingAtValue:childKey will respond to events at nodes with a value * greater than startValue, or equal to startValue and with a key greater than * or equal to childKey. * * @param startValue The lower bound, inclusive, for the value of data visible * to the returned FIRDatabaseQuery * @param childKey The lower bound, inclusive, for the key of nodes with value * equal to startValue * @return A FIRDatabaseQuery instance, limited to data with value greater than * or equal to startValue */ - (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue childKey:(nullable NSString *)childKey; /** * queryStartingAfterValue: is used to generate a reference to a limited view of * the data at this location. The FIRDatabaseQuery instance returned by * queryStartingAfterValue: will respond to events at nodes with a value greater * than startAfterValue. * * @param startAfterValue The lower bound, exclusive, for the value of data * visible to the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value greater than * startAfterValue */ - (FIRDatabaseQuery *)queryStartingAfterValue:(nullable id)startAfterValue; /** * queryStartingAfterValue:childKey: is used to generate a reference to a * limited view of the data at this location. The FIRDatabaseQuery instance * returned by queryStartingAfterValue:childKey will respond to events at nodes * with a value greater than startAfterValue, or equal to startAfterValue and * with a key greater than childKey. This is most useful when implementing * pagination in a case where multiple nodes can match the startAfterValue. * * @param startAfterValue The lower bound, inclusive, for the value of data * visible to the returned FIRDatabaseQuery * @param childKey The lower bound, exclusive, for the key of nodes with value * equal to startAfterValue * @return A FIRDatabaseQuery instance, limited to data with value greater than * or equal to startAfterValue, or equal to startAfterValue and with a key * greater than childKey. */ - (FIRDatabaseQuery *)queryStartingAfterValue:(nullable id)startAfterValue childKey:(nullable NSString *)childKey; /** * queryEndingAtValue: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryEndingAtValue: will respond to events at nodes with a value less than or * equal to endValue. * * @param endValue The upper bound, inclusive, for the value of data visible to * the returned FIRDatabaseQuery * @return A FIRDatabaseQuery instance, limited to data with value less than or * equal to endValue */ - (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue; /** * queryEndingAtValue:childKey: is used to generate a reference to a limited * view of the data at this location. The FIRDatabaseQuery instance returned by * queryEndingAtValue:childKey will respond to events at nodes with a value less * than endValue, or equal to endValue and with a key less than or equal to * childKey. * * @param endValue The upper bound, inclusive, for the value of data visible to * the returned FIRDatabaseQuery * @param childKey The upper bound, inclusive, for the key of nodes with value * equal to endValue * @return A FIRDatabaseQuery instance, limited to data with value less than or * equal to endValue */ - (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue childKey:(nullable NSString *)childKey; /** * queryEqualToValue: is used to generate a reference to a limited view of the * data at this location. The FIRDatabaseQuery instance returned by * queryEqualToValue: will respond to events at nodes with a value equal to the * supplied argument. * * @param value The value that the data returned by this FIRDatabaseQuery will * have * @return A FIRDatabaseQuery instance, limited to data with the supplied value. */ - (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value; /** * queryEqualToValue:childKey: is used to generate a reference to a limited view * of the data at this location. The FIRDatabaseQuery instance returned by * queryEqualToValue:childKey will respond to events at nodes with a value equal * to the supplied argument with a key equal to childKey. There will be at most * one node that matches because child keys are unique. * * @param value The value that the data returned by this FIRDatabaseQuery will * have * @param childKey The key of nodes with the right value * @return A FIRDatabaseQuery instance, limited to data with the supplied value * and the key. */ - (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value childKey:(nullable NSString *)childKey; #pragma mark - Managing presence /** * Ensure the data at this location is set to the specified value when * the client is disconnected (due to closing the browser, navigating * to a new page, or network issues). * * onDisconnectSetValue: is especially useful for implementing "presence" * systems, where a value should be changed or cleared when a user disconnects * so that he appears "offline" to other users. * * @param value The value to be set after the connection is lost. */ - (void)onDisconnectSetValue:(nullable id)value; /** * Ensure the data at this location is set to the specified value when * the client is disconnected (due to closing the browser, navigating * to a new page, or network issues). * * The completion block will be triggered when the operation has been * successfully queued up on the Firebase Database servers * * @param value The value to be set after the connection is lost. * @param block Block to be triggered when the operation has been queued up on * the Firebase Database servers */ - (void)onDisconnectSetValue:(nullable id)value withCompletionBlock:(void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Ensure the data at this location is set to the specified value and priority * when the client is disconnected (due to closing the browser, navigating to a * new page, or network issues). * * @param value The value to be set after the connection is lost. * @param priority The priority to be set after the connection is lost. */ - (void)onDisconnectSetValue:(nullable id)value andPriority:(id)priority; /** * Ensure the data at this location is set to the specified value and priority * when the client is disconnected (due to closing the browser, navigating to a * new page, or network issues). * * The completion block will be triggered when the operation has been * successfully queued up on the Firebase Database servers * * @param value The value to be set after the connection is lost. * @param priority The priority to be set after the connection is lost. * @param block Block to be triggered when the operation has been queued up on * the Firebase Database servers */ - (void)onDisconnectSetValue:(nullable id)value andPriority:(nullable id)priority withCompletionBlock:(void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Ensure the data at this location is removed when * the client is disconnected (due to closing the app, navigating * to a new page, or network issues). * * onDisconnectRemoveValue is especially useful for implementing "presence" * systems. */ - (void)onDisconnectRemoveValue; /** * Ensure the data at this location is removed when * the client is disconnected (due to closing the app, navigating * to a new page, or network issues). * * onDisconnectRemoveValueWithCompletionBlock: is especially useful for * implementing "presence" systems. * * @param block Block to be triggered when the operation has been queued up on * the Firebase Database servers */ - (void)onDisconnectRemoveValueWithCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Ensure the data has the specified child values updated when * the client is disconnected (due to closing the browser, navigating * to a new page, or network issues). * * * @param values A dictionary of child node keys and the values to set them to * after the connection is lost. */ - (void)onDisconnectUpdateChildValues:(NSDictionary *)values; /** * Ensure the data has the specified child values updated when * the client is disconnected (due to closing the browser, navigating * to a new page, or network issues). * * * @param values A dictionary of child node keys and the values to set them to * after the connection is lost. * @param block A block that will be called once the operation has been queued * up on the Firebase Database servers */ - (void)onDisconnectUpdateChildValues:(NSDictionary *)values withCompletionBlock: (void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; /** * Cancel any operations that are set to run on disconnect. If you previously * called onDisconnectSetValue:, onDisconnectRemoveValue:, or * onDisconnectUpdateChildValues:, and no longer want the values updated when * the connection is lost, call cancelDisconnectOperations: */ - (void)cancelDisconnectOperations; /** * Cancel any operations that are set to run on disconnect. If you previously * called onDisconnectSetValue:, onDisconnectRemoveValue:, or * onDisconnectUpdateChildValues:, and no longer want the values updated when * the connection is lost, call cancelDisconnectOperations: * * @param block A block that will be triggered once the Firebase Database * servers have acknowledged the cancel request. */ - (void)cancelDisconnectOperationsWithCompletionBlock: (nullable void (^)(NSError *__nullable error, FIRDatabaseReference *ref))block; #pragma mark - Manual Connection Management /** * Manually disconnect the Firebase Database client from the server and disable * automatic reconnection. * * The Firebase Database client automatically maintains a persistent connection * to the Firebase Database server, which will remain active indefinitely and * reconnect when disconnected. However, the goOffline( ) and goOnline( ) * methods may be used to manually control the client connection in cases where * a persistent connection is undesirable. * * While offline, the Firebase Database client will no longer receive data * updates from the server. However, all database operations performed locally * will continue to immediately fire events, allowing your application to * continue behaving normally. Additionally, each operation performed locally * will automatically be queued and retried upon reconnection to the Firebase * Database server. * * To reconnect to the Firebase Database server and begin receiving remote * events, see goOnline( ). Once the connection is reestablished, the Firebase * Database client will transmit the appropriate data and fire the appropriate * events so that your client "catches up" automatically. * * Note: Invoking this method will impact all Firebase Database connections. */ + (void)goOffline; /** * Manually reestablish a connection to the Firebase Database server and enable * automatic reconnection. * * The Firebase Database client automatically maintains a persistent connection * to the Firebase Database server, which will remain active indefinitely and * reconnect when disconnected. However, the goOffline( ) and goOnline( ) * methods may be used to manually control the client connection in cases where * a persistent connection is undesirable. * * This method should be used after invoking goOffline( ) to disable the active * connection. Once reconnected, the Firebase Database client will automatically * transmit the proper data and fire the appropriate events so that your client * "catches up" automatically. * * To disconnect from the Firebase Database server, see goOffline( ). * * Note: Invoking this method will impact all Firebase Database connections. */ + (void)goOnline; #pragma mark - Transactions /** * Performs an optimistic-concurrency transactional update to the data at this * location. Your block will be called with a FIRMutableData instance that * contains the current data at this location. Your block should update this * data to the value you wish to write to this location, and then return an * instance of FIRTransactionResult with the new data. * * If, when the operation reaches the server, it turns out that this client had * stale data, your block will be run again with the latest data from the * server. * * When your block is run, you may decide to abort the transaction by returning * [FIRTransactionResult abort]. * * @param block This block receives the current data at this location and must * return an instance of FIRTransactionResult */ - (void)runTransactionBlock: (FIRTransactionResult * (^)(FIRMutableData *currentData))block; /** * Performs an optimistic-concurrency transactional update to the data at this * location. Your block will be called with a FIRMutableData instance that * contains the current data at this location. Your block should update this * data to the value you wish to write to this location, and then return an * instance of FIRTransactionResult with the new data. * * If, when the operation reaches the server, it turns out that this client had * stale data, your block will be run again with the latest data from the * server. * * When your block is run, you may decide to abort the transaction by returning * [FIRTransactionResult abort]. * * @param block This block receives the current data at this location and must * return an instance of FIRTransactionResult * @param completionBlock This block will be triggered once the transaction is * complete, whether it was successful or not. It will indicate if there was an * error, whether or not the data was committed, and what the current value of * the data at this location is. */ - (void)runTransactionBlock: (FIRTransactionResult * (^)(FIRMutableData *currentData))block andCompletionBlock: (void (^)(NSError *__nullable error, BOOL committed, FIRDataSnapshot *__nullable snapshot))completionBlock; /** * Performs an optimistic-concurrency transactional update to the data at this * location. Your block will be called with a FIRMutableData instance that * contains the current data at this location. Your block should update this * data to the value you wish to write to this location, and then return an * instance of FIRTransactionResult with the new data. * * If, when the operation reaches the server, it turns out that this client had * stale data, your block will be run again with the latest data from the * server. * * When your block is run, you may decide to abort the transaction by return * [FIRTransactionResult abort]. * * Since your block may be run multiple times, this client could see several * immediate states that don't exist on the server. You can suppress those * immediate states until the server confirms the final state of the * transaction. * * @param block This block receives the current data at this location and must * return an instance of FIRTransactionResult * @param completionBlock This block will be triggered once the transaction is * complete, whether it was successful or not. It will indicate if there was an * error, whether or not the data was committed, and what the current value of * the data at this location is. * @param localEvents Set this to NO to suppress events raised for intermediate * states, and only get events based on the final state of the transaction. */ - (void)runTransactionBlock: (FIRTransactionResult * (^)(FIRMutableData *currentData))block andCompletionBlock: (nullable void (^)(NSError *__nullable error, BOOL committed, FIRDataSnapshot *__nullable snapshot)) completionBlock withLocalEvents:(BOOL)localEvents; #pragma mark - Retrieving String Representation /** * Gets the absolute URL of this Firebase Database location. * * @return The absolute URL of the referenced Firebase Database location. */ - (NSString *)description; #pragma mark - Properties /** * Gets a FIRDatabaseReference for the parent location. * If this instance refers to the root of your Firebase Database, it has no * parent, and therefore parent( ) will return null. * * @return A FIRDatabaseReference for the parent location. */ @property(strong, readonly, nonatomic, nullable) FIRDatabaseReference *parent; /** * Gets a FIRDatabaseReference for the root location * * @return A new FIRDatabaseReference to root location. */ @property(strong, readonly, nonatomic) FIRDatabaseReference *root; /** * Gets the last token in a Firebase Database location (e.g. 'fred' in * https://SampleChat.firebaseIO-demo.com/users/fred) * * @return The key of the location this reference points to. */ @property(strong, readonly, nonatomic, nullable) NSString *key; /** * Gets the URL for the Firebase Database location referenced by this * FIRDatabaseReference. * * @return The url of the location this reference points to. */ @property(strong, readonly, nonatomic) NSString *URL; /** * Gets the FIRDatabase instance associated with this reference. * * @return The FIRDatabase object for this reference. */ @property(strong, readonly, nonatomic) FIRDatabase *database; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRMutableData.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** * A FIRMutableData instance is populated with data from a Firebase Database * location. When you are using runTransactionBlock:, you will be given an * instance containing the current data at that location. Your block will be * responsible for updating that instance to the data you wish to save at that * location, and then returning using [FIRTransactionResult successWithValue:]. * * To modify the data, set its value property to any of the native types support * by Firebase Database: * * + NSNumber (includes BOOL) * + NSDictionary * + NSArray * + NSString * + nil / NSNull to remove the data * * Note that changes made to a child FIRMutableData instance will be visible to * the parent. */ NS_SWIFT_NAME(MutableData) @interface FIRMutableData : NSObject #pragma mark - Inspecting and navigating the data /** * Returns boolean indicating whether this mutable data has children. * * @return YES if this data contains child nodes. */ - (BOOL)hasChildren; /** * Indicates whether this mutable data has a child at the given path. * * @param path A path string, consisting either of a single segment, like * 'child', or multiple segments, 'a/deeper/child' * @return YES if this data contains a child at the specified relative path */ - (BOOL)hasChildAtPath:(NSString *)path; /** * Used to obtain a FIRMutableData instance that encapsulates the data at the * given relative path. Note that changes made to the child will be visible to * the parent. * * @param path A path string, consisting either of a single segment, like * 'child', or multiple segments, 'a/deeper/child' * @return A FIRMutableData instance containing the data at the given path */ - (FIRMutableData *)childDataByAppendingPath:(NSString *)path; #pragma mark - Properties /** * To modify the data contained by this instance of FIRMutableData, set this to * any of the native types supported by Firebase Database: * * + NSNumber (includes BOOL) * + NSDictionary * + NSArray * + NSString * + nil / NSNull to remove the data * * Note that setting this value will override the priority at this location. * * @return The current data at this location as a native object */ @property(strong, nonatomic, nullable) id value; /** * Set this property to update the priority of the data at this location. Can be * set to the following types: * * + NSNumber * + NSString * + nil / NSNull to remove the priority * * @return The priority of the data at this location */ @property(strong, nonatomic, nullable) id priority; /** * @return The number of child nodes at this location */ @property(readonly, nonatomic) NSUInteger childrenCount; /** * Used to iterate over the children at this location. You can use the native * for .. in syntax: * * for (FIRMutableData* child in data.children) { * ... * } * * Note that this enumerator operates on an immutable copy of the child list. * So, you can modify the instance during iteration, but the new additions will * not be visible until you get a new enumerator. */ @property(readonly, nonatomic, strong) NSEnumerator *children; /** * @return The key name of this node, or nil if it is the top-most location */ @property(readonly, nonatomic, strong, nullable) NSString *key; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRServerValue.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** * Placeholder values you may write into Firebase Database as a value or * priority that will automatically be populated by the Firebase Database * server. */ NS_SWIFT_NAME(ServerValue) @interface FIRServerValue : NSObject /** * Placeholder value for the number of milliseconds since the Unix epoch */ + (NSDictionary *)timestamp; /** * Returns a placeholder value that can be used to atomically increment the * current database value by the provided delta. * * The delta must be a long or double value. If the current value is not an * integer or double, or if the data does not yet exist, the transformation will * set the data to the delta value. If either of the delta value or the existing * data are doubles, both values will be interpreted as doubles. Double * arithmetic and representation of double values follow IEEE 754 semantics. If * there is positive/negative integer overflow, the sum is calculated as a * double. * * @param delta the amount to modify the current value atomically. * @return a placeholder value for modifying data atomically server-side. */ + (NSDictionary *)increment:(NSNumber *)delta NS_SWIFT_NAME(increment(_:)); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FIRTransactionResult.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRMutableData.h" #import NS_ASSUME_NONNULL_BEGIN /** * Used for runTransactionBlock:. An FIRTransactionResult instance is a * container for the results of the transaction. */ NS_SWIFT_NAME(TransactionResult) @interface FIRTransactionResult : NSObject /** * Used for runTransactionBlock:. Indicates that the new value should be saved * at this location * * @param value A FIRMutableData instance containing the new value to be set * @return An FIRTransactionResult instance that can be used as a return value * from the block given to runTransactionBlock: */ + (FIRTransactionResult *)successWithValue:(FIRMutableData *)value; /** * Used for runTransactionBlock:. Indicates that the current transaction should * no longer proceed. * * @return An FIRTransactionResult instance that can be used as a return value * from the block given to runTransactionBlock: */ + (FIRTransactionResult *)abort; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Headers/FirebaseDatabase.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FirebaseDatabase_h #define FirebaseDatabase_h #import "FIRDataEventType.h" #import "FIRDataSnapshot.h" #import "FIRDatabase.h" #import "FIRDatabaseQuery.h" #import "FIRDatabaseReference.h" #import "FIRMutableData.h" #import "FIRServerValue.h" #import "FIRTransactionResult.h" #endif /* FirebaseDatabase_h */ ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Info.plist ================================================ CFBundleExecutable FirebaseDatabase CFBundleIdentifier com.firebase.Firebase-FirebaseDatabase CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseDatabase CFBundlePackageType FMWK CFBundleVersion 8.2.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseDatabase.framework/Modules/module.modulemap ================================================ framework module FirebaseDatabase { umbrella header "FirebaseDatabase.h" export * module * { export * } link framework "AppKit" link framework "CFNetwork" link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link "c++" link "icucore" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Headers/FIRInstallations.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import @class FIRApp; @class FIRInstallationsAuthTokenResult; NS_ASSUME_NONNULL_BEGIN /** A notification with this name is sent each time an installation is created or deleted. */ // clang-format off // clang-format12 merges the next two lines. FOUNDATION_EXPORT const NSNotificationName FIRInstallationIDDidChangeNotification NS_SWIFT_NAME(InstallationIDDidChange); /** `userInfo` key for the `FirebaseApp.name` in `FIRInstallationIDDidChangeNotification`. */ FOUNDATION_EXPORT NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey NS_SWIFT_NAME(InstallationIDDidChangeAppNameKey); // clang-format on /** * An installation ID handler block. * @param identifier The installation ID string if exists or `nil` otherwise. * @param error The error when `identifier == nil` or `nil` otherwise. */ typedef void (^FIRInstallationsIDHandler)(NSString *__nullable identifier, NSError *__nullable error) NS_SWIFT_NAME(InstallationsIDHandler); /** * An authorization token handler block. * @param tokenResult An instance of `InstallationsAuthTokenResult` in case of success or `nil` * otherwise. * @param error The error when `tokenResult == nil` or `nil` otherwise. */ typedef void (^FIRInstallationsTokenHandler)( FIRInstallationsAuthTokenResult *__nullable tokenResult, NSError *__nullable error) NS_SWIFT_NAME(InstallationsTokenHandler); /** * The class provides API for Firebase Installations. * Each configured `FirebaseApp` has a corresponding single instance of `Installations`. * An instance of the class provides access to the installation info for the `FirebaseApp` as well * as the ability to delete it. A Firebase Installation is unique by `FirebaseApp.name` and * `FirebaseApp.options.googleAppID` . */ NS_SWIFT_NAME(Installations) @interface FIRInstallations : NSObject - (instancetype)init NS_UNAVAILABLE; /** * Returns a default instance of `Installations`. * @returns An instance of `Installations` for `FirebaseApp.defaultApp(). * @throw Throws an exception if the default app is not configured yet or required `FirebaseApp` * options are missing. */ + (FIRInstallations *)installations NS_SWIFT_NAME(installations()); /** * Returns an instance of `Installations` for an application. * @param application A configured `FirebaseApp` instance. * @returns An instance of `Installations` corresponding to the passed application. * @throw Throws an exception if required `FirebaseApp` options are missing. */ + (FIRInstallations *)installationsWithApp:(FIRApp *)application NS_SWIFT_NAME(installations(app:)); /** * The method creates or retrieves an installation ID. The installation ID is a stable identifier * that uniquely identifies the app instance. NOTE: If the application already has an existing * FirebaseInstanceID then the InstanceID identifier will be used. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsIDHandler` for additional details. */ - (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion; /** * Retrieves (locally if it exists or from the server) a valid installation auth token. An existing * token may be invalidated or expired, so it is recommended to fetch the installation auth token * before each server request. The method does the same as `Installations.authTokenForcingRefresh(:, * completion:)` with forcing refresh `NO`. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsTokenHandler` for additional details. */ - (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion; /** * Retrieves (locally or from the server depending on `forceRefresh` value) a valid installation * auth token. An existing token may be invalidated or expire, so it is recommended to fetch the * installation auth token before each server request. This method should be used with `forceRefresh * == YES` when e.g. a request with the previously fetched installation auth token failed with "Not * Authorized" error. * @param forceRefresh If `YES` then the locally cached installation auth token will be ignored and * a new one will be requested from the server. If `NO`, then the locally cached installation auth * token will be returned if exists and has not expired yet. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsTokenHandler` for additional details. */ - (void)authTokenForcingRefresh:(BOOL)forceRefresh completion:(FIRInstallationsTokenHandler)completion; /** * Deletes all the installation data including the unique identifier, auth tokens and * all related data on the server side. A network connection is required for the method to * succeed. If fails, the existing installation data remains untouched. * @param completion A completion handler which is invoked when the operation completes. `error == * nil` indicates success. */ - (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Headers/FIRInstallationsAuthTokenResult.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** The class represents a result of the installation auth token request. */ NS_SWIFT_NAME(InstallationsAuthTokenResult) @interface FIRInstallationsAuthTokenResult : NSObject /** The installation auth token string. */ @property(nonatomic, readonly) NSString *authToken; /** The installation auth token expiration date. */ @property(nonatomic, readonly) NSDate *expirationDate; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Headers/FIRInstallationsErrors.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import extern NSString *const kFirebaseInstallationsErrorDomain; typedef NS_ENUM(NSUInteger, FIRInstallationsErrorCode) { /** Unknown error. See `userInfo` for details. */ FIRInstallationsErrorCodeUnknown = 0, /** Keychain error. See `userInfo` for details. */ FIRInstallationsErrorCodeKeychain = 1, /** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */ FIRInstallationsErrorCodeServerUnreachable = 2, /** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details. */ FIRInstallationsErrorCodeInvalidConfiguration = 3, } NS_SWIFT_NAME(InstallationsErrorCode); ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Headers/FirebaseInstallations.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FIRInstallations.h" #import "FIRInstallationsAuthTokenResult.h" #import "FIRInstallationsErrors.h" ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Info.plist ================================================ CFBundleExecutable FirebaseInstallations CFBundleIdentifier com.firebase.Firebase-FirebaseInstallations CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseInstallations CFBundlePackageType FMWK CFBundleVersion 8.2.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/FirebaseInstallations.framework/Modules/module.modulemap ================================================ framework module FirebaseInstallations { umbrella header "FirebaseInstallations.h" export * module * { export * } link framework "AppKit" link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCORClock.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** This class manages the device clock and produces snapshots of the current time. */ @interface GDTCORClock : NSObject /** The wallclock time, UTC, in milliseconds. */ @property(nonatomic, readonly) int64_t timeMillis; /** The offset from UTC in seconds. */ @property(nonatomic, readonly) int64_t timezoneOffsetSeconds; /** The kernel boot time when this clock was created in nanoseconds. */ @property(nonatomic, readonly) int64_t kernelBootTimeNanoseconds; /** The device uptime when this clock was created in nanoseconds. */ @property(nonatomic, readonly) int64_t uptimeNanoseconds; @property(nonatomic, readonly) int64_t kernelBootTime DEPRECATED_MSG_ATTRIBUTE( "Please use `kernelBootTimeNanoseconds` instead"); @property(nonatomic, readonly) int64_t uptime DEPRECATED_MSG_ATTRIBUTE("Please use `uptimeNanoseconds` instead"); /** Creates a GDTCORClock object using the current time and offsets. * * @return A new GDTCORClock object representing the current time state. */ + (instancetype)snapshot; /** Creates a GDTCORClock object representing a time in the future, relative to now. * * @param millisInTheFuture The millis in the future from now this clock should represent. * @return An instance representing a future time. */ + (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture; /** Compares one clock with another, returns YES if the caller is after the parameter. * * @return YES if the calling clock's time is after the given clock's time. */ - (BOOL)isAfter:(GDTCORClock *)otherClock; /** Returns value of `uptime` property in milliseconds. */ - (int64_t)uptimeMilliseconds; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCORConsoleLogger.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import /** The current logging level. This value and higher will be printed. Declared as volatile to make * getting and setting atomic. */ FOUNDATION_EXPORT volatile NSInteger GDTCORConsoleLoggerLoggingLevel; /** A list of logging levels that GDT supports. */ typedef NS_ENUM(NSInteger, GDTCORLoggingLevel) { /** Causes all logs to be printed. */ GDTCORLoggingLevelDebug = 1, /** Causes all non-debug logs to be printed. */ GDTCORLoggingLevelVerbose = 2, /** Causes warnings and errors to be printed. */ GDTCORLoggingLevelWarnings = 3, /** Causes errors to be printed. This is the default value. */ GDTCORLoggingLevelErrors = 4 }; /** A list of message codes to print in the logger that help to correspond printed messages with * code locations. * * Prefixes: * - MCD => MessageCodeDebug * - MCW => MessageCodeWarning * - MCE => MessageCodeError */ typedef NS_ENUM(NSInteger, GDTCORMessageCode) { /** For debug logs. */ GDTCORMCDDebugLog = 0, /** For warning messages concerning transportBytes: not being implemented by a data object. */ GDTCORMCWDataObjectMissingBytesImpl = 1, /** For warning messages concerning a failed event upload. */ GDTCORMCWUploadFailed = 2, /** For warning messages concerning a forced event upload. */ GDTCORMCWForcedUpload = 3, /** For warning messages concerning a failed reachability call. */ GDTCORMCWReachabilityFailed = 4, /** For warning messages concerning a database warning. */ GDTCORMCWDatabaseWarning = 5, /** For warning messages concerning the reading of a event file. */ GDTCORMCWFileReadError = 6, /** For error messages concerning transformGDTEvent: not being implemented by an event transformer. */ GDTCORMCETransformerDoesntImplementTransform = 1000, /** For error messages concerning the creation of a directory failing. */ GDTCORMCEDirectoryCreationError = 1001, /** For error messages concerning the writing of a event file. */ GDTCORMCEFileWriteError = 1002, /** For error messages concerning the lack of a prioritizer for a given backend. */ GDTCORMCEPrioritizerError = 1003, /** For error messages concerning a package delivery API violation. */ GDTCORMCEDeliverTwice = 1004, /** For error messages concerning an error in an implementation of -transportBytes. */ GDTCORMCETransportBytesError = 1005, /** For general purpose error messages in a dependency. */ GDTCORMCEGeneralError = 1006, /** For fatal errors. Please go to https://github.com/firebase/firebase-ios-sdk/issues and open * an issue if you encounter an error with this code. */ GDTCORMCEFatalAssertion = 1007, /** For error messages concerning the reading of a event file. */ GDTCORMCEFileReadError = 1008, /** For errors related to running sqlite. */ GDTCORMCEDatabaseError = 1009, }; /** Prints the given code and format string to the console. * * @param code The message code describing the nature of the log. * @param logLevel The log level of this log. * @param format The format string. */ FOUNDATION_EXPORT void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *_Nonnull format, ...) NS_FORMAT_FUNCTION(3, 4); /** Prints an assert log to the console. * * @param wasFatal Send YES if the assertion should be fatal, NO otherwise. * @param file The file in which the failure occurred. * @param line The line number of the failure. * @param format The format string. */ FOUNDATION_EXPORT void GDTCORLogAssert(BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) NS_FORMAT_FUNCTION(4, 5); /** Returns the string that represents some message code. * * @param code The code to convert to a string. * @return The string representing the message code. */ FOUNDATION_EXPORT NSString *_Nonnull GDTCORMessageCodeEnumToString(GDTCORMessageCode code); #define GDTCORLogDebug(MESSAGE_FORMAT, ...) \ GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelDebug, MESSAGE_FORMAT, __VA_ARGS__); // A define to wrap GULLogWarning with slightly more convenient usage. #define GDTCORLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelWarnings, MESSAGE_FORMAT, __VA_ARGS__); // A define to wrap GULLogError with slightly more convenient usage and a failing assert. #define GDTCORLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelErrors, MESSAGE_FORMAT, __VA_ARGS__); ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCOREndpoints.h ================================================ /* * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GDTCORTargets.h" NS_ASSUME_NONNULL_BEGIN /* Class that manages the endpoints used by Google data transport library. */ @interface GDTCOREndpoints : NSObject - (instancetype)init NS_UNAVAILABLE; /** Returns the upload URL for a target specified. If the target is not available, returns nil. * * @param target GoogleDataTransport target for which the upload URL is being looked up for. * @return URL that will be used for uploading the events for the provided target. */ + (nullable NSURL *)uploadURLForTarget:(GDTCORTarget)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCOREvent.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GDTCOREventDataObject.h" #import "GDTCORTargets.h" @class GDTCORClock; NS_ASSUME_NONNULL_BEGIN /** The different possible quality of service specifiers. High values indicate high priority. */ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { /** The QoS tier wasn't set, and won't ever be sent. */ GDTCOREventQoSUnknown = 0, /** This event is internal telemetry data that should not be sent on its own if possible. */ GDTCOREventQoSTelemetry = 1, /** This event should be sent, but in a batch only roughly once per day. */ GDTCOREventQoSDaily = 2, /** This event should be sent when requested by the uploader. */ GDTCOREventQosDefault = 3, /** This event should be sent immediately along with any other data that can be batched. */ GDTCOREventQoSFast = 4, /** This event should only be uploaded on wifi. */ GDTCOREventQoSWifiOnly = 5, }; @interface GDTCOREvent : NSObject /** The unique ID of the event. */ @property(readonly, nonatomic) NSString *eventID; /** The mapping identifier, to allow backends to map the transport bytes to a proto. */ @property(nullable, readonly, nonatomic) NSString *mappingID; /** The identifier for the backend this event will eventually be sent to. */ @property(readonly, nonatomic) GDTCORTarget target; /** The data object encapsulated in the transport of your choice, as long as it implements * the GDTCOREventDataObject protocol. */ @property(nullable, nonatomic) id dataObject; /** The serialized bytes from calling [dataObject transportBytes]. */ @property(nullable, readonly, nonatomic) NSData *serializedDataObjectBytes; /** The quality of service tier this event belongs to. */ @property(nonatomic) GDTCOREventQoS qosTier; /** The clock snapshot at the time of the event. */ @property(nonatomic) GDTCORClock *clockSnapshot; /** The expiration date of the event. Default is 604800 seconds (7 days) from creation. */ @property(nonatomic) NSDate *expirationDate; /** Bytes that can be used by an uploader later on. */ @property(nullable, nonatomic) NSData *customBytes; /** Initializes an instance using the given mappingID. * * @param mappingID The mapping identifier. * @param target The event's target identifier. * @return An instance of this class. */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCOREventDataObject.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** This protocol defines the common interface that event protos should implement regardless of the * underlying transport technology (protobuf, nanopb, etc). */ @protocol GDTCOREventDataObject @required /** Returns the serialized proto bytes of the implementing event proto. * * @return the serialized proto bytes of the implementing event proto. */ - (NSData *)transportBytes; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCOREventTransformer.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import @class GDTCOREvent; NS_ASSUME_NONNULL_BEGIN /** Defines the API that event transformers must adopt. */ @protocol GDTCOREventTransformer @required /** Transforms an event by applying some logic to it. Events returned can be nil, for example, in * instances where the event should be sampled. * * @param event The event to transform. * @return A transformed event, or nil if the transformation dropped the event. */ - (nullable GDTCOREvent *)transformGDTEvent:(GDTCOREvent *)event; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCORTargets.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import /** The list of targets supported by the shared transport infrastructure. If adding a new target, * please use the previous value +1. */ typedef NS_ENUM(NSInteger, GDTCORTarget) { /** A target only used in testing. */ kGDTCORTargetTest = 999, /** The CCT target. */ kGDTCORTargetCCT = 1000, /** The FLL target. */ kGDTCORTargetFLL = 1001, /** The CSH target. The CSH target is a special-purpose backend. Please do not use it without * permission. */ kGDTCORTargetCSH = 1002, /** The INT target. */ kGDTCORTargetINT = 1003, }; ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GDTCORTransport.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GDTCOREventTransformer.h" #import "GDTCORTargets.h" @class GDTCOREvent; NS_ASSUME_NONNULL_BEGIN @interface GDTCORTransport : NSObject // Please use the designated initializer. - (instancetype)init NS_UNAVAILABLE; /** Initializes a new transport that will send events to the given target backend. * * @param mappingID The mapping identifier used by the backend to map the data object transport * bytes to a proto. * @param transformers A list of transformers to be applied to events that are sent. * @param target The target backend of this transport. * @return A transport that will send events. */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID transformers: (nullable NSArray> *)transformers target:(GDTCORTarget)target NS_DESIGNATED_INITIALIZER; /** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, * and sometimes won't be sent on their own. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. * @param completion A block that will be called when the event has been written or dropped. */ - (void)sendTelemetryEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; /** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, * and sometimes won't be sent on their own. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. */ - (void)sendTelemetryEvent:(GDTCOREvent *)event; /** Copies and sends an SDK service data event. Events send using this API are higher in priority, * and will cause a network request at some point in the relative near future. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. * @param completion A block that will be called when the event has been written or dropped. */ - (void)sendDataEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; /** Copies and sends an SDK service data event. Events send using this API are higher in priority, * and will cause a network request at some point in the relative near future. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. */ - (void)sendDataEvent:(GDTCOREvent *)event; /** Creates an event for use by this transport. * * @return An event that is suited for use by this transport. */ - (GDTCOREvent *)eventForTransport; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Headers/GoogleDataTransport.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "GDTCORClock.h" #import "GDTCORConsoleLogger.h" #import "GDTCOREndpoints.h" #import "GDTCOREvent.h" #import "GDTCOREventDataObject.h" #import "GDTCOREventTransformer.h" #import "GDTCORTargets.h" #import "GDTCORTransport.h" ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Info.plist ================================================ CFBundleExecutable GoogleDataTransport CFBundleIdentifier com.firebase.Firebase-GoogleDataTransport CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleDataTransport CFBundlePackageType FMWK CFBundleVersion 9.0.1 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/GoogleDataTransport.framework/Modules/module.modulemap ================================================ framework module GoogleDataTransport { umbrella header "GoogleDataTransport.h" export * module * { export * } link framework "CoreTelephony" link framework "Security" link framework "SystemConfiguration" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULAppDelegateSwizzler.h ================================================ /* * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULApplication.h" NS_ASSUME_NONNULL_BEGIN typedef NSString *const GULAppDelegateInterceptorID; /** This class contains methods that isa swizzle the app delegate. */ @interface GULAppDelegateSwizzler : NSProxy /** Registers an app delegate interceptor whose methods will be invoked as they're invoked on the * original app delegate. * * @param interceptor An instance of a class that conforms to the application delegate protocol. * The interceptor is NOT retained. * @return A unique GULAppDelegateInterceptorID if interceptor was successfully registered; nil * if it fails. */ + (nullable GULAppDelegateInterceptorID)registerAppDelegateInterceptor: (id)interceptor; /** Unregisters an interceptor with the given ID if it exists. * * @param interceptorID The object that was generated when the interceptor was registered. */ + (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID; /** This method ensures that the original app delegate has been proxied. Call this before * registering your interceptor. This method is safe to call multiple times (but it only proxies * the app delegate once). * * This method doesn't proxy APNS related methods: * @code * - application:didRegisterForRemoteNotificationsWithDeviceToken: * - application:didFailToRegisterForRemoteNotificationsWithError: * - application:didReceiveRemoteNotification:fetchCompletionHandler: * - application:didReceiveRemoteNotification: * @endcode * * To proxy these methods use +[GULAppDelegateSwizzler * proxyOriginalDelegateIncludingAPNSMethods]. The methods have to be proxied separately to * avoid potential warnings from Apple review about missing Push Notification Entitlement (e.g. * https://github.com/firebase/firebase-ios-sdk/issues/2807) * * The method has no effect for extensions. * * @see proxyOriginalDelegateIncludingAPNSMethods */ + (void)proxyOriginalDelegate; /** This method ensures that the original app delegate has been proxied including APNS related * methods. Call this before registering your interceptor. This method is safe to call multiple * times (but it only proxies the app delegate once) or * after +[GULAppDelegateSwizzler proxyOriginalDelegate] * * This method calls +[GULAppDelegateSwizzler proxyOriginalDelegate] under the hood. * After calling this method the following App Delegate methods will be proxied in addition to * the methods proxied by proxyOriginalDelegate: * @code * - application:didRegisterForRemoteNotificationsWithDeviceToken: * - application:didFailToRegisterForRemoteNotificationsWithError: * - application:didReceiveRemoteNotification:fetchCompletionHandler: * - application:didReceiveRemoteNotification: * @endcode * * The method has no effect for extensions. * * @see proxyOriginalDelegate */ + (void)proxyOriginalDelegateIncludingAPNSMethods; /** Indicates whether app delegate proxy is explicitly disabled or enabled. Enabled by default. * * @return YES if AppDelegateProxy is Enabled, NO otherwise. */ + (BOOL)isAppDelegateProxyEnabled; /** Returns the current sharedApplication. * * @return the current application instance if in an app, or nil if in extension or if it doesn't * exist. */ + (nullable GULApplication *)sharedApplication; /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; NS_ASSUME_NONNULL_END @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULAppEnvironmentUtil.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN @interface GULAppEnvironmentUtil : NSObject /// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator, /// development environment or sideloaded. + (BOOL)isFromAppStore; /// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt. /// Returns NO otherwise. + (BOOL)isAppStoreReceiptSandbox; /// Indicates whether the app is on simulator or not at runtime depending on the device /// architecture. + (BOOL)isSimulator; /// The current device model. Returns an empty string if device model cannot be retrieved. + (nullable NSString *)deviceModel; /// The current operating system version. Returns an empty string if the system version cannot be /// retrieved. + (NSString *)systemVersion; /// Indicates whether it is running inside an extension or an app. + (BOOL)isAppExtension; /// @return Returns @YES when is run on iOS version greater or equal to 7.0 + (BOOL)isIOS7OrHigher DEPRECATED_MSG_ATTRIBUTE( "Always `YES` because only iOS 8 and higher supported. The method will be removed."); /// @return YES if Swift runtime detected in the app. + (BOOL)hasSwiftRuntime; /// @return An Apple platform. Possible values "ios", "tvos", "macos", "watchos", "maccatalyst". + (NSString *)applePlatform; /// @return The way the library was added to the app, e.g. "swiftpm", "cocoapods", etc. + (NSString *)deploymentType; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULApplication.h ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #if TARGET_OS_IOS || TARGET_OS_TV #import #define GULApplication UIApplication #define GULApplicationDelegate UIApplicationDelegate #define GULUserActivityRestoring UIUserActivityRestoring static NSString *const kGULApplicationClassName = @"UIApplication"; #elif TARGET_OS_OSX #import #define GULApplication NSApplication #define GULApplicationDelegate NSApplicationDelegate #define GULUserActivityRestoring NSUserActivityRestoring static NSString *const kGULApplicationClassName = @"NSApplication"; #elif TARGET_OS_WATCH #import // We match the according watchOS API but swizzling should not work in watch #define GULApplication WKExtension #define GULApplicationDelegate WKExtensionDelegate #define GULUserActivityRestoring NSUserActivityRestoring static NSString *const kGULApplicationClassName = @"WKExtension"; #endif ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULHeartbeatDateStorable.h ================================================ /* * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** * Describes an object that can store and fetch heartbeat dates for given tags. */ @protocol GULHeartbeatDateStorable /** * Reads the date from the specified file for the given tag. * @return Returns date if exists, otherwise `nil`. */ - (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; /** * Saves the date for the specified tag in the specified file. * @return YES on success, NO otherwise. */ - (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULHeartbeatDateStorage.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULHeartbeatDateStorable.h" NS_ASSUME_NONNULL_BEGIN /// The name of the directory where the heartbeat data is stored. extern NSString *const kGULHeartbeatStorageDirectory; /// Stores either a date or a dictionary to a specified file. @interface GULHeartbeatDateStorage : NSObject - (instancetype)init NS_UNAVAILABLE; @property(nonatomic, readonly) NSURL *fileURL; /** * Default initializer. * @param fileName The name of the file to store the date information. * exist, it will be created if needed. */ - (instancetype)initWithFileName:(NSString *)fileName; /** * Reads the date from the specified file for the given tag. * @return Returns date if exists, otherwise `nil`. */ - (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; /** * Saves the date for the specified tag in the specified file. * @return YES on success, NO otherwise. */ - (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULHeartbeatDateStorageUserDefaults.h ================================================ /* * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULHeartbeatDateStorable.h" NS_ASSUME_NONNULL_BEGIN /// Stores either a date or a dictionary to a specified file. @interface GULHeartbeatDateStorageUserDefaults : NSObject /** * Default initializer. tvOS can only write to the cache directory and * there are no guarantees that the directory will persist. User defaults will * be retained, so that should be used instead. * @param defaults User defaults instance to store the heartbeat information. * @param key The key to be used with the user defaults instance. */ - (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key; - (instancetype)init NS_UNAVAILABLE; /** * Reads the date from the specified file for the given tag. * @return Returns date if exists, otherwise `nil`. */ - (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; /** * Saves the date for the specified tag in the specified file. * @return YES on success, NO otherwise. */ - (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULKeychainStorage.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /// The class provides a convenient abstraction on top of the iOS Keychain API to save data. @interface GULKeychainStorage : NSObject - (instancetype)init NS_UNAVAILABLE; /** Initializes the keychain storage with Keychain Service name. * @param service A Keychain Service name that will be used to store and retrieve objects. See also * `kSecAttrService`. */ - (instancetype)initWithService:(NSString *)service; /** * Get an object by key. * @param key The key. * @param objectClass The expected object class required by `NSSecureCoding`. * @param accessGroup The Keychain Access Group. * * @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved * with `nil` when the object not found. It fails on a Keychain error. */ - (FBLPromise> *)getObjectForKey:(NSString *)key objectClass:(Class)objectClass accessGroup:(nullable NSString *)accessGroup; /** * Saves the given object by the given key. * @param object The object to store. * @param key The key to store the object. If there is an existing object by the key, it will be * overridden. * @param accessGroup The Keychain Access Group. * * @return Returns which is resolved with `[NSNull null]` on success. */ - (FBLPromise *)setObject:(id)object forKey:(NSString *)key accessGroup:(nullable NSString *)accessGroup; /** * Removes the object by the given key. * @param key The key to store the object. If there is an existing object by the key, it will be * overridden. * @param accessGroup The Keychain Access Group. * * @return Returns which is resolved with `[NSNull null]` on success. */ - (FBLPromise *)removeObjectForKey:(NSString *)key accessGroup:(nullable NSString *)accessGroup; #if TARGET_OS_OSX /// If not `nil`, then only this keychain will be used to save and read data (see /// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests. @property(nonatomic, nullable) SecKeychainRef keychainRef; #endif // TARGET_OSX @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULKeychainUtils.h ================================================ /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const kGULKeychainUtilsErrorDomain; /// Helper functions to access Keychain. @interface GULKeychainUtils : NSObject /** Fetches a keychain item data matching to the provided query. * @param query A dictionary with Keychain query parameters. See docs for `SecItemCopyMatching` for * details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns Data for the first Keychain Item matching the provided query or `nil` if there is not * such an item (`outError` will be `nil` in this case) or an error occurred. */ + (nullable NSData *)getItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; /** Stores data to a Keychain Item matching to the provided query. An existing Keychain Item * matching the query parameters will be updated or a new will be created. * @param item A Keychain Item data to store. * @param query A dictionary with Keychain query parameters. See docs for `SecItemAdd` and * `SecItemUpdate` for details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns `YES` when data was successfully stored, `NO` otherwise. */ + (BOOL)setItem:(NSData *)item withQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; /** Removes a Keychain Item matching to the provided query. * @param query A dictionary with Keychain query parameters. See docs for `SecItemDelete` for * details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns `YES` if the item was removed successfully or doesn't exist, `NO` otherwise. */ + (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULLogger.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULLoggerLevel.h" NS_ASSUME_NONNULL_BEGIN /** * The services used in the logger. */ typedef NSString *const GULLoggerService; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Initialize GULLogger. */ extern void GULLoggerInitializeASL(void); /** * Override log level to Debug. */ void GULLoggerForceDebug(void); /** * Turn on logging to STDERR. */ extern void GULLoggerEnableSTDERR(void); /** * Changes the default logging level of GULLoggerLevelNotice to a user-specified level. * The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store. * (required) log level (one of the GULLoggerLevel enum values). */ extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel); /** * Checks if the specified logger level is loggable given the current settings. * (required) log level (one of the GULLoggerLevel enum values). */ extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel); /** * Register version to include in logs. * (required) version */ extern void GULLoggerRegisterVersion(NSString *version); /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming. * (required) log level (one of the GULLoggerLevel enum values). * (required) service name of type GULLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ extern void GULLogBasic(GULLoggerLevel level, GULLoggerService service, BOOL forceLog, NSString *messageCode, NSString *message, // On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable // See: http://stackoverflow.com/q/29095469 #if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX va_list args_ptr #else va_list _Nullable args_ptr #endif ); /** * The following functions accept the following parameters in order: * (required) service name of type GULLoggerService. * (required) message code starting from "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * See go/firebase-log-proposal for details. * (required) message string which can be a format string. * (optional) the list of arguments to substitute into the format string. * Example usage: * GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); */ extern void GULLogError(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogWarning(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogNotice(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogInfo(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogDebug(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); #ifdef __cplusplus } // extern "C" #endif // __cplusplus @interface GULLoggerWrapper : NSObject /** * Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger * (required) log level (one of the GULLoggerLevel enum values). * (required) service name of type GULLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ + (void)logWithLevel:(GULLoggerLevel)level withService:(GULLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULLoggerLevel.h ================================================ /* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import /** * The log levels used by internal logging. */ typedef NS_ENUM(NSInteger, GULLoggerLevel) { /** Error level, matches ASL_LEVEL_ERR. */ GULLoggerLevelError = 3, /** Warning level, matches ASL_LEVEL_WARNING. */ GULLoggerLevelWarning = 4, /** Notice level, matches ASL_LEVEL_NOTICE. */ GULLoggerLevelNotice = 5, /** Info level, matches ASL_LEVEL_INFO. */ GULLoggerLevelInfo = 6, /** Debug level, matches ASL_LEVEL_DEBUG. */ GULLoggerLevelDebug = 7, /** Minimum log level. */ GULLoggerLevelMin = GULLoggerLevelError, /** Maximum log level. */ GULLoggerLevelMax = GULLoggerLevelDebug } NS_SWIFT_NAME(GoogleLoggerLevel); ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULMutableDictionary.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import /// A mutable dictionary that provides atomic accessor and mutators. @interface GULMutableDictionary : NSObject /// Returns an object given a key in the dictionary or nil if not found. - (id)objectForKey:(id)key; /// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. - (void)setObject:(id)object forKey:(id)key; /// Removes the object given its session ID from the dictionary. - (void)removeObjectForKey:(id)key; /// Removes all objects. - (void)removeAllObjects; /// Returns the number of current objects in the dictionary. - (NSUInteger)count; /// Returns an object given a key in the dictionary or nil if not found. - (id)objectForKeyedSubscript:(id)key; /// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. - (void)setObject:(id)obj forKeyedSubscript:(id)key; /// Returns the immutable dictionary. - (NSDictionary *)dictionary; @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNSData+zlib.h ================================================ // Copyright 2018 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import /// This is a copy of Google Toolbox for Mac library to avoid creating an extra framework. // NOTE: For 64bit, none of these apis handle input sizes >32bits, they will return nil when given // such data. To handle data of that size you really should be streaming it rather then doing it all // in memory. @interface NSData (GULGzip) /// Returns an data as the result of decompressing the payload of |data|.The data to decompress must /// be a gzipped payloads. + (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error; /// Returns an compressed data with the result of gzipping the payload of |data|. Uses the default /// compression level. + (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber FOUNDATION_EXPORT NSString *const GULNSDataZlibRemainingBytesKey; // NSNumber typedef NS_ENUM(NSInteger, GULNSDataZlibError) { GULNSDataZlibErrorGreaterThan32BitsToCompress = 1024, // An internal zlib error. // GULNSDataZlibErrorKey will contain the error value. // NSLocalizedDescriptionKey may contain an error string from zlib. // Look in zlib.h for list of errors. GULNSDataZlibErrorInternal, // There was left over data in the buffer that was not used. // GULNSDataZlibRemainingBytesKey will contain number of remaining bytes. GULNSDataZlibErrorDataRemaining }; @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNetwork.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULNetworkConstants.h" #import "GULNetworkLoggerProtocol.h" #import "GULNetworkURLSession.h" /// Delegate protocol for GULNetwork events. @protocol GULNetworkReachabilityDelegate /// Tells the delegate to handle events when the network reachability changes to connected or not /// connected. - (void)reachabilityDidChange; @end /// The Network component that provides network status and handles network requests and responses. /// This is not thread safe. /// /// NOTE: /// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the /// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler: @interface GULNetwork : NSObject /// Indicates if network connectivity is available. @property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected; /// Indicates if there are any uploads in progress. @property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress; /// An optional delegate that can be used in the event when network reachability changes. @property(nonatomic, weak) id reachabilityDelegate; /// An optional delegate that can be used to log messages, warnings or errors that occur in the /// network operations. @property(nonatomic, weak) id loggerDelegate; /// Indicates whether the logger should display debug messages. @property(nonatomic, assign) BOOL isDebugModeEnabled; /// The time interval in seconds for the network request to timeout. @property(nonatomic, assign) NSTimeInterval timeoutInterval; /// Initializes with the default reachability host. - (instancetype)init; /// Initializes with a custom reachability host. - (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost; /// Handles events when background session with the given ID has finished. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; /// Compresses and sends a POST request with the provided data to the URL. The session will be /// background session if usingBackgroundSession is YES. Otherwise, the POST session is default /// session. Returns a session ID or nil if an error occurs. - (NSString *)postURL:(NSURL *)url payload:(NSData *)payload queue:(dispatch_queue_t)queue usingBackgroundSession:(BOOL)usingBackgroundSession completionHandler:(GULNetworkCompletionHandler)handler; /// Sends a GET request with the provided data to the URL. The session will be background session /// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a /// session ID or nil if an error occurs. - (NSString *)getURL:(NSURL *)url headers:(NSDictionary *)headers queue:(dispatch_queue_t)queue usingBackgroundSession:(BOOL)usingBackgroundSession completionHandler:(GULNetworkCompletionHandler)handler; @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNetworkConstants.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import /// Error codes in Firebase Network error domain. /// Note: these error codes should never change. It would make it harder to decode the errors if /// we inadvertently altered any of these codes in a future SDK version. typedef NS_ENUM(NSInteger, GULNetworkErrorCode) { /// Unknown error. GULNetworkErrorCodeUnknown = 0, /// Error occurs when the request URL is invalid. GULErrorCodeNetworkInvalidURL = 1, /// Error occurs when request cannot be constructed. GULErrorCodeNetworkRequestCreation = 2, /// Error occurs when payload cannot be compressed. GULErrorCodeNetworkPayloadCompression = 3, /// Error occurs when session task cannot be created. GULErrorCodeNetworkSessionTaskCreation = 4, /// Error occurs when there is no response. GULErrorCodeNetworkInvalidResponse = 5 }; #pragma mark - Network constants /// The prefix of the ID of the background session. extern NSString *const kGULNetworkBackgroundSessionConfigIDPrefix; /// The sub directory to store the files of data that is being uploaded in the background. extern NSString *const kGULNetworkApplicationSupportSubdirectory; /// Name of the temporary directory that stores files for background uploading. extern NSString *const kGULNetworkTempDirectoryName; /// The period when the temporary uploading file can stay. extern const NSTimeInterval kGULNetworkTempFolderExpireTime; /// The default network request timeout interval. extern const NSTimeInterval kGULNetworkTimeOutInterval; /// The host to check the reachability of the network. extern NSString *const kGULNetworkReachabilityHost; /// The key to get the error context of the UserInfo. extern NSString *const kGULNetworkErrorContext; #pragma mark - Network Status Code extern const int kGULNetworkHTTPStatusOK; extern const int kGULNetworkHTTPStatusNoContent; extern const int kGULNetworkHTTPStatusCodeMultipleChoices; extern const int kGULNetworkHTTPStatusCodeMovedPermanently; extern const int kGULNetworkHTTPStatusCodeFound; extern const int kGULNetworkHTTPStatusCodeNotModified; extern const int kGULNetworkHTTPStatusCodeMovedTemporarily; extern const int kGULNetworkHTTPStatusCodeNotFound; extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic; extern const int kGULNetworkHTTPStatusCodeUnavailable; ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNetworkLoggerProtocol.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULNetworkMessageCode.h" /// The log levels used by GULNetworkLogger. typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { kGULNetworkLogLevelError = 3, kGULNetworkLogLevelWarning = 4, kGULNetworkLogLevelInfo = 6, kGULNetworkLogLevelDebug = 7, }; @protocol GULNetworkLoggerDelegate @required /// Tells the delegate to log a message with an array of contexts and the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message contexts:(NSArray *)contexts; /// Tells the delegate to log a message with a context and the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message context:(id)context; /// Tells the delegate to log a message with the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message; @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNetworkMessageCode.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import // Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { // GULNetwork.m kGULNetworkMessageCodeNetwork000 = 900000, // I-NET900000 kGULNetworkMessageCodeNetwork001 = 900001, // I-NET900001 kGULNetworkMessageCodeNetwork002 = 900002, // I-NET900002 kGULNetworkMessageCodeNetwork003 = 900003, // I-NET900003 // GULNetworkURLSession.m kGULNetworkMessageCodeURLSession000 = 901000, // I-NET901000 kGULNetworkMessageCodeURLSession001 = 901001, // I-NET901001 kGULNetworkMessageCodeURLSession002 = 901002, // I-NET901002 kGULNetworkMessageCodeURLSession003 = 901003, // I-NET901003 kGULNetworkMessageCodeURLSession004 = 901004, // I-NET901004 kGULNetworkMessageCodeURLSession005 = 901005, // I-NET901005 kGULNetworkMessageCodeURLSession006 = 901006, // I-NET901006 kGULNetworkMessageCodeURLSession007 = 901007, // I-NET901007 kGULNetworkMessageCodeURLSession008 = 901008, // I-NET901008 kGULNetworkMessageCodeURLSession009 = 901009, // I-NET901009 kGULNetworkMessageCodeURLSession010 = 901010, // I-NET901010 kGULNetworkMessageCodeURLSession011 = 901011, // I-NET901011 kGULNetworkMessageCodeURLSession012 = 901012, // I-NET901012 kGULNetworkMessageCodeURLSession013 = 901013, // I-NET901013 kGULNetworkMessageCodeURLSession014 = 901014, // I-NET901014 kGULNetworkMessageCodeURLSession015 = 901015, // I-NET901015 kGULNetworkMessageCodeURLSession016 = 901016, // I-NET901016 kGULNetworkMessageCodeURLSession017 = 901017, // I-NET901017 kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018 kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019 }; ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULNetworkURLSession.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "GULNetworkLoggerProtocol.h" NS_ASSUME_NONNULL_BEGIN typedef void (^GULNetworkCompletionHandler)(NSHTTPURLResponse *_Nullable response, NSData *_Nullable data, NSError *_Nullable error); typedef void (^GULNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *_Nullable response, NSData *_Nullable data, NSString *sessionID, NSError *_Nullable error); typedef void (^GULNetworkSystemCompletionHandler)(void); /// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses. @interface GULNetworkURLSession : NSObject /// Indicates whether the background network is enabled. Default value is NO. @property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled; /// The logger delegate to log message, errors or warnings that occur during the network operations. @property(nonatomic, weak, nullable) id loggerDelegate; /// Calls the system provided completion handler after the background session is finished. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; /// Initializes with logger delegate. - (instancetype)initWithNetworkLoggerDelegate: (nullable id)networkLoggerDelegate NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; /// Sends an asynchronous POST request and calls the provided completion handler when the request /// completes or when errors occur, and returns an ID of the session/connection. - (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler; /// Sends an asynchronous GET request and calls the provided completion handler when the request /// completes or when errors occur, and returns an ID of the session. - (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler; NS_ASSUME_NONNULL_END @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULReachabilityChecker.h ================================================ /* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #if !TARGET_OS_WATCH #import #endif /// Reachability Status typedef enum { kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. kGULReachabilityNotReachable, ///< Host is not reachable. kGULReachabilityViaWifi, ///< Host is reachable via Wifi. kGULReachabilityViaCellular, ///< Host is reachable via cellular. } GULReachabilityStatus; const NSString *GULReachabilityStatusString(GULReachabilityStatus status); @class GULReachabilityChecker; /// Google Analytics iOS Reachability Checker. @protocol GULReachabilityDelegate @required /// Called when network status has changed. - (void)reachability:(GULReachabilityChecker *)reachability statusChanged:(GULReachabilityStatus)status; @end /// Google Analytics iOS Network Status Checker. @interface GULReachabilityChecker : NSObject /// The last known reachability status, or GULReachabilityStatusUnknown if the /// checker is not active. @property(nonatomic, readonly) GULReachabilityStatus reachabilityStatus; /// The host to which reachability status is to be checked. @property(nonatomic, copy, readonly) NSString *host; /// The delegate to be notified of reachability status changes. @property(nonatomic, weak) id reachabilityDelegate; /// `YES` if the reachability checker is active, `NO` otherwise. @property(nonatomic, readonly) BOOL isActive; /// Initialize the reachability checker. Note that you must call start to begin checking for and /// receiving notifications about network status changes. /// /// @param reachabilityDelegate The delegate to be notified when reachability status to host /// changes. /// /// @param host The name of the host. /// - (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate withHost:(NSString *)host; - (instancetype)init NS_UNAVAILABLE; /// Start checking for reachability to the specified host. This has no effect if the status /// checker is already checking for connectivity. /// /// @return `YES` if initiating status checking was successful or the status checking has already /// been initiated, `NO` otherwise. - (BOOL)start; /// Stop checking for reachability to the specified host. This has no effect if the status /// checker is not checking for connectivity. - (void)stop; @end ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULSceneDelegateSwizzler.h ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import #if !TARGET_OS_OSX #import #endif // !TARGET_OS_OSX #if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000)) #define UISCENE_SUPPORTED 1 #endif NS_ASSUME_NONNULL_BEGIN typedef NSString *const GULSceneDelegateInterceptorID; /** This class contains methods that isa swizzle the scene delegate. */ @interface GULSceneDelegateSwizzler : NSProxy #if UISCENE_SUPPORTED /** Registers a scene delegate interceptor whose methods will be invoked as they're invoked on the * original scene delegate. * * @param interceptor An instance of a class that conforms to the application delegate protocol. * The interceptor is NOT retained. * @return A unique GULSceneDelegateInterceptorID if interceptor was successfully registered; nil * if it fails. */ + (nullable GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor: (id)interceptor API_AVAILABLE(ios(13.0), tvos(13.0)); /** Unregisters an interceptor with the given ID if it exists. * * @param interceptorID The object that was generated when the interceptor was registered. */ + (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID API_AVAILABLE(ios(13.0), tvos(13.0)); /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; #endif // UISCENE_SUPPORTED /** This method ensures that the original scene delegate has been proxied. Call this before * registering your interceptor. This method is safe to call multiple times (but it only proxies * the scene delegate once). * * The method has no effect for extensions. */ + (void)proxyOriginalSceneDelegate; /** Indicates whether scene delegate proxy is explicitly disabled or enabled. Enabled by default. * * @return YES if SceneDelegateProxy is Enabled, NO otherwise. */ + (BOOL)isSceneDelegateProxyEnabled; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULSecureCoding.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import NS_ASSUME_NONNULL_BEGIN /** The class wraps `NSKeyedArchiver` and `NSKeyedUnarchiver` API to provide a unified secure coding * methods for iOS versions before and after 11. */ @interface GULSecureCoding : NSObject + (nullable id)unarchivedObjectOfClasses:(NSSet *)classes fromData:(NSData *)data error:(NSError **)outError; + (nullable id)unarchivedObjectOfClass:(Class)class fromData:(NSData *)data error:(NSError **)outError; + (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULURLSessionDataResponse.h ================================================ /* * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN /** The class represents HTTP response received from `NSURLSession`. */ @interface GULURLSessionDataResponse : NSObject @property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; @property(nonatomic, nullable, readonly) NSData *HTTPBody; - (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GULUserDefaults.h ================================================ // Copyright 2018 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import NS_ASSUME_NONNULL_BEGIN /// A thread-safe user defaults that uses C functions from CFPreferences.h instead of /// `NSUserDefaults`. This is to avoid sending an `NSNotification` when it's changed from a /// background thread to avoid crashing. // TODO: Insert radar number here. @interface GULUserDefaults : NSObject /// A shared user defaults similar to +[NSUserDefaults standardUserDefaults] and accesses the same /// data of the standardUserDefaults. + (GULUserDefaults *)standardUserDefaults; /// Initializes preferences with a suite name that is the same with the NSUserDefaults' suite name. /// Both of CFPreferences and NSUserDefaults share the same plist file so their data will exactly /// the same. /// /// @param suiteName The name of the suite of the user defaults. - (instancetype)initWithSuiteName:(nullable NSString *)suiteName; #pragma mark - Getters /// Searches the receiver's search list for a default with the key 'defaultName' and return it. If /// another process has changed defaults in the search list, NSUserDefaults will automatically /// update to the latest values. If the key in question has been marked as ubiquitous via a Defaults /// Configuration File, the latest value may not be immediately available, and the registered value /// will be returned instead. - (nullable id)objectForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will return nil if the value is not an NSArray. - (nullable NSArray *)arrayForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will return nil if the value /// is not an NSDictionary. - (nullable NSDictionary *)dictionaryForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will convert NSNumber values to their NSString /// representation. If a non-string non-number value is found, nil will be returned. - (nullable NSString *)stringForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the /// value is an NSNumber, the result of -integerValue will be returned. If the value is an NSString, /// it will be converted to NSInteger if possible. If the value is a boolean, it will be converted /// to either 1 for YES or 0 for NO. If the value is absent or can't be converted to an integer, 0 /// will be returned. - (NSInteger)integerForKey:(NSString *)defaultName; /// Similar to -integerForKey:, except that it returns a float, and boolean values will not be /// converted. - (float)floatForKey:(NSString *)defaultName; /// Similar to -integerForKey:, except that it returns a double, and boolean values will not be /// converted. - (double)doubleForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it converts the returned value to a BOOL. If the value /// is an NSNumber, NO will be returned if the value is 0, YES otherwise. If the value is an /// NSString, values of "YES" or "1" will return YES, and values of "NO", "0", or any other string /// will return NO. If the value is absent or can't be converted to a BOOL, NO will be returned. - (BOOL)boolForKey:(NSString *)defaultName; #pragma mark - Setters /// Immediately stores a value (or removes the value if `nil` is passed as the value) for the /// provided key in the search list entry for the receiver's suite name in the current user and any /// host, then asynchronously stores the value persistently, where it is made available to other /// processes. - (void)setObject:(nullable id)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a float to an NSNumber. - (void)setFloat:(float)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a double to an /// NSNumber. - (void)setDouble:(double)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from an NSInteger to an /// NSNumber. - (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a BOOL to an NSNumber. - (void)setBool:(BOOL)value forKey:(NSString *)defaultName; #pragma mark - Removing Defaults /// Equivalent to -[... setObject:nil forKey:defaultName] - (void)removeObjectForKey:(NSString *)defaultName; #pragma mark - Save data /// Blocks the calling thread until all in-progress set operations have completed. - (void)synchronize; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/GoogleUtilities-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "GULAppDelegateSwizzler.h" #import "GULApplication.h" #import "GULSceneDelegateSwizzler.h" #import "GULAppEnvironmentUtil.h" #import "GULHeartbeatDateStorable.h" #import "GULHeartbeatDateStorage.h" #import "GULHeartbeatDateStorageUserDefaults.h" #import "GULKeychainStorage.h" #import "GULKeychainUtils.h" #import "GULSecureCoding.h" #import "GULURLSessionDataResponse.h" #import "NSURLSession+GULPromises.h" #import "GULLogger.h" #import "GULLoggerLevel.h" #import "GULNSData+zlib.h" #import "GULMutableDictionary.h" #import "GULNetwork.h" #import "GULNetworkConstants.h" #import "GULNetworkLoggerProtocol.h" #import "GULNetworkMessageCode.h" #import "GULNetworkURLSession.h" #import "GULReachabilityChecker.h" #import "GULUserDefaults.h" FOUNDATION_EXPORT double GoogleUtilitiesVersionNumber; FOUNDATION_EXPORT const unsigned char GoogleUtilitiesVersionString[]; ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Headers/NSURLSession+GULPromises.h ================================================ /* * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import @class FBLPromise; @class GULURLSessionDataResponse; NS_ASSUME_NONNULL_BEGIN /** Promise based API for `NSURLSession`. */ @interface NSURLSession (GULPromises) /** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method. * @param URLRequest The request to create a data task with. * @return A promise that is fulfilled when an HTTP response is received (with any response code), * or is rejected with the error passed to the task completion. */ - (FBLPromise *)gul_dataTaskPromiseWithRequest: (NSURLRequest *)URLRequest; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Info.plist ================================================ CFBundleExecutable GoogleUtilities CFBundleIdentifier com.firebase.Firebase-GoogleUtilities CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleUtilities CFBundlePackageType FMWK CFBundleVersion 7.4.1 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/GoogleUtilities.framework/Modules/module.modulemap ================================================ framework module GoogleUtilities { umbrella header "GoogleUtilities-umbrella.h" export * module * { export * } link framework "Security" link framework "SystemConfiguration" link "z" } ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+All.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AllAdditions) /** Wait until all of the given promises are fulfilled. If one of the given promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param promises Promises to wait for. @return Promise of an array containing the values of input promises in the same order. */ + (FBLPromise *)all:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Wait until all of the given promises are fulfilled. If one of the given promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected FBLPromise correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param queue A queue to dispatch on. @param promises Promises to wait for. @return Promise of an array containing the values of input promises in the same order. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue all:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `all` operators. Usage: FBLPromise.all(@[ ... ]) */ @interface FBLPromise(DotSyntax_AllAdditions) + (FBLPromise * (^)(NSArray *))all FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))allOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Always.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AlwaysAdditions) typedef void (^FBLPromiseAlwaysWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); /** @param work A block that always executes, no matter if the receiver is rejected or fulfilled. @return A new pending promise to be resolved with same resolution as the receiver. */ - (FBLPromise *)always:(FBLPromiseAlwaysWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to dispatch on. @param work A block that always executes, no matter if the receiver is rejected or fulfilled. @return A new pending promise to be resolved with same resolution as the receiver. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `always` operators. Usage: promise.always(^{...}) */ @interface FBLPromise(DotSyntax_AlwaysAdditions) - (FBLPromise* (^)(FBLPromiseAlwaysWorkBlock))always FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAlwaysWorkBlock))alwaysOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Any.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AnyAdditions) /** Waits until all of the given promises are either fulfilled or rejected. If all promises are rejected, then the returned promise is rejected with same error as the last one rejected. If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of values or `NSErrors`, matching the original order of fulfilled or rejected promises respectively. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param promises Promises to wait for. @return Promise of array containing the values or `NSError`s of input promises in the same order. */ + (FBLPromise *)any:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Waits until all of the given promises are either fulfilled or rejected. If all promises are rejected, then the returned promise is rejected with same error as the last one rejected. If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of values or `NSError`s, matching the original order of fulfilled or rejected promises respectively. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param queue A queue to dispatch on. @param promises Promises to wait for. @return Promise of array containing the values or `NSError`s of input promises in the same order. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue any:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `any` operators. Usage: FBLPromise.any(@[ ... ]) */ @interface FBLPromise(DotSyntax_AnyAdditions) + (FBLPromise * (^)(NSArray *))any FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))anyOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Async.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AsyncAdditions) typedef void (^FBLPromiseFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseAsyncWorkBlock)(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously. @param work A block to perform any operations needed to resolve the promise. @return A new pending promise. */ + (instancetype)async:(FBLPromiseAsyncWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously on the given queue. @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @return A new pending promise. */ + (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `async` operators. Usage: FBLPromise.async(^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { ... }) */ @interface FBLPromise(DotSyntax_AsyncAdditions) + (FBLPromise* (^)(FBLPromiseAsyncWorkBlock))async FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAsyncWorkBlock))asyncOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Await.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Waits for promise resolution. The current thread blocks until the promise is resolved. @param promise Promise to wait for. @param error Error the promise was rejected with, or `nil` if the promise was fulfilled. @return Value the promise was fulfilled with. If the promise was rejected, the return value is always `nil`, but the error out arg is not. */ FOUNDATION_EXTERN id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **error) NS_REFINED_FOR_SWIFT; NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Catch.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(CatchAdditions) typedef void (^FBLPromiseCatchWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with same resolution as the receiver. If receiver is rejected, then `reject` block is executed asynchronously. @param reject A block to handle the error that receiver was rejected with. @return A new pending promise. */ - (FBLPromise *)catch:(FBLPromiseCatchWorkBlock)reject NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with same resolution as the receiver. If receiver is rejected, then `reject` block is executed asynchronously on the given queue. @param queue A queue to invoke the `reject` block on. @param reject A block to handle the error that receiver was rejected with. @return A new pending promise. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue catch:(FBLPromiseCatchWorkBlock)reject NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `catch` operators. Usage: promise.catch(^(NSError *error) { ... }) */ @interface FBLPromise(DotSyntax_CatchAdditions) - (FBLPromise* (^)(FBLPromiseCatchWorkBlock))catch FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseCatchWorkBlock))catchOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Delay.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(DelayAdditions) /** Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or rejects with the same error immediately. @param interval Time to wait in seconds. @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects with the same error immediately. */ - (FBLPromise *)delay:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); /** Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or rejects with the same error immediately. @param queue A queue to dispatch on. @param interval Time to wait in seconds. @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects with the same error immediately. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `delay` operators. Usage: promise.delay(...) */ @interface FBLPromise(DotSyntax_DelayAdditions) - (FBLPromise * (^)(NSTimeInterval))delay FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, NSTimeInterval))delayOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Do.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(DoAdditions) typedef id __nullable (^FBLPromiseDoWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously. @param work A block that returns a value or an error used to resolve the promise. @return A new pending promise. */ + (instancetype)do:(FBLPromiseDoWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously on the given queue. @param queue A queue to invoke the `work` block on. @param work A block that returns a value or an error used to resolve the promise. @return A new pending promise. */ + (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `do` operators. Usage: FBLPromise.doOn(queue, ^(NSError *error) { ... }) */ @interface FBLPromise(DotSyntax_DoAdditions) + (FBLPromise * (^)(dispatch_queue_t, FBLPromiseDoWorkBlock))doOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Race.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(RaceAdditions) /** Wait until any of the given promises are fulfilled. If one of the promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. @param promises Promises to wait for. @return A new pending promise to be resolved with the same resolution as the first promise, among the given ones, which was resolved. */ + (instancetype)race:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Wait until any of the given promises are fulfilled. If one of the promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. @param queue A queue to dispatch on. @param promises Promises to wait for. @return A new pending promise to be resolved with the same resolution as the first promise, among the given ones, which was resolved. */ + (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `race` operators. Usage: FBLPromise.race(@[ ... ]) */ @interface FBLPromise(DotSyntax_RaceAdditions) + (FBLPromise * (^)(NSArray *))race FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))raceOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Recover.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(RecoverAdditions) typedef id __nullable (^FBLPromiseRecoverWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); /** Provides a new promise to recover in case the receiver gets rejected. @param recovery A block to handle the error that the receiver was rejected with. @return A new pending promise to use instead of the rejected one that gets resolved with resolution returned from `recovery` block. */ - (FBLPromise *)recover:(FBLPromiseRecoverWorkBlock)recovery NS_SWIFT_UNAVAILABLE(""); /** Provides a new promise to recover in case the receiver gets rejected. @param queue A queue to dispatch on. @param recovery A block to handle the error that the receiver was rejected with. @return A new pending promise to use instead of the rejected one that gets resolved with resolution returned from `recovery` block. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `recover` operators. Usage: promise.recover(^id(NSError *error) {...}) */ @interface FBLPromise(DotSyntax_RecoverAdditions) - (FBLPromise * (^)(FBLPromiseRecoverWorkBlock))recover FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRecoverWorkBlock))recoverOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Reduce.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ReduceAdditions) typedef id __nullable (^FBLPromiseReducerBlock)(Value __nullable partial, id next) NS_SWIFT_UNAVAILABLE(""); /** Sequentially reduces a collection of values to a single promise using a given combining block and the value `self` resolves with as initial value. @param items An array of values to process in order. @param reducer A block to combine an accumulating value and an element of the sequence into the new accumulating value or a promise resolved with it, to be used in the next call of the `reducer` or returned to the caller. @return A new pending promise returned from the last `reducer` invocation. Or `self` if `items` is empty. */ - (FBLPromise *)reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); /** Sequentially reduces a collection of values to a single promise using a given combining block and the value `self` resolves with as initial value. @param queue A queue to dispatch on. @param items An array of values to process in order. @param reducer A block to combine an accumulating value and an element of the sequence into the new accumulating value or a promise resolved with it, to be used in the next call of the `reducer` or returned to the caller. @return A new pending promise returned from the last `reducer` invocation. Or `self` if `items` is empty. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); @end /** Convenience dot-syntax wrappers for `FBLPromise` `reduce` operators. Usage: promise.reduce(values, ^id(id partial, id next) { ... }) */ @interface FBLPromise(DotSyntax_ReduceAdditions) - (FBLPromise * (^)(NSArray *, FBLPromiseReducerBlock))reduce FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, NSArray *, FBLPromiseReducerBlock))reduceOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Retry.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** The default number of retry attempts is 1. */ FOUNDATION_EXTERN NSInteger const FBLPromiseRetryDefaultAttemptsCount NS_REFINED_FOR_SWIFT; /** The default delay interval before making a retry attempt is 1.0 second. */ FOUNDATION_EXTERN NSTimeInterval const FBLPromiseRetryDefaultDelayInterval NS_REFINED_FOR_SWIFT; @interface FBLPromise(RetryAdditions) typedef id __nullable (^FBLPromiseRetryWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); typedef BOOL (^FBLPromiseRetryPredicateBlock)(NSInteger, NSError *) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on rejection where the `work` block is retried after a delay of `FBLPromiseRetryDefaultDelayInterval` second(s). @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on rejection where the `work` block is retried on the given `queue` after a delay of `FBLPromiseRetryDefaultDelayInterval` second(s). @param queue A queue to invoke the `work` block on. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. @param queue A queue to invoke the `work` block on. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. On rejection, the `work` block is retried after the given delay `interval` and will continue to retry until the number of specified attempts have been exhausted or will bail early if the given condition is not met. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param interval Time to wait before the next retry attempt. @param predicate Condition to check before the next retry attempt. The predicate block provides the the number of remaining retry attempts and the error that the promise was rejected with. @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted or if the given condition is not met. */ + (FBLPromise *)attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. On rejection, the `work` block is retried after the given delay `interval` and will continue to retry until the number of specified attempts have been exhausted or will bail early if the given condition is not met. @param queue A queue to invoke the `work` block on. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param interval Time to wait before the next retry attempt. @param predicate Condition to check before the next retry attempt. The predicate block provides the the number of remaining retry attempts and the error that the promise was rejected with. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted or if the given condition is not met. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise+Retry` operators. Usage: FBLPromise.retry(^id { ... }) */ @interface FBLPromise(DotSyntax_RetryAdditions) + (FBLPromise * (^)(FBLPromiseRetryWorkBlock))retry FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRetryWorkBlock))retryOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock __nullable, FBLPromiseRetryWorkBlock))retryAgain FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock __nullable, FBLPromiseRetryWorkBlock))retryAgainOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Testing.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Waits for all scheduled promises blocks. @param timeout Maximum time to wait. @return YES if all promises blocks have completed before the timeout and NO otherwise. */ FOUNDATION_EXTERN BOOL FBLWaitForPromisesWithTimeout(NSTimeInterval timeout) NS_REFINED_FOR_SWIFT; @interface FBLPromise(TestingAdditions) /** Dispatch group for promises that is typically used to wait for all scheduled blocks. */ @property(class, nonatomic, readonly) dispatch_group_t dispatchGroup NS_REFINED_FOR_SWIFT; /** Properties to get the current state of the promise. */ @property(nonatomic, readonly) BOOL isPending NS_REFINED_FOR_SWIFT; @property(nonatomic, readonly) BOOL isFulfilled NS_REFINED_FOR_SWIFT; @property(nonatomic, readonly) BOOL isRejected NS_REFINED_FOR_SWIFT; /** Value the promise was fulfilled with. Can be nil if the promise is still pending, was resolved with nil or after it has been rejected. */ @property(nonatomic, readonly, nullable) Value value NS_REFINED_FOR_SWIFT; /** Error the promise was rejected with. Can be nil if the promise is still pending or after it has been fulfilled. */ @property(nonatomic, readonly, nullable) NSError *error NS_REFINED_FOR_SWIFT; @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Then.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ThenAdditions) typedef id __nullable (^FBLPromiseThenWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with resolution returned from `work` block: either value, error or another promise. The `work` block is executed asynchronously only when the receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with the same error. @param work A block to handle the value that receiver was fulfilled with. @return A new pending promise to be resolved with resolution returned from the `work` block. */ - (FBLPromise *)then:(FBLPromiseThenWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with resolution returned from `work` block: either value, error or another promise. The `work` block is executed asynchronously when the receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with the same error. @param queue A queue to invoke the `work` block on. @param work A block to handle the value that receiver was fulfilled with. @return A new pending promise to be resolved with resolution returned from the `work` block. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `then` operators. Usage: promise.then(^id(id value) { ... }) */ @interface FBLPromise(DotSyntax_ThenAdditions) - (FBLPromise* (^)(FBLPromiseThenWorkBlock))then FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseThenWorkBlock))thenOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Timeout.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(TimeoutAdditions) /** Waits for a promise with the specified `timeout`. @param interval Time to wait in seconds. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)timeout:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); /** Waits for a promise with the specified `timeout`. @param queue A queue to dispatch on. @param interval Time to wait in seconds. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `timeout` operators. Usage: promise.timeout(...) */ @interface FBLPromise(DotSyntax_TimeoutAdditions) - (FBLPromise* (^)(NSTimeInterval))timeout FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, NSTimeInterval))timeoutOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Validate.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ValidateAdditions) typedef BOOL (^FBLPromiseValidateWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); /** Validates a fulfilled value or rejects the value if it can not be validated. @param predicate An expression to validate. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)validate:(FBLPromiseValidateWorkBlock)predicate NS_SWIFT_UNAVAILABLE(""); /** Validates a fulfilled value or rejects the value if it can not be validated. @param queue A queue to dispatch on. @param predicate An expression to validate. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `validate` operators. Usage: promise.validate(^BOOL(id value) { ... }) */ @interface FBLPromise(DotSyntax_ValidateAdditions) - (FBLPromise * (^)(FBLPromiseValidateWorkBlock))validate FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseValidateWorkBlock))validateOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise+Wrap.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Different types of completion handlers available to be wrapped with promise. */ typedef void (^FBLPromiseCompletion)(void) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseObjectCompletion)(id __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseErrorCompletion)(NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseObjectOrErrorCompletion)(id __nullable, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseErrorOrObjectCompletion)(NSError* __nullable, id __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromise2ObjectsOrErrorCompletion)(id __nullable, id __nullable, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseBoolCompletion)(BOOL) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseBoolOrErrorCompletion)(BOOL, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseIntegerCompletion)(NSInteger) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseIntegerOrErrorCompletion)(NSInteger, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseDoubleCompletion)(double) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseDoubleOrErrorCompletion)(double, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); /** Provides an easy way to convert methods that use common callback patterns into promises. */ @interface FBLPromise(WrapAdditions) /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with `nil` when completion handler is invoked. */ + (instancetype)wrapCompletion:(void (^)(FBLPromiseCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with `nil` when completion handler is invoked. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapCompletion:(void (^)(FBLPromiseCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler. */ + (instancetype)wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error provided by completion handler. If error is `nil`, fulfills with `nil`, otherwise rejects with the error. */ + (instancetype)wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error provided by completion handler. If error is `nil`, fulfills with `nil`, otherwise rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler if error is `nil`. Otherwise, rejects with the error. */ + (instancetype)wrapObjectOrErrorCompletion: (void (^)(FBLPromiseObjectOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler if error is `nil`. Otherwise, rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error or object provided by completion handler. If error is not `nil`, rejects with the error. */ + (instancetype)wrapErrorOrObjectCompletion: (void (^)(FBLPromiseErrorOrObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error or object provided by completion handler. If error is not `nil`, rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an array of objects provided by completion handler in order if error is `nil`. Otherwise, rejects with the error. */ + (FBLPromise*)wrap2ObjectsOrErrorCompletion: (void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an array of objects provided by completion handler in order if error is `nil`. Otherwise, rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrap2ObjectsOrErrorCompletion:(void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO. */ + (FBLPromise*)wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapBoolOrErrorCompletion: (void (^)(FBLPromiseBoolOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapBoolOrErrorCompletion:(void (^)(FBLPromiseBoolOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer. */ + (FBLPromise*)wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapIntegerOrErrorCompletion: (void (^)(FBLPromiseIntegerOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapIntegerOrErrorCompletion:(void (^)(FBLPromiseIntegerOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double. */ + (FBLPromise*)wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapDoubleOrErrorCompletion: (void (^)(FBLPromiseDoubleOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapDoubleOrErrorCompletion:(void (^)(FBLPromiseDoubleOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); @end /** Convenience dot-syntax wrappers for `FBLPromise` `wrap` operators. Usage: FBLPromise.wrapCompletion(^(FBLPromiseCompletion handler) {...}) */ @interface FBLPromise(DotSyntax_WrapAdditions) + (FBLPromise* (^)(void (^)(FBLPromiseCompletion)))wrapCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseCompletion)))wrapCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromise.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromiseError.h" NS_ASSUME_NONNULL_BEGIN /** Promises synchronization construct in Objective-C. */ @interface FBLPromise<__covariant Value> : NSObject /** Default dispatch queue used for `FBLPromise`, which is `main` if a queue is not specified. */ @property(class) dispatch_queue_t defaultDispatchQueue NS_REFINED_FOR_SWIFT; /** Creates a pending promise. */ + (instancetype)pendingPromise NS_REFINED_FOR_SWIFT; /** Creates a resolved promise. @param resolution An object to resolve the promise with: either a value or an error. @return A new resolved promise. */ + (instancetype)resolvedWith:(nullable id)resolution NS_REFINED_FOR_SWIFT; /** Synchronously fulfills the promise with a value. @param value An arbitrary value to fulfill the promise with, including `nil`. */ - (void)fulfill:(nullable Value)value NS_REFINED_FOR_SWIFT; /** Synchronously rejects the promise with an error. @param error An error to reject the promise with. */ - (void)reject:(NSError *)error NS_REFINED_FOR_SWIFT; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; @end @interface FBLPromise() /** Adds an object to the set of pending objects to keep strongly while the promise is pending. Used by the Swift wrappers to keep them alive until the underlying ObjC promise is resolved. @param object An object to add. */ - (void)addPendingObject:(id)object NS_REFINED_FOR_SWIFT; @end #ifdef FBL_PROMISES_DOT_SYNTAX_IS_DEPRECATED #define FBL_PROMISES_DOT_SYNTAX __attribute__((deprecated)) #else #define FBL_PROMISES_DOT_SYNTAX #endif @interface FBLPromise(DotSyntaxAdditions) /** Convenience dot-syntax wrappers for FBLPromise. Usage: FBLPromise.pending() FBLPromise.resolved(value) */ + (instancetype (^)(void))pending FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (instancetype (^)(id __nullable))resolved FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromiseError.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXTERN NSErrorDomain const FBLPromiseErrorDomain NS_REFINED_FOR_SWIFT; /** Possible error codes in `FBLPromiseErrorDomain`. */ typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) { /** Promise failed to resolve in time. */ FBLPromiseErrorCodeTimedOut = 1, /** Validation predicate returned false. */ FBLPromiseErrorCodeValidationFailure = 2, } NS_REFINED_FOR_SWIFT; NS_INLINE BOOL FBLPromiseErrorIsTimedOut(NSError *error) NS_SWIFT_UNAVAILABLE("") { return error.domain == FBLPromiseErrorDomain && error.code == FBLPromiseErrorCodeTimedOut; } NS_INLINE BOOL FBLPromiseErrorIsValidationFailure(NSError *error) NS_SWIFT_UNAVAILABLE("") { return error.domain == FBLPromiseErrorDomain && error.code == FBLPromiseErrorCodeValidationFailure; } NS_ASSUME_NONNULL_END ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/FBLPromises.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "FBLPromise+All.h" #import "FBLPromise+Always.h" #import "FBLPromise+Any.h" #import "FBLPromise+Async.h" #import "FBLPromise+Await.h" #import "FBLPromise+Catch.h" #import "FBLPromise+Delay.h" #import "FBLPromise+Do.h" #import "FBLPromise+Race.h" #import "FBLPromise+Recover.h" #import "FBLPromise+Reduce.h" #import "FBLPromise+Retry.h" #import "FBLPromise+Then.h" #import "FBLPromise+Timeout.h" #import "FBLPromise+Validate.h" #import "FBLPromise+Wrap.h" ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Headers/PromisesObjC-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FBLPromise+All.h" #import "FBLPromise+Always.h" #import "FBLPromise+Any.h" #import "FBLPromise+Async.h" #import "FBLPromise+Await.h" #import "FBLPromise+Catch.h" #import "FBLPromise+Delay.h" #import "FBLPromise+Do.h" #import "FBLPromise+Race.h" #import "FBLPromise+Recover.h" #import "FBLPromise+Reduce.h" #import "FBLPromise+Retry.h" #import "FBLPromise+Testing.h" #import "FBLPromise+Then.h" #import "FBLPromise+Timeout.h" #import "FBLPromise+Validate.h" #import "FBLPromise+Wrap.h" #import "FBLPromise.h" #import "FBLPromiseError.h" #import "FBLPromises.h" FOUNDATION_EXPORT double FBLPromisesVersionNumber; FOUNDATION_EXPORT const unsigned char FBLPromisesVersionString[]; ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Info.plist ================================================ CFBundleExecutable PromisesObjC CFBundleIdentifier com.firebase.Firebase-PromisesObjC CFBundleInfoDictionaryVersion 6.0 CFBundleName PromisesObjC CFBundlePackageType FMWK CFBundleVersion 1.2.12 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/PromisesObjC.framework/Modules/module.modulemap ================================================ framework module PromisesObjC { umbrella header "PromisesObjC-umbrella.h" export * module * { export * } } ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/c.h ================================================ /* Copyright (c) 2011 The LevelDB Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. See the AUTHORS file for names of contributors. C bindings for leveldb. May be useful as a stable ABI that can be used by programs that keep leveldb in a shared library, or for a JNI api. Does not support: . getters for the option types . custom comparators that implement key shortening . custom iter, db, env, cache implementations using just the C bindings Some conventions: (1) We expose just opaque struct pointers and functions to clients. This allows us to change internal representations without having to recompile clients. (2) For simplicity, there is no equivalent to the Slice type. Instead, the caller has to pass the pointer and length as separate arguments. (3) Errors are represented by a null-terminated c string. NULL means no error. All operations that can raise an error are passed a "char** errptr" as the last argument. One of the following must be true on entry: *errptr == NULL *errptr points to a malloc()ed null-terminated error message (On Windows, *errptr must have been malloc()-ed by this library.) On success, a leveldb routine leaves *errptr unchanged. On failure, leveldb frees the old value of *errptr and set *errptr to a malloc()ed error message. (4) Bools have the type unsigned char (0 == false; rest == true) (5) All of the pointer arguments must be non-NULL. */ #ifndef STORAGE_LEVELDB_INCLUDE_C_H_ #define STORAGE_LEVELDB_INCLUDE_C_H_ #ifdef __cplusplus extern "C" { #endif #include #include #include #include "leveldb/export.h" /* Exported types */ typedef struct leveldb_t leveldb_t; typedef struct leveldb_cache_t leveldb_cache_t; typedef struct leveldb_comparator_t leveldb_comparator_t; typedef struct leveldb_env_t leveldb_env_t; typedef struct leveldb_filelock_t leveldb_filelock_t; typedef struct leveldb_filterpolicy_t leveldb_filterpolicy_t; typedef struct leveldb_iterator_t leveldb_iterator_t; typedef struct leveldb_logger_t leveldb_logger_t; typedef struct leveldb_options_t leveldb_options_t; typedef struct leveldb_randomfile_t leveldb_randomfile_t; typedef struct leveldb_readoptions_t leveldb_readoptions_t; typedef struct leveldb_seqfile_t leveldb_seqfile_t; typedef struct leveldb_snapshot_t leveldb_snapshot_t; typedef struct leveldb_writablefile_t leveldb_writablefile_t; typedef struct leveldb_writebatch_t leveldb_writebatch_t; typedef struct leveldb_writeoptions_t leveldb_writeoptions_t; /* DB operations */ LEVELDB_EXPORT leveldb_t* leveldb_open(const leveldb_options_t* options, const char* name, char** errptr); LEVELDB_EXPORT void leveldb_close(leveldb_t* db); LEVELDB_EXPORT void leveldb_put(leveldb_t* db, const leveldb_writeoptions_t* options, const char* key, size_t keylen, const char* val, size_t vallen, char** errptr); LEVELDB_EXPORT void leveldb_delete(leveldb_t* db, const leveldb_writeoptions_t* options, const char* key, size_t keylen, char** errptr); LEVELDB_EXPORT void leveldb_write(leveldb_t* db, const leveldb_writeoptions_t* options, leveldb_writebatch_t* batch, char** errptr); /* Returns NULL if not found. A malloc()ed array otherwise. Stores the length of the array in *vallen. */ LEVELDB_EXPORT char* leveldb_get(leveldb_t* db, const leveldb_readoptions_t* options, const char* key, size_t keylen, size_t* vallen, char** errptr); LEVELDB_EXPORT leveldb_iterator_t* leveldb_create_iterator( leveldb_t* db, const leveldb_readoptions_t* options); LEVELDB_EXPORT const leveldb_snapshot_t* leveldb_create_snapshot(leveldb_t* db); LEVELDB_EXPORT void leveldb_release_snapshot( leveldb_t* db, const leveldb_snapshot_t* snapshot); /* Returns NULL if property name is unknown. Else returns a pointer to a malloc()-ed null-terminated value. */ LEVELDB_EXPORT char* leveldb_property_value(leveldb_t* db, const char* propname); LEVELDB_EXPORT void leveldb_approximate_sizes( leveldb_t* db, int num_ranges, const char* const* range_start_key, const size_t* range_start_key_len, const char* const* range_limit_key, const size_t* range_limit_key_len, uint64_t* sizes); LEVELDB_EXPORT void leveldb_compact_range(leveldb_t* db, const char* start_key, size_t start_key_len, const char* limit_key, size_t limit_key_len); /* Management operations */ LEVELDB_EXPORT void leveldb_destroy_db(const leveldb_options_t* options, const char* name, char** errptr); LEVELDB_EXPORT void leveldb_repair_db(const leveldb_options_t* options, const char* name, char** errptr); /* Iterator */ LEVELDB_EXPORT void leveldb_iter_destroy(leveldb_iterator_t*); LEVELDB_EXPORT unsigned char leveldb_iter_valid(const leveldb_iterator_t*); LEVELDB_EXPORT void leveldb_iter_seek_to_first(leveldb_iterator_t*); LEVELDB_EXPORT void leveldb_iter_seek_to_last(leveldb_iterator_t*); LEVELDB_EXPORT void leveldb_iter_seek(leveldb_iterator_t*, const char* k, size_t klen); LEVELDB_EXPORT void leveldb_iter_next(leveldb_iterator_t*); LEVELDB_EXPORT void leveldb_iter_prev(leveldb_iterator_t*); LEVELDB_EXPORT const char* leveldb_iter_key(const leveldb_iterator_t*, size_t* klen); LEVELDB_EXPORT const char* leveldb_iter_value(const leveldb_iterator_t*, size_t* vlen); LEVELDB_EXPORT void leveldb_iter_get_error(const leveldb_iterator_t*, char** errptr); /* Write batch */ LEVELDB_EXPORT leveldb_writebatch_t* leveldb_writebatch_create(); LEVELDB_EXPORT void leveldb_writebatch_destroy(leveldb_writebatch_t*); LEVELDB_EXPORT void leveldb_writebatch_clear(leveldb_writebatch_t*); LEVELDB_EXPORT void leveldb_writebatch_put(leveldb_writebatch_t*, const char* key, size_t klen, const char* val, size_t vlen); LEVELDB_EXPORT void leveldb_writebatch_delete(leveldb_writebatch_t*, const char* key, size_t klen); LEVELDB_EXPORT void leveldb_writebatch_iterate( const leveldb_writebatch_t*, void* state, void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen), void (*deleted)(void*, const char* k, size_t klen)); LEVELDB_EXPORT void leveldb_writebatch_append( leveldb_writebatch_t* destination, const leveldb_writebatch_t* source); /* Options */ LEVELDB_EXPORT leveldb_options_t* leveldb_options_create(); LEVELDB_EXPORT void leveldb_options_destroy(leveldb_options_t*); LEVELDB_EXPORT void leveldb_options_set_comparator(leveldb_options_t*, leveldb_comparator_t*); LEVELDB_EXPORT void leveldb_options_set_filter_policy(leveldb_options_t*, leveldb_filterpolicy_t*); LEVELDB_EXPORT void leveldb_options_set_create_if_missing(leveldb_options_t*, unsigned char); LEVELDB_EXPORT void leveldb_options_set_error_if_exists(leveldb_options_t*, unsigned char); LEVELDB_EXPORT void leveldb_options_set_paranoid_checks(leveldb_options_t*, unsigned char); LEVELDB_EXPORT void leveldb_options_set_env(leveldb_options_t*, leveldb_env_t*); LEVELDB_EXPORT void leveldb_options_set_info_log(leveldb_options_t*, leveldb_logger_t*); LEVELDB_EXPORT void leveldb_options_set_write_buffer_size(leveldb_options_t*, size_t); LEVELDB_EXPORT void leveldb_options_set_max_open_files(leveldb_options_t*, int); LEVELDB_EXPORT void leveldb_options_set_cache(leveldb_options_t*, leveldb_cache_t*); LEVELDB_EXPORT void leveldb_options_set_block_size(leveldb_options_t*, size_t); LEVELDB_EXPORT void leveldb_options_set_block_restart_interval( leveldb_options_t*, int); LEVELDB_EXPORT void leveldb_options_set_max_file_size(leveldb_options_t*, size_t); enum { leveldb_no_compression = 0, leveldb_snappy_compression = 1 }; LEVELDB_EXPORT void leveldb_options_set_compression(leveldb_options_t*, int); /* Comparator */ LEVELDB_EXPORT leveldb_comparator_t* leveldb_comparator_create( void* state, void (*destructor)(void*), int (*compare)(void*, const char* a, size_t alen, const char* b, size_t blen), const char* (*name)(void*)); LEVELDB_EXPORT void leveldb_comparator_destroy(leveldb_comparator_t*); /* Filter policy */ LEVELDB_EXPORT leveldb_filterpolicy_t* leveldb_filterpolicy_create( void* state, void (*destructor)(void*), char* (*create_filter)(void*, const char* const* key_array, const size_t* key_length_array, int num_keys, size_t* filter_length), unsigned char (*key_may_match)(void*, const char* key, size_t length, const char* filter, size_t filter_length), const char* (*name)(void*)); LEVELDB_EXPORT void leveldb_filterpolicy_destroy(leveldb_filterpolicy_t*); LEVELDB_EXPORT leveldb_filterpolicy_t* leveldb_filterpolicy_create_bloom( int bits_per_key); /* Read options */ LEVELDB_EXPORT leveldb_readoptions_t* leveldb_readoptions_create(); LEVELDB_EXPORT void leveldb_readoptions_destroy(leveldb_readoptions_t*); LEVELDB_EXPORT void leveldb_readoptions_set_verify_checksums( leveldb_readoptions_t*, unsigned char); LEVELDB_EXPORT void leveldb_readoptions_set_fill_cache(leveldb_readoptions_t*, unsigned char); LEVELDB_EXPORT void leveldb_readoptions_set_snapshot(leveldb_readoptions_t*, const leveldb_snapshot_t*); /* Write options */ LEVELDB_EXPORT leveldb_writeoptions_t* leveldb_writeoptions_create(); LEVELDB_EXPORT void leveldb_writeoptions_destroy(leveldb_writeoptions_t*); LEVELDB_EXPORT void leveldb_writeoptions_set_sync(leveldb_writeoptions_t*, unsigned char); /* Cache */ LEVELDB_EXPORT leveldb_cache_t* leveldb_cache_create_lru(size_t capacity); LEVELDB_EXPORT void leveldb_cache_destroy(leveldb_cache_t* cache); /* Env */ LEVELDB_EXPORT leveldb_env_t* leveldb_create_default_env(); LEVELDB_EXPORT void leveldb_env_destroy(leveldb_env_t*); /* If not NULL, the returned buffer must be released using leveldb_free(). */ LEVELDB_EXPORT char* leveldb_env_get_test_directory(leveldb_env_t*); /* Utility */ /* Calls free(ptr). REQUIRES: ptr was malloc()-ed and returned by one of the routines in this file. Note that in certain cases (typically on Windows), you may need to call this routine instead of free(ptr) to dispose of malloc()-ed memory returned by this library. */ LEVELDB_EXPORT void leveldb_free(void* ptr); /* Return the major version number for this release. */ LEVELDB_EXPORT int leveldb_major_version(); /* Return the minor version number for this release. */ LEVELDB_EXPORT int leveldb_minor_version(); #ifdef __cplusplus } /* end extern "C" */ #endif #endif /* STORAGE_LEVELDB_INCLUDE_C_H_ */ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/cache.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // A Cache is an interface that maps keys to values. It has internal // synchronization and may be safely accessed concurrently from // multiple threads. It may automatically evict entries to make room // for new entries. Values have a specified charge against the cache // capacity. For example, a cache where the values are variable // length strings, may use the length of the string as the charge for // the string. // // A builtin cache implementation with a least-recently-used eviction // policy is provided. Clients may use their own implementations if // they want something more sophisticated (like scan-resistance, a // custom eviction policy, variable cache sizing, etc.) #ifndef STORAGE_LEVELDB_INCLUDE_CACHE_H_ #define STORAGE_LEVELDB_INCLUDE_CACHE_H_ #include #include "leveldb/export.h" #include "leveldb/slice.h" namespace leveldb { class LEVELDB_EXPORT Cache; // Create a new cache with a fixed size capacity. This implementation // of Cache uses a least-recently-used eviction policy. LEVELDB_EXPORT Cache* NewLRUCache(size_t capacity); class LEVELDB_EXPORT Cache { public: Cache() = default; Cache(const Cache&) = delete; Cache& operator=(const Cache&) = delete; // Destroys all existing entries by calling the "deleter" // function that was passed to the constructor. virtual ~Cache(); // Opaque handle to an entry stored in the cache. struct Handle {}; // Insert a mapping from key->value into the cache and assign it // the specified charge against the total cache capacity. // // Returns a handle that corresponds to the mapping. The caller // must call this->Release(handle) when the returned mapping is no // longer needed. // // When the inserted entry is no longer needed, the key and // value will be passed to "deleter". virtual Handle* Insert(const Slice& key, void* value, size_t charge, void (*deleter)(const Slice& key, void* value)) = 0; // If the cache has no mapping for "key", returns nullptr. // // Else return a handle that corresponds to the mapping. The caller // must call this->Release(handle) when the returned mapping is no // longer needed. virtual Handle* Lookup(const Slice& key) = 0; // Release a mapping returned by a previous Lookup(). // REQUIRES: handle must not have been released yet. // REQUIRES: handle must have been returned by a method on *this. virtual void Release(Handle* handle) = 0; // Return the value encapsulated in a handle returned by a // successful Lookup(). // REQUIRES: handle must not have been released yet. // REQUIRES: handle must have been returned by a method on *this. virtual void* Value(Handle* handle) = 0; // If the cache contains entry for key, erase it. Note that the // underlying entry will be kept around until all existing handles // to it have been released. virtual void Erase(const Slice& key) = 0; // Return a new numeric id. May be used by multiple clients who are // sharing the same cache to partition the key space. Typically the // client will allocate a new id at startup and prepend the id to // its cache keys. virtual uint64_t NewId() = 0; // Remove all cache entries that are not actively in use. Memory-constrained // applications may wish to call this method to reduce memory usage. // Default implementation of Prune() does nothing. Subclasses are strongly // encouraged to override the default implementation. A future release of // leveldb may change Prune() to a pure abstract method. virtual void Prune() {} // Return an estimate of the combined charges of all elements stored in the // cache. virtual size_t TotalCharge() const = 0; private: void LRU_Remove(Handle* e); void LRU_Append(Handle* e); void Unref(Handle* e); struct Rep; Rep* rep_; }; } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_CACHE_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/comparator.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ #define STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ #include #include "leveldb/export.h" namespace leveldb { class Slice; // A Comparator object provides a total order across slices that are // used as keys in an sstable or a database. A Comparator implementation // must be thread-safe since leveldb may invoke its methods concurrently // from multiple threads. class LEVELDB_EXPORT Comparator { public: virtual ~Comparator(); // Three-way comparison. Returns value: // < 0 iff "a" < "b", // == 0 iff "a" == "b", // > 0 iff "a" > "b" virtual int Compare(const Slice& a, const Slice& b) const = 0; // The name of the comparator. Used to check for comparator // mismatches (i.e., a DB created with one comparator is // accessed using a different comparator. // // The client of this package should switch to a new name whenever // the comparator implementation changes in a way that will cause // the relative ordering of any two keys to change. // // Names starting with "leveldb." are reserved and should not be used // by any clients of this package. virtual const char* Name() const = 0; // Advanced functions: these are used to reduce the space requirements // for internal data structures like index blocks. // If *start < limit, changes *start to a short string in [start,limit). // Simple comparator implementations may return with *start unchanged, // i.e., an implementation of this method that does nothing is correct. virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0; // Changes *key to a short string >= *key. // Simple comparator implementations may return with *key unchanged, // i.e., an implementation of this method that does nothing is correct. virtual void FindShortSuccessor(std::string* key) const = 0; }; // Return a builtin comparator that uses lexicographic byte-wise // ordering. The result remains the property of this module and // must not be deleted. LEVELDB_EXPORT const Comparator* BytewiseComparator(); } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_COMPARATOR_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/db.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_DB_H_ #define STORAGE_LEVELDB_INCLUDE_DB_H_ #include #include #include "leveldb/export.h" #include "leveldb/iterator.h" #include "leveldb/options.h" namespace leveldb { // Update CMakeLists.txt if you change these static const int kMajorVersion = 1; static const int kMinorVersion = 22; struct Options; struct ReadOptions; struct WriteOptions; class WriteBatch; // Abstract handle to particular state of a DB. // A Snapshot is an immutable object and can therefore be safely // accessed from multiple threads without any external synchronization. class LEVELDB_EXPORT Snapshot { protected: virtual ~Snapshot(); }; // A range of keys struct LEVELDB_EXPORT Range { Range() {} Range(const Slice& s, const Slice& l) : start(s), limit(l) {} Slice start; // Included in the range Slice limit; // Not included in the range }; // A DB is a persistent ordered map from keys to values. // A DB is safe for concurrent access from multiple threads without // any external synchronization. class LEVELDB_EXPORT DB { public: // Open the database with the specified "name". // Stores a pointer to a heap-allocated database in *dbptr and returns // OK on success. // Stores nullptr in *dbptr and returns a non-OK status on error. // Caller should delete *dbptr when it is no longer needed. static Status Open(const Options& options, const std::string& name, DB** dbptr); DB() = default; DB(const DB&) = delete; DB& operator=(const DB&) = delete; virtual ~DB(); // Set the database entry for "key" to "value". Returns OK on success, // and a non-OK status on error. // Note: consider setting options.sync = true. virtual Status Put(const WriteOptions& options, const Slice& key, const Slice& value) = 0; // Remove the database entry (if any) for "key". Returns OK on // success, and a non-OK status on error. It is not an error if "key" // did not exist in the database. // Note: consider setting options.sync = true. virtual Status Delete(const WriteOptions& options, const Slice& key) = 0; // Apply the specified updates to the database. // Returns OK on success, non-OK on failure. // Note: consider setting options.sync = true. virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0; // If the database contains an entry for "key" store the // corresponding value in *value and return OK. // // If there is no entry for "key" leave *value unchanged and return // a status for which Status::IsNotFound() returns true. // // May return some other Status on an error. virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0; // Return a heap-allocated iterator over the contents of the database. // The result of NewIterator() is initially invalid (caller must // call one of the Seek methods on the iterator before using it). // // Caller should delete the iterator when it is no longer needed. // The returned iterator should be deleted before this db is deleted. virtual Iterator* NewIterator(const ReadOptions& options) = 0; // Return a handle to the current DB state. Iterators created with // this handle will all observe a stable snapshot of the current DB // state. The caller must call ReleaseSnapshot(result) when the // snapshot is no longer needed. virtual const Snapshot* GetSnapshot() = 0; // Release a previously acquired snapshot. The caller must not // use "snapshot" after this call. virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0; // DB implementations can export properties about their state // via this method. If "property" is a valid property understood by this // DB implementation, fills "*value" with its current value and returns // true. Otherwise returns false. // // // Valid property names include: // // "leveldb.num-files-at-level" - return the number of files at level , // where is an ASCII representation of a level number (e.g. "0"). // "leveldb.stats" - returns a multi-line string that describes statistics // about the internal operation of the DB. // "leveldb.sstables" - returns a multi-line string that describes all // of the sstables that make up the db contents. // "leveldb.approximate-memory-usage" - returns the approximate number of // bytes of memory in use by the DB. virtual bool GetProperty(const Slice& property, std::string* value) = 0; // For each i in [0,n-1], store in "sizes[i]", the approximate // file system space used by keys in "[range[i].start .. range[i].limit)". // // Note that the returned sizes measure file system space usage, so // if the user data compresses by a factor of ten, the returned // sizes will be one-tenth the size of the corresponding user data size. // // The results may not include the sizes of recently written data. virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes) = 0; // Compact the underlying storage for the key range [*begin,*end]. // In particular, deleted and overwritten versions are discarded, // and the data is rearranged to reduce the cost of operations // needed to access the data. This operation should typically only // be invoked by users who understand the underlying implementation. // // begin==nullptr is treated as a key before all keys in the database. // end==nullptr is treated as a key after all keys in the database. // Therefore the following call will compact the entire database: // db->CompactRange(nullptr, nullptr); virtual void CompactRange(const Slice* begin, const Slice* end) = 0; }; // Destroy the contents of the specified database. // Be very careful using this method. // // Note: For backwards compatibility, if DestroyDB is unable to list the // database files, Status::OK() will still be returned masking this failure. LEVELDB_EXPORT Status DestroyDB(const std::string& name, const Options& options); // If a DB cannot be opened, you may attempt to call this method to // resurrect as much of the contents of the database as possible. // Some data may be lost, so be careful when calling this function // on a database that contains important information. LEVELDB_EXPORT Status RepairDB(const std::string& dbname, const Options& options); } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_DB_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/dumpfile.h ================================================ // Copyright (c) 2014 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_DUMPFILE_H_ #define STORAGE_LEVELDB_INCLUDE_DUMPFILE_H_ #include #include "leveldb/env.h" #include "leveldb/export.h" #include "leveldb/status.h" namespace leveldb { // Dump the contents of the file named by fname in text format to // *dst. Makes a sequence of dst->Append() calls; each call is passed // the newline-terminated text corresponding to a single item found // in the file. // // Returns a non-OK result if fname does not name a leveldb storage // file, or if the file cannot be read. LEVELDB_EXPORT Status DumpFile(Env* env, const std::string& fname, WritableFile* dst); } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_DUMPFILE_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/env.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // An Env is an interface used by the leveldb implementation to access // operating system functionality like the filesystem etc. Callers // may wish to provide a custom Env object when opening a database to // get fine gain control; e.g., to rate limit file system operations. // // All Env implementations are safe for concurrent access from // multiple threads without any external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_ENV_H_ #define STORAGE_LEVELDB_INCLUDE_ENV_H_ #include #include #include #include #include "leveldb/export.h" #include "leveldb/status.h" #if defined(_WIN32) // The leveldb::Env class below contains a DeleteFile method. // At the same time, , a fairly popular header // file for Windows applications, defines a DeleteFile macro. // // Without any intervention on our part, the result of this // unfortunate coincidence is that the name of the // leveldb::Env::DeleteFile method seen by the compiler depends on // whether was included before or after the LevelDB // headers. // // To avoid headaches, we undefined DeleteFile (if defined) and // redefine it at the bottom of this file. This way // can be included before this file (or not at all) and the // exported method will always be leveldb::Env::DeleteFile. #if defined(DeleteFile) #undef DeleteFile #define LEVELDB_DELETEFILE_UNDEFINED #endif // defined(DeleteFile) #endif // defined(_WIN32) namespace leveldb { class FileLock; class Logger; class RandomAccessFile; class SequentialFile; class Slice; class WritableFile; class LEVELDB_EXPORT Env { public: Env() = default; Env(const Env&) = delete; Env& operator=(const Env&) = delete; virtual ~Env(); // Return a default environment suitable for the current operating // system. Sophisticated users may wish to provide their own Env // implementation instead of relying on this default environment. // // The result of Default() belongs to leveldb and must never be deleted. static Env* Default(); // Create an object that sequentially reads the file with the specified name. // On success, stores a pointer to the new file in *result and returns OK. // On failure stores nullptr in *result and returns non-OK. If the file does // not exist, returns a non-OK status. Implementations should return a // NotFound status when the file does not exist. // // The returned file will only be accessed by one thread at a time. virtual Status NewSequentialFile(const std::string& fname, SequentialFile** result) = 0; // Create an object supporting random-access reads from the file with the // specified name. On success, stores a pointer to the new file in // *result and returns OK. On failure stores nullptr in *result and // returns non-OK. If the file does not exist, returns a non-OK // status. Implementations should return a NotFound status when the file does // not exist. // // The returned file may be concurrently accessed by multiple threads. virtual Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) = 0; // Create an object that writes to a new file with the specified // name. Deletes any existing file with the same name and creates a // new file. On success, stores a pointer to the new file in // *result and returns OK. On failure stores nullptr in *result and // returns non-OK. // // The returned file will only be accessed by one thread at a time. virtual Status NewWritableFile(const std::string& fname, WritableFile** result) = 0; // Create an object that either appends to an existing file, or // writes to a new file (if the file does not exist to begin with). // On success, stores a pointer to the new file in *result and // returns OK. On failure stores nullptr in *result and returns // non-OK. // // The returned file will only be accessed by one thread at a time. // // May return an IsNotSupportedError error if this Env does // not allow appending to an existing file. Users of Env (including // the leveldb implementation) must be prepared to deal with // an Env that does not support appending. virtual Status NewAppendableFile(const std::string& fname, WritableFile** result); // Returns true iff the named file exists. virtual bool FileExists(const std::string& fname) = 0; // Store in *result the names of the children of the specified directory. // The names are relative to "dir". // Original contents of *results are dropped. virtual Status GetChildren(const std::string& dir, std::vector* result) = 0; // Delete the named file. virtual Status DeleteFile(const std::string& fname) = 0; // Create the specified directory. virtual Status CreateDir(const std::string& dirname) = 0; // Delete the specified directory. virtual Status DeleteDir(const std::string& dirname) = 0; // Store the size of fname in *file_size. virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) = 0; // Rename file src to target. virtual Status RenameFile(const std::string& src, const std::string& target) = 0; // Lock the specified file. Used to prevent concurrent access to // the same db by multiple processes. On failure, stores nullptr in // *lock and returns non-OK. // // On success, stores a pointer to the object that represents the // acquired lock in *lock and returns OK. The caller should call // UnlockFile(*lock) to release the lock. If the process exits, // the lock will be automatically released. // // If somebody else already holds the lock, finishes immediately // with a failure. I.e., this call does not wait for existing locks // to go away. // // May create the named file if it does not already exist. virtual Status LockFile(const std::string& fname, FileLock** lock) = 0; // Release the lock acquired by a previous successful call to LockFile. // REQUIRES: lock was returned by a successful LockFile() call // REQUIRES: lock has not already been unlocked. virtual Status UnlockFile(FileLock* lock) = 0; // Arrange to run "(*function)(arg)" once in a background thread. // // "function" may run in an unspecified thread. Multiple functions // added to the same Env may run concurrently in different threads. // I.e., the caller may not assume that background work items are // serialized. virtual void Schedule(void (*function)(void* arg), void* arg) = 0; // Start a new thread, invoking "function(arg)" within the new thread. // When "function(arg)" returns, the thread will be destroyed. virtual void StartThread(void (*function)(void* arg), void* arg) = 0; // *path is set to a temporary directory that can be used for testing. It may // or may not have just been created. The directory may or may not differ // between runs of the same process, but subsequent calls will return the // same directory. virtual Status GetTestDirectory(std::string* path) = 0; // Create and return a log file for storing informational messages. virtual Status NewLogger(const std::string& fname, Logger** result) = 0; // Returns the number of micro-seconds since some fixed point in time. Only // useful for computing deltas of time. virtual uint64_t NowMicros() = 0; // Sleep/delay the thread for the prescribed number of micro-seconds. virtual void SleepForMicroseconds(int micros) = 0; }; // A file abstraction for reading sequentially through a file class LEVELDB_EXPORT SequentialFile { public: SequentialFile() = default; SequentialFile(const SequentialFile&) = delete; SequentialFile& operator=(const SequentialFile&) = delete; virtual ~SequentialFile(); // Read up to "n" bytes from the file. "scratch[0..n-1]" may be // written by this routine. Sets "*result" to the data that was // read (including if fewer than "n" bytes were successfully read). // May set "*result" to point at data in "scratch[0..n-1]", so // "scratch[0..n-1]" must be live when "*result" is used. // If an error was encountered, returns a non-OK status. // // REQUIRES: External synchronization virtual Status Read(size_t n, Slice* result, char* scratch) = 0; // Skip "n" bytes from the file. This is guaranteed to be no // slower that reading the same data, but may be faster. // // If end of file is reached, skipping will stop at the end of the // file, and Skip will return OK. // // REQUIRES: External synchronization virtual Status Skip(uint64_t n) = 0; }; // A file abstraction for randomly reading the contents of a file. class LEVELDB_EXPORT RandomAccessFile { public: RandomAccessFile() = default; RandomAccessFile(const RandomAccessFile&) = delete; RandomAccessFile& operator=(const RandomAccessFile&) = delete; virtual ~RandomAccessFile(); // Read up to "n" bytes from the file starting at "offset". // "scratch[0..n-1]" may be written by this routine. Sets "*result" // to the data that was read (including if fewer than "n" bytes were // successfully read). May set "*result" to point at data in // "scratch[0..n-1]", so "scratch[0..n-1]" must be live when // "*result" is used. If an error was encountered, returns a non-OK // status. // // Safe for concurrent use by multiple threads. virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const = 0; }; // A file abstraction for sequential writing. The implementation // must provide buffering since callers may append small fragments // at a time to the file. class LEVELDB_EXPORT WritableFile { public: WritableFile() = default; WritableFile(const WritableFile&) = delete; WritableFile& operator=(const WritableFile&) = delete; virtual ~WritableFile(); virtual Status Append(const Slice& data) = 0; virtual Status Close() = 0; virtual Status Flush() = 0; virtual Status Sync() = 0; }; // An interface for writing log messages. class LEVELDB_EXPORT Logger { public: Logger() = default; Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; virtual ~Logger(); // Write an entry to the log file with the specified format. virtual void Logv(const char* format, va_list ap) = 0; }; // Identifies a locked file. class LEVELDB_EXPORT FileLock { public: FileLock() = default; FileLock(const FileLock&) = delete; FileLock& operator=(const FileLock&) = delete; virtual ~FileLock(); }; // Log the specified data to *info_log if info_log is non-null. void Log(Logger* info_log, const char* format, ...) #if defined(__GNUC__) || defined(__clang__) __attribute__((__format__(__printf__, 2, 3))) #endif ; // A utility routine: write "data" to the named file. LEVELDB_EXPORT Status WriteStringToFile(Env* env, const Slice& data, const std::string& fname); // A utility routine: read contents of named file into *data LEVELDB_EXPORT Status ReadFileToString(Env* env, const std::string& fname, std::string* data); // An implementation of Env that forwards all calls to another Env. // May be useful to clients who wish to override just part of the // functionality of another Env. class LEVELDB_EXPORT EnvWrapper : public Env { public: // Initialize an EnvWrapper that delegates all calls to *t. explicit EnvWrapper(Env* t) : target_(t) {} virtual ~EnvWrapper(); // Return the target to which this Env forwards all calls. Env* target() const { return target_; } // The following text is boilerplate that forwards all methods to target(). Status NewSequentialFile(const std::string& f, SequentialFile** r) override { return target_->NewSequentialFile(f, r); } Status NewRandomAccessFile(const std::string& f, RandomAccessFile** r) override { return target_->NewRandomAccessFile(f, r); } Status NewWritableFile(const std::string& f, WritableFile** r) override { return target_->NewWritableFile(f, r); } Status NewAppendableFile(const std::string& f, WritableFile** r) override { return target_->NewAppendableFile(f, r); } bool FileExists(const std::string& f) override { return target_->FileExists(f); } Status GetChildren(const std::string& dir, std::vector* r) override { return target_->GetChildren(dir, r); } Status DeleteFile(const std::string& f) override { return target_->DeleteFile(f); } Status CreateDir(const std::string& d) override { return target_->CreateDir(d); } Status DeleteDir(const std::string& d) override { return target_->DeleteDir(d); } Status GetFileSize(const std::string& f, uint64_t* s) override { return target_->GetFileSize(f, s); } Status RenameFile(const std::string& s, const std::string& t) override { return target_->RenameFile(s, t); } Status LockFile(const std::string& f, FileLock** l) override { return target_->LockFile(f, l); } Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); } void Schedule(void (*f)(void*), void* a) override { return target_->Schedule(f, a); } void StartThread(void (*f)(void*), void* a) override { return target_->StartThread(f, a); } Status GetTestDirectory(std::string* path) override { return target_->GetTestDirectory(path); } Status NewLogger(const std::string& fname, Logger** result) override { return target_->NewLogger(fname, result); } uint64_t NowMicros() override { return target_->NowMicros(); } void SleepForMicroseconds(int micros) override { target_->SleepForMicroseconds(micros); } private: Env* target_; }; } // namespace leveldb // Redefine DeleteFile if necessary. #if defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED) #if defined(UNICODE) #define DeleteFile DeleteFileW #else #define DeleteFile DeleteFileA #endif // defined(UNICODE) #endif // defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED) #endif // STORAGE_LEVELDB_INCLUDE_ENV_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/export.h ================================================ // Copyright (c) 2017 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_EXPORT_H_ #define STORAGE_LEVELDB_INCLUDE_EXPORT_H_ #if !defined(LEVELDB_EXPORT) #if defined(LEVELDB_SHARED_LIBRARY) #if defined(_WIN32) #if defined(LEVELDB_COMPILE_LIBRARY) #define LEVELDB_EXPORT __declspec(dllexport) #else #define LEVELDB_EXPORT __declspec(dllimport) #endif // defined(LEVELDB_COMPILE_LIBRARY) #else // defined(_WIN32) #if defined(LEVELDB_COMPILE_LIBRARY) #define LEVELDB_EXPORT __attribute__((visibility("default"))) #else #define LEVELDB_EXPORT #endif #endif // defined(_WIN32) #else // defined(LEVELDB_SHARED_LIBRARY) #define LEVELDB_EXPORT #endif #endif // !defined(LEVELDB_EXPORT) #endif // STORAGE_LEVELDB_INCLUDE_EXPORT_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/filter_policy.h ================================================ // Copyright (c) 2012 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // A database can be configured with a custom FilterPolicy object. // This object is responsible for creating a small filter from a set // of keys. These filters are stored in leveldb and are consulted // automatically by leveldb to decide whether or not to read some // information from disk. In many cases, a filter can cut down the // number of disk seeks form a handful to a single disk seek per // DB::Get() call. // // Most people will want to use the builtin bloom filter support (see // NewBloomFilterPolicy() below). #ifndef STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ #define STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ #include #include "leveldb/export.h" namespace leveldb { class Slice; class LEVELDB_EXPORT FilterPolicy { public: virtual ~FilterPolicy(); // Return the name of this policy. Note that if the filter encoding // changes in an incompatible way, the name returned by this method // must be changed. Otherwise, old incompatible filters may be // passed to methods of this type. virtual const char* Name() const = 0; // keys[0,n-1] contains a list of keys (potentially with duplicates) // that are ordered according to the user supplied comparator. // Append a filter that summarizes keys[0,n-1] to *dst. // // Warning: do not change the initial contents of *dst. Instead, // append the newly constructed filter to *dst. virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const = 0; // "filter" contains the data appended by a preceding call to // CreateFilter() on this class. This method must return true if // the key was in the list of keys passed to CreateFilter(). // This method may return true or false if the key was not on the // list, but it should aim to return false with a high probability. virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0; }; // Return a new filter policy that uses a bloom filter with approximately // the specified number of bits per key. A good value for bits_per_key // is 10, which yields a filter with ~ 1% false positive rate. // // Callers must delete the result after any database that is using the // result has been closed. // // Note: if you are using a custom comparator that ignores some parts // of the keys being compared, you must not use NewBloomFilterPolicy() // and must provide your own FilterPolicy that also ignores the // corresponding parts of the keys. For example, if the comparator // ignores trailing spaces, it would be incorrect to use a // FilterPolicy (like NewBloomFilterPolicy) that does not ignore // trailing spaces in keys. LEVELDB_EXPORT const FilterPolicy* NewBloomFilterPolicy(int bits_per_key); } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_FILTER_POLICY_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/iterator.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // An iterator yields a sequence of key/value pairs from a source. // The following class defines the interface. Multiple implementations // are provided by this library. In particular, iterators are provided // to access the contents of a Table or a DB. // // Multiple threads can invoke const methods on an Iterator without // external synchronization, but if any of the threads may call a // non-const method, all threads accessing the same Iterator must use // external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_ITERATOR_H_ #define STORAGE_LEVELDB_INCLUDE_ITERATOR_H_ #include "leveldb/export.h" #include "leveldb/slice.h" #include "leveldb/status.h" namespace leveldb { class LEVELDB_EXPORT Iterator { public: Iterator(); Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete; virtual ~Iterator(); // An iterator is either positioned at a key/value pair, or // not valid. This method returns true iff the iterator is valid. virtual bool Valid() const = 0; // Position at the first key in the source. The iterator is Valid() // after this call iff the source is not empty. virtual void SeekToFirst() = 0; // Position at the last key in the source. The iterator is // Valid() after this call iff the source is not empty. virtual void SeekToLast() = 0; // Position at the first key in the source that is at or past target. // The iterator is Valid() after this call iff the source contains // an entry that comes at or past target. virtual void Seek(const Slice& target) = 0; // Moves to the next entry in the source. After this call, Valid() is // true iff the iterator was not positioned at the last entry in the source. // REQUIRES: Valid() virtual void Next() = 0; // Moves to the previous entry in the source. After this call, Valid() is // true iff the iterator was not positioned at the first entry in source. // REQUIRES: Valid() virtual void Prev() = 0; // Return the key for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. // REQUIRES: Valid() virtual Slice key() const = 0; // Return the value for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. // REQUIRES: Valid() virtual Slice value() const = 0; // If an error has occurred, return it. Else return an ok status. virtual Status status() const = 0; // Clients are allowed to register function/arg1/arg2 triples that // will be invoked when this iterator is destroyed. // // Note that unlike all of the preceding methods, this method is // not abstract and therefore clients should not override it. using CleanupFunction = void (*)(void* arg1, void* arg2); void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2); private: // Cleanup functions are stored in a single-linked list. // The list's head node is inlined in the iterator. struct CleanupNode { // True if the node is not used. Only head nodes might be unused. bool IsEmpty() const { return function == nullptr; } // Invokes the cleanup function. void Run() { assert(function != nullptr); (*function)(arg1, arg2); } // The head node is used if the function pointer is not null. CleanupFunction function; void* arg1; void* arg2; CleanupNode* next; }; CleanupNode cleanup_head_; }; // Return an empty iterator (yields nothing). LEVELDB_EXPORT Iterator* NewEmptyIterator(); // Return an empty iterator with the specified status. LEVELDB_EXPORT Iterator* NewErrorIterator(const Status& status); } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_ITERATOR_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/leveldb-library-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "c.h" #import "cache.h" #import "comparator.h" #import "db.h" #import "dumpfile.h" #import "env.h" #import "export.h" #import "filter_policy.h" #import "iterator.h" #import "options.h" #import "slice.h" #import "status.h" #import "table.h" #import "table_builder.h" #import "write_batch.h" FOUNDATION_EXPORT double leveldbVersionNumber; FOUNDATION_EXPORT const unsigned char leveldbVersionString[]; ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/options.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ #define STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ #include #include "leveldb/export.h" namespace leveldb { class Cache; class Comparator; class Env; class FilterPolicy; class Logger; class Snapshot; // DB contents are stored in a set of blocks, each of which holds a // sequence of key,value pairs. Each block may be compressed before // being stored in a file. The following enum describes which // compression method (if any) is used to compress a block. enum CompressionType { // NOTE: do not change the values of existing entries, as these are // part of the persistent format on disk. kNoCompression = 0x0, kSnappyCompression = 0x1 }; // Options to control the behavior of a database (passed to DB::Open) struct LEVELDB_EXPORT Options { // Create an Options object with default values for all fields. Options(); // ------------------- // Parameters that affect behavior // Comparator used to define the order of keys in the table. // Default: a comparator that uses lexicographic byte-wise ordering // // REQUIRES: The client must ensure that the comparator supplied // here has the same name and orders keys *exactly* the same as the // comparator provided to previous open calls on the same DB. const Comparator* comparator; // If true, the database will be created if it is missing. bool create_if_missing = false; // If true, an error is raised if the database already exists. bool error_if_exists = false; // If true, the implementation will do aggressive checking of the // data it is processing and will stop early if it detects any // errors. This may have unforeseen ramifications: for example, a // corruption of one DB entry may cause a large number of entries to // become unreadable or for the entire DB to become unopenable. bool paranoid_checks = false; // Use the specified object to interact with the environment, // e.g. to read/write files, schedule background work, etc. // Default: Env::Default() Env* env; // Any internal progress/error information generated by the db will // be written to info_log if it is non-null, or to a file stored // in the same directory as the DB contents if info_log is null. Logger* info_log = nullptr; // ------------------- // Parameters that affect performance // Amount of data to build up in memory (backed by an unsorted log // on disk) before converting to a sorted on-disk file. // // Larger values increase performance, especially during bulk loads. // Up to two write buffers may be held in memory at the same time, // so you may wish to adjust this parameter to control memory usage. // Also, a larger write buffer will result in a longer recovery time // the next time the database is opened. size_t write_buffer_size = 4 * 1024 * 1024; // Number of open files that can be used by the DB. You may need to // increase this if your database has a large working set (budget // one open file per 2MB of working set). int max_open_files = 1000; // Control over blocks (user data is stored in a set of blocks, and // a block is the unit of reading from disk). // If non-null, use the specified cache for blocks. // If null, leveldb will automatically create and use an 8MB internal cache. Cache* block_cache = nullptr; // Approximate size of user data packed per block. Note that the // block size specified here corresponds to uncompressed data. The // actual size of the unit read from disk may be smaller if // compression is enabled. This parameter can be changed dynamically. size_t block_size = 4 * 1024; // Number of keys between restart points for delta encoding of keys. // This parameter can be changed dynamically. Most clients should // leave this parameter alone. int block_restart_interval = 16; // Leveldb will write up to this amount of bytes to a file before // switching to a new one. // Most clients should leave this parameter alone. However if your // filesystem is more efficient with larger files, you could // consider increasing the value. The downside will be longer // compactions and hence longer latency/performance hiccups. // Another reason to increase this parameter might be when you are // initially populating a large database. size_t max_file_size = 2 * 1024 * 1024; // Compress blocks using the specified compression algorithm. This // parameter can be changed dynamically. // // Default: kSnappyCompression, which gives lightweight but fast // compression. // // Typical speeds of kSnappyCompression on an Intel(R) Core(TM)2 2.4GHz: // ~200-500MB/s compression // ~400-800MB/s decompression // Note that these speeds are significantly faster than most // persistent storage speeds, and therefore it is typically never // worth switching to kNoCompression. Even if the input data is // incompressible, the kSnappyCompression implementation will // efficiently detect that and will switch to uncompressed mode. CompressionType compression = kSnappyCompression; // EXPERIMENTAL: If true, append to existing MANIFEST and log files // when a database is opened. This can significantly speed up open. // // Default: currently false, but may become true later. bool reuse_logs = false; // If non-null, use the specified filter policy to reduce disk reads. // Many applications will benefit from passing the result of // NewBloomFilterPolicy() here. const FilterPolicy* filter_policy = nullptr; }; // Options that control read operations struct LEVELDB_EXPORT ReadOptions { ReadOptions() = default; // If true, all data read from underlying storage will be // verified against corresponding checksums. bool verify_checksums = false; // Should the data read for this iteration be cached in memory? // Callers may wish to set this field to false for bulk scans. bool fill_cache = true; // If "snapshot" is non-null, read as of the supplied snapshot // (which must belong to the DB that is being read and which must // not have been released). If "snapshot" is null, use an implicit // snapshot of the state at the beginning of this read operation. const Snapshot* snapshot = nullptr; }; // Options that control write operations struct LEVELDB_EXPORT WriteOptions { WriteOptions() = default; // If true, the write will be flushed from the operating system // buffer cache (by calling WritableFile::Sync()) before the write // is considered complete. If this flag is true, writes will be // slower. // // If this flag is false, and the machine crashes, some recent // writes may be lost. Note that if it is just the process that // crashes (i.e., the machine does not reboot), no writes will be // lost even if sync==false. // // In other words, a DB write with sync==false has similar // crash semantics as the "write()" system call. A DB write // with sync==true has similar crash semantics to a "write()" // system call followed by "fsync()". bool sync = false; }; } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_OPTIONS_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/slice.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // Slice is a simple structure containing a pointer into some external // storage and a size. The user of a Slice must ensure that the slice // is not used after the corresponding external storage has been // deallocated. // // Multiple threads can invoke const methods on a Slice without // external synchronization, but if any of the threads may call a // non-const method, all threads accessing the same Slice must use // external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_SLICE_H_ #define STORAGE_LEVELDB_INCLUDE_SLICE_H_ #include #include #include #include #include "leveldb/export.h" namespace leveldb { class LEVELDB_EXPORT Slice { public: // Create an empty slice. Slice() : data_(""), size_(0) {} // Create a slice that refers to d[0,n-1]. Slice(const char* d, size_t n) : data_(d), size_(n) {} // Create a slice that refers to the contents of "s" Slice(const std::string& s) : data_(s.data()), size_(s.size()) {} // Create a slice that refers to s[0,strlen(s)-1] Slice(const char* s) : data_(s), size_(strlen(s)) {} // Intentionally copyable. Slice(const Slice&) = default; Slice& operator=(const Slice&) = default; // Return a pointer to the beginning of the referenced data const char* data() const { return data_; } // Return the length (in bytes) of the referenced data size_t size() const { return size_; } // Return true iff the length of the referenced data is zero bool empty() const { return size_ == 0; } // Return the ith byte in the referenced data. // REQUIRES: n < size() char operator[](size_t n) const { assert(n < size()); return data_[n]; } // Change this slice to refer to an empty array void clear() { data_ = ""; size_ = 0; } // Drop the first "n" bytes from this slice. void remove_prefix(size_t n) { assert(n <= size()); data_ += n; size_ -= n; } // Return a string that contains the copy of the referenced data. std::string ToString() const { return std::string(data_, size_); } // Three-way comparison. Returns value: // < 0 iff "*this" < "b", // == 0 iff "*this" == "b", // > 0 iff "*this" > "b" int compare(const Slice& b) const; // Return true iff "x" is a prefix of "*this" bool starts_with(const Slice& x) const { return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0)); } private: const char* data_; size_t size_; }; inline bool operator==(const Slice& x, const Slice& y) { return ((x.size() == y.size()) && (memcmp(x.data(), y.data(), x.size()) == 0)); } inline bool operator!=(const Slice& x, const Slice& y) { return !(x == y); } inline int Slice::compare(const Slice& b) const { const size_t min_len = (size_ < b.size_) ? size_ : b.size_; int r = memcmp(data_, b.data_, min_len); if (r == 0) { if (size_ < b.size_) r = -1; else if (size_ > b.size_) r = +1; } return r; } } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_SLICE_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/status.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // A Status encapsulates the result of an operation. It may indicate success, // or it may indicate an error with an associated error message. // // Multiple threads can invoke const methods on a Status without // external synchronization, but if any of the threads may call a // non-const method, all threads accessing the same Status must use // external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_STATUS_H_ #define STORAGE_LEVELDB_INCLUDE_STATUS_H_ #include #include #include "leveldb/export.h" #include "leveldb/slice.h" namespace leveldb { class LEVELDB_EXPORT Status { public: // Create a success status. Status() noexcept : state_(nullptr) {} ~Status() { delete[] state_; } Status(const Status& rhs); Status& operator=(const Status& rhs); Status(Status&& rhs) noexcept : state_(rhs.state_) { rhs.state_ = nullptr; } Status& operator=(Status&& rhs) noexcept; // Return a success status. static Status OK() { return Status(); } // Return error status of an appropriate type. static Status NotFound(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kNotFound, msg, msg2); } static Status Corruption(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kCorruption, msg, msg2); } static Status NotSupported(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kNotSupported, msg, msg2); } static Status InvalidArgument(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kInvalidArgument, msg, msg2); } static Status IOError(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kIOError, msg, msg2); } // Returns true iff the status indicates success. bool ok() const { return (state_ == nullptr); } // Returns true iff the status indicates a NotFound error. bool IsNotFound() const { return code() == kNotFound; } // Returns true iff the status indicates a Corruption error. bool IsCorruption() const { return code() == kCorruption; } // Returns true iff the status indicates an IOError. bool IsIOError() const { return code() == kIOError; } // Returns true iff the status indicates a NotSupportedError. bool IsNotSupportedError() const { return code() == kNotSupported; } // Returns true iff the status indicates an InvalidArgument. bool IsInvalidArgument() const { return code() == kInvalidArgument; } // Return a string representation of this status suitable for printing. // Returns the string "OK" for success. std::string ToString() const; private: enum Code { kOk = 0, kNotFound = 1, kCorruption = 2, kNotSupported = 3, kInvalidArgument = 4, kIOError = 5 }; Code code() const { return (state_ == nullptr) ? kOk : static_cast(state_[4]); } Status(Code code, const Slice& msg, const Slice& msg2); static const char* CopyState(const char* s); // OK status has a null state_. Otherwise, state_ is a new[] array // of the following form: // state_[0..3] == length of message // state_[4] == code // state_[5..] == message const char* state_; }; inline Status::Status(const Status& rhs) { state_ = (rhs.state_ == nullptr) ? nullptr : CopyState(rhs.state_); } inline Status& Status::operator=(const Status& rhs) { // The following condition catches both aliasing (when this == &rhs), // and the common case where both rhs and *this are ok. if (state_ != rhs.state_) { delete[] state_; state_ = (rhs.state_ == nullptr) ? nullptr : CopyState(rhs.state_); } return *this; } inline Status& Status::operator=(Status&& rhs) noexcept { std::swap(state_, rhs.state_); return *this; } } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_STATUS_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/table.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #ifndef STORAGE_LEVELDB_INCLUDE_TABLE_H_ #define STORAGE_LEVELDB_INCLUDE_TABLE_H_ #include #include "leveldb/export.h" #include "leveldb/iterator.h" namespace leveldb { class Block; class BlockHandle; class Footer; struct Options; class RandomAccessFile; struct ReadOptions; class TableCache; // A Table is a sorted map from strings to strings. Tables are // immutable and persistent. A Table may be safely accessed from // multiple threads without external synchronization. class LEVELDB_EXPORT Table { public: // Attempt to open the table that is stored in bytes [0..file_size) // of "file", and read the metadata entries necessary to allow // retrieving data from the table. // // If successful, returns ok and sets "*table" to the newly opened // table. The client should delete "*table" when no longer needed. // If there was an error while initializing the table, sets "*table" // to nullptr and returns a non-ok status. Does not take ownership of // "*source", but the client must ensure that "source" remains live // for the duration of the returned table's lifetime. // // *file must remain live while this Table is in use. static Status Open(const Options& options, RandomAccessFile* file, uint64_t file_size, Table** table); Table(const Table&) = delete; Table& operator=(const Table&) = delete; ~Table(); // Returns a new iterator over the table contents. // The result of NewIterator() is initially invalid (caller must // call one of the Seek methods on the iterator before using it). Iterator* NewIterator(const ReadOptions&) const; // Given a key, return an approximate byte offset in the file where // the data for that key begins (or would begin if the key were // present in the file). The returned value is in terms of file // bytes, and so includes effects like compression of the underlying data. // E.g., the approximate offset of the last key in the table will // be close to the file length. uint64_t ApproximateOffsetOf(const Slice& key) const; private: friend class TableCache; struct Rep; static Iterator* BlockReader(void*, const ReadOptions&, const Slice&); explicit Table(Rep* rep) : rep_(rep) {} // Calls (*handle_result)(arg, ...) with the entry found after a call // to Seek(key). May not make such a call if filter policy says // that key is not present. Status InternalGet(const ReadOptions&, const Slice& key, void* arg, void (*handle_result)(void* arg, const Slice& k, const Slice& v)); void ReadMeta(const Footer& footer); void ReadFilter(const Slice& filter_handle_value); Rep* const rep_; }; } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_TABLE_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/table_builder.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // TableBuilder provides the interface used to build a Table // (an immutable and sorted map from keys to values). // // Multiple threads can invoke const methods on a TableBuilder without // external synchronization, but if any of the threads may call a // non-const method, all threads accessing the same TableBuilder must use // external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ #define STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ #include #include "leveldb/export.h" #include "leveldb/options.h" #include "leveldb/status.h" namespace leveldb { class BlockBuilder; class BlockHandle; class WritableFile; class LEVELDB_EXPORT TableBuilder { public: // Create a builder that will store the contents of the table it is // building in *file. Does not close the file. It is up to the // caller to close the file after calling Finish(). TableBuilder(const Options& options, WritableFile* file); TableBuilder(const TableBuilder&) = delete; TableBuilder& operator=(const TableBuilder&) = delete; // REQUIRES: Either Finish() or Abandon() has been called. ~TableBuilder(); // Change the options used by this builder. Note: only some of the // option fields can be changed after construction. If a field is // not allowed to change dynamically and its value in the structure // passed to the constructor is different from its value in the // structure passed to this method, this method will return an error // without changing any fields. Status ChangeOptions(const Options& options); // Add key,value to the table being constructed. // REQUIRES: key is after any previously added key according to comparator. // REQUIRES: Finish(), Abandon() have not been called void Add(const Slice& key, const Slice& value); // Advanced operation: flush any buffered key/value pairs to file. // Can be used to ensure that two adjacent entries never live in // the same data block. Most clients should not need to use this method. // REQUIRES: Finish(), Abandon() have not been called void Flush(); // Return non-ok iff some error has been detected. Status status() const; // Finish building the table. Stops using the file passed to the // constructor after this function returns. // REQUIRES: Finish(), Abandon() have not been called Status Finish(); // Indicate that the contents of this builder should be abandoned. Stops // using the file passed to the constructor after this function returns. // If the caller is not going to call Finish(), it must call Abandon() // before destroying this builder. // REQUIRES: Finish(), Abandon() have not been called void Abandon(); // Number of calls to Add() so far. uint64_t NumEntries() const; // Size of the file generated so far. If invoked after a successful // Finish() call, returns the size of the final generated file. uint64_t FileSize() const; private: bool ok() const { return status().ok(); } void WriteBlock(BlockBuilder* block, BlockHandle* handle); void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle); struct Rep; Rep* rep_; }; } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_TABLE_BUILDER_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Headers/write_batch.h ================================================ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. // // WriteBatch holds a collection of updates to apply atomically to a DB. // // The updates are applied in the order in which they are added // to the WriteBatch. For example, the value of "key" will be "v3" // after the following batch is written: // // batch.Put("key", "v1"); // batch.Delete("key"); // batch.Put("key", "v2"); // batch.Put("key", "v3"); // // Multiple threads can invoke const methods on a WriteBatch without // external synchronization, but if any of the threads may call a // non-const method, all threads accessing the same WriteBatch must use // external synchronization. #ifndef STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ #define STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ #include #include "leveldb/export.h" #include "leveldb/status.h" namespace leveldb { class Slice; class LEVELDB_EXPORT WriteBatch { public: class LEVELDB_EXPORT Handler { public: virtual ~Handler(); virtual void Put(const Slice& key, const Slice& value) = 0; virtual void Delete(const Slice& key) = 0; }; WriteBatch(); // Intentionally copyable. WriteBatch(const WriteBatch&) = default; WriteBatch& operator=(const WriteBatch&) = default; ~WriteBatch(); // Store the mapping "key->value" in the database. void Put(const Slice& key, const Slice& value); // If the database contains a mapping for "key", erase it. Else do nothing. void Delete(const Slice& key); // Clear all updates buffered in this batch. void Clear(); // The size of the database changes caused by this batch. // // This number is tied to implementation details, and may change across // releases. It is intended for LevelDB usage metrics. size_t ApproximateSize() const; // Copies the operations in "source" to this batch. // // This runs in O(source size) time. However, the constant factor is better // than calling Iterate() over the source batch with a Handler that replicates // the operations into this batch. void Append(const WriteBatch& source); // Support for iterating over the contents of a batch. Status Iterate(Handler* handler) const; private: friend class WriteBatchInternal; std::string rep_; // See comment in write_batch.cc for the format of rep_ }; } // namespace leveldb #endif // STORAGE_LEVELDB_INCLUDE_WRITE_BATCH_H_ ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Info.plist ================================================ CFBundleExecutable leveldb-library CFBundleIdentifier com.firebase.Firebase-leveldb-library CFBundleInfoDictionaryVersion 6.0 CFBundleName leveldb-library CFBundlePackageType FMWK CFBundleVersion 1.22.1 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/leveldb-library.framework/Modules/module.modulemap ================================================ framework module leveldb-library { umbrella header "leveldb-library-umbrella.h" export * module * { export * } link "c++" } ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Headers/nanopb-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "pb.h" #import "pb_common.h" #import "pb_decode.h" #import "pb_encode.h" #import "pb.h" #import "pb_decode.h" #import "pb_common.h" #import "pb.h" #import "pb_encode.h" #import "pb_common.h" FOUNDATION_EXPORT double nanopbVersionNumber; FOUNDATION_EXPORT const unsigned char nanopbVersionString[]; ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Headers/pb.h ================================================ /* Common parts of the nanopb library. Most of these are quite low-level * stuff. For the high-level interface, see pb_encode.h and pb_decode.h. */ #ifndef PB_H_INCLUDED #define PB_H_INCLUDED /***************************************************************** * Nanopb compilation time options. You can change these here by * * uncommenting the lines, or on the compiler command line. * *****************************************************************/ /* Enable support for dynamically allocated fields */ /* #define PB_ENABLE_MALLOC 1 */ /* Define this if your CPU / compiler combination does not support * unaligned memory access to packed structures. */ /* #define PB_NO_PACKED_STRUCTS 1 */ /* Increase the number of required fields that are tracked. * A compiler warning will tell if you need this. */ /* #define PB_MAX_REQUIRED_FIELDS 256 */ /* Add support for tag numbers > 255 and fields larger than 255 bytes. */ /* #define PB_FIELD_16BIT 1 */ /* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */ /* #define PB_FIELD_32BIT 1 */ /* Disable support for error messages in order to save some code space. */ /* #define PB_NO_ERRMSG 1 */ /* Disable support for custom streams (support only memory buffers). */ /* #define PB_BUFFER_ONLY 1 */ /* Switch back to the old-style callback function signature. * This was the default until nanopb-0.2.1. */ /* #define PB_OLD_CALLBACK_STYLE */ /* Don't encode scalar arrays as packed. This is only to be used when * the decoder on the receiving side cannot process packed scalar arrays. * Such example is older protobuf.js. */ /* #define PB_ENCODE_ARRAYS_UNPACKED 1 */ /****************************************************************** * You usually don't need to change anything below this line. * * Feel free to look around and use the defined macros, though. * ******************************************************************/ /* Version of the nanopb library. Just in case you want to check it in * your own program. */ #define NANOPB_VERSION nanopb-0.3.9.8 /* Include all the system headers needed by nanopb. You will need the * definitions of the following: * - strlen, memcpy, memset functions * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t * - size_t * - bool * * If you don't have the standard header files, you can instead provide * a custom header that defines or includes all this. In that case, * define PB_SYSTEM_HEADER to the path of this file. */ #ifdef PB_SYSTEM_HEADER #include PB_SYSTEM_HEADER #else #include #include #include #include #ifdef PB_ENABLE_MALLOC #include #endif #endif /* Macro for defining packed structures (compiler dependent). * This just reduces memory requirements, but is not required. */ #if defined(PB_NO_PACKED_STRUCTS) /* Disable struct packing */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed #elif defined(__GNUC__) || defined(__clang__) /* For GCC and clang */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed __attribute__((packed)) #elif defined(__ICCARM__) || defined(__CC_ARM) /* For IAR ARM and Keil MDK-ARM compilers */ # define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)") # define PB_PACKED_STRUCT_END _Pragma("pack(pop)") # define pb_packed #elif defined(_MSC_VER) && (_MSC_VER >= 1500) /* For Microsoft Visual C++ */ # define PB_PACKED_STRUCT_START __pragma(pack(push, 1)) # define PB_PACKED_STRUCT_END __pragma(pack(pop)) # define pb_packed #else /* Unknown compiler */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed #endif /* Handly macro for suppressing unreferenced-parameter compiler warnings. */ #ifndef PB_UNUSED #define PB_UNUSED(x) (void)(x) #endif /* Compile-time assertion, used for checking compatible compilation options. * If this does not work properly on your compiler, use * #define PB_NO_STATIC_ASSERT to disable it. * * But before doing that, check carefully the error message / place where it * comes from to see if the error has a real cause. Unfortunately the error * message is not always very clear to read, but you can see the reason better * in the place where the PB_STATIC_ASSERT macro was called. */ #ifndef PB_NO_STATIC_ASSERT #ifndef PB_STATIC_ASSERT #define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1]; #define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) #define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##LINE##COUNTER #endif #else #define PB_STATIC_ASSERT(COND,MSG) #endif /* Number of required fields to keep track of. */ #ifndef PB_MAX_REQUIRED_FIELDS #define PB_MAX_REQUIRED_FIELDS 64 #endif #if PB_MAX_REQUIRED_FIELDS < 64 #error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64). #endif /* List of possible field types. These are used in the autogenerated code. * Least-significant 4 bits tell the scalar type * Most-significant 4 bits specify repeated/required/packed etc. */ typedef uint_least8_t pb_type_t; /**** Field data types ****/ /* Numeric types */ #define PB_LTYPE_BOOL 0x00 /* bool */ #define PB_LTYPE_VARINT 0x01 /* int32, int64, enum, bool */ #define PB_LTYPE_UVARINT 0x02 /* uint32, uint64 */ #define PB_LTYPE_SVARINT 0x03 /* sint32, sint64 */ #define PB_LTYPE_FIXED32 0x04 /* fixed32, sfixed32, float */ #define PB_LTYPE_FIXED64 0x05 /* fixed64, sfixed64, double */ /* Marker for last packable field type. */ #define PB_LTYPE_LAST_PACKABLE 0x05 /* Byte array with pre-allocated buffer. * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ #define PB_LTYPE_BYTES 0x06 /* String with pre-allocated buffer. * data_size is the maximum length. */ #define PB_LTYPE_STRING 0x07 /* Submessage * submsg_fields is pointer to field descriptions */ #define PB_LTYPE_SUBMESSAGE 0x08 /* Extension pseudo-field * The field contains a pointer to pb_extension_t */ #define PB_LTYPE_EXTENSION 0x09 /* Byte array with inline, pre-allocated byffer. * data_size is the length of the inline, allocated buffer. * This differs from PB_LTYPE_BYTES by defining the element as * pb_byte_t[data_size] rather than pb_bytes_array_t. */ #define PB_LTYPE_FIXED_LENGTH_BYTES 0x0A /* Number of declared LTYPES */ #define PB_LTYPES_COUNT 0x0B #define PB_LTYPE_MASK 0x0F /**** Field repetition rules ****/ #define PB_HTYPE_REQUIRED 0x00 #define PB_HTYPE_OPTIONAL 0x10 #define PB_HTYPE_REPEATED 0x20 #define PB_HTYPE_ONEOF 0x30 #define PB_HTYPE_MASK 0x30 /**** Field allocation types ****/ #define PB_ATYPE_STATIC 0x00 #define PB_ATYPE_POINTER 0x80 #define PB_ATYPE_CALLBACK 0x40 #define PB_ATYPE_MASK 0xC0 #define PB_ATYPE(x) ((x) & PB_ATYPE_MASK) #define PB_HTYPE(x) ((x) & PB_HTYPE_MASK) #define PB_LTYPE(x) ((x) & PB_LTYPE_MASK) /* Data type used for storing sizes of struct fields * and array counts. */ #if defined(PB_FIELD_32BIT) typedef uint32_t pb_size_t; typedef int32_t pb_ssize_t; #elif defined(PB_FIELD_16BIT) typedef uint_least16_t pb_size_t; typedef int_least16_t pb_ssize_t; #else typedef uint_least8_t pb_size_t; typedef int_least8_t pb_ssize_t; #endif #define PB_SIZE_MAX ((pb_size_t)-1) /* Data type for storing encoded data and other byte streams. * This typedef exists to support platforms where uint8_t does not exist. * You can regard it as equivalent on uint8_t on other platforms. */ typedef uint_least8_t pb_byte_t; /* This structure is used in auto-generated constants * to specify struct fields. * You can change field sizes if you need structures * larger than 256 bytes or field tags larger than 256. * The compiler should complain if your .proto has such * structures. Fix that by defining PB_FIELD_16BIT or * PB_FIELD_32BIT. */ PB_PACKED_STRUCT_START typedef struct pb_field_s pb_field_t; struct pb_field_s { pb_size_t tag; pb_type_t type; pb_size_t data_offset; /* Offset of field data, relative to previous field. */ pb_ssize_t size_offset; /* Offset of array size or has-boolean, relative to data */ pb_size_t data_size; /* Data size in bytes for a single item */ pb_size_t array_size; /* Maximum number of entries in array */ /* Field definitions for submessage * OR default value for all other non-array, non-callback types * If null, then field will zeroed. */ const void *ptr; } pb_packed; PB_PACKED_STRUCT_END /* Make sure that the standard integer types are of the expected sizes. * Otherwise fixed32/fixed64 fields can break. * * If you get errors here, it probably means that your stdint.h is not * correct for your platform. */ #ifndef PB_WITHOUT_64BIT PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE) PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE) #endif /* This structure is used for 'bytes' arrays. * It has the number of bytes in the beginning, and after that an array. * Note that actual structs used will have a different length of bytes array. */ #define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; } #define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes)) struct pb_bytes_array_s { pb_size_t size; pb_byte_t bytes[1]; }; typedef struct pb_bytes_array_s pb_bytes_array_t; /* This structure is used for giving the callback function. * It is stored in the message structure and filled in by the method that * calls pb_decode. * * The decoding callback will be given a limited-length stream * If the wire type was string, the length is the length of the string. * If the wire type was a varint/fixed32/fixed64, the length is the length * of the actual value. * The function may be called multiple times (especially for repeated types, * but also otherwise if the message happens to contain the field multiple * times.) * * The encoding callback will receive the actual output stream. * It should write all the data in one call, including the field tag and * wire type. It can write multiple fields. * * The callback can be null if you want to skip a field. */ typedef struct pb_istream_s pb_istream_t; typedef struct pb_ostream_s pb_ostream_t; typedef struct pb_callback_s pb_callback_t; struct pb_callback_s { #ifdef PB_OLD_CALLBACK_STYLE /* Deprecated since nanopb-0.2.1 */ union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); } funcs; #else /* New function signature, which allows modifying arg contents in callback. */ union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); } funcs; #endif /* Free arg for use by callback */ void *arg; }; /* Wire types. Library user needs these only in encoder callbacks. */ typedef enum { PB_WT_VARINT = 0, PB_WT_64BIT = 1, PB_WT_STRING = 2, PB_WT_32BIT = 5 } pb_wire_type_t; /* Structure for defining the handling of unknown/extension fields. * Usually the pb_extension_type_t structure is automatically generated, * while the pb_extension_t structure is created by the user. However, * if you want to catch all unknown fields, you can also create a custom * pb_extension_type_t with your own callback. */ typedef struct pb_extension_type_s pb_extension_type_t; typedef struct pb_extension_s pb_extension_t; struct pb_extension_type_s { /* Called for each unknown field in the message. * If you handle the field, read off all of its data and return true. * If you do not handle the field, do not read anything and return true. * If you run into an error, return false. * Set to NULL for default handler. */ bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); /* Called once after all regular fields have been encoded. * If you have something to write, do so and return true. * If you do not have anything to write, just return true. * If you run into an error, return false. * Set to NULL for default handler. */ bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension); /* Free field for use by the callback. */ const void *arg; }; struct pb_extension_s { /* Type describing the extension field. Usually you'll initialize * this to a pointer to the automatically generated structure. */ const pb_extension_type_t *type; /* Destination for the decoded data. This must match the datatype * of the extension field. */ void *dest; /* Pointer to the next extension handler, or NULL. * If this extension does not match a field, the next handler is * automatically called. */ pb_extension_t *next; /* The decoder sets this to true if the extension was found. * Ignored for encoding. */ bool found; }; /* Memory allocation functions to use. You can define pb_realloc and * pb_free to custom functions if you want. */ #ifdef PB_ENABLE_MALLOC # ifndef pb_realloc # define pb_realloc(ptr, size) realloc(ptr, size) # endif # ifndef pb_free # define pb_free(ptr) free(ptr) # endif #endif /* This is used to inform about need to regenerate .pb.h/.pb.c files. */ #define PB_PROTO_HEADER_VERSION 30 /* These macros are used to declare pb_field_t's in the constant array. */ /* Size of a structure member, in bytes. */ #define pb_membersize(st, m) (sizeof ((st*)0)->m) /* Number of entries in an array. */ #define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) /* Delta from start of one member to the start of another member. */ #define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) /* Marks the end of the field list */ #define PB_LAST_FIELD {0,(pb_type_t) 0,0,0,0,0,0} /* Macros for filling in the data_offset field */ /* data_offset for first field in a message */ #define PB_DATAOFFSET_FIRST(st, m1, m2) (offsetof(st, m1)) /* data_offset for subsequent fields */ #define PB_DATAOFFSET_OTHER(st, m1, m2) (offsetof(st, m1) - offsetof(st, m2) - pb_membersize(st, m2)) /* data offset for subsequent fields inside an union (oneof) */ #define PB_DATAOFFSET_UNION(st, m1, m2) (PB_SIZE_MAX) /* Choose first/other based on m1 == m2 (deprecated, remains for backwards compatibility) */ #define PB_DATAOFFSET_CHOOSE(st, m1, m2) (int)(offsetof(st, m1) == offsetof(st, m2) \ ? PB_DATAOFFSET_FIRST(st, m1, m2) \ : PB_DATAOFFSET_OTHER(st, m1, m2)) /* Required fields are the simplest. They just have delta (padding) from * previous field end, and the size of the field. Pointer is used for * submessages and default values. */ #define PB_REQUIRED_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Optional fields add the delta to the has_ variable. */ #define PB_OPTIONAL_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \ fd, \ pb_delta(st, has_ ## m, m), \ pb_membersize(st, m), 0, ptr} #define PB_SINGULAR_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Repeated fields have a _count field and also the maximum number of entries. */ #define PB_REPEATED_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | ltype, \ fd, \ pb_delta(st, m ## _count, m), \ pb_membersize(st, m[0]), \ pb_arraysize(st, m), ptr} /* Allocated fields carry the size of the actual data, not the pointer */ #define PB_REQUIRED_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Optional fields don't need a has_ variable, as information would be redundant */ #define PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Same as optional fields*/ #define PB_SINGULAR_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Repeated fields have a _count field and a pointer to array of pointers */ #define PB_REPEATED_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_REPEATED | ltype, \ fd, pb_delta(st, m ## _count, m), \ pb_membersize(st, m[0]), 0, ptr} /* Callbacks are much like required fields except with special datatype. */ #define PB_REQUIRED_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_SINGULAR_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_REPEATED_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REPEATED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Optional extensions don't have the has_ field, as that would be redundant. * Furthermore, the combination of OPTIONAL without has_ field is used * for indicating proto3 style fields. Extensions exist in proto2 mode only, * so they should be encoded according to proto2 rules. To avoid the conflict, * extensions are marked as REQUIRED instead. */ #define PB_OPTEXT_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \ 0, \ 0, \ pb_membersize(st, m), 0, ptr} #define PB_OPTEXT_POINTER(tag, st, m, fd, ltype, ptr) \ PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr) #define PB_OPTEXT_CALLBACK(tag, st, m, fd, ltype, ptr) \ PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) /* The mapping from protobuf types to LTYPEs is done using these macros. */ #define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL #define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES #define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT #define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT #define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT #define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT #define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE #define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT #define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT #define PB_LTYPE_MAP_STRING PB_LTYPE_STRING #define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT #define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT #define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION #define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES /* This is the actual macro used in field descriptions. * It takes these arguments: * - Field tag number * - Field type: BOOL, BYTES, DOUBLE, ENUM, UENUM, FIXED32, FIXED64, * FLOAT, INT32, INT64, MESSAGE, SFIXED32, SFIXED64 * SINT32, SINT64, STRING, UINT32, UINT64 or EXTENSION * - Field rules: REQUIRED, OPTIONAL or REPEATED * - Allocation: STATIC, CALLBACK or POINTER * - Placement: FIRST or OTHER, depending on if this is the first field in structure. * - Message name * - Field name * - Previous field name (or field name again for first field) * - Pointer to default value or submsg fields. */ #define PB_FIELD(tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ ## rules ## _ ## allocation(tag, message, field, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) /* Field description for repeated static fixed count fields.*/ #define PB_REPEATED_FIXED_COUNT(tag, type, placement, message, field, prevfield, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | PB_LTYPE_MAP_ ## type, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ 0, \ pb_membersize(message, field[0]), \ pb_arraysize(message, field), ptr} /* Field description for oneof fields. This requires taking into account the * union name also, that's why a separate set of macros is needed. */ #define PB_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, u.m), \ pb_membersize(st, u.m), 0, ptr} #define PB_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, u.m), \ pb_membersize(st, u.m[0]), 0, ptr} #define PB_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ONEOF_ ## allocation(union_name, tag, message, field, \ PB_DATAOFFSET_ ## placement(message, union_name.field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) #define PB_ANONYMOUS_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, m), \ pb_membersize(st, m), 0, ptr} #define PB_ANONYMOUS_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, m), \ pb_membersize(st, m[0]), 0, ptr} #define PB_ANONYMOUS_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ANONYMOUS_ONEOF_ ## allocation(union_name, tag, message, field, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) /* These macros are used for giving out error messages. * They are mostly a debugging aid; the main error information * is the true/false return value from functions. * Some code space can be saved by disabling the error * messages if not used. * * PB_SET_ERROR() sets the error message if none has been set yet. * msg must be a constant string literal. * PB_GET_ERROR() always returns a pointer to a string. * PB_RETURN_ERROR() sets the error and returns false from current * function. */ #ifdef PB_NO_ERRMSG #define PB_SET_ERROR(stream, msg) PB_UNUSED(stream) #define PB_GET_ERROR(stream) "(errmsg disabled)" #else #define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg)) #define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)") #endif #define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false #endif ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Headers/pb_common.h ================================================ /* pb_common.h: Common support functions for pb_encode.c and pb_decode.c. * These functions are rarely needed by applications directly. */ #ifndef PB_COMMON_H_INCLUDED #define PB_COMMON_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Iterator for pb_field_t list */ struct pb_field_iter_s { const pb_field_t *start; /* Start of the pb_field_t array */ const pb_field_t *pos; /* Current position of the iterator */ unsigned required_field_index; /* Zero-based index that counts only the required fields */ void *dest_struct; /* Pointer to start of the structure */ void *pData; /* Pointer to current field value */ void *pSize; /* Pointer to count/has field */ }; typedef struct pb_field_iter_s pb_field_iter_t; /* Initialize the field iterator structure to beginning. * Returns false if the message type is empty. */ bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct); /* Advance the iterator to the next field. * Returns false when the iterator wraps back to the first field. */ bool pb_field_iter_next(pb_field_iter_t *iter); /* Advance the iterator until it points at a field with the given tag. * Returns false if no such field exists. */ bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Headers/pb_decode.h ================================================ /* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. * The main function is pb_decode. You also need an input stream, and the * field descriptions created by nanopb_generator.py. */ #ifndef PB_DECODE_H_INCLUDED #define PB_DECODE_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Structure for defining custom input streams. You will need to provide * a callback function to read the bytes from your storage, which can be * for example a file or a network socket. * * The callback must conform to these rules: * * 1) Return false on IO errors. This will cause decoding to abort. * 2) You can use state to store your own data (e.g. buffer pointer), * and rely on pb_read to verify that no-body reads past bytes_left. * 3) Your callback may be used with substreams, in which case bytes_left * is different than from the main stream. Don't use bytes_left to compute * any pointers. */ struct pb_istream_s { #ifdef PB_BUFFER_ONLY /* Callback pointer is not used in buffer-only configuration. * Having an int pointer here allows binary compatibility but * gives an error if someone tries to assign callback function. */ int *callback; #else bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count); #endif void *state; /* Free field for use by callback implementation */ size_t bytes_left; #ifndef PB_NO_ERRMSG const char *errmsg; #endif }; /*************************** * Main decoding functions * ***************************/ /* Decode a single protocol buffers message from input stream into a C structure. * Returns true on success, false on any failure. * The actual struct pointed to by dest must match the description in fields. * Callback fields of the destination structure must be initialized by caller. * All other fields will be initialized by this function. * * Example usage: * MyMessage msg = {}; * uint8_t buffer[64]; * pb_istream_t stream; * * // ... read some data into buffer ... * * stream = pb_istream_from_buffer(buffer, count); * pb_decode(&stream, MyMessage_fields, &msg); */ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except does not initialize the destination structure * to default values. This is slightly faster if you need no default values * and just do memset(struct, 0, sizeof(struct)) yourself. * * This can also be used for 'merging' two messages, i.e. update only the * fields that exist in the new message. * * Note: If this function returns with an error, it will not release any * dynamically allocated fields. You will need to call pb_release() yourself. */ bool pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except expects the stream to start with the message size * encoded as varint. Corresponds to parseDelimitedFrom() in Google's * protobuf API. */ bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode_delimited, except that it does not initialize the destination structure. * See pb_decode_noinit */ bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except allows the message to be terminated with a null byte. * NOTE: Until nanopb-0.4.0, pb_decode() also allows null-termination. This behaviour * is not supported in most other protobuf implementations, so pb_decode_delimited() * is a better option for compatibility. */ bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); #ifdef PB_ENABLE_MALLOC /* Release any allocated pointer fields. If you use dynamic allocation, you should * call this for any successfully decoded message when you are done with it. If * pb_decode() returns with an error, the message is already released. */ void pb_release(const pb_field_t fields[], void *dest_struct); #endif /************************************** * Functions for manipulating streams * **************************************/ /* Create an input stream for reading from a memory buffer. * * Alternatively, you can use a custom stream that reads directly from e.g. * a file or a network socket. */ pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize); /* Function to read from a pb_istream_t. You can use this if you need to * read some custom header data, or to read data in field callbacks. */ bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); /************************************************ * Helper functions for writing field callbacks * ************************************************/ /* Decode the tag for the next field in the stream. Gives the wire type and * field tag. At end of the message, returns false and sets eof to true. */ bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof); /* Skip the field payload data, given the wire type. */ bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); /* Decode an integer in the varint format. This works for enum, int32, * int64, uint32 and uint64 field types. */ #ifndef PB_WITHOUT_64BIT bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); #else #define pb_decode_varint pb_decode_varint32 #endif /* Decode an integer in the varint format. This works for enum, int32, * and uint32 field types. */ bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); /* Decode a bool value in varint format. */ bool pb_decode_bool(pb_istream_t *stream, bool *dest); /* Decode an integer in the zig-zagged svarint format. This works for sint32 * and sint64. */ #ifndef PB_WITHOUT_64BIT bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); #else bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest); #endif /* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to * a 4-byte wide C variable. */ bool pb_decode_fixed32(pb_istream_t *stream, void *dest); #ifndef PB_WITHOUT_64BIT /* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to * a 8-byte wide C variable. */ bool pb_decode_fixed64(pb_istream_t *stream, void *dest); #endif /* Make a limited-length substream for reading a PB_WT_STRING field. */ bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream); bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Headers/pb_encode.h ================================================ /* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. * The main function is pb_encode. You also need an output stream, and the * field descriptions created by nanopb_generator.py. */ #ifndef PB_ENCODE_H_INCLUDED #define PB_ENCODE_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Structure for defining custom output streams. You will need to provide * a callback function to write the bytes to your storage, which can be * for example a file or a network socket. * * The callback must conform to these rules: * * 1) Return false on IO errors. This will cause encoding to abort. * 2) You can use state to store your own data (e.g. buffer pointer). * 3) pb_write will update bytes_written after your callback runs. * 4) Substreams will modify max_size and bytes_written. Don't use them * to calculate any pointers. */ struct pb_ostream_s { #ifdef PB_BUFFER_ONLY /* Callback pointer is not used in buffer-only configuration. * Having an int pointer here allows binary compatibility but * gives an error if someone tries to assign callback function. * Also, NULL pointer marks a 'sizing stream' that does not * write anything. */ int *callback; #else bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); #endif void *state; /* Free field for use by callback implementation. */ size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */ size_t bytes_written; /* Number of bytes written so far. */ #ifndef PB_NO_ERRMSG const char *errmsg; #endif }; /*************************** * Main encoding functions * ***************************/ /* Encode a single protocol buffers message from C structure into a stream. * Returns true on success, false on any failure. * The actual struct pointed to by src_struct must match the description in fields. * All required fields in the struct are assumed to have been filled in. * * Example usage: * MyMessage msg = {}; * uint8_t buffer[64]; * pb_ostream_t stream; * * msg.field1 = 42; * stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); * pb_encode(&stream, MyMessage_fields, &msg); */ bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Same as pb_encode, but prepends the length of the message as a varint. * Corresponds to writeDelimitedTo() in Google's protobuf API. */ bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Same as pb_encode, but appends a null byte to the message for termination. * NOTE: This behaviour is not supported in most other protobuf implementations, so pb_encode_delimited() * is a better option for compatibility. */ bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Encode the message to get the size of the encoded data, but do not store * the data. */ bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct); /************************************** * Functions for manipulating streams * **************************************/ /* Create an output stream for writing into a memory buffer. * The number of bytes written can be found in stream.bytes_written after * encoding the message. * * Alternatively, you can use a custom stream that writes directly to e.g. * a file or a network socket. */ pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); /* Pseudo-stream for measuring the size of a message without actually storing * the encoded data. * * Example usage: * MyMessage msg = {}; * pb_ostream_t stream = PB_OSTREAM_SIZING; * pb_encode(&stream, MyMessage_fields, &msg); * printf("Message size is %d\n", stream.bytes_written); */ #ifndef PB_NO_ERRMSG #define PB_OSTREAM_SIZING {0,0,0,0,0} #else #define PB_OSTREAM_SIZING {0,0,0,0} #endif /* Function to write into a pb_ostream_t stream. You can use this if you need * to append or prepend some custom headers to the message. */ bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); /************************************************ * Helper functions for writing field callbacks * ************************************************/ /* Encode field header based on type and field number defined in the field * structure. Call this from the callback before writing out field contents. */ bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field); /* Encode field header by manually specifying wire type. You need to use this * if you want to write out packed arrays from a callback field. */ bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); /* Encode an integer in the varint format. * This works for bool, enum, int32, int64, uint32 and uint64 field types. */ #ifndef PB_WITHOUT_64BIT bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); #else bool pb_encode_varint(pb_ostream_t *stream, uint32_t value); #endif /* Encode an integer in the zig-zagged svarint format. * This works for sint32 and sint64. */ #ifndef PB_WITHOUT_64BIT bool pb_encode_svarint(pb_ostream_t *stream, int64_t value); #else bool pb_encode_svarint(pb_ostream_t *stream, int32_t value); #endif /* Encode a string or bytes type field. For strings, pass strlen(s) as size. */ bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); /* Encode a fixed32, sfixed32 or float value. * You need to pass a pointer to a 4-byte wide C variable. */ bool pb_encode_fixed32(pb_ostream_t *stream, const void *value); #ifndef PB_WITHOUT_64BIT /* Encode a fixed64, sfixed64 or double value. * You need to pass a pointer to a 8-byte wide C variable. */ bool pb_encode_fixed64(pb_ostream_t *stream, const void *value); #endif /* Encode a submessage field. * You need to pass the pb_field_t array and pointer to struct, just like * with pb_encode(). This internally encodes the submessage twice, first to * calculate message size and then to actually write it out. */ bool pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Info.plist ================================================ CFBundleExecutable nanopb CFBundleIdentifier com.firebase.Firebase-nanopb CFBundleInfoDictionaryVersion 6.0 CFBundleName nanopb CFBundlePackageType FMWK CFBundleVersion 2.30908.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Clocker/Frameworks/Firebase/nanopb.framework/Modules/module.modulemap ================================================ framework module nanopb { umbrella header "nanopb-umbrella.h" export * module * { export * } } ================================================ FILE: Clocker/GoogleService-Info.plist ================================================ CLIENT_ID 59694130826-ra8s6202rhdb12j7kqgjij4utu6c467r.apps.googleusercontent.com REVERSED_CLIENT_ID com.googleusercontent.apps.59694130826-ra8s6202rhdb12j7kqgjij4utu6c467r API_KEY AIzaSyAUFTmji4YcfRPoWhTZ4hZs4JdxX5y_R2w GCM_SENDER_ID 59694130826 PLIST_VERSION 1 BUNDLE_ID com.abhishek.Clocker PROJECT_ID fiery-heat-5237 STORAGE_BUCKET fiery-heat-5237.appspot.com IS_ADS_ENABLED IS_ANALYTICS_ENABLED IS_APPINVITE_ENABLED IS_GCM_ENABLED IS_SIGNIN_ENABLED GOOGLE_APP_ID 1:59694130826:ios:6d8dbaa6eee09934a76f73 DATABASE_URL https://fiery-heat-5237.firebaseio.com ================================================ FILE: Clocker/Keys.plist.example ================================================ GeocodingKey YOUR_GOOGLE_GEOCODING_API_KEY ================================================ FILE: Clocker/Media.xcassets/Accent Color.colorset/Contents.json ================================================ { "colors" : [ { "color" : { "color-space" : "extended-srgb", "components" : { "alpha" : "1.000", "blue" : "0.999", "green" : "0.000", "red" : "0.092" } }, "idiom" : "universal" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "color" : { "color-space" : "extended-srgb", "components" : { "alpha" : "1.000", "blue" : "0.109", "green" : "0.958", "red" : "0.071" } }, "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Add Icon/Add Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "noun_991600_cc.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "AddWhite.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Add Icon/Add Highlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "AddHighlighted.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Add Icon/Add Icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_991600_cc.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Add Icon/Add White.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "AddWhite.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Add Icon/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "filename" : "32-1.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "filename" : "32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "filename" : "clocker-64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "filename" : "128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "filename" : "256-1.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "filename" : "256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "filename" : "512-1.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "filename" : "512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "filename" : "Clocker2_1024x1.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Calendar Icon Remove/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Calendar Icon Remove/Remove Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "noun_991605_cc.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "WhiteRemoveButton.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Calendar Icon Remove/Remove.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_991605_cc.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Calendar Icon Remove/WhiteRemove.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "WhiteRemoveButton.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Clocker Icon/ClockerIcon-512.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "icon-512x512@2x.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Clocker Icon/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/Extra Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "ExtraOptions.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ExtraOptionsWhite.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/Extra.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ExtraOptions.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/ExtraHighlighted Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "ExtraOptions.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ExtraOptionsWhite.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/ExtraHighlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ExtraOptions.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/ExtraWhite.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ExtraOptionsWhite.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Extra Options/ExtraWhiteHighlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ExtraOptionsWhite.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Location/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Location/CurrentLocation.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "CurrentLocation.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Location/CurrentLocationDynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "CurrentLocation.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "CurrentLocationWhite.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Location/CurrentLocationWhite.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "CurrentLocationWhite.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Menubar Icons/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Menubar Icons/LightModeIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "black-transparent.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "black-transparent@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: Clocker/Media.xcassets/Onboarding/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Onboarding/Dark Menubar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "Screen Shot 2020-10-11 at 2.16.37 PM.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Onboarding/Dynamic Menubar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "1x" }, { "filename" : "Screen Shot 2020-10-11 at 2.16.37 PM.png", "idiom" : "universal", "scale" : "2x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "Screen Shot 2020-10-11 at 2.19.26 PM.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Onboarding/Light Menubar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "Screen Shot 2020-10-11 at 2.16.37 PM.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Pin/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Pin/Float-White.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Pin_1x.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "info panel detach button_Normal@2x.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Pin/Float.imageset/Contents.json ================================================ { "images" : [ { "filename" : "Pin_Light_1x.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "light-mode.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Pin/Pin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "Pin_1x.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "light-mode.png", "idiom" : "universal", "scale" : "2x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "info panel detach button_Normal@2x.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Power Icon/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Power Icon/Power.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "1x" }, { "filename" : "More.png", "idiom" : "universal", "scale" : "2x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "More-Dark.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Power Icon/PowerIcon-White.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "More-Dark.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Power Icon/PowerIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "More.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Preferences Toolbar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Preferences.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Privacy/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Privacy/Privacy Dark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Privacy Dynamic.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Privacy/Privacy Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Privacy Dynamic.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "1x" }, { "idiom" : "universal", "filename" : "Privacy.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Privacy Dynamic-1.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Privacy/Privacy.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Privacy.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Settings/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Settings/Settings-White.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_960833_cc.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Settings/Settings.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Settings.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sharing/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sharing/Sharing Dynamic.imageset/Contents.json ================================================ { "images" : [ { "filename" : "share1x_light.png", "idiom" : "universal", "scale" : "1x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "share1x.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "share2x_light.png", "idiom" : "universal", "scale" : "2x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "share2x.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Sharing/Sharing.imageset/Contents.json ================================================ { "images" : [ { "filename" : "share1x_light.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "share2x_light.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Sharing/SharingDarkIcon.imageset/Contents.json ================================================ { "images" : [ { "filename" : "share1x.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "share2x.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/Sunrise Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "Sunrise-Black.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Sunrise-White.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/Sunrise.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Sunrise-Black.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/Sunset Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "Sunset-Black.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Sunset-White.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/Sunset.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Sunset-Black.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/WhiteSunrise.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Sunrise-White.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Sunrise/WhiteSunset.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Sunset-White.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Appearance Dark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Appearance Dynamic.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Appearance Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "Appearance1.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Appearance Dynamic.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Appearance.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Appearance1.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "compression-type" : "gpu-optimized-best" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Calendar Tab Dark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Calendar Tab Dynamic.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Calendar Tab Dynamic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "1x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] }, { "idiom" : "universal", "filename" : "Calendar.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Calendar Tab Dynamic.png", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" }, { "idiom" : "universal", "scale" : "3x", "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ] } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Calendar Tab Icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Calendar.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Tabs/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Media.xcassets/Trash.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_1051408_cc.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Clocker/Menu Bar/MenubarHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import EventKit class MenubarHandler: NSObject { func titleForMenubar() -> String? { if let nextEvent = checkForUpcomingEvents() { return nextEvent } guard let menubarTitles = DataStore.shared().menubarTimezones() else { return nil } // If the menubar is in compact mode, we don't need any of the below calculations; exit early if DataStore.shared().shouldDisplay(.menubarCompactMode) { return nil } if menubarTitles.isEmpty == false { let titles = menubarTitles.map { data -> String? in let timezone = TimezoneData.customObject(from: data) let operationsObject = TimezoneDataOperations(with: timezone!) return "\(operationsObject.menuTitle().trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines))" } let titlesStringified = titles.compactMap { $0 } return titlesStringified.joined(separator: " ") } return nil } private func checkForUpcomingEvents() -> String? { if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { let filteredDates = EventCenter.sharedCenter().eventsForDate let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else { return nil } for event in events { if event.event.startDate.timeIntervalSinceNow > 0, !event.isAllDay { let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60 if timeForEventToStart > 30 { Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") continue } return EventCenter.sharedCenter().format(event: event.event) } } } return nil } } ================================================ FILE: Clocker/Menu Bar/StatusContainerView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa func bufferCalculatedWidth() -> Int { var totalWidth = 55 if DataStore.shared().shouldShowDayInMenubar() { totalWidth += 12 } if DataStore.shared().isBufferRequiredForTwelveHourFormats() { totalWidth += 20 } if DataStore.shared().shouldShowDateInMenubar() { totalWidth += 20 } return totalWidth } func compactWidth(for timezone: TimezoneData) -> Int { var totalWidth = 55 let timeFormat = timezone.timezoneFormat() if DataStore.shared().shouldShowDayInMenubar() { totalWidth += 12 } if timeFormat == DateFormat.twelveHour || timeFormat == DateFormat.twelveHourWithSeconds || timeFormat == DateFormat.twelveHourWithZero || timeFormat == DateFormat.twelveHourWithSeconds { totalWidth += 20 } else if timeFormat == DateFormat.twentyFourHour || timeFormat == DateFormat.twentyFourHourWithSeconds { totalWidth += 0 } if timezone.shouldShowSeconds() { // Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM totalWidth += 15 } if DataStore.shared().shouldShowDateInMenubar() { totalWidth += 20 } print("-- Compact Width is \(totalWidth)") return totalWidth } // Test with Sat 12:46 AM let bufferWidth: CGFloat = 9.5 class StatusContainerView: NSView { private var previousX: Int = 0 override func awakeFromNib() { super.awakeFromNib() wantsLayer = true layer?.backgroundColor = NSColor.clear.cgColor } init(with timezones: [Data]) { func addSubviews() { timezones.forEach { if let timezoneObject = TimezoneData.customObject(from: $0) { addTimezone(timezoneObject) } } } let timeBasedAttributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] func containerWidth(for timezones: [Data]) -> CGFloat { let compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in if let timezoneObject = TimezoneData.customObject(from: timezone) { let precalculatedWidth = Double(compactWidth(for: timezoneObject)) let operationObject = TimezoneDataOperations(with: timezoneObject) let calculatedSubtitleSize = compactModeTimeFont.size(operationObject.compactMenuSubtitle(), precalculatedWidth, attributes: timeBasedAttributes) let calculatedTitleSize = compactModeTimeFont.size(operationObject.compactMenuTitle(), precalculatedWidth, attributes: timeBasedAttributes) let showSeconds = timezoneObject.shouldShowSeconds() let secondsBuffer: CGFloat = showSeconds ? 7 : 0 return result + max(calculatedTitleSize.width, calculatedSubtitleSize.width) + bufferWidth + secondsBuffer } return result + CGFloat(bufferCalculatedWidth()) } let calculatedWidth = min(compressedWidth, CGFloat(timezones.count * bufferCalculatedWidth())) return calculatedWidth } let statusItemWidth = containerWidth(for: timezones) let frame = NSRect(x: 0, y: 0, width: statusItemWidth, height: 30) super.init(frame: frame) addSubviews() } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } func addTimezone(_ timezone: TimezoneData) { let calculatedWidth = bestWidth(for: timezone) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let statusItemView = StatusItemView(frame: frame) statusItemView.dataObject = timezone addSubview(statusItemView) previousX += calculatedWidth } private func bestWidth(for timezone: TimezoneData) -> Int { var textColor = hasDarkAppearance ? NSColor.white : NSColor.black if #available(OSX 11.0, *) { textColor = NSColor.white } let timeBasedAttributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] let operation = TimezoneDataOperations(with: timezone) let bestSize = compactModeTimeFont.size(operation.compactMenuSubtitle(), Double(compactWidth(for: timezone)), attributes: timeBasedAttributes) let bestTitleSize = compactModeTimeFont.size(operation.compactMenuTitle(), Double(compactWidth(for: timezone)), attributes: timeBasedAttributes) return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) } func updateTime() { if subviews.isEmpty { assertionFailure("Subviews count should > 0") } // See if frame's width needs any adjustment adjustWidthIfNeccessary() } private func adjustWidthIfNeccessary() { var newWidth: CGFloat = 0 subviews.forEach { if let statusItem = $0 as? StatusItemView, statusItem.isHidden == false { // Determine what's the best width required to display the current string. let newBestWidth = CGFloat(bestWidth(for: statusItem.dataObject)) // Let's note if the current width is too small/correct newWidth += statusItem.frame.size.width != newBestWidth ? newBestWidth : statusItem.frame.size.width statusItem.frame = CGRect(x: statusItem.frame.origin.x, y: statusItem.frame.origin.y, width: newBestWidth, height: statusItem.frame.size.height) statusItem.updateTimeInMenubar() } } if newWidth != frame.size.width, newWidth > frame.size.width + 2.0 { Logger.info("Correcting our width to \(newWidth) and the previous width was \(frame.size.width)") frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: newWidth, height: frame.size.height) } } } ================================================ FILE: Clocker/Menu Bar/StatusItemHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa private enum MenubarState { case compactText case standardText case icon } class StatusItemHandler: NSObject { var hasActiveIcon: Bool = false var menubarTimer: Timer? var statusItem: NSStatusItem = { let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem.highlightMode = false return statusItem }() private var menubarTitleHandler = MenubarHandler() private var parentView: StatusContainerView? private var nsCalendar = Calendar.autoupdatingCurrent private lazy var units: Set = Set([.era, .year, .month, .day, .hour, .minute]) private var userNotificationsDidChangeNotif: NSObjectProtocol? // Current State is set twice when the user first launches an app. // First, when StatusItemHandler() is instantiated in AppDelegate // Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet. // TODO: Make sure it's set just once. private var currentState: MenubarState = .standardText { didSet { // Do some cleanup switch oldValue { case .compactText: statusItem.button?.subviews = [] parentView = nil case .standardText: statusItem.button?.title = CLEmptyString case .icon: statusItem.button?.image = nil } // Now setup for the new menubar state switch currentState { case .compactText: setupForCompactTextMode() case .standardText: setupForStandardTextMode() case .icon: setClockerIcon() } Logger.info("\nStatus Bar Current State changed: \(currentState)\n") } } override init() { super.init() setupStatusItem() setupNotificationObservers() } func setupStatusItem() { // Let's figure out the initial menubar state var menubarState = MenubarState.icon let shouldTextBeDisplayed = DataStore.shared().menubarTimezones()?.isEmpty ?? true if !shouldTextBeDisplayed || DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if DataStore.shared().shouldDisplay(.menubarCompactMode) { menubarState = .compactText } else { menubarState = .standardText } } // Initial state has been figured out. Time to set it! currentState = menubarState func setSelector() { if #available(macOS 10.14, *) { statusItem.button?.action = #selector(menubarIconClicked(_:)) } else { statusItem.action = #selector(menubarIconClicked(_:)) } } statusItem.target = self statusItem.autosaveName = NSStatusItem.AutosaveName("ClockerStatusItem") setSelector() } private func setupNotificationObservers() { let center = NotificationCenter.default let mainQueue = OperationQueue.main center.addObserver(self, selector: #selector(updateMenubar), name: NSWorkspace.didWakeNotification, object: nil) DistributedNotificationCenter.default.addObserver(self, selector: #selector(respondToInterfaceStyleChange), name: .interfaceStyleDidChange, object: nil) userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification, object: self, queue: mainQueue) { _ in self.setupStatusItem() } } deinit { if let userNotifsDidChange = userNotificationsDidChangeNotif { NotificationCenter.default.removeObserver(userNotifsDidChange) } } private func constructCompactView() { statusItem.button?.subviews = [] parentView = nil let menubarTimezones = retrieveSyncedMenubarTimezones() if menubarTimezones.isEmpty { currentState = .icon return } parentView = StatusContainerView(with: menubarTimezones) guard let view = parentView else { return } statusItem.button?.addSubview(view) statusItem.button?.frame = view.bounds statusItem.button?.window?.backgroundColor = NSColor.clear } private func retrieveSyncedMenubarTimezones() -> [Data] { let defaultPreferences = DataStore.shared().retrieve(key: CLDefaultPreferenceKey) as? [Data] ?? [] let menubarTimezones = defaultPreferences.filter { data -> Bool in if let timezoneObj = TimezoneData.customObject(from: data) { return timezoneObj.isFavourite == 1 } return false } return menubarTimezones } // This is called when the Apple interface style pre-Mojave is changed. // In High Sierra and before, we could have a dark or light menubar and dock // Our icon is template, so it changes automatically; so is our standard status bar text // Only need to handle the compact mode! @objc func respondToInterfaceStyleChange() { if DataStore.shared().shouldDisplay(.menubarCompactMode) { updateCompactMenubar() } } @objc func setHasActiveIcon(_ value: Bool) { hasActiveIcon = value } @objc func menubarIconClicked(_ sender: Any) { guard let mainDelegate = NSApplication.shared.delegate as? AppDelegate else { return } mainDelegate.togglePanel(sender) } @objc func updateMenubar() { guard let fireDate = calculateFireDate() else { return } let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() menubarTimer = Timer(fire: fireDate, interval: 0, repeats: false, block: { [weak self] _ in if let strongSelf = self { strongSelf.performTimerWork() } }) // Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20 guard let runLoopTimer = menubarTimer else { Logger.info("Timer is unexpectedly nil") return } RunLoop.main.add(runLoopTimer, forMode: .common) } private func shouldDisplaySecondsInMenubar() -> Bool { let syncedTimezones = retrieveSyncedMenubarTimezones() for timezone in syncedTimezones { if let timezoneObj = TimezoneData.customObject(from: timezone) { let shouldShowSeconds = timezoneObj.shouldShowSeconds() if shouldShowSeconds { return true } } continue } return false } private func calculateFireDate() -> Date? { let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() let menubarFavourites = DataStore.shared().menubarTimezones() if !units.contains(.second), shouldDisplaySeconds { units.insert(.second) } var components = nsCalendar.dateComponents(units, from: Date()) // We want to update every second only when there's a timezone present! if shouldDisplaySeconds, let seconds = components.second, let favourites = menubarFavourites, !favourites.isEmpty { components.second = seconds + 1 } else if let minutes = components.minute { components.minute = minutes + 1 } else { Logger.info("Unable to create date components for the menubar timewr") return nil } guard let fireDate = nsCalendar.date(from: components) else { Logger.info("Unable to form Fire Date") return nil } return fireDate } func updateCompactMenubar() { parentView?.updateTime() } func performTimerWork() { if currentState == .compactText { updateCompactMenubar() updateMenubar() } else if currentState == .standardText, let title = menubarTitleHandler.titleForMenubar() { // Need setting button's image to nil // Especially if we have showUpcomingEvents turned to true and menubar timezones are empty statusItem.button?.image = nil statusItem.button?.title = title updateMenubar() } else { setClockerIcon() menubarTimer?.invalidate() } } private func setupForStandardTextMode() { Logger.info("Initializing menubar timer") // Let's invalidate the previous timer menubarTimer?.invalidate() menubarTimer = nil setupForStandardText() updateMenubar() } func invalidateTimer(showIcon show: Bool, isSyncing sync: Bool) { // Check if user is not showing // 1. Timezones // 2. Upcoming Event let menubarFavourites = DataStore.shared().menubarTimezones() ?? [] if menubarFavourites.isEmpty, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { Logger.info("Invalidating menubar timer!") invalidation() if show { currentState = .icon } } else if sync { Logger.info("Invalidating menubar timer for sync purposes!") invalidation() if show { setClockerIcon() } } else { Logger.info("Not stopping menubar timer!") } } private func invalidation() { menubarTimer?.invalidate() } private func setClockerIcon() { if statusItem.button?.subviews.isEmpty == false { statusItem.button?.subviews = [] } if statusItem.button?.image?.name() == NSImage.Name.menubarIcon { return } statusItem.button?.title = CLEmptyString statusItem.button?.image = NSImage(named: .menubarIcon) statusItem.button?.imagePosition = .imageOnly } private func setupForStandardText() { var menubarText = CLEmptyString if let menubarTitle = menubarTitleHandler.titleForMenubar() { menubarText = menubarTitle } else if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { // Don't have any meeting to show } else { // We have no favourites to display and no meetings to show. // That means we should display our icon! } guard !menubarText.isEmpty else { setClockerIcon() return } statusItem.button?.title = menubarText statusItem.button?.font = NSFont.monospacedDigitSystemFont(ofSize: 14.0, weight: NSFont.Weight.regular) statusItem.button?.image = nil statusItem.button?.imagePosition = .imageLeft } private func setupForCompactTextMode() { // Let's invalidate the previous timer menubarTimer?.invalidate() menubarTimer = nil constructCompactView() updateMenubar() } } ================================================ FILE: Clocker/Menu Bar/StatusItemView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa var defaultParagraphStyle: NSMutableParagraphStyle { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center paragraphStyle.lineBreakMode = .byTruncatingTail return paragraphStyle } var compactModeTimeFont: NSFont { return NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .regular) } extension NSView { var hasDarkAppearance: Bool { if #available(OSX 10.14, *) { switch effectiveAppearance.name { case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark: return true default: return false } } else { switch effectiveAppearance.name { case .vibrantDark: return true default: return false } } } } class StatusItemView: NSView { // MARK: Private variables private let locationView = NSTextField(labelWithString: "Hello") private let timeView = NSTextField(labelWithString: "Mon 19:14 PM") private var operationsObject: TimezoneDataOperations { return TimezoneDataOperations(with: dataObject) } private var timeAttributes: [NSAttributedString.Key: AnyObject] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let attributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] return attributes } private var textFontAttributes: [NSAttributedString.Key: Any] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let textFontAttributes = [ NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] return textFontAttributes } // MARK: Public var dataObject: TimezoneData! { didSet { initialSetup() } } override init(frame frameRect: NSRect) { super.init(frame: frameRect) [timeView, locationView].forEach { $0.wantsLayer = true $0.applyDefaultStyle() $0.translatesAutoresizingMaskIntoConstraints = false addSubview($0) } timeView.disableWrapping() NSLayoutConstraint.activate([ locationView.leadingAnchor.constraint(equalTo: leadingAnchor), locationView.trailingAnchor.constraint(equalTo: trailingAnchor), locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7), locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35), ]) NSLayoutConstraint.activate([ timeView.leadingAnchor.constraint(equalTo: leadingAnchor), timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), timeView.topAnchor.constraint(equalTo: locationView.bottomAnchor), timeView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) } @available(OSX 10.14, *) override func viewDidChangeEffectiveAppearance() { super.viewDidChangeEffectiveAppearance() updateTimeInMenubar() } func updateTimeInMenubar() { locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes) } private func initialSetup() { locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes) } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) guard let mainDelegate = NSApplication.shared.delegate as? AppDelegate else { return } mainDelegate.togglePanel(event) } } ================================================ FILE: Clocker/Onboarding/FinalOnboardingViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit struct EmailSignupConstants { static let CLEmailSignupEmailProperty = "email" static let CLOperatingSystemVersion = "OS" static let CLClockerVersion = "Clocker version" static let CLAppFeedbackDateProperty = "date" static let CLAppLanguageKey = "language" } class FinalOnboardingViewController: NSViewController { @IBOutlet var titleLabel: NSTextField! @IBOutlet var subtitleLabel: NSTextField! @IBOutlet var accesoryLabel: NSTextField! @IBOutlet var accessoryImageView: NSImageView! @IBOutlet var emailTextField: NSTextField! @IBOutlet var localizationButton: PointingHandCursorButton! override func viewDidLoad() { super.viewDidLoad() titleLabel.stringValue = "You're all set!".localized() subtitleLabel.stringValue = "Thank you for the details.".localized() accesoryLabel.stringValue = "You'll see a clock icon in your Menu Bar when you launch the app. If you'd like to see a dock icon, go to Preferences.".localized() accessoryImageView.image = Themer.shared().menubarOnboardingImage() emailTextField.isHidden = true setupLocalizationButton() } private func setupLocalizationButton() { let mutableParaghStyle = NSMutableParagraphStyle() mutableParaghStyle.alignment = .center let underlineRange = NSRange(location: 0, length: 9) let originalText = NSMutableAttributedString(string: "Follow us on Twitter for occasional updates!") originalText.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: Int8(NSUnderlineStyle.single.rawValue)), range: underlineRange) originalText.addAttribute(NSAttributedString.Key.foregroundColor, value: Themer.shared().mainTextColor(), range: NSRange(location: 0, length: localizationButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.font, value: (localizationButton?.font)!, range: NSRange(location: 0, length: localizationButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: mutableParaghStyle, range: NSRange(location: 0, length: localizationButton.attributedTitle.string.count)) localizationButton.attributedTitle = originalText } @IBAction func localizationAction(_: Any) { guard let localizationURL = URL(string: AboutUsConstants.TwitterFollowIntentLink), let languageCode = Locale.preferredLanguages.first else { return } NSWorkspace.shared.open(localizationURL) // Log this let custom: [String: Any] = ["Language": languageCode] Logger.log(object: custom, for: "Opened Localization Link") guard let parentVC = parent as? OnboardingParentViewController else { return } parentVC.performFinalStepsBeforeFinishing() } override func viewWillAppear() { super.viewWillAppear() emailTextField.becomeFirstResponder() } } ================================================ FILE: Clocker/Onboarding/Onboarding.storyboard ================================================ ================================================ FILE: Clocker/Onboarding/OnboardingController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class OnboardingController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() window?.setAccessibilityIdentifier("OnboardingWindow") window?.titlebarAppearsTransparent = true window?.backgroundColor = Themer.shared().mainBackgroundColor() window?.delegate = self window?.standardWindowButton(.miniaturizeButton)?.isHidden = true window?.standardWindowButton(.zoomButton)?.isHidden = true window?.standardWindowButton(.closeButton)?.isHidden = true } override func showWindow(_ sender: Any?) { super.showWindow(sender) window?.center() } func launch() { showWindow(nil) NSApp.activate(ignoringOtherApps: true) } } extension OnboardingController: NSWindowDelegate { func windowWillClose(_: Notification) { if let contentViewController = window?.contentViewController as? OnboardingParentViewController { contentViewController.logExitPoint() } } } ================================================ FILE: Clocker/Onboarding/OnboardingParentViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import StartupKit extension NSStoryboard.SceneIdentifier { static let welcomeIdentifier = NSStoryboard.SceneIdentifier("welcomeVC") static let onboardingPermissionsIdentifier = NSStoryboard.SceneIdentifier("onboardingPermissionsVC") static let startAtLoginIdentifier = NSStoryboard.SceneIdentifier("startAtLoginVC") static let onboardingSearchIdentifier = NSStoryboard.SceneIdentifier("onboardingSearchVC") static let finalOnboardingIdentifier = NSStoryboard.SceneIdentifier("finalOnboardingVC") } private enum OnboardingType: Int { case welcome case permissions case launchAtLogin case search case final case complete // Added for logging purposes } class OnboardingParentViewController: NSViewController { @IBOutlet private var containerView: NSView! @IBOutlet private var negativeButton: NSButton! @IBOutlet private var backButton: NSButton! @IBOutlet private var positiveButton: NSButton! private lazy var startupManager = StartupManager() private lazy var welcomeVC = (storyboard?.instantiateController(withIdentifier: .welcomeIdentifier) as? OnboardingWelcomeViewController) private lazy var permissionsVC = (storyboard?.instantiateController(withIdentifier: .onboardingPermissionsIdentifier) as? OnboardingPermissionsViewController) private lazy var startAtLoginVC = (storyboard?.instantiateController(withIdentifier: .startAtLoginIdentifier) as? StartAtLoginViewController) private lazy var onboardingSearchVC = (storyboard?.instantiateController(withIdentifier: .onboardingSearchIdentifier) as? OnboardingSearchController) private lazy var finalOnboardingVC = (storyboard?.instantiateController(withIdentifier: .finalOnboardingIdentifier) as? FinalOnboardingViewController) override func viewDidLoad() { super.viewDidLoad() setup() } private func setup() { setupWelcomeScreen() setupUI() } private func setupWelcomeScreen() { guard let firstVC = welcomeVC else { assertionFailure() return } addChildIfNeccessary(firstVC) containerView.addSubview(firstVC.view) firstVC.view.frame = containerView.bounds } private func setupUI() { setIdentifiersForTests() positiveButton.title = NSLocalizedString("Get Started", comment: "Title for Welcome View Controller's Continue Button") positiveButton.tag = OnboardingType.welcome.rawValue backButton.tag = OnboardingType.welcome.rawValue [negativeButton, backButton].forEach { $0?.isHidden = true } if #available(OSX 11.0, *) { negativeButton.controlSize = .large positiveButton.controlSize = .large backButton.controlSize = .large } backButton.title = NSLocalizedString("Back", comment: "Button title for going back to the previous screen") } private func setIdentifiersForTests() { positiveButton.setAccessibilityIdentifier("Forward") negativeButton.setAccessibilityIdentifier("Alternate") backButton.setAccessibilityIdentifier("Backward") } @IBAction func negativeAction(_: Any) { guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else { assertionFailure() return } addChildIfNeccessary(toViewController) shouldStartAtLogin(false) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.positiveButton.tag = OnboardingType.search.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } @IBAction func continueOnboarding(_: NSButton) { if positiveButton.tag == OnboardingType.welcome.rawValue { navigateToPermissions() } else if positiveButton.tag == OnboardingType.permissions.rawValue { navigateToStartAtLogin() } else if positiveButton.tag == OnboardingType.launchAtLogin.rawValue { navigateToSearch() } else if positiveButton.tag == OnboardingType.search.rawValue { navigateToFinalStage() } else { performFinalStepsBeforeFinishing() } } private func navigateToPermissions() { guard let fromViewController = welcomeVC, let toViewController = permissionsVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.positiveButton.tag = OnboardingType.permissions.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.backButton.isHidden = false } } private func navigateToStartAtLogin() { guard let fromViewController = permissionsVC, let toViewController = startAtLoginVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.permissions.rawValue self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = "Open Clocker At Login".localized() self.negativeButton.isHidden = false } } private func navigateToSearch() { guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else { assertionFailure() return } addChildIfNeccessary(toViewController) shouldStartAtLogin(true) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.tag = OnboardingType.search.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } private func fetchLocalTimezone() { let identifier = TimeZone.autoupdatingCurrent.identifier let currentTimezone = TimezoneData() currentTimezone.timezoneID = identifier currentTimezone.setLabel(identifier) currentTimezone.formattedAddress = identifier currentTimezone.isSystemTimezone = true currentTimezone.placeID = "Home" let operations = TimezoneDataOperations(with: currentTimezone, store: DataStore.shared()) operations.saveObject(at: 0) } private func navigateToFinalStage() { if UserDefaults.standard.object(forKey: UserDefaultKeys.installHomeIndicatorObject) == nil, DataStore.shared().timezones().isEmpty { fetchLocalTimezone() UserDefaults.standard.set(1, forKey: UserDefaultKeys.installHomeIndicatorObject) } guard let fromViewController = onboardingSearchVC, let toViewController = finalOnboardingVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.search.rawValue self.positiveButton.tag = OnboardingType.final.rawValue self.positiveButton.title = "Launch Clocker".localized() } } func performFinalStepsBeforeFinishing() { positiveButton.tag = OnboardingType.complete.rawValue view.window?.close() if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.onboardingTestsLaunchArgument) == false { UserDefaults.standard.set(true, forKey: UserDefaultKeys.showOnboardingFlow) } // Install the menubar option! let appDelegate = NSApplication.shared.delegate as? AppDelegate appDelegate?.continueUsually() } private func addChildIfNeccessary(_ viewController: NSViewController) { if children.contains(viewController) == false { addChild(viewController) } } @IBAction func back(_: Any) { if backButton.tag == OnboardingType.welcome.rawValue { goBackToWelcomeScreen() } else if backButton.tag == OnboardingType.permissions.rawValue { goBackToPermissions() } else if backButton.tag == OnboardingType.launchAtLogin.rawValue { goBackToStartAtLogin() } else if backButton.tag == OnboardingType.search.rawValue { goBackToSearch() } } private func goBackToSearch() { guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.search.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } private func goBackToStartAtLogin() { guard let fromViewController = onboardingSearchVC, let toViewController = startAtLoginVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.backButton.tag = OnboardingType.permissions.rawValue self.positiveButton.title = "Open Clocker At Login".localized() self.negativeButton.isHidden = false } } private func goBackToPermissions() { // We're on StartAtLogin VC and we have to go back to Permissions guard let fromViewController = startAtLoginVC, let toViewController = permissionsVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.permissions.rawValue self.backButton.tag = OnboardingType.welcome.rawValue self.negativeButton.isHidden = true self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") } } private func goBackToWelcomeScreen() { guard let fromViewController = permissionsVC, let toViewController = welcomeVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.welcome.rawValue self.backButton.isHidden = true self.positiveButton.title = NSLocalizedString("Get Started", comment: "Title for Welcome View Controller's Continue Button") } } private func shouldStartAtLogin(_ shouldStart: Bool) { // If tests are going on, we don't want to enable/disable launch at login! if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.onboardingTestsLaunchArgument) { return } UserDefaults.standard.set(shouldStart ? 1 : 0, forKey: UserDefaultKeys.startAtLogin) startupManager.toggleLogin(shouldStart) shouldStart ? Logger.log(object: nil, for: "Enable Launch at Login while Onboarding") : Logger.log(object: nil, for: "Disable Launch at Login while Onboarding") } func logExitPoint() { let currentViewController = currentController() Logger.log(object: currentViewController, for: "Onboarding Process Exit") } private func currentController() -> [String: String] { switch positiveButton.tag { case 0: return ["Onboarding Process Interrupted": "Welcome View"] case 1: return ["Onboarding Process Interrupted": "Onboarding Permissions View"] case 2: return ["Onboarding Process Interrupted": "Start At Login View"] case 3: return ["Onboarding Process Interrupted": "Onboarding Search View"] case 4: return ["Onboarding Process Interrupted": "Finish Onboarding View"] case 5: return ["Onboarding Process Completed": "Successfully"] default: return ["Onboarding Process Interrupted": "Error"] } } } ================================================ FILE: Clocker/Onboarding/OnboardingPermissionsViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class OnboardingPermissionsViewController: NSViewController { @IBOutlet var reminderGrantButton: NSButton! @IBOutlet var calendarGrantButton: NSButton! @IBOutlet var reminderView: NSView! @IBOutlet var calendarView: NSView! @IBOutlet var locationView: NSView! @IBOutlet var reminderActivityIndicator: NSProgressIndicator! @IBOutlet var calendarActivityIndicator: NSProgressIndicator! @IBOutlet var locationActivityIndicator: NSProgressIndicator! @IBOutlet var appLabel: NSTextField! @IBOutlet var onboardingTypeLabel: NSTextField! @IBOutlet var reminderHeaderLabel: NSTextField! @IBOutlet var reminderDetailLabel: NSTextField! @IBOutlet var calendarHeaderLabel: NSTextField! @IBOutlet var calendarDetailLabel: NSTextField! @IBOutlet var locationHeaderLabel: NSTextField! @IBOutlet var locationDetailLabel: NSTextField! @IBOutlet var privacyLabel: NSTextField! override func viewDidLoad() { super.viewDidLoad() [calendarView, reminderView].forEach { $0?.applyShadow() } setup() } override func viewWillAppear() { super.viewWillAppear() setupButtons() } private func setup() { appLabel.stringValue = NSLocalizedString("Permissions Tab", comment: "Title for Permissions screen") onboardingTypeLabel.stringValue = "Your data doesn't leave your device 🔐" reminderHeaderLabel.stringValue = NSLocalizedString("Reminders Access Title",comment: "Title for Reminders Access Label") reminderDetailLabel.stringValue = "Set reminders in the timezone of the location of your choice. Your reminders are stored in the default Reminders app." calendarHeaderLabel.stringValue = NSLocalizedString("Calendar Access Title",comment: "Title for Calendar access label") calendarDetailLabel.stringValue = "Calendar Detail".localized() [calendarHeaderLabel, calendarDetailLabel, privacyLabel, reminderDetailLabel, reminderHeaderLabel, onboardingTypeLabel, appLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } } private func setupButtons() { // if LocationController.sharedInstance.locationAccessGranted() { // locationButton.title = "Granted" // } else if LocationController.sharedInstance.locationAccessDenied() { // locationButton.title = "Denied" // } else if LocationController.sharedInstance.locationAccessNotDetermined() { // locationButton.title = "Grant" // } else { // locationButton.title = "Unexpected" // } if EventCenter.sharedCenter().calendarAccessGranted() { calendarGrantButton.title = "Granted".localized() } else if EventCenter.sharedCenter().calendarAccessDenied() { calendarGrantButton.title = "Denied".localized() } else if EventCenter.sharedCenter().calendarAccessNotDetermined() { calendarGrantButton.title = "Grant".localized() } else { calendarGrantButton.title = "Unexpected".localized() } if EventCenter.sharedCenter().reminderAccessGranted() { reminderGrantButton.title = "Granted".localized() } else if EventCenter.sharedCenter().reminderAccessDenied() { reminderGrantButton.title = "Denied".localized() } else if EventCenter.sharedCenter().reminderAccessNotDetermined() { reminderGrantButton.title = "Grant".localized() } else { reminderGrantButton.title = "Unexpected".localized() } } @IBAction func calendarAction(_: Any) { let eventCenter = EventCenter.sharedCenter() if eventCenter.calendarAccessNotDetermined() { calendarActivityIndicator.startAnimation(nil) eventCenter.requestAccess(to: .event, completionHandler: { [weak self] granted in OperationQueue.main.addOperation { guard let self = self else { return } self.calendarActivityIndicator.stopAnimation(nil) if granted { self.calendarGrantButton.title = "Granted".localized() self.view.window?.orderBack(nil) NSApp.activate(ignoringOtherApps: true) // Used to update CalendarViewController's view NotificationCenter.default.post(name: .calendarAccessGranted, object: nil) } else { Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted") } } }) } else if eventCenter.calendarAccessGranted() { calendarGrantButton.title = "Granted".localized() } else { calendarGrantButton.title = "Denied".localized() } } @IBAction func remindersAction(_: NSButton) { let eventCenter = EventCenter.sharedCenter() if eventCenter.reminderAccessNotDetermined() { reminderActivityIndicator.startAnimation(nil) eventCenter.requestAccess(to: .reminder, completionHandler: { granted in OperationQueue.main.addOperation { self.reminderActivityIndicator.stopAnimation(nil) } if granted { OperationQueue.main.addOperation { self.view.window?.orderBack(nil) NSApp.activate(ignoringOtherApps: true) self.reminderGrantButton.title = "Granted".localized() } } else { Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted") } }) } else if eventCenter.reminderAccessGranted() { reminderGrantButton.title = "Granted".localized() } else { reminderGrantButton.title = "Denied".localized() } } } ================================================ FILE: Clocker/Onboarding/OnboardingSearchController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit /* Behaviour is as follows: - When the user first sees the screen, show all available timezones - When the user searches and tap enters, filter on both cities/locations + timezones - On double-tapping, add the timezone to the list - Show confirmation with undo screen */ class OnboardingSearchController: NSViewController { @IBOutlet private var appName: NSTextField! @IBOutlet private var onboardingTypeLabel: NSTextField! @IBOutlet private var searchBar: NSSearchField! @IBOutlet private var resultsTableView: NSTableView! @IBOutlet private var accessoryLabel: NSTextField! @IBOutlet var undoButton: NSButton! private var searchResultsDataSource: SearchDataSource? private var dataTask: URLSessionDataTask? = .none private var themeDidChangeNotification: NSObjectProtocol? private var geocodingKey: String = { guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"), let dictionary = NSDictionary(contentsOfFile: path), let apiKey = dictionary["GeocodingKey"] as? String else { assertionFailure("Unable to find the API key") return "" } return apiKey }() override func viewDidLoad() { super.viewDidLoad() view.wantsLayer = true searchResultsDataSource = SearchDataSource(with: searchBar, location: .onboarding) resultsTableView.isHidden = true resultsTableView.delegate = self resultsTableView.setAccessibility("ResultsTableView") resultsTableView.dataSource = self resultsTableView.target = self resultsTableView.doubleAction = #selector(doubleClickAction(_:)) if #available(OSX 11.0, *) { resultsTableView.style = .plain } setup() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setup() } resultsTableView.reloadData() func setupUndoButton() { let font = NSFont(name: "Avenir", size: 13) ?? NSFont.systemFont(ofSize: 13) let attributes = [NSAttributedString.Key.foregroundColor: NSColor.linkColor, NSAttributedString.Key.font: font] undoButton.attributedTitle = NSAttributedString(string: "UNDO", attributes: attributes) undoButton.setAccessibility("UndoButton") } setupUndoButton() } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } @objc func doubleClickAction(_ tableView: NSTableView) { [accessoryLabel].forEach { $0?.isHidden = false } if tableView.selectedRow >= 0, tableView.selectedRow < (searchResultsDataSource?.resultsCount() ?? 0) { let selectedType = searchResultsDataSource?.placeForRow(resultsTableView.selectedRow) switch selectedType { case .city: if let filteredGoogleResult = searchResultsDataSource?.retrieveFilteredResultFromGoogleAPI(resultsTableView.selectedRow) { addTimezoneToDefaults(filteredGoogleResult) } return case .timezone: cleanupAfterInstallingTimezone() return case .none: return } } } private func cleanupAfterInstallingTimezone() { let data = TimezoneData() data.setLabel(UserDefaultKeys.emptyString) if let currentSelection = searchResultsDataSource?.retrieveSelectedTimezone(resultsTableView.selectedRow) { let metaInfo = metadata(for: currentSelection) data.timezoneID = metaInfo.0.name data.formattedAddress = metaInfo.1.formattedName data.selectionType = .timezone data.isSystemTimezone = metaInfo.0.name == NSTimeZone.system.identifier let operationObject = TimezoneDataOperations(with: data, store: DataStore.shared()) operationObject.saveObject() searchResultsDataSource?.cleanupFilterArray() searchResultsDataSource?.timezoneFilteredArray = [] searchResultsDataSource?.calculateChangesets() searchBar.stringValue = UserDefaultKeys.emptyString accessoryLabel.stringValue = "Added \(metaInfo.1.formattedName)." undoButton.isHidden = false setupLabelHidingTimer() resultsTableView.reloadData() resultsTableView.isHidden = true } } private func metadata(for selection: TimezoneMetadata) -> (NSTimeZone, TimezoneMetadata) { if selection.formattedName == "Anywhere on Earth" { return (NSTimeZone(name: "GMT-1200")!, selection) } else if selection.formattedName == "UTC" { return (NSTimeZone(name: "GMT")!, selection) } else { return (selection.timezone, selection) } } private func setupLabelHidingTimer() { Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { _ in OperationQueue.main.addOperation { self.setInfoLabel(UserDefaultKeys.emptyString) } } } private func addTimezoneToDefaults(_ timezone: TimezoneData) { if resultsTableView.selectedRow == -1 { setInfoLabel(PreferencesConstants.noTimezoneSelectedErrorMessage) setupLabelHidingTimer() return } if DataStore.shared().timezones().count >= 100 { setInfoLabel(PreferencesConstants.maxTimezonesErrorMessage) setupLabelHidingTimer() return } guard let latitude = timezone.latitude, let longitude = timezone.longitude else { setInfoLabel("Unable to fetch latitude/longitude. Try again.") return } fetchTimezone(for: latitude, and: longitude, timezone) } // We want to display the undo button only if we've added a timezone. // If else, we want it hidden. This below method ensures that. private func setInfoLabel(_ text: String) { accessoryLabel.stringValue = text undoButton.isHidden = true } /// Returns true if there's an error. private func handleEdgeCase(for response: Data?) -> Bool { func setErrorPlaceholders() { setInfoLabel("No timezone found! Try entering an exact name.") searchBar.placeholderString = placeholders.randomElement() } guard let json = response, let jsonUnserialized = try? JSONSerialization.jsonObject(with: json, options: .allowFragments), let unwrapped = jsonUnserialized as? [String: Any] else { setErrorPlaceholders() return true } if let status = unwrapped["status"] as? String, status == ResultStatus.zeroResults { setErrorPlaceholders() return true } return false } private func fetchTimezone(for latitude: Double, and longitude: Double, _ dataObject: TimezoneData) { if NetworkManager.isConnected() == false || ProcessInfo.processInfo.arguments.contains("mockTimezoneDown") { setInfoLabel(PreferencesConstants.noInternetConnectivityError) searchResultsDataSource?.cleanupFilterArray() resultsTableView.reloadData() return } resultsTableView.isHidden = true let tuple = "\(latitude),\(longitude)" let timeStamp = Date().timeIntervalSince1970 let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)×tamp=\(timeStamp)&key=\(geocodingKey)" NetworkManager.task(with: urlString) { [weak self] response, error in guard let self = self else { return } OperationQueue.main.addOperation { if self.handleEdgeCase(for: response) == true { return } if error == nil, let json = response, let response = json.decodeTimezone() { if self.resultsTableView.selectedRow >= 0, self.resultsTableView.selectedRow < (self.searchResultsDataSource?.resultsCount()) ?? 0 { var filteredAddress = "Error" if let address = dataObject.formattedAddress { filteredAddress = address.filteredName() } let newTimeZone = [ UserDefaultKeys.timezoneID: response.timeZoneId, UserDefaultKeys.timezoneName: filteredAddress, UserDefaultKeys.placeIdentifier: dataObject.placeID!, "latitude": latitude, "longitude": longitude, "nextUpdate": UserDefaultKeys.emptyString, UserDefaultKeys.customLabel: filteredAddress, ] as [String: Any] DataStore.shared().addTimezone(TimezoneData(with: newTimeZone)) Logger.log(object: ["PlaceName": filteredAddress, "Timezone": response.timeZoneId], for: "Filtered Address") self.accessoryLabel.stringValue = "Added \(filteredAddress)." self.undoButton.isHidden = false Logger.log(object: ["Place Name": filteredAddress], for: "Added Timezone while Onboarding") } // Cleanup. self.resetSearchView() } else { OperationQueue.main.addOperation { if error?.localizedDescription == "The Internet connection appears to be offline." { self.setInfoLabel(PreferencesConstants.noInternetConnectivityError) } else { self.setInfoLabel(PreferencesConstants.noInternetConnectivityError) } } } } } } private var placeholders: [String] = ["New York", "Los Angeles", "Chicago", "Moscow", "Tokyo", "Istanbul", "Beijing", "Shanghai", "Sao Paulo", "Cairo", "Mexico City", "London", "Seoul", "Copenhagen", "Tel Aviv", "Bern", "San Francisco", "Los Angeles", "Sydney NSW", "Berlin"] private func setup() { appName.stringValue = "Quick Add Locations".localized() onboardingTypeLabel.stringValue = "More search options in Clocker Preferences.".localized() setInfoLabel(UserDefaultKeys.emptyString) searchBar.bezelStyle = .roundedBezel searchBar.placeholderString = "Press Enter to Search!" searchBar.delegate = self searchBar.setAccessibility("MainSearchField") resultsTableView.backgroundColor = Themer.shared().mainBackgroundColor() resultsTableView.enclosingScrollView?.backgroundColor = Themer.shared().mainBackgroundColor() [appName, onboardingTypeLabel, accessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } [accessoryLabel].forEach { $0?.isHidden = true } } @IBAction func search(_ sender: NSSearchField) { resultsTableView.deselectAll(nil) let searchString = sender.stringValue if searchString.isEmpty { resetSearchView() setInfoLabel(UserDefaultKeys.emptyString) return } if resultsTableView.isHidden { resultsTableView.isHidden = false } accessoryLabel.isHidden = false NSObject.cancelPreviousPerformRequests(withTarget: self) perform(#selector(OnboardingSearchController.actualSearch), with: nil, afterDelay: 0.2) } fileprivate func resetIfNeccesary(_ searchString: String) { if searchString.isEmpty { resetSearchView() setInfoLabel(UserDefaultKeys.emptyString) } } @objc func actualSearch() { func setupForError() { searchResultsDataSource?.calculateChangesets() resultsTableView.isHidden = true } let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" var searchString = searchBar.stringValue let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines) searchString = words.joined(separator: UserDefaultKeys.emptyString) if searchString.count < 3 { resetIfNeccesary(searchString) return } let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(geocodingKey)&language=\(userPreferredLanguage)" dataTask = NetworkManager.task(with: urlString, completionHandler: { [weak self] response, error in guard let self = self else { return } OperationQueue.main.addOperation { let currentSearchBarValue = self.searchBar.stringValue let words = currentSearchBarValue.components(separatedBy: CharacterSet.whitespacesAndNewlines) if words.joined(separator: UserDefaultKeys.emptyString) != searchString { return } self.searchResultsDataSource?.cleanupFilterArray() self.searchResultsDataSource?.timezoneFilteredArray = [] if let errorPresent = error { self.findLocalSearchResultsForTimezones() if self.searchResultsDataSource?.timezoneFilteredArray.count == 0 { self.presentErrorMessage(errorPresent.localizedDescription) setupForError() return } self.prepareUIForPresentingResults() return } guard let data = response else { self.setInfoLabel(PreferencesConstants.tryAgainMessage) setupForError() return } let searchResults = data.decode() if searchResults?.status == ResultStatus.zeroResults { self.setInfoLabel("No results! 😔 Try entering the exact name.") setupForError() return } self.appendResultsToFilteredArray(searchResults!.results) self.findLocalSearchResultsForTimezones() self.prepareUIForPresentingResults() } }) } private func presentErrorMessage(_ errorMessage: String) { if errorMessage == PreferencesConstants.offlineErrorMessage { setInfoLabel(PreferencesConstants.noInternetConnectivityError) } else { setInfoLabel(PreferencesConstants.tryAgainMessage) } } private func findLocalSearchResultsForTimezones() { let lowercasedSearchString = searchBar.stringValue.lowercased() searchResultsDataSource?.searchTimezones(lowercasedSearchString) } private func prepareUIForPresentingResults() { setInfoLabel(UserDefaultKeys.emptyString) if let dataSource = searchResultsDataSource, dataSource.calculateChangesets() { resultsTableView.isHidden = false resultsTableView.reloadData() } } private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) { let finalTimezones: [TimezoneData] = results.map { result -> TimezoneData in let location = result.geometry.location let latitude = location.lat let longitude = location.lng let formattedAddress = result.formattedAddress let totalPackage = [ "latitude": latitude, "longitude": longitude, UserDefaultKeys.timezoneName: formattedAddress, UserDefaultKeys.customLabel: formattedAddress, UserDefaultKeys.timezoneID: UserDefaultKeys.emptyString, UserDefaultKeys.placeIdentifier: result.placeId, ] as [String: Any] return TimezoneData(with: totalPackage) } searchResultsDataSource?.setFilteredArrayValue(finalTimezones) } private func resetSearchView() { searchResultsDataSource?.cleanupFilterArray() searchResultsDataSource?.timezoneFilteredArray = [] searchResultsDataSource?.calculateChangesets() resultsTableView.reloadData() searchBar.stringValue = UserDefaultKeys.emptyString searchBar.placeholderString = "Press Enter to Search" } @IBAction func undoAction(_: Any) { DataStore.shared().removeLastTimezone() setInfoLabel("Removed.") } } extension OnboardingSearchController: NSTableViewDataSource { func numberOfRows(in _: NSTableView) -> Int { return searchResultsDataSource?.resultsCount() ?? 0 } func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { if let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCellView"), owner: self) as? ResultTableViewCell, row >= 0, row < (searchResultsDataSource?.resultsCount() ?? 0) { let currentSelection = searchResultsDataSource?.retrieveResult(row) if let timezone = currentSelection as? TimezoneMetadata { result.result.stringValue = " \(timezone.formattedName)" } else if let location = currentSelection as? TimezoneData { result.result.stringValue = " \(location.formattedAddress ?? "Place Name")" } result.result.textColor = Themer.shared().mainTextColor() return result } return nil } } extension OnboardingSearchController: NSTableViewDelegate { func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat { if row == 0, searchResultsDataSource?.resultsCount() == 0 { return 30 } return 36 } func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { return searchResultsDataSource?.resultsCount() == 0 ? row != 0 : true } func tableView(_: NSTableView, rowViewForRow _: Int) -> NSTableRowView? { return OnboardingSelectionTableRowView() } } class ResultSectionHeaderTableViewCell: NSTableCellView { @IBOutlet var headerLabel: NSTextField! } class OnboardingSelectionTableRowView: NSTableRowView { override func drawSelection(in _: NSRect) { if selectionHighlightStyle != .none { let selectionRect = bounds.insetBy(dx: 1, dy: 1) NSColor(calibratedWhite: 0.4, alpha: 1).setStroke() NSColor(calibratedWhite: 0.4, alpha: 1).setFill() let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6) selectionPath.fill() selectionPath.stroke() } } } class ResultTableViewCell: NSTableCellView { @IBOutlet var result: NSTextField! } extension OnboardingSearchController: NSSearchFieldDelegate { func control(_ control: NSControl, textView _: NSTextView, doCommandBy commandSelector: Selector) -> Bool { guard let searchField = control as? NSSearchField else { return false } if commandSelector == #selector(NSResponder.insertNewline(_:)) { self.search(searchField) return true } else if commandSelector == #selector(NSResponder.deleteForward(_:)) || commandSelector == #selector(NSResponder.deleteBackward(_:)) { // Handle DELETE key self.search(searchField) return false } Logger.info("Not Handled") // return true if the action was handled; otherwise false return false } func searchFieldDidEndSearching(_ sender: NSSearchField) { search(sender) } } ================================================ FILE: Clocker/Onboarding/OnboardingWelcomeViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class OnboardingWelcomeViewController: NSViewController { @IBOutlet var appLabel: NSTextField! @IBOutlet var accessoryLabel: NSTextField! override func viewDidLoad() { super.viewDidLoad() setup() } private func setup() { appLabel.stringValue = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "Clocker" accessoryLabel.stringValue = NSLocalizedString("It only takes 3 steps to set up Clocker.", comment: "App Setup Description") [appLabel, accessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } } } ================================================ FILE: Clocker/Onboarding/StartAtLoginViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class StartAtLoginViewController: NSViewController { @IBOutlet var appName: NSTextField! @IBOutlet var onboardingType: NSTextField! @IBOutlet var accessoryLabel: NSTextField! @IBOutlet var privacyLabel: NSTextField! override func viewDidLoad() { super.viewDidLoad() setup() } private func setup() { appName.stringValue = "Launch at Login".localized() onboardingType.stringValue = "This can be configured later in Clocker Preferences.".localized() // स्टार्टअप पर स्वचालित रूप से ऐप खोलना चाहिए accessoryLabel.stringValue = "Should Clocker open automatically on startup?".localized() privacyLabel.stringValue = " " [privacyLabel, accessoryLabel, onboardingType, appName].forEach { $0?.textColor = Themer.shared().mainTextColor() } } } ================================================ FILE: Clocker/Onboarding/WelcomeView.xib ================================================ ================================================ FILE: Clocker/Overall App/AppDefaults.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class AppDefaults { class func initialize(with store: DataStore, defaults: UserDefaults) { initializeDefaults(with: store, defaults: defaults) } private class func initializeDefaults(with store: DataStore, defaults: UserDefaults) { let timezones = store.timezones() let selectedCalendars = store.selectedCalendars() // Register the usual suspects defaults.register(defaults: defaultsDictionary()) store.setTimezones(timezones) defaults.set(selectedCalendars, forKey: UserDefaultKeys.selectedCalendars) } private class func defaultsDictionary() -> [String: Any] { let calendars: [String] = [] return [UserDefaultKeys.themeKey: 0, UserDefaultKeys.displayFutureSliderKey: 0, UserDefaultKeys.selectedTimeZoneFormatKey: 0, // 12-hour format UserDefaultKeys.relativeDateKey: 0, UserDefaultKeys.showDayInMenu: 0, UserDefaultKeys.showDateInMenu: 1, UserDefaultKeys.showPlaceInMenu: 0, UserDefaultKeys.startAtLogin: 0, UserDefaultKeys.sunriseSunsetTime: 1, UserDefaultKeys.userFontSizePreference: 4, UserDefaultKeys.showUpcomingEventView: "YES", UserDefaultKeys.showAppInForeground: 0, UserDefaultKeys.futureSliderRange: 0, UserDefaultKeys.showAllDayEventsInUpcomingView: 1, UserDefaultKeys.showMeetingInMenubar: 0, UserDefaultKeys.truncateTextLength: 30, UserDefaultKeys.selectedCalendars: calendars, UserDefaultKeys.appDisplayOptions: 0, UserDefaultKeys.menubarCompactMode: 1] } } extension UserDefaults { // Use this with caution. Exposing this for debugging purposes only. func wipe(for bundleID: String = "com.abhishek.Clocker") { removePersistentDomain(forName: bundleID) } } ================================================ FILE: Clocker/Overall App/AppKit + Additions.swift ================================================ // Copyright © 2015 Abhishek Banthia extension NSTextField { func applyDefaultStyle() { backgroundColor = NSColor.clear isEditable = false isBordered = false allowsDefaultTighteningForTruncation = true isAutomaticTextCompletionEnabled = false allowsCharacterPickerTouchBarItem = false } func disableWrapping() { usesSingleLineMode = false cell?.wraps = false cell?.isScrollable = true } } extension NSFont { func size(for string: String, width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize { let size = CGSize(width: width, height: Double.greatestFiniteMagnitude) var otherAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font: self] attributes.forEach { arg in let (key, value) = arg; otherAttributes[key] = value } return NSString(string: string).boundingRect(with: size, options: NSString.DrawingOptions.usesLineFragmentOrigin, attributes: attributes).size } } ================================================ FILE: Clocker/Overall App/ConfigExport.swift ================================================ // Copyright © 2015 Abhishek Banthia import CoreLoggerKit import CoreModelKit import Foundation struct ConfigExport { private func generateJSON(from store: DataStore) { let selectedKeys: Set = Set([ UserDefaultKeys.showOnboardingFlow, UserDefaultKeys.selectedTimeZoneFormatKey, UserDefaultKeys.themeKey, UserDefaultKeys.showDayInMenu, UserDefaultKeys.showDateInMenu, UserDefaultKeys.showPlaceInMenu, UserDefaultKeys.displayFutureSliderKey, UserDefaultKeys.startAtLogin, UserDefaultKeys.showAppInForeground, UserDefaultKeys.sunriseSunsetTime, UserDefaultKeys.userFontSizePreference, UserDefaultKeys.showUpcomingEventView, UserDefaultKeys.showAllDayEventsInUpcomingView, UserDefaultKeys.showMeetingInMenubar, UserDefaultKeys.truncateTextLength, UserDefaultKeys.futureSliderRange, UserDefaultKeys.selectedCalendars, UserDefaultKeys.appDisplayOptions, UserDefaultKeys.longStatusBarWarningMessage, UserDefaultKeys.menubarCompactMode, UserDefaultKeys.defaultMenubarMode, UserDefaultKeys.installHomeIndicatorObject, UserDefaultKeys.switchToCompactModeAlert, ]) let dictionaryRep = UserDefaults.standard.dictionaryRepresentation() var clockerPrefs: [String: Any] = [:] for (key, value) in dictionaryRep { if selectedKeys.contains(key) { Logger.info("Config Export: Key is \(key) and value is \(value)") clockerPrefs[key] = value } } do { let decodeJSON: [[String: Any]] = store.timezones().compactMap { data -> [String: Any]? in guard let customObject = TimezoneData.customObject(from: data) else { return nil } let timezoneDictionary: [String: Any] = [ "Name": customObject.formattedAddress ?? "", "Custom": customObject.customLabel ?? "", "TimezoneID": customObject.timezoneID ?? "N/A", "Is System": customObject.isSystemTimezone ? 1 : 0, "Is Favorite": customObject.isFavourite == 1 ? 1 : 0, "Sunrise or Sunset": customObject.isSunriseOrSunset ? 1 : 0, "Latitude": customObject.latitude ?? 0.0, "Longitude": customObject.longitude ?? 0.0, "Place Identifier": customObject.placeID ?? "0.0", "Selection Type": "\(customObject.selectionType)", ] return timezoneDictionary } let timezoneDict = ["Timezones": decodeJSON] clockerPrefs.merge(timezoneDict) { current, _ in current } guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let fileUrl = documentDirectoryUrl.appendingPathComponent("Persons.json") // Transform array into data and save it into file do { let data = try JSONSerialization.data(withJSONObject: clockerPrefs, options: []) try data.write(to: fileUrl, options: []) } catch { print(error) } let json = try JSONSerialization.data(withJSONObject: clockerPrefs, options: .prettyPrinted) print(json) } catch { Logger.info("Failure Observed \(error.localizedDescription)") } } } ================================================ FILE: Clocker/Overall App/DataStore.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit enum ViewType { case futureSlider case upcomingEventView case twelveHour case sunrise case showMeetingInMenubar case showAllDayEventsInMenubar case showAppInForeground case appDisplayOptions case dateInMenubar case placeInMenubar case dayInMenubar case menubarCompactMode case sync } class DataStore: NSObject { private static var sharedStore = DataStore(with: UserDefaults.standard) private var userDefaults: UserDefaults! private var ubiquitousStore: NSUbiquitousKeyValueStore? private var cachedTimezones: [Data] private var cachedMenubarTimezones: [Data] private static let timeFormatsWithSuffix: Set = Set([NSNumber(integerLiteral: 0), NSNumber(integerLiteral: 3), NSNumber(integerLiteral: 4), NSNumber(integerLiteral: 6), NSNumber(integerLiteral: 7)]) class func shared() -> DataStore { return sharedStore } init(with defaults: UserDefaults) { cachedTimezones = (defaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data]) ?? [] cachedMenubarTimezones = cachedTimezones.filter { let customTimezone = TimezoneData.customObject(from: $0) return customTimezone?.isFavourite == 1 } userDefaults = defaults super.init() setupSyncNotification() } func setupSyncNotification() { if shouldDisplay(.sync) { ubiquitousStore = NSUbiquitousKeyValueStore.default NotificationCenter.default.addObserver(self, selector: #selector(ubiquitousKeyValueStoreChanged), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default) let synchronizationResult = ubiquitousStore?.synchronize() ?? false let resultString = synchronizationResult ? "successfully" : "unsuccessfully" Logger.info("Ubiquitous Store synchronized \(resultString)") } else { NotificationCenter.default.removeObserver(self, name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: nil) } } @objc func ubiquitousKeyValueStoreChanged(_ notification: Notification) { let userInfo = notification.userInfo ?? [:] let ubiquitousStore = notification.object as? NSUbiquitousKeyValueStore Logger.info("Ubiquitous Store Changed: User Info is \(userInfo)") let currentTimezones = userDefaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data] let cloudTimezones = ubiquitousStore?.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data] let cloudLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey) as? Date) ?? Date() let defaultsLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.userDefaultsLastUpdateKey) as? Date) ?? Date() if cloudTimezones == currentTimezones { Logger.info("Ubiquitous Store timezones aren't equal to current timezones") } if defaultsLastUpdateDate.isLaterThanOrEqual(to: cloudLastUpdateDate) { Logger.info("Ubiquitous Store is stale as compared to User Defaults") } if cloudTimezones != currentTimezones, cloudLastUpdateDate.isLaterThanOrEqual(to: defaultsLastUpdateDate) { Logger.info("Syncing local timezones with data from the ☁️. ☁️ last update timestamp is recent") userDefaults.set(cloudTimezones, forKey: UserDefaultKeys.defaultPreferenceKey) userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey) NotificationCenter.default.post(name: DataStore.didSyncFromExternalSourceNotification, object: self) return } } func timezones() -> [Data] { return cachedTimezones } func setTimezones(_ timezones: [Data]?) { userDefaults.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey) userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey) cachedTimezones = timezones ?? [] cachedMenubarTimezones = cachedTimezones.filter { let customTimezone = TimezoneData.customObject(from: $0) return customTimezone?.isFavourite == 1 } // iCloud sync ubiquitousStore?.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey) ubiquitousStore?.set(Date(), forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey) } func menubarTimezones() -> [Data]? { return cachedMenubarTimezones } func selectedCalendars() -> [String]? { return userDefaults.array(forKey: UserDefaultKeys.selectedCalendars) as? [String] } // MARK: Date (May 8th) in Compact Menubar func shouldShowDateInMenubar() -> Bool { return shouldDisplay(.dateInMenubar) } // MARK: Day (Sun, Mon etc.) in Compact Menubar func shouldShowDayInMenubar() -> Bool { return shouldDisplay(.dayInMenubar) } func retrieve(key: String) -> Any? { return userDefaults.object(forKey: key) } func addTimezone(_ timezone: TimezoneData) { guard let encodedTimezone = NSKeyedArchiver.clocker_archive(with: timezone) else { return } var defaults: [Data] = timezones() defaults.append(encodedTimezone) setTimezones(defaults) } func removeLastTimezone() { var currentLineup = timezones() if currentLineup.isEmpty { return } currentLineup.removeLast() Logger.log(object: [:], for: "Undo Action Executed during Onboarding") setTimezones(currentLineup) } func timezoneFormat() -> NSNumber { return userDefaults.object(forKey: UserDefaultKeys.selectedTimeZoneFormatKey) as? NSNumber ?? NSNumber(integerLiteral: 0) } func isBufferRequiredForTwelveHourFormats() -> Bool { return DataStore.timeFormatsWithSuffix.contains(timezoneFormat()) } func shouldDisplay(_ type: ViewType) -> Bool { switch type { case .futureSlider: guard let value = retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber else { return false } return value != 1 // Display slider is 0 and Hide is 1. case .upcomingEventView: guard let value = retrieve(key: UserDefaultKeys.showUpcomingEventView) as? NSString else { return false } return value == "YES" case .twelveHour: return shouldDisplayHelper(UserDefaultKeys.selectedTimeZoneFormatKey) case .showAllDayEventsInMenubar: return shouldDisplayHelper(UserDefaultKeys.showAllDayEventsInUpcomingView) case .sunrise: return shouldDisplayHelper(UserDefaultKeys.sunriseSunsetTime) case .showMeetingInMenubar: return shouldDisplayHelper(UserDefaultKeys.showMeetingInMenubar) case .showAppInForeground: guard let value = retrieve(key: UserDefaultKeys.showAppInForeground) as? NSNumber else { return false } return value.isEqual(to: NSNumber(value: 1)) case .dateInMenubar: return shouldDisplayNonObjectHelper(UserDefaultKeys.showDateInMenu) case .placeInMenubar: return shouldDisplayHelper(UserDefaultKeys.showPlaceInMenu) case .dayInMenubar: return shouldDisplayNonObjectHelper(UserDefaultKeys.showDayInMenu) case .appDisplayOptions: return shouldDisplayHelper(UserDefaultKeys.appDisplayOptions) case .menubarCompactMode: guard let value = retrieve(key: UserDefaultKeys.menubarCompactMode) as? Int else { return false } return value == 0 case .sync: return shouldDisplayHelper(UserDefaultKeys.enableSyncKey) } } // MARK: Private private func shouldDisplayHelper(_ key: String) -> Bool { guard let value = retrieve(key: key) as? NSNumber else { return false } return value.isEqual(to: NSNumber(value: 0)) } // MARK: Some values are stored as plain integers; objectForKey: will return nil, so using integerForKey: private func shouldDisplayNonObjectHelper(_ key: String) -> Bool { let value = userDefaults.integer(forKey: key) return value == 0 } } extension DataStore { public static let didSyncFromExternalSourceNotification: NSNotification.Name = .init("didSyncFromExternalSourceNotification") } ================================================ FILE: Clocker/Overall App/DateFormatterManager.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class DateFormatterManager: NSObject { private static var dateFormatter = DateFormatter() private static var calendarDateFormatter = DateFormatter() private static var simpleFormatter = DateFormatter() private static var specializedFormatter = DateFormatter() private static var localizedForamtter = DateFormatter() private static var localizedSimpleFormatter = DateFormatter() private static var gregorianCalendar = Calendar(identifier: Calendar.Identifier.gregorian) private static var USLocale = Locale(identifier: "en_US") class func dateFormatter(with style: DateFormatter.Style, for timezoneIdentifier: String) -> DateFormatter { dateFormatter.dateStyle = style dateFormatter.timeStyle = style dateFormatter.locale = USLocale dateFormatter.timeZone = TimeZone(identifier: timezoneIdentifier) return dateFormatter } class func dateFormatterWithFormat(with style: DateFormatter.Style, format: String, timezoneIdentifier: String, locale: Locale = Locale(identifier: "en_US")) -> DateFormatter { if (specializedFormatter.dateStyle != style) { specializedFormatter.dateStyle = style } if (specializedFormatter.timeStyle != style) { specializedFormatter.timeStyle = style } if (specializedFormatter.dateFormat != format) { specializedFormatter.dateFormat = format } if (specializedFormatter.timeZone.identifier != timezoneIdentifier) { specializedFormatter.timeZone = TimeZone(identifier: timezoneIdentifier) } if (specializedFormatter.locale.identifier != locale.identifier) { specializedFormatter.locale = locale } return specializedFormatter } class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale _: Locale = Locale.autoupdatingCurrent) -> DateFormatter { dateFormatter.dateStyle = .none dateFormatter.timeStyle = .none dateFormatter.locale = Locale.autoupdatingCurrent dateFormatter.dateFormat = format dateFormatter.timeZone = TimeZone(identifier: timezoneIdentifier) return dateFormatter } class func localizedSimpleFormatter(_ format: String) -> DateFormatter { localizedSimpleFormatter.dateStyle = .none localizedSimpleFormatter.timeStyle = .none localizedSimpleFormatter.dateFormat = format localizedSimpleFormatter.locale = Locale.autoupdatingCurrent return localizedSimpleFormatter } } ================================================ FILE: Clocker/Overall App/Foundation + Additions.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreModelKit extension NSNotification.Name { static let themeDidChange = NSNotification.Name("ThemeDidChangeNotification") static let customLabelChanged = NSNotification.Name("CLCustomLabelChangedNotification") static let calendarAccessGranted = NSNotification.Name("CalendarAccessStatus") static let interfaceStyleDidChange = NSNotification.Name("AppleInterfaceThemeChangedNotification") } extension NSPasteboard.PasteboardType { static let dragSession = NSPasteboard.PasteboardType(rawValue: "public.text") } extension NSNib.Name { static let floatingWindowIdentifier = NSNib.Name("FloatingWindow") static let notesPopover = NSNib.Name("NotesPopover") static let panel = NSNib.Name("Panel") static let permissions = NSNib.Name("Permissions") static let onboardingPermissions = NSNib.Name("OnboardingPermissions") } extension NSImage.Name { static let permissionTabIcon = NSImage.Name("Privacy Dynamic") static let calendarTabIcon = NSImage.Name("Calendar Tab Dynamic") static let appearanceTabIcon = NSImage.Name("Appearance Dynamic") static let addIcon = NSImage.Name("Add") static let addDynamicIcon = NSImage.Name("Add Dynamic") static let privacyIcon = NSImage.Name("Privacy Dynamic") static let sortToggleIcon = NSImage.Name("Additional Preferences Dynamic") static let sortToggleAlternateIcon = NSImage.Name("Additional Preferences Highlighted Dynamic") static let menubarIcon = NSImage.Name("LightModeIcon") } public extension Data { // Extracting this out for tests func decode() -> SearchResult? { let jsonDecoder = JSONDecoder() do { let decodedObject = try jsonDecoder.decode(SearchResult.self, from: self) return decodedObject } catch { return nil } } func decodeTimezone() -> Timezone? { let jsonDecoder = JSONDecoder() do { let decodedObject = try jsonDecoder.decode(Timezone.self, from: self) return decodedObject } catch { return nil } } } extension NSKeyedArchiver { static func clocker_archive(with object: Any) -> Data? { return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true) } } ================================================ FILE: Clocker/Overall App/NetworkManager.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class NetworkManager: NSObject { static let parsingError: NSError = { let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Parsing Error"] let error = NSError(domain: "APIError", code: 102, userInfo: userInfoDictionary) return error }() static let internalServerError: NSError = { let localizedError = """ There was a problem retrieving your information. Please try again later. If the problem continues please contact App Support. """ let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Internal Error", NSLocalizedFailureReasonErrorKey: localizedError] let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary) return error }() static let unableToGenerateURL: NSError = { let localizedError = """ There was a problem searching the location. Please try again later. If the problem continues please contact App Support. """ let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Unable to generate URL", NSLocalizedFailureReasonErrorKey: localizedError] let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary) return error }() } extension NetworkManager { @discardableResult class func task(with path: String, completionHandler: @escaping (_ response: Data?, _ error: NSError?) -> Void) -> URLSessionDataTask? { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 20 let session = URLSession(configuration: configuration) guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedPath) else { completionHandler(nil, unableToGenerateURL) return nil } var request = URLRequest(url: url) request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") let dataTask = session.dataTask(with: request) { data, urlResponse, error in // Check if we're running a network UI test if ProcessInfo.processInfo.arguments.contains("mockTimezoneDown") { completionHandler(nil, internalServerError) return } guard error == nil, let httpURLResponse = urlResponse as? HTTPURLResponse, let json = data else { completionHandler(nil, internalServerError) return } if httpURLResponse.statusCode != 200 { completionHandler(nil, internalServerError) return } completionHandler(json, nil) } dataTask.resume() return dataTask } class func isConnected() -> Bool { // For tests if ProcessInfo.processInfo.arguments.contains("mockNetworkDown") { return false } let status = Reach().connectionStatus() switch status { case .online(.wwan): return true case .online(.wiFi): return true default: return false } } } ================================================ FILE: Clocker/Overall App/Reach.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import SystemConfiguration let reachabilityStatusChangedNotification = "ReachabilityStatusChangedNotification" enum ReachabilityType: CustomStringConvertible { case wwan case wiFi var description: String { switch self { case .wwan: return "WWAN" case .wiFi: return "WiFi" } } } enum ReachabilityStatus: CustomStringConvertible { case offline case online(ReachabilityType) case unknown var description: String { switch self { case .offline: return "Offline" case let .online(type): return "Online (\(type))" case .unknown: return "Unknown" } } } open class Reach { func connectionStatus() -> ReachabilityStatus { var zeroAddress = sockaddr_in() zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, $0) } }) else { return .unknown } var flags: SCNetworkReachabilityFlags = [] if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { return .unknown } return ReachabilityStatus(reachabilityFlags: flags) } } private extension ReachabilityStatus { init(reachabilityFlags flags: SCNetworkReachabilityFlags) { let connectionRequired = flags.contains(.connectionRequired) let isReachable = flags.contains(.reachable) if !connectionRequired, isReachable { self = .online(.wiFi) } else { self = .offline } } } ================================================ FILE: Clocker/Overall App/String + Additions.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa extension String { func filteredName() -> String { var filteredAddress = self let commaSeperatedComponents = components(separatedBy: ",") if let first = commaSeperatedComponents.first { filteredAddress = first } return filteredAddress } func localized() -> String { return NSLocalizedString(self, comment: "Title for \(self)") } } ================================================ FILE: Clocker/Overall App/Strings.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa public enum UserDefaultKeys { static let emptyString = "" static let defaultPreferenceKey = "defaultPreferences" static let timezoneName = "formattedAddress" static let customLabel = "customLabel" static let selectedTimeZoneFormatKey = "is24HourFormatSelected" static let dragSessionKey = "public.text" static let timezoneID = "timezoneID" static let placeIdentifier = "place_id" static let relativeDateKey = "relativeDate" static let themeKey = "defaultTheme" static let showDayInMenu = "showDay" static let showDateInMenu = "showDate" static let showPlaceInMenu = "showPlaceName" static let displayFutureSliderKey = "displayFutureSlider" static let startAtLogin = "startAtLogin" static let showAppInForeground = "displayAppAsForegroundApp" static let sunriseSunsetTime = "showSunriseSetTime" static let userFontSizePreference = "userFontSize" static let showUpcomingEventView = "ShowUpcomingEventView" static let showAllDayEventsInUpcomingView = "showAllDayEventsInUpcomingView" static let showMeetingInMenubar = "showMeetingInfoInMenubar" static let truncateTextLength = "truncateTextLength" static let futureSliderRange = "sliderDayRange" static let selectedCalendars = "SelectedCalendars" static let showOnboardingFlow = "com.abhishek.showOnboardingFlow" static let appDisplayOptions = "com.abhishek.appDisplayOptions" static let longStatusBarWarningMessage = "com.abhishek.longStatusBarWarning" static let testingLaunchArgument = "isUITesting" static let onboardingTestsLaunchArgument = "isTestingTheOnboardingFlow" static let menubarCompactMode = "com.abhishek.menubarCompactMode" static let defaultMenubarMode = "com.abhishek.shouldDefaultToCompactMode" static let installHomeIndicatorObject = "installHomeIndicatorObject" static let switchToCompactModeAlert = "com.abhishek.switchToCompactMode" static let appleInterfaceStyleKey = "AppleInterfaceStyle" // Sync Keys static let enableSyncKey = "com.abhishek.enableSync" static let ubiquitousStoreLastUpdateKey = "com.abhishek.ubiquitousLastUpdateKey" static let userDefaultsLastUpdateKey = "com.abhishek.defaultsLastUpdateKey" } ================================================ FILE: Clocker/Overall App/Themer.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa extension Notification.Name { static let themeDidChangeNotification = Notification.Name(rawValue: "ThemeDidChangeNotification") } class Themer: NSObject { // Adding a new theme should automatically cause the compiler to complain asking to make the switches in this class to be more exhaustive enum Theme: Int { case light = 0 case dark case system case solarizedLight case solarizedDark } private static var sharedInstance = Themer(index: UserDefaults.standard.integer(forKey: UserDefaultKeys.themeKey)) private var effectiveApperanceObserver: NSKeyValueObservation? private var themeIndex: Theme { didSet { NotificationCenter.default.post(name: .themeDidChangeNotification, object: nil) } } init(index: Int) { switch index { case 0: themeIndex = Theme.light case 1: themeIndex = Theme.dark case 2: themeIndex = Theme.system case 3: themeIndex = Theme.solarizedLight case 4: themeIndex = Theme.solarizedDark default: themeIndex = Theme.light } super.init() setAppAppearance() DistributedNotificationCenter.default.addObserver(self, selector: #selector(respondToInterfaceStyle), name: .interfaceStyleDidChange, object: nil) effectiveApperanceObserver = NSApp.observe(\.effectiveAppearance) { _, _ in NotificationCenter.default.post(name: .themeDidChangeNotification, object: nil) } } } extension Themer { class func shared() -> Themer { return sharedInstance } func set(theme: Int) { if themeIndex.rawValue == theme { return } switch theme { case 0: themeIndex = Theme.light case 1: themeIndex = Theme.dark case 2: themeIndex = Theme.system case 3: themeIndex = Theme.solarizedLight case 4: themeIndex = Theme.solarizedDark default: themeIndex = Theme.light } setAppAppearance() } @objc func respondToInterfaceStyle() { OperationQueue.main.addOperation { self.setAppAppearance() } } // MARK: Color func sliderKnobColor() -> NSColor { switch themeIndex { case .light, .solarizedLight: return NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) case .dark, .solarizedDark: return NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) case .system: return retrieveCurrentSystem() == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) } } func sliderRightColor() -> NSColor { switch themeIndex { case .dark: return NSColor.white case .system: return retrieveCurrentSystem() == .dark ? NSColor.white : NSColor.gray default: return NSColor.gray } } func mainBackgroundColor() -> NSColor { switch themeIndex { case .light: return NSColor.white case .dark: return NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0) case .system: return retrieveCurrentSystem() == .light ? NSColor.white : NSColor.windowBackgroundColor case .solarizedLight: return NSColor(deviceRed: 253.0 / 255.0, green: 246.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0) case .solarizedDark: return NSColor(deviceRed: 7.0 / 255.0, green: 54.0 / 255.0, blue: 66.0 / 255.0, alpha: 1.0) } } func textBackgroundColor() -> NSColor { switch themeIndex { case .light: return NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) case .dark: return NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) case .system: return retrieveCurrentSystem() == .light ? NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) : NSColor.controlBackgroundColor case .solarizedLight: return NSColor(deviceRed: 238.0 / 255.0, green: 232.0 / 255.0, blue: 213.0 / 255.0, alpha: 1.0) case .solarizedDark: return NSColor(deviceRed: 0.0 / 255.0, green: 43.0 / 255.0, blue: 54.0 / 255.0, alpha: 1.0) } } func mainTextColor() -> NSColor { switch themeIndex { case .light: return NSColor.black case .dark: return NSColor.white case .system: return NSColor.textColor case .solarizedLight: return NSColor.black case .solarizedDark: return NSColor.white } } // MARK: Images func shutdownImage() -> NSImage { if let symbolImageForShutdown = symbolImage(for: "ellipsis.circle") { return symbolImageForShutdown } return fallbackImageProvider(NSImage(named: NSImage.Name("PowerIcon"))!, NSImage(named: NSImage.Name("PowerIcon-White"))!, NSImage(named: NSImage.Name("Power"))!, NSImage(named: NSImage.Name("PowerIcon"))!, NSImage(named: NSImage.Name("PowerIcon-White"))!) } func preferenceImage() -> NSImage { if let symbolImageForPreference = symbolImage(for: "plus") { return symbolImageForPreference } return fallbackImageProvider(NSImage(named: NSImage.Name("Settings"))!, NSImage(named: NSImage.Name("Settings-White"))!, NSImage(named: NSImage.actionTemplateName)!, NSImage(named: NSImage.Name("Settings"))!, NSImage(named: NSImage.Name("Settings-White"))!) } func pinImage() -> NSImage { if let pinImage = symbolImage(for: "macwindow.on.rectangle") { return pinImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Float"))!, NSImage(named: NSImage.Name("Float-White"))!, NSImage(named: NSImage.Name("Pin"))!, NSImage(named: NSImage.Name("Float"))!, NSImage(named: NSImage.Name("Float-White"))!) } func sunriseImage() -> NSImage { if let symbolImage = symbolImage(for: "sunrise.fill") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Sunrise"))!, NSImage(named: NSImage.Name("WhiteSunrise"))!, NSImage(named: NSImage.Name("Sunrise Dynamic"))!, NSImage(named: NSImage.Name("Sunrise"))!, NSImage(named: NSImage.Name("WhiteSunrise"))!) } func sunsetImage() -> NSImage { if let symbolImage = symbolImage(for: "sunset.fill") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Sunset"))!, NSImage(named: NSImage.Name("WhiteSunset"))!, NSImage(named: NSImage.Name("Sunset Dynamic"))!, NSImage(named: NSImage.Name("Sunset"))!, NSImage(named: NSImage.Name("WhiteSunset"))!) } func remove() -> NSImage { if let symbolImage = symbolImage(for: "xmark") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!, NSImage(named: NSImage.Name("Remove Dynamic"))!, NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!) } func removeImage() -> NSImage { if let symbolImage = symbolImage(for: "xmark.circle") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!, NSImage(named: NSImage.Name("Remove Dynamic"))!, NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!) } func removeAlternateImage() -> NSImage { if let symbolImage = symbolImage(for: "xmark.circle.fill") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!, NSImage(named: NSImage.Name("Remove Dynamic"))!, NSImage(named: NSImage.Name("Remove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!) } func extraOptionsImage() -> NSImage { if let infoImage = symbolImage(for: "info.circle") { return infoImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Extra"))!, NSImage(named: NSImage.Name("ExtraWhite"))!, NSImage(named: NSImage.Name("Extra Dynamic"))!, NSImage(named: NSImage.Name("Extra"))!, NSImage(named: NSImage.Name("ExtraWhite"))!) } func menubarOnboardingImage() -> NSImage { switch themeIndex { case .system: return NSImage(named: NSImage.Name("Dynamic Menubar"))! case .light, .solarizedLight: return NSImage(named: NSImage.Name("Light Menubar"))! case .dark, .solarizedDark: return NSImage(named: NSImage.Name("Dark Menubar"))! } } func extraOptionsHighlightedImage() -> NSImage { if let infoImage = symbolImage(for: "info.circle.fill") { return infoImage } return fallbackImageProvider(NSImage(named: NSImage.Name("ExtraHighlighted"))!, NSImage(named: NSImage.Name("ExtraWhiteHighlighted"))!, NSImage(named: NSImage.Name("ExtraHighlighted Dynamic"))!, NSImage(named: NSImage.Name("ExtraHighlighted"))!, NSImage(named: NSImage.Name("ExtraWhiteHighlighted"))!) } func copyImage() -> NSImage { if let copyImage = symbolImage(for: "doc.on.doc") { return copyImage } return NSImage() } func highlightedCopyImage() -> NSImage? { if let copyImage = symbolImage(for: "doc.on.doc.fill") { return copyImage } return nil } func sharingImage() -> NSImage { if let sharingImage = symbolImage(for: "doc.on.doc") { return sharingImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Sharing"))!, NSImage(named: NSImage.Name("SharingDarkIcon"))!, NSImage(named: NSImage.Name("Sharing Dynamic"))!, NSImage(named: NSImage.Name("Sharing"))!, NSImage(named: NSImage.Name("SharingDarkIcon"))!) } func sharingImageAlternate() -> NSImage { if let sharingImage = symbolImage(for: "doc.on.doc.fill") { return sharingImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Sharing"))!, NSImage(named: NSImage.Name("SharingDarkIcon"))!, NSImage(named: NSImage.Name("Sharing Dynamic"))!, NSImage(named: NSImage.Name("Sharing"))!, NSImage(named: NSImage.Name("SharingDarkIcon"))!) } func currentLocationImage() -> NSImage { if let symbolImage = symbolImage(for: "location.fill") { return symbolImage } return fallbackImageProvider(NSImage(named: NSImage.Name("CurrentLocation"))!, NSImage(named: NSImage.Name("CurrentLocationWhite"))!, NSImage(named: NSImage.Name("CurrentLocationDynamic"))!, NSImage(named: NSImage.Name("CurrentLocation"))!, NSImage(named: NSImage.Name("CurrentLocationWhite"))!) } func popoverAppearance() -> NSAppearance { switch themeIndex { case .light, .solarizedLight: return NSAppearance(named: NSAppearance.Name.vibrantLight)! case .dark, .solarizedDark: return NSAppearance(named: NSAppearance.Name.vibrantDark)! case .system: return NSAppearance.currentDrawing() } } func addImage() -> NSImage { if let symbolImageForPreference = symbolImage(for: "plus") { return symbolImageForPreference } return fallbackImageProvider(NSImage(named: NSImage.Name("Add Icon"))!, NSImage(named: NSImage.Name("Add White"))!, NSImage(named: .addDynamicIcon)!, NSImage(named: NSImage.Name("Add Icon"))!, NSImage(named: NSImage.Name("Add White"))!) } func privacyTabImage() -> NSImage { if let privacyTabSFImage = symbolImage(for: "lock") { return privacyTabSFImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Privacy"))!, NSImage(named: NSImage.Name("Privacy Dark"))!, NSImage(named: .permissionTabIcon)!, NSImage(named: NSImage.Name("Privacy"))!, NSImage(named: NSImage.Name("Privacy Dark"))!) } func appearanceTabImage() -> NSImage { if let appearanceTabImage = symbolImage(for: "eye") { return appearanceTabImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Appearance"))!, NSImage(named: NSImage.Name("Appearance Dark"))!, NSImage(named: .appearanceTabIcon)!, NSImage(named: NSImage.Name("Appearance"))!, NSImage(named: NSImage.Name("Appearance Dark"))!) } func calendarTabImage() -> NSImage { if let calendarTabImage = symbolImage(for: "calendar") { return calendarTabImage } return fallbackImageProvider(NSImage(named: NSImage.Name("Calendar Tab Icon"))!, NSImage(named: NSImage.Name("Calendar Tab Dark"))!, NSImage(named: .calendarTabIcon)!, NSImage(named: NSImage.Name("Calendar Tab Icon"))!, NSImage(named: NSImage.Name("Calendar Tab Dark"))!) } func generalTabImage() -> NSImage? { return symbolImage(for: "gearshape") } func aboutTabImage() -> NSImage? { return symbolImage(for: "info.circle") } func videoCallImage() -> NSImage? { if #available(macOS 11.0, *) { let symbolConfig = NSImage.SymbolConfiguration(pointSize: 20, weight: .regular) return symbolImage(for: "video.circle.fill")?.withSymbolConfiguration(symbolConfig) } else { return nil } } func filledTrashImage() -> NSImage? { return symbolImage(for: "trash.fill") } // Modern Slider func goBackwardsImage() -> NSImage? { return symbolImage(for: "gobackward.15") } func goForwardsImage() -> NSImage? { return symbolImage(for: "goforward.15") } func resetModernSliderImage() -> NSImage? { if let xmarkImage = symbolImage(for: "xmark.circle.fill") { return xmarkImage } return removeImage() } // MARK: Debug Description override var debugDescription: String { if themeIndex == .system { return "System Theme is \(retrieveCurrentSystem())" } return "Current Theme is \(themeIndex)" } override var description: String { return debugDescription } // MARK: Private private func symbolImage(for name: String) -> NSImage? { assert(name.isEmpty == false) if #available(OSX 11.0, *) { return NSImage(systemSymbolName: name, accessibilityDescription: name) } return nil } private func retrieveCurrentSystem() -> Theme { if #available(OSX 10.14, *) { if let appleInterfaceStyle = UserDefaults.standard.object(forKey: UserDefaultKeys.appleInterfaceStyleKey) as? String { if appleInterfaceStyle.lowercased().contains("dark") { return .dark } } } return .light } private func setAppAppearance() { var appAppearance = NSAppearance(named: .aqua) if themeIndex == .dark || themeIndex == .solarizedDark { appAppearance = NSAppearance(named: .darkAqua) } else if themeIndex == .system { appAppearance = retrieveCurrentSystem() == .dark ? NSAppearance(named: .darkAqua) : NSAppearance(named: .aqua) } if NSApp.appearance != appAppearance { NSApp.appearance = appAppearance } } private func fallbackImageProvider(_ lightImage: NSImage, _ darkImage: NSImage, _ systemImage: NSImage, _ solarizedLightImage: NSImage, _ solarizedDarkImage: NSImage) -> NSImage { switch themeIndex { case .light: return lightImage case .dark: return darkImage case .system: return systemImage case .solarizedLight: return solarizedLightImage case .solarizedDark: return solarizedDarkImage } } } ================================================ FILE: Clocker/Overall App/Timer.swift ================================================ // Copyright © 2015 Abhishek Banthia import Foundation open class Repeater: Equatable { /// State of the timer /// /// - paused: idle (never started yet or paused) /// - running: timer is running /// - executing: the observers are being executed /// - finished: timer lifetime is finished public enum State: Equatable, CustomStringConvertible { case paused case running case executing case finished public static func == (lhs: State, rhs: State) -> Bool { switch (lhs, rhs) { case (.paused, .paused), (.running, .running), (.executing, .executing), (.finished, .finished): return true default: return false } } /// Return `true` if timer is currently running, including when the observers are being executed. public var isRunning: Bool { guard self == .running || self == .executing else { return false } return true } /// Return `true` if the observers are being executed. public var isExecuting: Bool { guard case .executing = self else { return false } return true } /// Is timer finished its lifetime? /// It return always `false` for infinite timers. /// It return `true` for `.once` mode timer after the first fire, /// and when `.remainingIterations` is zero for `.finite` mode timers public var isFinished: Bool { guard case .finished = self else { return false } return true } /// State description public var description: String { switch self { case .paused: return "idle/paused" case .finished: return "finished" case .running: return "running" case .executing: return "executing" } } } /// Repeat interval public enum Interval { case nanoseconds(_: Int) case microseconds(_: Int) case milliseconds(_: Int) case minutes(_: Int) case seconds(_: Double) case hours(_: Int) case days(_: Int) internal var value: DispatchTimeInterval { switch self { case let .nanoseconds(value): return .nanoseconds(value) case let .microseconds(value): return .microseconds(value) case let .milliseconds(value): return .milliseconds(value) case let .seconds(value): return .milliseconds(Int(Double(value) * Double(1000))) case let .minutes(value): return .seconds(value * 60) case let .hours(value): return .seconds(value * 3600) case let .days(value): return .seconds(value * 86400) } } } /// Mode of the timer. /// /// - infinite: infinite number of repeats. /// - finite: finite number of repeats. /// - once: single repeat. public enum Mode { case infinite case finite(_: Int) case once /// Is timer a repeating timer? internal var isRepeating: Bool { switch self { case .once: return false default: return true } } /// Number of repeats, if applicable. Otherwise `nil` public var countIterations: Int? { switch self { case let .finite(counts): return counts default: return nil } } /// Is infinite timer public var isInfinite: Bool { guard case .infinite = self else { return false } return true } } /// Handler typealias public typealias Observer = (Repeater) -> Void /// Token assigned to the observer public typealias ObserverToken = UInt64 /// Current state of the timer public private(set) var state: State = .paused { didSet { onStateChanged?(self, state) } } /// Callback called to intercept state's change of the timer public var onStateChanged: ((_ timer: Repeater, _ state: State) -> Void)? /// List of the observer of the timer private var observers = [ObserverToken: Observer]() /// Next token of the timer private var nextObserverID: UInt64 = 0 /// Internal GCD Timer private var timer: DispatchSourceTimer? /// Is timer a repeat timer public private(set) var mode: Mode /// Number of remaining repeats count public private(set) var remainingIterations: Int? /// Interval of the timer private var interval: Interval /// Accuracy of the timer private var tolerance: DispatchTimeInterval /// Dispatch queue parent of the timer private var queue: DispatchQueue? /// Initialize a new timer. /// /// - Parameters: /// - interval: interval of the timer /// - mode: mode of the timer /// - tolerance: tolerance of the timer, 0 is default. /// - queue: queue in which the timer should be executed; if `nil` a new queue is created automatically. /// - observer: observer public init(interval: Interval, mode: Mode = .infinite, tolerance: DispatchTimeInterval = .nanoseconds(3), queue: DispatchQueue? = nil, observer: @escaping Observer) { self.mode = mode self.interval = interval self.tolerance = tolerance remainingIterations = mode.countIterations self.queue = (queue ?? DispatchQueue(label: "com.abhishek.Clocker")) timer = configureTimer() observe(observer) } /// Add new a listener to the timer. /// /// - Parameter callback: callback to call for fire events. /// - Returns: token used to remove the handler @discardableResult public func observe(_ observer: @escaping Observer) -> ObserverToken { var (new, overflow) = nextObserverID.addingReportingOverflow(1) if overflow { // you need to add an incredible number of offset...sure you can't nextObserverID = 0 new = 0 } nextObserverID = new observers[new] = observer return new } /// Remove an observer of the timer. /// /// - Parameter id: id of the observer to remove public func remove(observer identifier: ObserverToken) { observers.removeValue(forKey: identifier) } /// Remove all observers of the timer. /// /// - Parameter stopTimer: `true` to also stop timer by calling `pause()` function. public func removeAllObservers(thenStop stopTimer: Bool = false) { observers.removeAll() if stopTimer { pause() } } /// Configure a new timer session. /// /// - Returns: dispatch timer private func configureTimer() -> DispatchSourceTimer { let associatedQueue = (queue ?? DispatchQueue(label: "com.repeat.\(NSUUID().uuidString)")) let timer = DispatchSource.makeTimerSource(queue: associatedQueue) let repeatInterval = interval.value let deadline: DispatchTime = (DispatchTime.now() + repeatInterval) if mode.isRepeating { timer.schedule(deadline: deadline, repeating: repeatInterval, leeway: tolerance) } else { timer.schedule(deadline: deadline, leeway: tolerance) } timer.setEventHandler { [weak self] in if let unwrapped = self { unwrapped.timeFired() } } return timer } /// Destroy current timer private func destroyTimer() { timer?.setEventHandler(handler: nil) timer?.cancel() if state == .paused || state == .finished { timer?.resume() } } /// Create and schedule a timer that will call `handler` once after the specified time. /// /// - Parameters: /// - interval: interval delay for single fire /// - queue: destination queue, if `nil` a new `DispatchQueue` is created automatically. /// - observer: handler to call when timer fires. /// - Returns: timer instance @discardableResult public class func once(after interval: Interval, queue: DispatchQueue? = nil, _ observer: @escaping Observer) -> Repeater { let timer = Repeater(interval: interval, mode: .once, queue: queue, observer: observer) timer.start() return timer } /// Create and schedule a timer that will fire every interval optionally by limiting the number of fires. /// /// - Parameters: /// - interval: interval of fire /// - count: a non `nil` and > 0 value to limit the number of fire, `nil` to set it as infinite. /// - queue: destination queue, if `nil` a new `DispatchQueue` is created automatically. /// - handler: handler to call on fire /// - Returns: timer @discardableResult public class func every(_ interval: Interval, count: Int? = nil, queue: DispatchQueue? = nil, _ handler: @escaping Observer) -> Repeater { let mode: Mode = (count != nil ? .finite(count!) : .infinite) let timer = Repeater(interval: interval, mode: mode, queue: queue, observer: handler) timer.start() return timer } /// Force fire. /// /// - Parameter pause: `true` to pause after fire, `false` to continue the regular firing schedule. public func fire(andPause pause: Bool = false) { timeFired() if pause == true { self.pause() } } /// Reset the state of the timer, optionally changing the fire interval. /// /// - Parameters: /// - interval: new fire interval; pass `nil` to keep the latest interval set. /// - restart: `true` to automatically restart the timer, `false` to keep it stopped after configuration. public func reset(_ interval: Interval?, restart: Bool = true) { if state.isRunning { setPause(from: state) } // For finite counter we want to also reset the repeat count if case let .finite(count) = mode { self.remainingIterations = count } // Create a new instance of timer configured if let newInterval = interval { self.interval = newInterval } // update interval destroyTimer() timer = configureTimer() state = .paused if restart { timer?.resume() state = .running } } /// Start timer. If timer is already running it does nothing. @discardableResult public func start() -> Bool { guard state.isRunning == false else { return false } // If timer has not finished its lifetime we want simply // restart it from the current state. guard state.isFinished == true else { state = .running timer?.resume() return true } // Otherwise we need to reset the state based upon the mode // and start it again. reset(nil, restart: true) return true } /// Pause a running timer. If timer is paused it does nothing. @discardableResult public func pause() -> Bool { guard state != .paused, state != .finished else { return false } return setPause(from: state) } /// Pause a running timer optionally changing the state with regard to the current state. /// /// - Parameters: /// - from: the state which the timer should only be paused if it is the current state /// - to: the new state to change to if the timer is paused /// - Returns: `true` if timer is paused @discardableResult private func setPause(from currentState: State, to newState: State = .paused) -> Bool { guard state == currentState else { return false } timer?.suspend() state = newState return true } /// Called when timer is fired private func timeFired() { state = .executing // dispatch to observers observers.values.forEach { $0(self) } // manage lifetime switch mode { case .once: // once timer's lifetime is finished after the first fire // you can reset it by calling `reset()` function. setPause(from: .executing, to: .finished) case .finite: // for finite intervals we decrement the left iterations count... remainingIterations! -= 1 if remainingIterations! == 0 { // ...if left count is zero we just pause the timer and stop setPause(from: .executing, to: .finished) } case .infinite: // infinite timer does nothing special on the state machine break } } deinit { self.observers.removeAll() self.destroyTimer() } public static func == (lhs: Repeater, rhs: Repeater) -> Bool { return lhs === rhs } } ================================================ FILE: Clocker/Overall App/UserDefaults + KVOExtensions.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa extension UserDefaults { @objc dynamic var displayFutureSlider: Int { return integer(forKey: UserDefaultKeys.displayFutureSliderKey) } @objc dynamic var userFontSize: Int { return integer(forKey: UserDefaultKeys.userFontSizePreference) } @objc dynamic var sliderDayRange: Int { return integer(forKey: UserDefaultKeys.futureSliderRange) } } ================================================ FILE: Clocker/Overall App/VersionUpdateHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class VersionUpdateHandler: NSObject { enum VersionUpdateHandlerPriority: Comparable { case defaultPri case low case medium case high } static let kSecondsInDay: Double = 86400.0 static let kMacAppStoreRefreshDelay: Double = 5.0 static let kMacRequestTimeout: Double = 60.0 static let kVersionCheckLastVersionKey = "VersionCheckLastVersionKey" static let kVersionIgnoreVersionKey = "VersionCheckIgnoreVersionKey" static let kMacAppStoreIDKey = "VersionCheckAppStoreIDKey" static let kVersionLastCheckedKey = "VersionLastCheckedKey" static let kVersionLastRemindedKey = "VersionLastRemindedKey" static let kVersionMacAppStoreBundleID = "com.apple.AppStore"; static let kVersionMacAppStoreAppID = 1056643111 private var appStoreCountry: String! private var applicationVersion: String! private var applicationBundleID: String = Bundle.main.bundleIdentifier ?? "N/A" private var updatePriority = VersionUpdateHandlerPriority.defaultPri public var useAllAvailableLanguages: Bool = true private var onlyPromptIfMainWindowIsAvailable: Bool = true private var checkAtLaunch: Bool = true private var checkPeriod: Double = 0.0 private var remindPeriod: Double = 1.0 private var verboseLogging: Bool = true private var checkingForNewVersion: Bool = false private var remoteVersionsDict: [String: Any] = [:] private var downloadError: Error? private var dataTask: URLSessionDataTask? = .none private var visibleLocalAlert: NSAlert? private var visibleRemoteAlert: NSAlert? private var remoteRepeater: Repeater? private var localRepeater: Repeater? private var showOnFirstLaunch: Bool = false public var previewMode: Bool = false private var versionDetails: String? private let store: DataStore init(with dataStore: DataStore) { // Setup App Store Country store = dataStore appStoreCountry = Locale.current.regionCode if appStoreCountry == "150" { appStoreCountry = "eu" } else if appStoreCountry.replacingOccurrences(of: "[A-Za-z]{2}", with: "", options: .regularExpression, range: appStoreCountry.startIndex ..< appStoreCountry.endIndex).isEmpty == false { appStoreCountry = "us" } // Setup App Version var appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String if appVersion == nil { appVersion = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String } applicationVersion = appVersion ?? "N/A" // Bundle Identifier self.applicationBundleID = Bundle.main.bundleIdentifier ?? "com.abhishek.Clocker" //default settings self.updatePriority = .defaultPri; self.useAllAvailableLanguages = true; self.onlyPromptIfMainWindowIsAvailable = true; self.checkAtLaunch = true; self.checkPeriod = 0.0; self.remindPeriod = 1.0; self.verboseLogging = true; super.init() applicationLaunched() } private func inThisVersionTitle() -> String { return "New in this version" } private func updateAvailableTitle() -> String { return "New version available" } private func versionLabelFormat() -> String { return "Version %@" } private func okayButtonLabel() -> String { return "OK" } private func ignoreButtonLabel() -> String { return "Ignore" } private func downloadButtonLabel() -> String { return "Download" } private func remindButtonLabel() -> String { return "Remind Me Later" } private func updatedURL() -> URL { // Last resort return URL(string: "macappstore://itunes.apple.com/us/app/clocker/id1056643111")! } @objc private func setLastChecked(_ date: Date) { UserDefaults.standard.set(date, forKey: VersionUpdateHandler.kVersionLastCheckedKey) } private func lastChecked() -> Date? { return store.retrieve(key: VersionUpdateHandler.kVersionLastCheckedKey) as? Date } private func setLastReminded(_ date: Date?) { UserDefaults.standard.set(date, forKey: VersionUpdateHandler.kVersionLastRemindedKey) } private func lastReminded() -> Date? { return store.retrieve(key: VersionUpdateHandler.kVersionLastRemindedKey) as? Date } private func ignoredVersion() -> String? { return store.retrieve(key: VersionUpdateHandler.kVersionIgnoreVersionKey) as? String } private func setIgnoredVersion(_ version: String) { UserDefaults.standard.set(version, forKey: VersionUpdateHandler.kVersionIgnoreVersionKey) } private func setViewedVersionDetails(_ viewed: Bool) { UserDefaults.standard.set(viewed ? applicationVersion : nil, forKey: VersionUpdateHandler.kVersionCheckLastVersionKey) } private func viewedVersionDetails() -> Bool { let lastVersionKey = store.retrieve(key: VersionUpdateHandler.kVersionCheckLastVersionKey) as? String ?? "" return lastVersionKey == applicationVersion } private func lastVersion() -> String { return store.retrieve(key: VersionUpdateHandler.kVersionCheckLastVersionKey) as? String ?? "" } private func setLastVersion(_ version: String) { UserDefaults.standard.set(version, forKey: VersionUpdateHandler.kVersionCheckLastVersionKey) } private func localVersionsDict() -> [String: Any] { return [String: Any]() } private func versionDetails(_ version: String, _ dict: [String: Any]) -> String? { if let versionData = dict[version] as? String { return versionData } else if let versionDataArray = dict[version] as? NSArray { return versionDataArray.componentsJoined(by: "\n") } return nil } private func versionDetails(since lastVersion: String, in dict: [String: Any]) -> String? { var lastVersionCopy = lastVersion if previewMode { lastVersionCopy = "0" } var newVersionFound = false var details = "" let versions = dict.keys.sorted() for version in versions { if version.compareVersion(lastVersionCopy) == .orderedDescending { newVersionFound = true } details.append(versionDetails(version, dict) ?? "") details.append("\n") } if newVersionFound { return details.trimmingCharacters(in: CharacterSet.newlines) } return nil } private func shouldCheckForNewVersion() -> Bool { if (!self.previewMode) { if let lastRemindedDate = lastReminded() { // Reminder takes priority over check period if Date().timeIntervalSince(lastRemindedDate) < Double(remindPeriod * Self.kSecondsInDay) { if verboseLogging { Logger.info("iVersion did not check for a new version because the user last asked to be reminded less than \(self.remindPeriod) days ago") } return false } } else if let lastCheckedDate = lastChecked(), Date().timeIntervalSince(lastCheckedDate) < Double(self.checkPeriod * Self.kSecondsInDay) { if (self.verboseLogging) { Logger.info("iVersion did not check for a new version because the last check was less than \(self.checkPeriod) days ago") } return false } } else if (self.verboseLogging) { Logger.info("iVersion debug mode is enabled - make sure you disable this for release") } // perform the check return true } private func checkForNewVersionInBackground() { var newerVersionAvailable = false var osVersionSupported = false var latestVersion: String? = nil var versions: [String:String]? = nil var itunesServiceURL = "http://itunes.apple.com/\(self.appStoreCountry ?? "us")/lookup" itunesServiceURL = itunesServiceURL.appendingFormat("?bundleId=%@", self.applicationBundleID) if (verboseLogging) { Logger.info("iVersion is checking \(itunesServiceURL) for a new app version...") } dataTask = NetworkManager.task(with: itunesServiceURL) { [weak self] response, error in guard let self = self, let data = response else {return } if (error != nil || response == nil) { Logger.info("Response is nil or error is non-nil") } let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) if let unwrapped = json as? [String: Any], let results = unwrapped["results"] as? Array, let firstResult = results.first as? [String: Any], let bundleID = firstResult["bundleId"] as? String { if (bundleID == self.applicationBundleID) { guard let minimumSupportedOSVersion = firstResult["minimumOsVersion"] as? String else { return } let version = ProcessInfo.processInfo.operatingSystemVersion let systemVersion = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" osVersionSupported = systemVersion.compareVersion(minimumSupportedOSVersion) != ComparisonResult.orderedAscending if (!osVersionSupported) { Logger.info("Current OS version is not supported") } // get version details let releaseNotes = firstResult["releaseNotes"] latestVersion = firstResult["version"] as? String if let version = latestVersion, osVersionSupported { versions = [version : (releaseNotes as? String) ?? ""] } newerVersionAvailable = latestVersion?.compareVersion(self.applicationVersion) == .orderedDescending if (self.verboseLogging) { if (newerVersionAvailable) { Logger.info("iVersion found a new version \(latestVersion ?? "N/A") of the app on iTunes. Current version is \(self.applicationVersion ?? "nil")") } else { Logger.info("iVersion did not find a new version of the app on iTunes. Current version is \(self.applicationVersion ?? "nil") and the latest version is \(latestVersion ?? "nil")") } } } else { if (self.verboseLogging) { Logger.info("iVersion found that the application bundle ID \(self.applicationBundleID) does not match the bundle ID of the app found on iTunes \(bundleID) with the specified App Store ID") } } } else { Logger.info("Server returned an error while fetching version info") } //TODO: Set download error Logger.info("Versions downloaded \(versions ?? [:])") self.performSelector(onMainThread: #selector(VersionUpdateHandler.setRemoteVersionsDict(_:)), with: versions, waitUntilDone: true) self.performSelector(onMainThread: #selector(VersionUpdateHandler.setLastChecked(_:)), with: Date(), waitUntilDone: true) self.performSelector(onMainThread: #selector(Self.downloadVersionsData), with: nil, waitUntilDone: true) } dataTask?.resume() } @objc private func setRemoteVersionsDict(_ dict: [String: Any]?) { if let unwrappedDict = dict { Logger.info("Setting Remote Versions Dict to \(unwrappedDict)") remoteVersionsDict = unwrappedDict } } private func checkForNewVersion() { if (!self.checkingForNewVersion) { self.checkingForNewVersion = true DispatchQueue.global(qos: .userInitiated).async { self.checkForNewVersionInBackground() } } } private func applicationLaunched() { if checkAtLaunch { checkIfNewVersion() if (shouldCheckForNewVersion()) { checkForNewVersion() } } else if verboseLogging { Logger.info("iVersion will not check for updatess because checkAtLaunch option is disabled") } } private func versionDetailsString() -> String { if versionDetails == nil { if viewedVersionDetails() { versionDetails = versionDetails(applicationVersion, localVersionsDict()) } } else { versionDetails = versionDetails(since: lastVersion(), in: localVersionsDict()) } return versionDetails! } private func mostRecentVersionInDict(_ dict: [String: Any]) -> String { // return [dictionary.allKeys sortedArrayUsingSelector:@selector(compareVersion:)].lastObject; // TODO: Fix this sorting return dict.keys.sorted().last ?? "" } private func showAlertWithTitle(_ title: String, _ details: String, _ defaultButton: String, _ ignoreButton: String?, _ remindButton: String?) -> NSAlert { Logger.info("Showing alert") let floatMax = CGFloat.greatestFiniteMagnitude let alert = NSAlert() alert.messageText = title alert.informativeText = inThisVersionTitle() alert.addButton(withTitle: defaultButton) let scrollView = NSScrollView(frame: NSRect(x: 0.0, y: 0.0, width: 380.0, height: 15.0)) let contentSize = scrollView.contentSize scrollView.borderType = .bezelBorder scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = false scrollView.autoresizingMask = [.width, .height] let textView = NSTextView(frame: NSRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height)) textView.minSize = NSSize(width: 0.0, height: contentSize.height) textView.maxSize = NSSize(width: floatMax, height: floatMax) textView.isVerticallyResizable = true textView.isHorizontallyResizable = false textView.isEditable = false textView.autoresizingMask = .width textView.textContainer?.containerSize = NSSize(width: contentSize.width, height: floatMax) textView.textContainer?.widthTracksTextView = true textView.string = details scrollView.documentView = textView textView.sizeToFit() let height = min(200.0, scrollView.documentView?.frame.size.height ?? 200.0) + 3.0 scrollView.frame = NSRect(x: 0.0, y: 0.0, width: scrollView.frame.size.width, height: height) alert.accessoryView = scrollView if let ignoreButtonTitle = ignoreButton { alert.addButton(withTitle: ignoreButtonTitle) } if let remindButtonTitle = remindButton { alert.addButton(withTitle: remindButtonTitle) let modalResponse = alert.runModal() if modalResponse == .alertFirstButtonReturn { // Right most button didDismissAlert(alert, 0) } else if modalResponse == .alertSecondButtonReturn { didDismissAlert(alert, 1) } else { didDismissAlert(alert, 2) } } return alert } private func showIgnoreButton() -> Bool { return ignoreButtonLabel().isEmpty == false && updatePriority < VersionUpdateHandlerPriority.medium } private func showRemindButtton() -> Bool { return remindButtonLabel().isEmpty == false && updatePriority < VersionUpdateHandlerPriority.high } private func didDismissAlert(_ alert: NSAlert, _ buttonIndex: Int) { // Get Button Indice let downloadButtonIndex = 0 let ignoreButtonIndex = showIgnoreButton() ? 1 : 0 let remindButtonIndex = showRemindButtton() ? ignoreButtonIndex + 1 : 0 let latestVersion = mostRecentVersionInDict(self.remoteVersionsDict) if (self.visibleLocalAlert == alert) { setViewedVersionDetails(true) visibleLocalAlert = nil return } if (buttonIndex == downloadButtonIndex) { setLastReminded(nil) showAppPageInAppStore() } else if (buttonIndex == ignoreButtonIndex) { // ignore this version setIgnoredVersion(latestVersion) setLastReminded(nil) } else if (buttonIndex == remindButtonIndex) { setLastReminded(Date()) } self.visibleRemoteAlert = nil } private func showAppPageInAppStore() { if (self.verboseLogging) { Logger.info("iVersion will open App Store using the following URL \(updatedURL())") } NSWorkspace.shared.open(updatedURL()) } @objc private func downloadVersionsData() { if onlyPromptIfMainWindowIsAvailable, NSApplication.shared.mainWindow == nil { Logger.info("Main window not available in downloadVersionsData") remoteRepeater = Repeater(interval: .seconds(5), mode: .once) { _ in OperationQueue.main.addOperation { [weak self] in guard let self = self else { return } self.downloadVersionsData() } } remoteRepeater?.start() return } if checkingForNewVersion { checkingForNewVersion = false if remoteVersionsDict.isEmpty { if downloadError != nil { Logger.info("Update Check Failed because of \(downloadError!.localizedDescription)") } else { Logger.info("Version Update Check because an unknown error occurred") } return } } let details = versionDetails(since: applicationVersion, in: remoteVersionsDict) let mostRecentVersion = mostRecentVersionInDict(remoteVersionsDict) if details != nil { Logger.info("About to show visible remote alert") // Check if ignored let showDetails = ignoredVersion() != mostRecentVersion || previewMode // show details if showDetails && self.visibleRemoteAlert == nil { var title = updateAvailableTitle() title = title.appending(" (\(mostRecentVersion))") self.visibleRemoteAlert = showAlertWithTitle(title, details ?? "N/A", self.downloadButtonLabel(), showIgnoreButton() ? self.ignoreButtonLabel() : nil, showRemindButtton() ? self.remindButtonLabel() : nil) } remoteRepeater = nil } } private func checkIfNewVersion() { if onlyPromptIfMainWindowIsAvailable, NSApplication.shared.mainWindow == nil { Logger.info("Main window not available in checkIfNewVersion") localRepeater = Repeater(interval: .seconds(5), mode: .once) { _ in OperationQueue.main.addOperation { [weak self] in guard let self = self else { return } self.checkIfNewVersion() } } localRepeater?.start() return } let lastVersionString = lastVersion() if lastVersionString.isEmpty == false || showOnFirstLaunch || previewMode { if applicationVersion.compareVersion(lastVersionString) == ComparisonResult.orderedDescending || previewMode { // Clear Reminder setLastReminded(nil) if (self.versionDetails != nil && visibleLocalAlert == nil && visibleRemoteAlert == nil) { Logger.info("Visible Local Alert about to be display") visibleLocalAlert = showAlertWithTitle(inThisVersionTitle(), self.versionDetailsString(), okayButtonLabel(), nil, nil) } else { Logger.info("Skipping to show local alert because version details is \(self.versionDetails ?? "nil")") } } } else { //record this as last viewed release Logger.info("Set Viewed Version Details") setViewedVersionDetails(true) } localRepeater = nil } } extension String { func compareVersion(_ version: String) -> ComparisonResult { return compare(version, options: CompareOptions.numeric, range: nil, locale: nil) } func compareVersionDescending(_ version: String) -> ComparisonResult { let comparsionResult = (0 - compareVersion(version).rawValue) switch comparsionResult { case -1: return ComparisonResult.orderedAscending case 0: return ComparisonResult.orderedSame case 1: return ComparisonResult.orderedDescending default: assertionFailure("Invalid Comparison Result") return .orderedSame } } } ================================================ FILE: Clocker/Panel/Data Layer/TimezoneDataOperations.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLocation import CoreLoggerKit import CoreModelKit class TimezoneDataOperations: NSObject { private let dataObject: TimezoneData private let store: DataStore private lazy var nsCalendar = Calendar.autoupdatingCurrent private static var gregorianCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) private static var swiftyCalendar = Calendar(identifier: .gregorian) private static let currentLocale = Locale.current.identifier init(with timezone: TimezoneData, store: DataStore) { dataObject = timezone self.store = store super.init() } } extension TimezoneDataOperations { func time(with sliderValue: Int) -> String { guard let newDate = TimezoneDataOperations.gregorianCalendar?.date(byAdding: .minute, value: sliderValue, to: Date(), options: .matchFirst) else { assertionFailure("Data was unexpectedly nil") return UserDefaultKeys.emptyString } if dataObject.timezoneFormat(store.timezoneFormat()) == DateFormat.epochTime { let timezone = TimeZone(identifier: dataObject.timezone()) let offset = timezone?.secondsFromGMT(for: newDate) ?? 0 let value = Int(Date().timeIntervalSince1970 + Double(offset)) return "\(value)" } let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none, format: dataObject.timezoneFormat(store.timezoneFormat()), timezoneIdentifier: dataObject.timezone(), locale: Locale.autoupdatingCurrent) return dateFormatter.string(from: newDate) } func nextDaylightSavingsTransitionIfAvailable(with sliderValue: Int) -> String? { let currentTimezone = TimeZone(identifier: dataObject.timezone()) guard let nextDaylightSavingsTransition = currentTimezone?.nextDaylightSavingTimeTransition else { return nil } guard let newDate = TimezoneDataOperations.gregorianCalendar?.date(byAdding: .minute, value: sliderValue, to: Date(), options: .matchFirst) else { assertionFailure("Data was unexpectedly nil") return nil } let calendar = Calendar.autoupdatingCurrent let numberOfDays = nextDaylightSavingsTransition.days(from: newDate, calendar: calendar) // We'd like to show upcoming DST changes within the 7 day range. // Using 8 as a fail-safe as timezones behind CDT can sometimes be wrongly attributed if numberOfDays > 8 || numberOfDays < 0 { return nil } if numberOfDays == 0 { let hoursLeft = nextDaylightSavingsTransition.hours(from: newDate) let suffix = hoursLeft == 1 ? "hour" : "hours" return "DST transition will occur in \(hoursLeft) \(suffix)." } let suffix = numberOfDays == 1 ? "day" : "days" return "DST transition will occur in \(numberOfDays) \(suffix)." } func compactMenuTitle() -> String { var subtitle = UserDefaultKeys.emptyString let shouldLabelBeShownAlongWithTime = !store.shouldDisplay(.placeInMenubar) if shouldLabelBeShownAlongWithTime == false { return dataObject.formattedTimezoneLabel() } let shouldDayBeShown = store.shouldShowDayInMenubar() if shouldDayBeShown, shouldLabelBeShownAlongWithTime { let substring = date(with: 0, displayType: .menu) subtitle.append(substring) } let shouldDateBeShown = store.shouldShowDateInMenubar() if shouldDateBeShown, shouldLabelBeShownAlongWithTime { let date = Date().formatter(with: "MMM d", timeZone: dataObject.timezone()) subtitle.isEmpty ? subtitle.append("\(date)") : subtitle.append(" \(date)") } return subtitle.isEmpty ? dataObject.formattedTimezoneLabel() : subtitle } func compactMenuSubtitle() -> String { var subtitle = UserDefaultKeys.emptyString let shouldDayBeShown = store.shouldShowDayInMenubar() let shouldLabelsNotBeShownAlongWithTime = store.shouldDisplay(.placeInMenubar) if shouldDayBeShown, shouldLabelsNotBeShownAlongWithTime { let substring = date(with: 0, displayType: .menu) subtitle.append(substring) } let shouldDateBeShown = store.shouldShowDateInMenubar() if shouldDateBeShown, shouldLabelsNotBeShownAlongWithTime { let date = Date().formatter(with: "MMM d", timeZone: dataObject.timezone()) subtitle.isEmpty ? subtitle.append("\(date)") : subtitle.append(" \(date)") } subtitle.isEmpty ? subtitle.append(time(with: 0)) : subtitle.append(" \(time(with: 0))") return subtitle } func menuTitle() -> String { var menuTitle = UserDefaultKeys.emptyString let dataStore = store let shouldCityBeShown = dataStore.shouldDisplay(.placeInMenubar) let shouldDayBeShown = dataStore.shouldShowDayInMenubar() let shouldDateBeShown = dataStore.shouldShowDateInMenubar() if shouldCityBeShown { if let address = dataObject.formattedAddress, address.isEmpty == false { if let label = dataObject.customLabel { label.isEmpty == false ? menuTitle.append(label) : menuTitle.append(address) } else { menuTitle.append(address) } } else { if let label = dataObject.customLabel { label.isEmpty == false ? menuTitle.append(label) : menuTitle.append(dataObject.timezone()) } else { menuTitle.append(dataObject.timezone()) } } } if shouldDayBeShown { var substring = date(with: 0, displayType: .menu) if substring.count > 3 { let endIndex = substring.index(substring.startIndex, offsetBy: 2) substring = String(substring[substring.startIndex ... endIndex]) } if menuTitle.isEmpty == false { menuTitle.append(" \(substring.capitalized)") } else { menuTitle.append(substring.capitalized) } } if shouldDateBeShown { let date = Date().formatter(with: "MMM d", timeZone: dataObject.timezone()) if menuTitle.isEmpty == false { menuTitle.append(" \(date)") } else { menuTitle.append("\(date)") } } menuTitle.isEmpty == false ? menuTitle.append(" \(time(with: 0))") : menuTitle.append(time(with: 0)) return menuTitle } private func timezoneDate(with sliderValue: Int, _ calendar: Calendar) -> Date { let source = timezoneDateByAdding(minutesToAdd: sliderValue, calendar) let sourceTimezone = TimeZone.current let destinationTimezone = TimeZone(identifier: dataObject.timezone()) let sourceGMTOffset = Double(sourceTimezone.secondsFromGMT(for: source)) let destinationGMTOffset = Double(destinationTimezone?.secondsFromGMT(for: source) ?? 0) let interval = destinationGMTOffset - sourceGMTOffset return Date(timeInterval: interval, since: source) } // calendar.dateByAdding takes a 0.1% or 0.2% according to TimeProfiler // Let's not use it unless neccesary! private func timezoneDateByAdding(minutesToAdd: Int, _ calendar: Calendar?) -> Date { if minutesToAdd == 0 { return Date() } return calendar?.date(byAdding: .minute, value: minutesToAdd, to: Date()) ?? Date() } func date(with sliderValue: Int, displayType: TimezoneData.DateDisplayType) -> String { guard let relativeDayPreference = store.retrieve(key: UserDefaultKeys.relativeDateKey) as? NSNumber else { assertionFailure("Data was unexpectedly nil") return UserDefaultKeys.emptyString } if relativeDayPreference.intValue == 3 { return UserDefaultKeys.emptyString } var currentCalendar = Calendar(identifier: .gregorian) currentCalendar.locale = Locale.autoupdatingCurrent let convertedDate = timezoneDate(with: sliderValue, currentCalendar) if displayType == .panel { // Yesterday, tomorrow, etc if relativeDayPreference.intValue == 0 { let localFormatter = DateFormatterManager.localizedSimpleFormatter("EEEE") let local = localFormatter.date(from: localeDate(with: "EEEE")) // Gets local week day number and timezone's week day number for comparison let weekDay = currentCalendar.component(.weekday, from: local!) let timezoneWeekday = currentCalendar.component(.weekday, from: convertedDate) if weekDay == timezoneWeekday + 1 { return "Yesterday\(timeDifference())" } else if weekDay == timezoneWeekday { return "Today\(timeDifference())" } else if weekDay + 1 == timezoneWeekday || weekDay - 6 == timezoneWeekday { return "Tomorrow\(timeDifference())" } else { return "\(weekdayText(from: convertedDate))\(timeDifference())" } } // Day name: Thursday, Friday etc if relativeDayPreference.intValue == 1 { return "\(weekdayText(from: convertedDate))\(timeDifference())" } // Date in mmm/dd if relativeDayPreference.intValue == 2 { return "\(todaysDate(with: sliderValue))\(timeDifference())" } let errorDictionary: [String: Any] = ["Timezone": dataObject.timezone(), "Current Locale": Locale.autoupdatingCurrent.identifier, "Slider Value": sliderValue, "Today's Date": Date()] Logger.log(object: errorDictionary, for: "Unable to get date") return "Error" } else { return "\(shortWeekdayText(convertedDate))" } } // Returns shortened weekday given a date // For eg. Thu or Thursday, Tues for Tuesday etc private func shortWeekdayText(_ date: Date) -> String { let localizedFormatter = DateFormatterManager.localizedSimpleFormatter("E") return localizedFormatter.string(from: date) } // Returns proper weekday given a date // For eg. Thursday, Sunday, Friday etc private func weekdayText(from date: Date) -> String { let dateFormatter = DateFormatterManager.localizedFormatter(with: "EEEE", for: TimeZone.current.identifier) return dateFormatter.string(from: date) } // Exposed to public for tests! public func timeDifference() -> String { let localFormatter = DateFormatterManager.localizedSimpleFormatter("d MMM yyyy HH:mm:ss") let local = localFormatter.date(from: localeDate(with: "d MMM yyyy HH:mm:ss"))! let newDate = timezoneDateByAdding(minutesToAdd: 0, TimezoneDataOperations.swiftyCalendar) let dateFormatter = DateFormatterManager.localizedFormatter(with: "d MMM yyyy HH:mm:ss", for: dataObject.timezone()) guard let timezoneDate = localFormatter.date(from: dateFormatter.string(from: newDate)) else { let unableToConvertDateParameters = [ "New Date": newDate, "Timezone": dataObject.timezone(), "Locale": dateFormatter.locale.identifier, ] as [String: Any] Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil") return UserDefaultKeys.emptyString } let timeDifference = local.timeAgo(since: timezoneDate) if timeDifference.isEmpty { return UserDefaultKeys.emptyString } if (local as NSDate).earlierDate(timezoneDate) == local { var replaceAgo = UserDefaultKeys.emptyString replaceAgo.append(", +") let agoString = timezoneDate.timeAgo(since: local, numericDates: true) replaceAgo.append(agoString.replacingOccurrences(of: "ago", with: UserDefaultKeys.emptyString)) if !TimezoneDataOperations.currentLocale.contains("en") { if TimezoneDataOperations.currentLocale.contains("de") { replaceAgo = replaceAgo.replacingOccurrences(of: "Vor ", with: UserDefaultKeys.emptyString) replaceAgo.append(" vor") } return replaceAgo } let minuteDifference = calculateTimeDifference(with: local as NSDate, timezoneDate: timezoneDate as NSDate) minuteDifference == 0 ? replaceAgo.append("") : replaceAgo.append("\(minuteDifference)m") return replaceAgo.lowercased() } var replaceAgo = UserDefaultKeys.emptyString replaceAgo.append(", -") let replaced = timeDifference.replacingOccurrences(of: "ago", with: UserDefaultKeys.emptyString) replaceAgo.append(replaced) if !TimezoneDataOperations.currentLocale.contains("en") { if TimezoneDataOperations.currentLocale.contains("de") { replaceAgo = replaceAgo.replacingOccurrences(of: "Vor ", with: UserDefaultKeys.emptyString) replaceAgo.append(" zurück") } return replaceAgo } let minuteDifference = calculateTimeDifference(with: local as NSDate, timezoneDate: timezoneDate as NSDate) minuteDifference == 0 ? replaceAgo.append("") : replaceAgo.append("\(minuteDifference)m") return replaceAgo.lowercased() } private func initializeSunriseSunset(with sliderValue: Int) { let currentDate = nsCalendar.date(byAdding: .minute, value: sliderValue, to: Date()) guard let lat = dataObject.latitude, let long = dataObject.longitude else { assertionFailure("Data was unexpectedly nil.") return } let coordinates = CLLocationCoordinate2D(latitude: lat, longitude: long) guard let dateForCalculation = currentDate, let solar = Solar(for: dateForCalculation, coordinate: coordinates) else { return } if let sunrise = solar.sunrise, let sunset = solar.sunset { dataObject.sunriseTime = sunrise dataObject.sunsetTime = sunset dataObject.isSunriseOrSunset = solar.isNighttime } else { Logger.log(object: ["Unable to fetch sunrise/sunset": dataObject.formattedTimezoneLabel()], for: "Sunrise/Sunset Error") } } private func calculateTimeDifference(with localDate: NSDate, timezoneDate: NSDate) -> Int { let earliest = localDate.earlierDate(timezoneDate as Date) let latest = earliest == localDate as Date ? timezoneDate : localDate // if timeAgo < 24h => compare DateTime else compare Date only let upToHours: Set = [.second, .minute, .hour] let difference = nsCalendar.dateComponents(upToHours, from: earliest, to: latest as Date) return difference.minute! } func formattedSunriseTime(with sliderValue: Int) -> String { /* We have to call this everytime so that we get an updated value everytime! */ if dataObject.selectionType == .timezone || (dataObject.latitude == nil || dataObject.longitude == nil) { return UserDefaultKeys.emptyString } initializeSunriseSunset(with: sliderValue) if let sunrise = dataObject.sunriseTime, let sunset = dataObject.sunsetTime { let correct = dataObject.isSunriseOrSunset ? sunrise : sunset let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US") dateFormatter.timeZone = TimeZone(identifier: dataObject.timezone()) dateFormatter.dateFormat = dataObject.timezoneFormat(store.timezoneFormat()) return dateFormatter.string(from: correct) } return UserDefaultKeys.emptyString } func todaysDate(with sliderValue: Int, locale: Locale = Locale(identifier: "en-US")) -> String { let newDate = TimezoneDataOperations.gregorianCalendar?.date(byAdding: .minute, value: sliderValue, to: Date(), options: .matchFirst) let date = newDate!.formatter(with: "MMM d", timeZone: dataObject.timezone(), locale: locale) return date } private func localDate() -> String { let dateFormatter = DateFormatterManager.dateFormatter(with: .medium, for: TimeZone.autoupdatingCurrent.identifier) return dateFormatter.string(from: Date()) } private func localeDate(with format: String) -> String { let dateFormatter = DateFormatterManager.localizedFormatter(with: format, for: TimeZone.autoupdatingCurrent.identifier) return dateFormatter.string(from: Date()) } func saveObject(at index: Int = -1) { var defaults = store.timezones() guard let encodedObject = NSKeyedArchiver.clocker_archive(with:dataObject as Any) else { return } index == -1 ? defaults.append(encodedObject) : defaults.insert(encodedObject, at: index) store.setTimezones(defaults) } } extension Date { func formatter(with format: String, timeZone: String, locale: Locale = Locale(identifier: "en-US")) -> String { let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .medium, format: format, timezoneIdentifier: timeZone, locale: locale) return dateFormatter.string(from: self) } } ================================================ FILE: Clocker/Panel/FloatingWindowController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class FloatingWindowController: ParentPanelController { private var repeater: Repeater? static var sharedWindow = FloatingWindowController(windowNibName: NSNib.Name.floatingWindowIdentifier) override func windowDidLoad() { super.windowDidLoad() window?.standardWindowButton(.miniaturizeButton)?.isHidden = true window?.standardWindowButton(.zoomButton)?.isHidden = true } class func shared() -> FloatingWindowController { return sharedWindow } override func awakeFromNib() { super.awakeFromNib() setup() windowFrameAutosaveName = NSWindow.FrameAutosaveName("FloatingWindowAutoSave") dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US") NotificationCenter.default.addObserver(self, selector: #selector(themeChanges), name: Notification.Name.themeDidChange, object: nil) updateTheme() updateDefaultPreferences() mainTableView.registerForDraggedTypes([.dragSession]) reviewView.isHidden = !showReviewCell reviewView.layer?.backgroundColor = Themer.shared().mainBackgroundColor().cgColor mainTableView.setAccessibility("FloatingTableView") } override func updatePanelColor() { super.updatePanelColor() updateTheme() } override func showNotesPopover(forRow row: Int, relativeTo positioningRect: NSRect, andButton target: NSButton!) -> Bool { guard let popover = additionalOptionsPopover else { return false } target.image = Themer.shared().extraOptionsHighlightedImage() if popover.isShown, row == previousPopoverRow { popover.close() target.image = Themer.shared().extraOptionsImage() previousPopoverRow = -1 return false } previousPopoverRow = row super.showNotesPopover(forRow: row, relativeTo: positioningRect, andButton: target) guard let contentView = window?.contentView else { assertionFailure("Window was unexpectedly nil") return false } popover.show(relativeTo: positioningRect, of: contentView, preferredEdge: .minX) return true } private func updateTheme() { let shared = Themer.shared() if let panel = window { panel.acceptsMouseMovedEvents = true panel.level = .popUpMenu panel.isOpaque = false } shutdownButton.image = shared.shutdownImage() preferencesButton.image = shared.preferenceImage() pinButton.image = shared.pinImage() sharingButton.image = shared.sharingImage() sharingButton.alternateImage = shared.sharingImageAlternate() mainTableView.backgroundColor = shared.mainBackgroundColor() window?.backgroundColor = shared.mainBackgroundColor() } @objc override func updateDefaultPreferences() { super.updateDefaultPreferences() updateTime() mainTableView.backgroundColor = Themer.shared().mainBackgroundColor() } @objc override func updateTime() { retrieveCalendarEvents() super.updateTime() } @objc func themeChanges() { updateTheme() super.updatePanelColor() mainTableView.reloadData() } private func setup() { window?.contentView?.wantsLayer = true window?.titlebarAppearsTransparent = true window?.titleVisibility = .hidden window?.contentView?.layer?.cornerRadius = 20 window?.contentView?.layer?.masksToBounds = true window?.isOpaque = false window?.backgroundColor = NSColor.clear window?.collectionBehavior = .canJoinAllSpaces } func startWindowTimer() { repeater = Repeater(interval: .seconds(1), mode: .infinite) { _ in OperationQueue.main.addOperation { self.updateTime() } } repeater!.start() super.dismissRowActions() } override func showWindow(_: Any?) { super.showWindow(nil) determineUpcomingViewVisibility() } } extension NSView { func setAccessibility(_ identifier: String) { setAccessibilityEnabled(true) setAccessibilityIdentifier(identifier) } } extension FloatingWindowController: NSWindowDelegate { func windowWillClose(_: Notification) { setTimezoneDatasourceSlider(sliderValue: 0) if let timer = repeater { timer.pause() repeater = nil } } } ================================================ FILE: Clocker/Panel/Notes Popover/NotesPopover.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import UserNotifications class NotesPopover: NSViewController { private enum OverrideType { case timezoneFormat case seconds } var dataObject: TimezoneData? var timezoneObjects: [Data]? var currentRow: Int = -1 weak var popover: NSPopover? @IBOutlet var customLabel: NSTextField! @IBOutlet var reminderPicker: NSDatePicker! @IBOutlet var reminderView: NSView! @IBOutlet var timeFormatTweakingView: NSView! @IBOutlet var alertPopupButton: NSPopUpButton! @IBOutlet var scriptExecutionIndicator: NSProgressIndicator! @IBOutlet var saveButton: NSButton! @IBOutlet var setReminderCheckbox: NSButton! @IBOutlet var remindersButton: NSButton! @IBOutlet var timeFormatControl: NSPopUpButton! @IBOutlet var notesTextView: TextViewWithPlaceholder! private func convertOverrideFormatToPopupControlSelection() -> Int { var chosenFormat: Int = dataObject?.overrideFormat.rawValue ?? 0 if chosenFormat == 3 { chosenFormat = 4 } else if chosenFormat == 6 { chosenFormat = 7 } else if chosenFormat == 9 { chosenFormat = 10 } return chosenFormat } override func viewDidLoad() { super.viewDidLoad() setupAlarmTextField() setupUI() NotificationCenter.default.addObserver(self, selector: #selector(themeChanged), name: NSNotification.Name.themeDidChange, object: nil) let titles = [ "None", "At time of the event", "5 minutes before", "10 minutes before", "15 minutes before", "30 minutes before", "1 hour before", "2 hour before", "1 day before", "2 days before", ] alertPopupButton.removeAllItems() alertPopupButton.addItems(withTitles: titles) alertPopupButton.selectItem(at: 1) // Set up time control let supportedTimeFormats = ["Respect Global Preference", "h:mm a (7:08 PM)", "HH:mm (19:08)", "-- With Seconds --", "h:mm:ss a (7:08:09 PM)", "HH:mm:ss (19:08:09)", "-- 12 Hour with Preceding 0 --", "hh:mm a (07:08 PM)", "hh:mm:ss a (07:08:09 PM)", "-- 12 Hour w/o AM/PM --", "hh:mm (07:08)", "hh:mm:ss (07:08:09)", "Epoch Time"] timeFormatControl.removeAllItems() timeFormatControl.addItems(withTitles: supportedTimeFormats) timeFormatControl.item(at: 3)?.isEnabled = false timeFormatControl.item(at: 6)?.isEnabled = false timeFormatControl.item(at: 9)?.isEnabled = false timeFormatControl.autoenablesItems = false timeFormatControl.selectItem(at: convertOverrideFormatToPopupControlSelection()) // Set Accessibility Identifiers for UI tests customLabel.setAccessibilityIdentifier("CustomLabel") saveButton.setAccessibilityIdentifier("SaveButton") notesTextView.setAccessibilityIdentifier("NotesTextView") setReminderCheckbox.setAccessibilityIdentifier("ReminderCheckbox") alertPopupButton.setAccessibilityIdentifier("RemindersAlertPopup") reminderView.setAccessibilityIdentifier("RemindersView") if #available(OSX 11.0, *) { alertPopupButton.controlSize = .large } } override func viewWillAppear() { super.viewWillAppear() scriptExecutionIndicator.stopAnimation(nil) updateContent() } private func setupUI() { if let saveCell = saveButton.cell as? NSButtonCell { setCellState(buttonCell: saveCell) } if let remindersCell = remindersButton.cell as? NSButtonCell { setCellState(buttonCell: remindersCell) } notesTextView.font = NSFont(name: "Avenir", size: 14) notesTextView.enclosingScrollView?.hasVerticalScroller = false themeChanged() } private func setCellState(buttonCell: NSButtonCell) { buttonCell.highlightsBy = .contentsCellMask buttonCell.showsStateBy = .pushInCellMask } private func setupAlarmTextField() { reminderPicker.datePickerStyle = .textField reminderPicker.isBezeled = false reminderPicker.isBordered = false reminderPicker.drawsBackground = false reminderPicker.datePickerElements = [.yearMonthDay, .hourMinute] reminderPicker.target = self } private func setInitialReminderTime() { // If self.calSelectedDate is today, the initialStart is set to // the next whole hour. Otherwise, 8am of self.calselectedDate. getCurrentTimezoneDate { finalDate in let currentDate = finalDate ?? Date() self.continueProcess(with: currentDate) } } private func continueProcess(with currentDate: Date) { let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) var hour = 0 calendar?.getHour(&hour, minute: nil, second: nil, nanosecond: nil, from: currentDate) hour = (hour == 23) ? 0 : hour + 1 guard let initialStart = calendar?.nextDate(after: currentDate, matching: NSCalendar.Unit.hour, value: hour, options: NSCalendar.Options.matchPreviousTimePreservingSmallerUnits) else { assertionFailure("Initial Date object was unexepectedly nil") return } reminderPicker.minDate = currentDate reminderPicker.dateValue = initialStart } private func getCurrentTimezoneDate(completionHandler: @escaping (_ response: Date?) -> Void) { guard let timezoneID = dataObject?.timezone() else { assertionFailure("Unable to retrieve timezoneID from the model") completionHandler(nil) return } let currentCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) guard let newDate = currentCalendar?.date(byAdding: NSCalendar.Unit.minute, value: 0, to: Date(), options: NSCalendar.Options.matchLast) else { assertionFailure("Initial Date object was unexepectedly nil") completionHandler(nil) return } let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short formatter.locale = Locale(identifier: "en_US") formatter.timeZone = TimeZone(identifier: timezoneID) let dateStyle = formatter.string(from: newDate) let type: NSTextCheckingResult.CheckingType = .date do { let detector = try NSDataDetector(types: type.rawValue) detector.enumerateMatches(in: dateStyle, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange(location: 0, length: dateStyle.count), using: { result, _, _ in guard let completedDate = result?.date else { return } completionHandler(completedDate) }) } catch { assertionFailure("Failed to successfully initialize DataDetector") completionHandler(nil) } } private func setAttributedTitle(title: String, for button: NSButton) { let style = NSMutableParagraphStyle() style.alignment = .center guard let font = NSFont(name: "Avenir-Book", size: 13) else { return } let attributesDictionary = [ NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.paragraphStyle: style, ] button.attributedTitle = NSAttributedString(string: title, attributes: attributesDictionary) } @IBAction func saveAction(_: Any) { updateLabel() dataObject?.note = notesTextView.string dataObject?.setShouldOverrideGlobalTimeFormat(timeFormatControl.indexOfSelectedItem) insertTimezoneInDefaultPreferences() if setReminderCheckbox.state == .on { setReminderAlarm() Logger.log(object: nil, for: "Reminder Set") } refreshMainTableView() NotificationCenter.default.post(name: NSNotification.Name.customLabelChanged, object: nil) popover?.close() } @IBAction func seeReminders(_: Any) { OperationQueue.main.addOperation { self.scriptExecutionIndicator.startAnimation(nil) let source = """ tell application \"Reminders\" tell default account show (first list where name is \"Clocker Reminders\") activate application end tell end tell """ var scriptExecutionErrors: NSDictionary? = .none let remindersScript = NSAppleScript(source: source) let eventDescriptor = remindersScript?.executeAndReturnError(&scriptExecutionErrors) if let errors = scriptExecutionErrors, errors.allKeys.isEmpty == false { if let convertedType = errors as? [String: Any] { Logger.log(object: convertedType, for: "Script Execution Errors") } NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/Reminders.app")) } else if eventDescriptor == nil { Logger.log(object: nil, for: "Event Description is unexpectedly nil") NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/Reminders.app")) } else { Logger.log(object: ["Successfully Executed Apple Script": "YES"], for: "Successfully Executed Apple Script") } self.scriptExecutionIndicator.stopAnimation(nil) } } @IBAction func checkboxAction(_: Any) { enableReminderView(!alertPopupButton.isEnabled) } @IBAction func customizeTimeFormat(_ sender: NSSegmentedControl) { updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat) refreshMainTableView() // Update the display if the chosen menubar mode is compact! if let delegate = NSApplication.shared.delegate as? AppDelegate { let handler = delegate.statusItemForPanel() handler.setupStatusItem() } } private func insertTimezoneInDefaultPreferences() { guard let model = dataObject, var timezones = timezoneObjects, let encodedObject = NSKeyedArchiver.clocker_archive(with:model) else { return } timezones[currentRow] = encodedObject DataStore.shared().setTimezones(timezones) } private func updateTimezoneInDefaultPreferences(with override: Int, _: OverrideType) { let timezones = DataStore.shared().timezones() var timezoneObjects: [TimezoneData] = [] for timezone in timezones { if let model = TimezoneData.customObject(from: timezone) { timezoneObjects.append(model) } } for timezoneObject in timezoneObjects where timezoneObject.isEqual(dataObject) { timezoneObject.setShouldOverrideGlobalTimeFormat(override) } var datas: [Data] = [] for updatedObject in timezoneObjects { guard let dataObject = NSKeyedArchiver.clocker_archive(with: updatedObject) else { continue } datas.append(dataObject) } DataStore.shared().setTimezones(datas) } private func setReminderAlarm() { let eventCenter = EventCenter.sharedCenter() if eventCenter.reminderAccessNotDetermined() { eventCenter.requestAccess(to: .reminder, completionHandler: { granted in if granted { OperationQueue.main.addOperation { self.createReminder() } } else { Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted") } }) } else if eventCenter.reminderAccessGranted() { createReminder() } else { showAlertForPermissionNotGiven() } } private func refreshMainTableView() { OperationQueue.main.addOperation { if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { let currentInstance = FloatingWindowController.shared() currentInstance.updateDefaultPreferences() } else { guard let panelController = PanelController.panel() else { return } panelController.updateDefaultPreferences() panelController.updateTableContent() } } } private func createReminder() { guard let model = dataObject else { return } if setReminderCheckbox.state == .on { let eventCenter = EventCenter.sharedCenter() let alertIndex = alertPopupButton.indexOfSelectedItem if eventCenter.createReminder(with: model.customLabel!, timezone: model.timezone(), alertIndex: alertIndex, reminderDate: reminderPicker.dateValue, additionalNotes: model.note) { showSuccessMessage() } } } private func showAlertForPermissionNotGiven() { NSApplication.shared.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = "Clocker needs access to Reminders 😅" alert.informativeText = "Please go to System Preferences -> Security & Privacy -> Privacy -> Reminders to allow Clocker to set reminders." alert.addButton(withTitle: "Okay") let alertResponse = alert.runModal() if alertResponse == .stop { OperationQueue.main.addOperation { self.popover?.close() } } } private func showSuccessMessage() { let content = UNMutableNotificationContent() content.title = "Reminder Set".localized() content.subtitle = "Successfully set.".localized() let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) } func setDataSource(data: TimezoneData) { dataObject = data if isViewLoaded { updateContent() } } } extension NotesPopover { func setRow(row: Int) { currentRow = row } func set(timezones: [Data]) { timezoneObjects = timezones } func set(with popover: NSPopover) { self.popover = popover } @objc func themeChanged() { notesTextView.textColor = Themer.shared().mainTextColor() customLabel.textColor = Themer.shared().mainTextColor() reminderPicker.textColor = Themer.shared().mainTextColor() popover?.appearance = Themer.shared().popoverAppearance() setAttributedTitle(title: saveButton.title, for: saveButton) setAttributedTitle(title: remindersButton.title, for: remindersButton) } func updateContent() { guard let model = dataObject else { assertionFailure("Model object was unexepectedly nil") return } enableReminderView(false) setReminderCheckbox.state = .off if let label = model.customLabel, !label.isEmpty { customLabel.stringValue = label } else { customLabel.stringValue = model.formattedTimezoneLabel() } if let note = model.note, !note.isEmpty { notesTextView.string = note } else { notesTextView.string = UserDefaultKeys.emptyString } setInitialReminderTime() updateTimeFormat() } private func updateTimeFormat() { timeFormatControl.selectItem(at: convertOverrideFormatToPopupControlSelection()) } private func enableReminderView(_ shouldEnable: Bool) { reminderPicker.isEnabled = shouldEnable alertPopupButton.isEnabled = shouldEnable reminderPicker.alphaValue = shouldEnable ? 1.0 : 0.25 } } extension NotesPopover: NSTextFieldDelegate { func controlTextDidChange(_: Notification) { updateLabel() } private func updateLabel() { // We need to do a couple of things if the customLabel is updated // 1. Update the userDefaults // 2. Check if the timezone is displayed in the menubar; if so, update the model guard let model = dataObject else { return } model.setLabel(customLabel.stringValue) insertTimezoneInDefaultPreferences() NotificationCenter.default.post(name: NSNotification.Name.customLabelChanged, object: nil) } } ================================================ FILE: Clocker/Panel/Notes Popover/NotesPopover.xib ================================================ ================================================ FILE: Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import Cocoa class TextViewWithPlaceholder: NSTextView { let placeholder = makePlaceHolder() override init(frame frameRect: NSRect) { super.init(frame: frameRect) } required init?(coder: NSCoder) { super.init(coder: coder) } class func makePlaceHolder() -> NSAttributedString { if let placeHolderFont = NSFont(name: "Avenir", size: 14) { let textDict = [ NSAttributedString.Key.foregroundColor: NSColor.gray, NSAttributedString.Key.font: placeHolderFont, ] return NSAttributedString(string: " Add your notes here.", attributes: textDict) } return NSAttributedString(string: " Add your notes here") } override func becomeFirstResponder() -> Bool { setNeedsDisplay(frame) return super.becomeFirstResponder() } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) if string == UserDefaultKeys.emptyString, self != window?.firstResponder { placeholder.draw(at: NSPoint(x: 0, y: 0)) } } override func resignFirstResponder() -> Bool { setNeedsDisplay(frame) return super.resignFirstResponder() } } ================================================ FILE: Clocker/Panel/PanelController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class PanelController: ParentPanelController { @objc dynamic var hasActivePanel: Bool = false @IBOutlet var backgroundView: BackgroundPanelView! override func windowDidLoad() { super.windowDidLoad() } override func awakeFromNib() { super.awakeFromNib() enablePerformanceLoggingIfNeccessary() window?.title = "Clocker Panel" window?.setAccessibilityIdentifier("Clocker Panel") // Otherwise, the panel can be dragged around while we try to scroll through the modern slider window?.isMovableByWindowBackground = false if let panel = window { panel.acceptsMouseMovedEvents = true panel.level = .popUpMenu panel.isOpaque = false panel.backgroundColor = NSColor.clear } mainTableView.registerForDraggedTypes([.dragSession]) super.updatePanelColor() super.updateDefaultPreferences() } private func enablePerformanceLoggingIfNeccessary() { if !ProcessInfo.processInfo.environment.keys.contains("ENABLE_PERF_LOGGING") { PerfLogger.disable() } } func setFrameTheNewWay(_ rect: NSRect, _ maxX: CGFloat) { // Calculate window's top left point. // First, center window under status item. let width = (window?.frame)!.width var xPoint = CGFloat(roundf(Float(rect.midX - width / 2))) let yPoint = CGFloat(rect.minY - 2) let kMinimumSpaceBetweenWindowAndScreenEdge: CGFloat = 10 if xPoint + width + kMinimumSpaceBetweenWindowAndScreenEdge > maxX { xPoint = maxX - width - kMinimumSpaceBetweenWindowAndScreenEdge } window?.setFrameTopLeftPoint(NSPoint(x: xPoint, y: yPoint)) window?.invalidateShadow() } func open() { PerfLogger.startMarker("Open") guard isWindowLoaded == true else { return } super.dismissRowActions() updateDefaultPreferences() setupUpcomingEventViewCollectionViewIfNeccesary() if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false { modernContainerView.isHidden = true } else if let value = DataStore.shared().retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber, modernContainerView != nil { if value.intValue == 1 { modernContainerView.isHidden = true } else if value.intValue == 0 { modernContainerView.isHidden = false } } // Reset future slider value to zero closestQuarterTimeRepresentation = findClosestQuarterTimeApproximation() modernSliderLabel.stringValue = "Time Scroller" resetModernSliderButton.isHidden = true if modernSlider != nil { let indexPaths: Set = Set([IndexPath(item: modernSlider.numberOfItems(inSection: 0) / 2, section: 0)]) modernSlider.scrollToItems(at: indexPaths, scrollPosition: .centeredHorizontally) } goForwardButton.alphaValue = 0 goBackwardsButton.alphaValue = 0 setTimezoneDatasourceSlider(sliderValue: 0) reviewView.isHidden = !ReviewController.canPrompt() reviewView.layer?.backgroundColor = NSColor.clear.cgColor setPanelFrame() startWindowTimer() if DataStore.shared().shouldDisplay(ViewType.upcomingEventView) { retrieveCalendarEvents() } else { removeUpcomingEventView() super.setScrollViewConstraint() } // This is done to make the UI look updated. mainTableView.reloadData() log() PerfLogger.endMarker("Open") } // New way to set the panel's frame. // This takes into account the screen's dimensions. private func setPanelFrame() { PerfLogger.startMarker("Set Panel Frame") guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return } var statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.button?.window var statusView = appDelegate.statusItemForPanel().statusItem.button // This below is a better way than actually checking if the menubar compact mode is set. if statusBackgroundWindow == nil || statusView == nil { statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.button?.window statusView = appDelegate.statusItemForPanel().statusItem.button } if let statusWindow = statusBackgroundWindow, let statusButton = statusView { var statusItemFrame = statusWindow.convertToScreen(statusButton.frame) var statusItemScreen = NSScreen.main var testPoint = statusItemFrame.origin testPoint.y -= 100 for screen in NSScreen.screens where screen.frame.contains(testPoint) { statusItemScreen = screen break } let screenMaxX = (statusItemScreen?.frame)!.maxX let minY = statusItemFrame.origin.y < (statusItemScreen?.frame)!.maxY ? statusItemFrame.origin.y : (statusItemScreen?.frame)!.maxY statusItemFrame.origin.y = minY setFrameTheNewWay(statusItemFrame, screenMaxX) PerfLogger.endMarker("Set Panel Frame") } } private func log() { PerfLogger.startMarker("Logging") let preferences = DataStore.shared().timezones() guard let theme = DataStore.shared().retrieve(key: UserDefaultKeys.themeKey) as? NSNumber, let displayFutureSliderKey = DataStore.shared().retrieve(key: UserDefaultKeys.themeKey) as? NSNumber, let showAppInForeground = DataStore.shared().retrieve(key: UserDefaultKeys.showAppInForeground) as? NSNumber, let relativeDateKey = DataStore.shared().retrieve(key: UserDefaultKeys.relativeDateKey) as? NSNumber, let fontSize = DataStore.shared().retrieve(key: UserDefaultKeys.userFontSizePreference) as? NSNumber, let sunriseTime = DataStore.shared().retrieve(key: UserDefaultKeys.sunriseSunsetTime) as? NSNumber, let showDayInMenu = DataStore.shared().retrieve(key: UserDefaultKeys.showDayInMenu) as? NSNumber, let showDateInMenu = DataStore.shared().retrieve(key: UserDefaultKeys.showDateInMenu) as? NSNumber, let showPlaceInMenu = DataStore.shared().retrieve(key: UserDefaultKeys.showPlaceInMenu) as? NSNumber, let showUpcomingEventView = DataStore.shared().retrieve(key: UserDefaultKeys.showUpcomingEventView) as? String, let country = Locale.autoupdatingCurrent.region?.identifier else { return } var relativeDate = "Relative" if relativeDateKey.isEqual(to: NSNumber(value: 1)) { relativeDate = "Actual Day" } else if relativeDateKey.isEqual(to: NSNumber(value: 2)) { relativeDate = "Date" } let panelEvent: [String: Any] = [ "Theme": theme.isEqual(to: NSNumber(value: 0)) ? "Default" : "Black", "Display Future Slider": displayFutureSliderKey.isEqual(to: NSNumber(value: 0)) ? "Yes" : "No", "Clocker mode": showAppInForeground.isEqual(to: NSNumber(value: 0)) ? "Menubar" : "Floating", "Relative Date": relativeDate, "Font Size": fontSize, "Sunrise Sunset": sunriseTime.isEqual(to: NSNumber(value: 0)) ? "Yes" : "No", "Show Day in Menu": showDayInMenu.isEqual(to: NSNumber(value: 0)) ? "Yes" : "No", "Show Date in Menu": showDateInMenu.isEqual(to: NSNumber(value: 0)) ? "Yes" : "No", "Show Place in Menu": showPlaceInMenu.isEqual(to: NSNumber(value: 0)) ? "Yes" : "No", "Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No", "Country": country, "Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No", "Number of Timezones": preferences.count, ] Logger.log(object: panelEvent, for: "openedPanel") PerfLogger.endMarker("Logging") } private func startWindowTimer() { PerfLogger.startMarker("Start Window Timer") stopMenubarTimerIfNeccesary() if let timer = parentTimer, timer.state == .paused { parentTimer?.start() PerfLogger.endMarker("Start Window Timer") return } startTimer() PerfLogger.endMarker("Start Window Timer") } private func startTimer() { Logger.info("Start timer called") parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in OperationQueue.main.addOperation { self.updateTime() } } parentTimer!.start() } private func stopMenubarTimerIfNeccesary() { let count = DataStore.shared().menubarTimezones()?.count ?? 0 if count >= 1 || DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if let delegate = NSApplication.shared.delegate as? AppDelegate { Logger.info("We will be invalidating the menubar timer as we want the parent timer to take care of both panel and menubar ") delegate.invalidateMenubarTimer(false) } } } func cancelOperation() { setActivePanel(newValue: false) } func hasActivePanelGetter() -> Bool { return hasActivePanel } func minimize() { let delegate = NSApplication.shared.delegate as? AppDelegate let count = DataStore.shared().menubarTimezones()?.count ?? 0 if count >= 1 || DataStore.shared().shouldDisplay(.showMeetingInMenubar) == true { if let handler = delegate?.statusItemForPanel(), let timer = handler.menubarTimer, !timer.isValid { delegate?.setupMenubarTimer() } } parentTimer?.pause() updatePopoverDisplayState() NSAnimationContext.beginGrouping() NSAnimationContext.current.duration = 0.1 window?.animator().alphaValue = 0 additionalOptionsPopover?.close() NSAnimationContext.endGrouping() window?.orderOut(nil) datasource = nil parentTimer?.pause() parentTimer = nil } func setActivePanel(newValue: Bool) { hasActivePanel = newValue hasActivePanel ? open() : minimize() } class func panel() -> PanelController? { let panel = NSApplication.shared.windows.compactMap { window -> PanelController? in guard let parent = window.windowController as? PanelController else { return nil } return parent } return panel.first } override func showNotesPopover(forRow row: Int, relativeTo positioningRect: NSRect, andButton target: NSButton!) -> Bool { if additionalOptionsPopover == nil { additionalOptionsPopover = NSPopover() } guard let popover = additionalOptionsPopover else { return false } target.image = Themer.shared().extraOptionsHighlightedImage() if popover.isShown, row == previousPopoverRow { popover.close() target.image = Themer.shared().extraOptionsImage() previousPopoverRow = -1 return false } previousPopoverRow = row super.showNotesPopover(forRow: row, relativeTo: positioningRect, andButton: target) popover.show(relativeTo: positioningRect, of: target, preferredEdge: .minX) if let timer = parentTimer, timer.state == .paused { timer.start() } return true } func setupMenubarTimer() { if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } } func pauseTimer() { if let timer = parentTimer { timer.pause() } } func refreshBackgroundView() { backgroundView.setNeedsDisplay(backgroundView.bounds) } override func scrollWheel(with event: NSEvent) { if event.phase == NSEvent.Phase.ended { Logger.log(object: nil, for: "Scroll Event Ended") } // We only want to move the slider if the slider is visible. // If the parent view is hidden, then that doesn't automatically mean that all the childViews are also hidden // Hence, check if the parent view is totally hidden or not.. if modernSlider.isHidden { // TODO: Move modern slider } } } extension PanelController: NSWindowDelegate { func windowWillClose(_: Notification) { parentTimer = nil setActivePanel(newValue: false) } func windowDidResignKey(_: Notification) { parentTimer = nil if let isVisible = window?.isVisible, isVisible == true { setActivePanel(newValue: false) } if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.statusItemForPanel().statusItem.button?.state = .off } } } ================================================ FILE: Clocker/Panel/ParentPanelController+ModernSlider.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import CoreLoggerKit import Foundation extension ParentPanelController: NSCollectionViewDataSource { func collectionView(_: NSCollectionView, numberOfItemsInSection _: Int) -> Int { let futureSliderDayPreference = DataStore.shared().retrieve(key: UserDefaultKeys.futureSliderRange) as? NSNumber ?? 5 let futureSliderDayRange = (futureSliderDayPreference.intValue + 1) return (PanelConstants.modernSliderPointsInADay * futureSliderDayRange * 2) + 1 } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { guard let item = collectionView.makeItem(withIdentifier: TimeMarkerViewItem.reuseIdentifier, for: indexPath) as? TimeMarkerViewItem else { return NSCollectionViewItem() } item.setup(with: indexPath.item) return item } } extension ParentPanelController { func setupModernSliderIfNeccessary() { if modernSlider != nil { modernSliderLabel.alignment = .center if #available(OSX 11.0, *) { resetModernSliderButton.image = Themer.shared().resetModernSliderImage() } else { resetModernSliderButton.layer?.backgroundColor = NSColor.lightGray.cgColor resetModernSliderButton.layer?.masksToBounds = true resetModernSliderButton.layer?.cornerRadius = resetModernSliderButton.frame.width / 2 } if let scrollView = modernSlider.superview?.superview as? NSScrollView { scrollView.scrollerStyle = NSScroller.Style.overlay } goBackwardsButton.image = Themer.shared().goBackwardsImage() goForwardButton.image = Themer.shared().goForwardsImage() goForwardButton.isContinuous = true goBackwardsButton.isContinuous = true goBackwardsButton.toolTip = "Navigate 15 mins back" goForwardButton.toolTip = "Navigate 15 mins forward" modernSlider.wantsLayer = true // Required for animating reset to center modernSlider.enclosingScrollView?.scrollerInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) modernSlider.enclosingScrollView?.backgroundColor = NSColor.clear modernSlider.setAccessibility("ModernSlider") modernSlider.postsBoundsChangedNotifications = true NotificationCenter.default.addObserver(self, selector: #selector(collectionViewDidScroll(_:)), name: NSView.boundsDidChangeNotification, object: modernSlider.superview) // Set the modern slider label! closestQuarterTimeRepresentation = findClosestQuarterTimeApproximation() if let unwrappedClosetQuarterTime = closestQuarterTimeRepresentation { modernSliderLabel.stringValue = timezoneFormattedStringRepresentation(unwrappedClosetQuarterTime) } // Make sure modern slider is centered horizontally! let indexPaths: Set = Set([IndexPath(item: modernSlider.numberOfItems(inSection: 0) / 2, section: 0)]) modernSlider.scrollToItems(at: indexPaths, scrollPosition: .centeredHorizontally) } } @IBAction func goForward(_: NSButton) { navigateModernSliderToSpecificIndex(1) } @IBAction func goBackward(_: NSButton) { navigateModernSliderToSpecificIndex(-1) } private func animateButton(_ hidden: Bool) { NSAnimationContext.runAnimationGroup({ context in context.duration = 0.5 context.timingFunction = CAMediaTimingFunction(name: hidden ? CAMediaTimingFunctionName.easeOut : CAMediaTimingFunctionName.easeIn) resetModernSliderButton.animator().alphaValue = hidden ? 0.0 : 1.0 }, completionHandler: { [weak self] in guard let strongSelf = self else { return } strongSelf.resetModernSliderButton.animator().isHidden = hidden }) } private func showAccessoryButtonsIfNeccesary(_ hide: Bool) { NSAnimationContext.runAnimationGroup({ context in context.duration = 0.5 context.timingFunction = CAMediaTimingFunction(name: hide ? CAMediaTimingFunctionName.easeOut : CAMediaTimingFunctionName.easeIn) goForwardButton.animator().alphaValue = hide ? 0.0 : 1.0 goBackwardsButton.animator().alphaValue = hide ? 0.0 : 1.0 }, completionHandler: nil) } @IBAction func resetModernSlider(_: NSButton) { closestQuarterTimeRepresentation = findClosestQuarterTimeApproximation() modernSliderLabel.stringValue = "Time Scroller" if modernSlider != nil { let indexPaths: Set = Set([IndexPath(item: modernSlider.numberOfItems(inSection: 0) / 2, section: 0)]) NSAnimationContext.runAnimationGroup({ context in context.duration = 0.5 context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) modernSlider.animator().scrollToItems(at: indexPaths, scrollPosition: .centeredHorizontally) }, completionHandler: { [weak self] in guard let strongSelf = self else { return } strongSelf.animateButton(true) strongSelf.showAccessoryButtonsIfNeccesary(true) }) } } private func navigateModernSliderToSpecificIndex(_ index: Int) { guard let contentView = modernSlider.superview as? NSClipView else { return } let changedOrigin = contentView.documentVisibleRect.origin let newPoint = NSPoint(x: changedOrigin.x + contentView.frame.width / 2, y: changedOrigin.y) if let indexPath = modernSlider.indexPathForItem(at: newPoint) { let previousIndexPath = IndexPath(item: indexPath.item + index, section: indexPath.section) modernSlider.scrollToItems(at: Set([previousIndexPath]), scrollPosition: .centeredHorizontally) } } @objc func collectionViewDidScroll(_ notification: NSNotification) { guard let contentView = notification.object as? NSClipView else { return } let changedOrigin = contentView.documentVisibleRect.origin let newPoint = NSPoint(x: changedOrigin.x + contentView.frame.width / 2, y: changedOrigin.y) let indexPath = modernSlider.indexPathForItem(at: newPoint) if let correctIndexPath = indexPath?.item, currentCenterIndexPath != correctIndexPath { showAccessoryButtonsIfNeccesary(false) currentCenterIndexPath = correctIndexPath let minutesToAdd = setDefaultDateLabel(correctIndexPath) setTimezoneDatasourceSlider(sliderValue: minutesToAdd) mainTableView.reloadData() } } public func findClosestQuarterTimeApproximation() -> Date { let defaultParameters = minuteFromCalendar() let hourQuarterDate = Calendar.current.nextDate(after: defaultParameters.0, matching: DateComponents(minute: defaultParameters.1), matchingPolicy: .strict, repeatedTimePolicy: .first, direction: .forward)! return hourQuarterDate } public func setDefaultDateLabel(_ index: Int) -> Int { let futureSliderDayPreference = DataStore.shared().retrieve(key: UserDefaultKeys.futureSliderRange) as? NSNumber ?? 5 let futureSliderDayRange = (futureSliderDayPreference.intValue + 1) let totalCount = (PanelConstants.modernSliderPointsInADay * futureSliderDayRange * 2) + 1 let centerPoint = Int(ceil(Double(totalCount / 2))) if index >= (centerPoint + 1) { let remainder = (index % (centerPoint + 1)) let nextDate = Calendar.current.date(byAdding: .minute, value: remainder * 15, to: closestQuarterTimeRepresentation ?? Date())! modernSliderLabel.stringValue = timezoneFormattedStringRepresentation(nextDate) if resetModernSliderButton.isHidden { animateButton(false) } return nextDate.minutes(from: Date()) + 1 } else if index < centerPoint { let remainder = centerPoint - index + 1 let previousDate = Calendar.current.date(byAdding: .minute, value: -1 * remainder * 15, to: closestQuarterTimeRepresentation ?? Date())! modernSliderLabel.stringValue = timezoneFormattedStringRepresentation(previousDate) if resetModernSliderButton.isHidden { animateButton(false) } return previousDate.minutes(from: Date()) } else { modernSliderLabel.stringValue = "Time Scroller" if !resetModernSliderButton.isHidden { animateButton(true) } return 0 } } private func minuteFromCalendar() -> (Date, Int) { let currentDate = Date() var minute = Calendar.current.component(.minute, from: currentDate) if minute < 15 { minute = 15 } else if minute < 30 { minute = 30 } else if minute < 45 { minute = 45 } else { minute = 0 } return (currentDate, minute) } private func timezoneFormattedStringRepresentation(_ date: Date) -> String { let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none, format: "MMM d HH:mm", timezoneIdentifier: TimeZone.current.identifier, locale: Locale.autoupdatingCurrent) return dateFormatter.string(from: date) } } ================================================ FILE: Clocker/Panel/ParentPanelController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import EventKit struct PanelConstants { static let notReallyButtonTitle = "Not Really" static let feedbackString = "Mind giving feedback?" static let noThanksTitle = "No, thanks" static let yesWithQuestionMark = "Yes?" static let yesWithExclamation = "Yes!" static let modernSliderPointsInADay = 96 } class ParentPanelController: NSWindowController { private var futureSliderObserver: NSKeyValueObservation? private var userFontSizeSelectionObserver: NSKeyValueObservation? private var futureSliderRangeObserver: NSKeyValueObservation? private var eventStoreChangedNotification: NSObjectProtocol? var dateFormatter = DateFormatter() var futureSliderValue: Int = 0 var parentTimer: Repeater? var showReviewCell: Bool = false var previousPopoverRow: Int = -1 var additionalOptionsPopover: NSPopover? var datasource: TimezoneDataSource? private var feedbackWindow: AppFeedbackWindowController? private var notePopover: NotesPopover? private lazy var oneWindow: OneWindowController? = { let preferencesStoryboard = NSStoryboard(name: "Preferences", bundle: nil) return preferencesStoryboard.instantiateInitialController() as? OneWindowController }() @IBOutlet var mainTableView: PanelTableView! @IBOutlet var stackView: NSStackView! @IBOutlet var scrollViewHeight: NSLayoutConstraint! @IBOutlet var reviewView: NSView! @IBOutlet var leftField: NSTextField! @IBOutlet var sharingButton: NSButton! @IBOutlet var leftButton: NSButton! @IBOutlet var rightButton: NSButton! @IBOutlet var shutdownButton: NSButton! @IBOutlet var preferencesButton: NSButton! @IBOutlet var pinButton: NSButton! @IBOutlet var roundedDateView: NSView! // Modern Slider public var currentCenterIndexPath: Int = -1 public var closestQuarterTimeRepresentation: Date? @IBOutlet var modernSlider: NSCollectionView! @IBOutlet var modernSliderLabel: NSTextField! @IBOutlet var modernContainerView: ModernSliderContainerView! @IBOutlet var goBackwardsButton: NSButton! @IBOutlet var goForwardButton: NSButton! @IBOutlet var resetModernSliderButton: NSButton! // Upcoming Events @IBOutlet var upcomingEventCollectionView: NSCollectionView! @IBOutlet var upcomingEventContainerView: NSView! public var upcomingEventsDataSource: UpcomingEventsDataSource? var defaultPreferences: [Data] { return DataStore.shared().timezones() } deinit { datasource = nil if let eventStoreNotif = eventStoreChangedNotification { NotificationCenter.default.removeObserver(eventStoreNotif) } [futureSliderObserver, userFontSizeSelectionObserver, futureSliderRangeObserver].forEach { $0?.invalidate() } } private func setupObservers() { futureSliderObserver = UserDefaults.standard.observe(\.displayFutureSlider, options: [.new]) { _, change in if let changedValue = change.newValue { if changedValue == 0 { if self.modernContainerView != nil { self.modernContainerView.isHidden = false } } else if changedValue == 1 { if self.modernContainerView != nil { self.modernContainerView.isHidden = true } } else { if self.modernContainerView != nil { self.modernContainerView.isHidden = true } } } } userFontSizeSelectionObserver = UserDefaults.standard.observe(\.userFontSize, options: [.new]) { _, change in if let newFontSize = change.newValue { Logger.log(object: ["FontSize": newFontSize], for: "User Font Size Preference") self.mainTableView.reloadData() self.setScrollViewConstraint() } } futureSliderRangeObserver = UserDefaults.standard.observe(\.sliderDayRange, options: [.new]) { _, change in if change.newValue != nil { self.adjustFutureSliderBasedOnPreferences() if self.modernSlider != nil { self.modernSlider.reloadData() } } } } override func awakeFromNib() { super.awakeFromNib() // Setup table mainTableView.backgroundColor = NSColor.clear mainTableView.selectionHighlightStyle = .none mainTableView.enclosingScrollView?.hasVerticalScroller = false if #available(OSX 11.0, *) { mainTableView.style = .plain } // Setup images let sharedThemer = Themer.shared() shutdownButton.image = sharedThemer.shutdownImage() preferencesButton.image = sharedThemer.preferenceImage() pinButton.image = sharedThemer.pinImage() sharingButton.image = sharedThemer.sharingImage() sharingButton.alternateImage = sharedThemer.sharingImageAlternate() // Setup KVO observers for user default changes setupObservers() updateReviewViewFontColor() // Set the background color of the bottom buttons view to something different to indicate we're not in a release candidate #if DEBUG stackView.arrangedSubviews.last?.layer?.backgroundColor = NSColor(deviceRed: 255.0 / 255.0, green: 150.0 / 255.0, blue: 122.0 / 255.0, alpha: 0.5).cgColor stackView.arrangedSubviews.last?.toolTip = "Debug Mode" #endif // Setup layers reviewView.wantsLayer = true // Setup notifications NotificationCenter.default.addObserver(self, selector: #selector(themeChanged), name: Notification.Name.themeDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(systemTimezoneDidChange), name: NSNotification.Name.NSSystemTimeZoneDidChange, object: nil) NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification, object: self, queue: OperationQueue.main) { [weak self] _ in if let sSelf = self { sSelf.mainTableView.reloadData() sSelf.setScrollViewConstraint() } } // Setup upcoming events view upcomingEventContainerView.setAccessibility("UpcomingEventView") determineUpcomingViewVisibility() setupUpcomingEventViewCollectionViewIfNeccesary() // Setup colors based on the curren theme themeChanged() // UI adjustments based on user preferences if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false { if modernContainerView != nil { modernContainerView.isHidden = true } } else if let value = DataStore.shared().retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber { if value.intValue == 1 { if modernContainerView != nil { modernContainerView.isHidden = true } } else if value.intValue == 0 { // Floating Window doesn't support modern slider yet! if modernContainerView != nil { modernContainerView.isHidden = false } } } // More UI adjustments sharingButton.sendAction(on: .leftMouseDown) adjustFutureSliderBasedOnPreferences() setupModernSliderIfNeccessary() if roundedDateView != nil { setupRoundedDateView() } } private func setupRoundedDateView() { roundedDateView.wantsLayer = true roundedDateView.layer?.cornerRadius = 12.0 roundedDateView.layer?.masksToBounds = false roundedDateView.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor } @objc func systemTimezoneDidChange() { OperationQueue.main.addOperation { /* let locationController = LocationController.sharedController() locationController.determineAndRequestLocationAuthorization()*/ self.updateHomeObject(with: TimeZone.autoupdatingCurrent.identifier, coordinates: nil) } } private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) { let timezones = DataStore.shared().timezones() var timezoneObjects: [TimezoneData] = [] for timezone in timezones { if let model = TimezoneData.customObject(from: timezone) { timezoneObjects.append(model) } } for timezoneObject in timezoneObjects where timezoneObject.isSystemTimezone == true { timezoneObject.setLabel(customLabel) timezoneObject.formattedAddress = customLabel if let latlong = coordinates { timezoneObject.longitude = latlong.longitude timezoneObject.latitude = latlong.latitude } } var datas: [Data] = [] for updatedObject in timezoneObjects { guard let dataObject = NSKeyedArchiver.clocker_archive(with: updatedObject) else { continue } datas.append(dataObject) } DataStore.shared().setTimezones(datas) if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } } func determineUpcomingViewVisibility() { let showUpcomingEventView = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) if showUpcomingEventView == false { upcomingEventContainerView?.isHidden = true } else { upcomingEventContainerView?.isHidden = false setupUpcomingEventView() eventStoreChangedNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.EKEventStoreChanged, object: self, queue: OperationQueue.main) { _ in self.fetchCalendarEvents() } } } private func adjustFutureSliderBasedOnPreferences() { setTimezoneDatasourceSlider(sliderValue: 0) updateTableContent() } private func setupUpcomingEventView() { let eventCenter = EventCenter.sharedCenter() if eventCenter.calendarAccessGranted() { // Nice. Events will be retrieved when we open the panel } else if eventCenter.calendarAccessNotDetermined() { upcomingEventCollectionView.reloadData() } else { removeUpcomingEventView() } themeChanged() } private func updateReviewViewFontColor() { let textColor = Themer.shared().mainTextColor() leftField.textColor = textColor let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let styleAttributes = [ NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13), ] let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes) leftButton.attributedTitle = leftButtonAttributedTitle let rightButtonAttributedTitle = NSAttributedString(string: rightButton.title, attributes: styleAttributes) rightButton.attributedTitle = rightButtonAttributedTitle } @objc func themeChanged() { let sharedThemer = Themer.shared() if upcomingEventContainerView?.isHidden == false { upcomingEventContainerView?.layer?.backgroundColor = NSColor.clear.cgColor } shutdownButton.image = sharedThemer.shutdownImage() preferencesButton.image = sharedThemer.preferenceImage() pinButton.image = sharedThemer.pinImage() sharingButton.image = sharedThemer.sharingImage() sharingButton.alternateImage = sharedThemer.sharingImageAlternate() if roundedDateView != nil { roundedDateView.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor } updateReviewViewFontColor() } override func windowDidLoad() { super.windowDidLoad() additionalOptionsPopover = NSPopover() } func screenHeight() -> CGFloat { guard let main = NSScreen.main else { return 100 } let mouseLocation = NSEvent.mouseLocation var current = main.frame.height let activeScreens = NSScreen.screens.filter { current -> Bool in NSMouseInRect(mouseLocation, current.frame, false) } if let main = activeScreens.first { current = main.frame.height } return current } func invalidateMenubarTimer() { parentTimer = nil } func setScrollViewConstraint() { let preferences = defaultPreferences // This is for the Add Cell View case if preferences.isEmpty { scrollViewHeight.constant = 100.0 return } mainTableView.layoutSubtreeIfNeeded() scrollViewHeight.constant = mainTableView.fittingSize.height if DataStore.shared().shouldDisplay(ViewType.upcomingEventView) { if scrollViewHeight.constant > (screenHeight() - 160) { scrollViewHeight.constant = (screenHeight() - 160) } } else { if scrollViewHeight.constant > (screenHeight() - 100) { scrollViewHeight.constant = (screenHeight() - 100) } } if DataStore.shared().shouldDisplay(.futureSlider) { let isModernSliderDisplayed = DataStore.shared().retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber ?? 0 if isModernSliderDisplayed == 0 { if scrollViewHeight.constant >= (screenHeight() - 200) { scrollViewHeight.constant = (screenHeight() - 300) } } else { if scrollViewHeight.constant >= (screenHeight() - 200) { scrollViewHeight.constant = (screenHeight() - 200) } } } } func updateDefaultPreferences() { PerfLogger.startMarker("Update Default Preferences") updatePanelColor() let store = DataStore.shared() let defaults = store.timezones() let convertedTimezones = defaults.map { data -> TimezoneData in TimezoneData.customObject(from: data)! } datasource = TimezoneDataSource(items: convertedTimezones, store: store) mainTableView.dataSource = datasource mainTableView.delegate = datasource mainTableView.panelDelegate = datasource updateDatasource(with: convertedTimezones) PerfLogger.endMarker("Update Default Preferences") } func updateDatasource(with timezones: [TimezoneData]) { datasource?.setItems(items: timezones) datasource?.setSlider(value: futureSliderValue) mainTableView.reloadData() setScrollViewConstraint() } func updatePanelColor() { window?.alphaValue = 1.0 } func setTimezoneDatasourceSlider(sliderValue: Int) { futureSliderValue = sliderValue datasource?.setSlider(value: sliderValue) } @IBAction func openPreferences(_: NSButton) { updatePopoverDisplayState() // Popover's class has access to all timezones. Need to close the popover, so that we don't have two copies of selections openPreferencesWindow() } func deleteTimezone(at row: Int) { var defaults = defaultPreferences // Remove from panel defaults.remove(at: row) DataStore.shared().setTimezones(defaults) updateDefaultPreferences() NotificationCenter.default.post(name: Notification.Name.customLabelChanged, object: nil) // Now log! Logger.log(object: nil, for: "Deleted Timezone Through Swipe") } private lazy var menubarTitleHandler = MenubarTitleProvider(with: DataStore.shared(), eventStore: EventCenter.sharedCenter()) private static let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: NSFont.monospacedDigitSystemFont(ofSize: 13.0, weight: NSFont.Weight.regular), NSAttributedString.Key.baselineOffset: 0.1] @objc func updateTime() { let store = DataStore.shared() let menubarCount = store.menubarTimezones()?.count ?? 0 if menubarCount >= 1 || store.shouldDisplay(.showMeetingInMenubar) == true { if let status = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() { if store.shouldDisplay(.menubarCompactMode) { status.updateCompactMenubar() } else { status.statusItem.button?.attributedTitle = NSAttributedString(string: menubarTitleHandler.titleForMenubar() ?? "", attributes: ParentPanelController.attributes) } } } let preferences = store.timezones() if modernSlider != nil, modernSlider.isHidden == false, modernContainerView.currentlyInFocus == false { if currentCenterIndexPath != -1, currentCenterIndexPath != modernSlider.numberOfItems(inSection: 0) / 2 { // User is currently scrolling, return! return } } let hoverRow = mainTableView.hoverRow stride(from: 0, to: preferences.count, by: 1).forEach { let current = preferences[$0] if $0 < mainTableView.numberOfRows, let cellView = mainTableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? TimezoneCellView, let model = TimezoneData.customObject(from: current) { if modernContainerView != nil, modernSlider.isHidden == false, modernContainerView.currentlyInFocus { return } let dataOperation = TimezoneDataOperations(with: model, store: DataStore.shared()) cellView.time.stringValue = dataOperation.time(with: futureSliderValue) cellView.sunriseSetTime.stringValue = dataOperation.formattedSunriseTime(with: futureSliderValue) cellView.sunriseSetTime.lineBreakMode = .byClipping if $0 != hoverRow { cellView.relativeDate.stringValue = dataOperation.date(with: futureSliderValue, displayType: .panel) } cellView.currentLocationIndicator.isHidden = !model.isSystemTimezone cellView.sunriseImage.image = model.isSunriseOrSunset ? Themer.shared().sunriseImage() : Themer.shared().sunsetImage() cellView.sunriseImage.contentTintColor = model.isSunriseOrSunset ? NSColor.systemYellow : NSColor.systemOrange if let note = model.note, !note.isEmpty { cellView.noteLabel.stringValue = note } else if let value = TimezoneDataOperations(with: model, store: DataStore.shared()).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) { cellView.noteLabel.stringValue = value } else { cellView.noteLabel.stringValue = UserDefaultKeys.emptyString } cellView.layout(with: model) // TODO: Update modern slider } } } @discardableResult func showNotesPopover(forRow row: Int, relativeTo _: NSRect, andButton target: NSButton!) -> Bool { let defaults = DataStore.shared().timezones() guard let popover = additionalOptionsPopover else { assertionFailure("Data was unexpectedly nil") return false } var correctRow = row target.image = Themer.shared().extraOptionsHighlightedImage() popover.animates = true if notePopover == nil { notePopover = NotesPopover(nibName: NSNib.Name.notesPopover, bundle: nil) popover.behavior = .applicationDefined popover.delegate = self } // Found a case where row number was 8 but we had only 2 timezones if correctRow >= defaults.count { correctRow = defaults.count - 1 } let current = defaults[correctRow] if let model = TimezoneData.customObject(from: current) { notePopover?.setDataSource(data: model) notePopover?.setRow(row: correctRow) notePopover?.set(timezones: defaults) popover.contentViewController = notePopover notePopover?.set(with: popover) return true } return false } func dismissRowActions() { mainTableView.rowActionsVisible = false } @objc func updateTableContent() { mainTableView.reloadData() } @objc private func openPreferencesWindow() { oneWindow?.openGeneralPane() } @IBAction func dismissNextEventLabel(_: NSButton) { let eventCenter = EventCenter.sharedCenter() let now = Date() if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false { if let upcomingEvent = eventCenter.nextOccuring(events), let meetingLink = upcomingEvent.meetingURL { NSWorkspace.shared.open(meetingLink) } } else { removeUpcomingEventView() } } func removeUpcomingEventView() { OperationQueue.main.addOperation { if self.upcomingEventCollectionView != nil { if self.stackView.arrangedSubviews.contains(self.upcomingEventContainerView!), self.upcomingEventContainerView?.isHidden == false { self.upcomingEventContainerView?.isHidden = true UserDefaults.standard.set("NO", forKey: UserDefaultKeys.showUpcomingEventView) Logger.log(object: ["Removed": "YES"], for: "Removed Upcoming Event View") } } } } @IBAction func calendarButtonAction(_ sender: NSButton) { if sender.title == NSLocalizedString("Click here to start.", comment: "Button Title for no Calendar access") { showPermissionsWindow() } else { retrieveCalendarEvents() } } private func showPermissionsWindow() { oneWindow?.openPermissionsPane() NSApp.activate(ignoringOtherApps: true) } func retrieveCalendarEvents() { PerfLogger.startMarker("Retrieve Calendar Events") let eventCenter = EventCenter.sharedCenter() if eventCenter.calendarAccessGranted() { fetchCalendarEvents() } else if eventCenter.calendarAccessNotDetermined() { /* Wait till we get the thumbs up. */ } else { removeUpcomingEventView() } PerfLogger.endMarker("Retrieve Calendar Events") } @IBAction func shareAction(_ sender: NSButton) { let copyAllTimes = retrieveAllTimes() let pasteboard = NSPasteboard.general pasteboard.declareTypes([.string], owner: nil) pasteboard.setString(copyAllTimes, forType: .string) self.window?.contentView?.makeToast("Copied to Clipboard".localized()) } @IBAction func convertToFloatingWindow(_: NSButton) { guard let sharedDelegate = NSApplication.shared.delegate as? AppDelegate else { assertionFailure("Data was unexpectedly nil") return } let showAppInForeground = DataStore.shared().shouldDisplay(ViewType.showAppInForeground) let inverseSelection = showAppInForeground ? NSNumber(value: 0) : NSNumber(value: 1) UserDefaults.standard.set(inverseSelection, forKey: UserDefaultKeys.showAppInForeground) close() if inverseSelection.isEqual(to: NSNumber(value: 1)) { sharedDelegate.setupFloatingWindow(false) } else { sharedDelegate.setupFloatingWindow(true) updateDefaultPreferences() } let mode = inverseSelection.isEqual(to: NSNumber(value: 1)) ? "Floating Mode" : "Menubar Mode" Logger.log(object: ["displayMode": mode], for: "Clocker Mode") } func showUpcomingEventView() { OperationQueue.main.addOperation { if let upcomingView = self.upcomingEventContainerView, upcomingView.isHidden { self.upcomingEventContainerView?.isHidden = false UserDefaults.standard.set("YES", forKey: UserDefaultKeys.showUpcomingEventView) Logger.log(object: ["Shown": "YES"], for: "Added Upcoming Event View") self.themeChanged() } } } private func fetchCalendarEvents() { PerfLogger.startMarker("Fetch Calendar Events") let eventCenter = EventCenter.sharedCenter() let now = Date() if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false { OperationQueue.main.addOperation { if self.upcomingEventCollectionView != nil, let upcomingEvents = eventCenter.upcomingEventsForDay(events) { self.upcomingEventsDataSource?.updateEventsDataSource(upcomingEvents) self.upcomingEventCollectionView.reloadData() return } PerfLogger.endMarker("Fetch Calendar Events") } } else { if upcomingEventCollectionView != nil { upcomingEventsDataSource?.updateEventsDataSource([]) upcomingEventCollectionView.reloadData() return } PerfLogger.endMarker("Fetch Calendar Events") } } // If the popover is displayed, close it // Called when preferences are going to be displayed! func updatePopoverDisplayState() { if notePopover != nil, let isShown = notePopover?.popover?.isShown, isShown { notePopover?.popover?.close() } additionalOptionsPopover = nil } // MARK: Review @IBAction func actionOnNegativeFeedback(_ sender: NSButton) { if sender.title == PanelConstants.notReallyButtonTitle { setAnimated(title: PanelConstants.feedbackString, field: leftField, leftTitle: PanelConstants.noThanksTitle, rightTitle: PanelConstants.yesWithQuestionMark) } else { updateReviewView() ReviewController.prompted() if let countryCode = Locale.autoupdatingCurrent.region?.identifier { Logger.log(object: ["CurrentCountry": countryCode], for: "Remind Later for Feedback") } } } @IBAction func actionOnPositiveFeedback(_ sender: NSButton) { if sender.title == PanelConstants.yesWithExclamation { setAnimated(title: "Would you like to rate us?", field: leftField, leftTitle: PanelConstants.noThanksTitle, rightTitle: "Yes") } else if sender.title == PanelConstants.yesWithQuestionMark { ReviewController.prompted() updateReviewView() feedbackWindow = AppFeedbackWindowController.shared() feedbackWindow?.appFeedbackWindowDelegate = self feedbackWindow?.showWindow(nil) NSApp.activate(ignoringOtherApps: true) } else { updateReviewView() ReviewController.prompt() } } private func updateReviewView() { reviewView.isHidden = true showReviewCell = false leftField.stringValue = NSLocalizedString("Enjoy using Clocker?", comment: "Title asking users if they like the app") let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let styleAttributes = [ NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, ] leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes) rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes) } private func setAnimated(title: String, field: NSTextField, leftTitle: String, rightTitle: String) { if field.stringValue == title { return } NSAnimationContext.runAnimationGroup({ context in context.duration = 1 context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) leftButton.animator().alphaValue = 0.0 rightButton.animator().alphaValue = 0.0 }, completionHandler: { field.stringValue = title NSAnimationContext.runAnimationGroup({ context in context.duration = 1 context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) self.runAnimationCompletionBlock(leftTitle, rightTitle) }, completionHandler: {}) }) } private func runAnimationCompletionBlock(_ leftButtonTitle: String, _ rightButtonTitle: String) { leftButton.animator().alphaValue = 1.0 rightButton.animator().alphaValue = 1.0 let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let styleAttributes = [ NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, ] if leftButton.attributedTitle.string == "Not Really" { leftButton.animator().attributedTitle = NSAttributedString(string: PanelConstants.noThanksTitle, attributes: styleAttributes) } if rightButton.attributedTitle.string == PanelConstants.yesWithExclamation { rightButton.animator().attributedTitle = NSAttributedString(string: "Yes, sure", attributes: styleAttributes) } leftButton.animator().attributedTitle = NSAttributedString(string: leftButtonTitle, attributes: styleAttributes) rightButton.animator().attributedTitle = NSAttributedString(string: rightButtonTitle, attributes: styleAttributes) } // MARK: Date Picker + Slider func minutes(from date: Date, other: Date) -> Int { return Calendar.current.dateComponents([.minute], from: date, to: other).minute ?? 0 } @objc func terminateClocker() { NSApplication.shared.terminate(nil) } @objc func reportIssue() { feedbackWindow = AppFeedbackWindowController.shared() feedbackWindow?.appFeedbackWindowDelegate = self feedbackWindow?.showWindow(nil) NSApp.activate(ignoringOtherApps: true) window?.orderOut(nil) if let countryCode = Locale.autoupdatingCurrent.region?.identifier { let custom: [String: Any] = ["Country": countryCode] Logger.log(object: custom, for: "Report Issue Opened") } } @objc func openCrowdin() { guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink), let languageCode = Locale.preferredLanguages.first else { return } NSWorkspace.shared.open(localizationURL) // Log this let custom: [String: Any] = ["Language": languageCode] Logger.log(object: custom, for: "Opened Localization Link") } @objc func rate() { guard let sourceURL = URL(string: AboutUsConstants.AppStoreLink) else { return } NSWorkspace.shared.open(sourceURL) } @objc func openFAQs() { guard let sourceURL = URL(string: AboutUsConstants.FAQsLink) else { return } NSWorkspace.shared.open(sourceURL) } @IBAction func showMoreOptions(_ sender: NSButton) { let menuItem = NSMenu(title: "More Options") let terminateOption = NSMenuItem(title: "Quit Clocker", action: #selector(terminateClocker), keyEquivalent: "") let rateClocker = NSMenuItem(title: "Support Clocker...", action: #selector(rate), keyEquivalent: "") let sendFeedback = NSMenuItem(title: "Send Feedback...", action: #selector(reportIssue), keyEquivalent: "") let localizeClocker = NSMenuItem(title: "Localize Clocker...", action: #selector(openCrowdin), keyEquivalent: "") let openPreferences = NSMenuItem(title: "Settings", action: #selector(openPreferencesWindow), keyEquivalent: "") let appDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") ?? "Clocker" let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "N/A" let longVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "N/A" let versionInfo = "\(appDisplayName) \(shortVersion) (\(longVersion))" let clockerVersionInfo = NSMenuItem(title: versionInfo, action: nil, keyEquivalent: "") clockerVersionInfo.isEnabled = false menuItem.addItem(openPreferences) menuItem.addItem(rateClocker) menuItem.addItem(withTitle: "FAQs", action: #selector(openFAQs), keyEquivalent: "") menuItem.addItem(sendFeedback) menuItem.addItem(localizeClocker) menuItem.addItem(NSMenuItem.separator()) menuItem.addItem(clockerVersionInfo) menuItem.addItem(NSMenuItem.separator()) menuItem.addItem(terminateOption) NSMenu.popUpContextMenu(menuItem, with: NSApp.currentEvent!, for: sender) } } extension ParentPanelController: NSPopoverDelegate { func popoverShouldClose(_: NSPopover) -> Bool { return false } } extension ParentPanelController: NSSharingServicePickerDelegate { func sharingServicePicker(_: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { Logger.log(object: ["Service Title": sharingService.title], for: "Sharing Service Executed") return self as? NSSharingServiceDelegate } func sharingServicePicker(_: NSSharingServicePicker, sharingServicesForItems _: [Any], proposedSharingServices proposed: [NSSharingService]) -> [NSSharingService] { let themer = Themer.shared() let copySharingService = NSSharingService(title: "Copy All Times", image:themer.copyImage(), alternateImage: themer.highlightedCopyImage()) { [weak self] in guard let strongSelf = self else { return } let clipboardCopy = strongSelf.retrieveAllTimes() let pasteboard = NSPasteboard.general pasteboard.declareTypes([.string], owner: nil) pasteboard.setString(clipboardCopy, forType: .string) } let allowedServices: Set = Set(["Messages", "Notes"]) let filteredServices = proposed.filter { service in allowedServices.contains(service.title) } var newProposedServices: [NSSharingService] = [copySharingService] newProposedServices.append(contentsOf: filteredServices) return newProposedServices } /// Retrieves all the times from user's added timezones. Times are sorted by date. For eg: /// Feb 5 /// California - 17:17:01 /// Feb 6 /// London - 01:17:01 private func retrieveAllTimes() -> String { var clipboardCopy = String() // Get all timezones let timezones = DataStore.shared().timezones() if timezones.isEmpty { return clipboardCopy } // Sort them in ascending order let sortedByTime = timezones.sorted { obj1, obj2 -> Bool in let system = NSTimeZone.system guard let object1 = TimezoneData.customObject(from: obj1), let object2 = TimezoneData.customObject(from: obj2) else { assertionFailure("Data was unexpectedly nil") return false } let timezone1 = NSTimeZone(name: object1.timezone()) let timezone2 = NSTimeZone(name: object2.timezone()) let difference1 = system.secondsFromGMT() - timezone1!.secondsFromGMT let difference2 = system.secondsFromGMT() - timezone2!.secondsFromGMT return difference1 > difference2 } // Grab date in first place and store it as local variable guard let earliestTimezone = TimezoneData.customObject(from: sortedByTime.first) else { return clipboardCopy } let timezoneOperations = TimezoneDataOperations(with: earliestTimezone, store: DataStore.shared()) let futureSliderValue = datasource?.sliderValue ?? 0 var sectionTitle = timezoneOperations.todaysDate(with: futureSliderValue) clipboardCopy.append("\(sectionTitle)\n") stride(from: 0, to: sortedByTime.count, by: 1).forEach { if $0 < sortedByTime.count, let dataModel = TimezoneData.customObject(from: sortedByTime[$0]) { let dataOperations = TimezoneDataOperations(with: dataModel, store: DataStore.shared()) let date = dataOperations.todaysDate(with: futureSliderValue) let time = dataOperations.time(with: futureSliderValue) if date != sectionTitle { sectionTitle = date clipboardCopy.append("\n\(sectionTitle)\n") } clipboardCopy.append("\(dataModel.formattedTimezoneLabel()) - \(time)\n") } } return clipboardCopy } } extension ParentPanelController: AppFeedbackWindowControllerDelegate { func appFeedbackWindowWillClose() { feedbackWindow = nil } func appFeedbackWindoEntryPoint() -> String { return "parent_panel_controller" } } ================================================ FILE: Clocker/Panel/Rate Controller/ReviewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import StoreKit final class ReviewController { private static var storage = UserDefaults.standard private static var debugging = false private enum Keys { static let lastPrompt = "last-prompt" static let lastVersion = "last-version" static let install = "install" } class func applicationDidLaunch(_ defaults: UserDefaults) { if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.testingLaunchArgument) { debugging = true } storage = defaults if defaults.object(forKey: Keys.install) == nil { defaults.set(Date(), forKey: Keys.install) } } class func setPreviewMode(_ value: Bool) { debugging = value } class func prompted() { storage.set(Date(), forKey: Keys.lastPrompt) storage.set(Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String, forKey: Keys.lastVersion) } class func canPrompt() -> Bool { if debugging == true { return true } let day: TimeInterval = -1 * 60 * 60 * 24 let minInstall: TimeInterval = day * 7 // Check if the app has been installed for atleast 7 days guard let install = storage.object(forKey: Keys.install) as? Date, install.timeIntervalSinceNow < minInstall else { return false } // If we have never been prompted before, go ahead and prompt guard let lastPrompt = storage.object(forKey: Keys.lastPrompt) as? Date, let lastVersion = storage.object(forKey: Keys.lastVersion) as? String else { return true } // Minimum interval between two versions should be 3 months let minInterval: TimeInterval = day * 90 // never prompt w/in the same version return lastVersion != (Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String) // limit all types of prompts to at least 1mo intervals && lastPrompt.timeIntervalSinceNow < minInterval } class func prompt() { SKStoreReviewController.requestReview() prompted() } } ================================================ FILE: Clocker/Panel/Rate Controller/UpcomingEventView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class ModernSliderContainerView: NSView { private var trackingArea: NSTrackingArea? public var currentlyInFocus = false override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) currentlyInFocus = true } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) currentlyInFocus = false } override func updateTrackingAreas() { super.updateTrackingAreas() if let trackingArea = self.trackingArea { removeTrackingArea(trackingArea) } let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways] let trackingArea = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil) addTrackingArea(trackingArea) } } class ThinScroller: NSScroller { private var trackingArea: NSTrackingArea? override class func scrollerWidth(for _: NSControl.ControlSize, scrollerStyle _: NSScroller.Style) -> CGFloat { return 10 } override func drawKnobSlot(in _: NSRect, highlight _: Bool) { // Leaving this empty to prevent background drawing } } class DraggableClipView: NSClipView { private var clickPoint: NSPoint! private var trackingArea: NSTrackingArea? override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) clickPoint = event.locationInWindow var gestureInProgress = true while gestureInProgress { let newEvent = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp, .leftMouseDown]) switch newEvent?.type { case .leftMouseDragged: let newPoint = newEvent?.locationInWindow let xCoOrdinate = clickPoint.x - (newPoint?.x ?? 0) let newOrigin = NSPoint(x: bounds.origin.x + xCoOrdinate, y: 0) let constrainedRect = constrainBoundsRect(NSRect(origin: newOrigin, size: bounds.size)) scroll(to: constrainedRect.origin) superview?.reflectScrolledClipView(self) case .leftMouseDown: clickPoint = event.locationInWindow case .leftMouseUp: clickPoint = nil gestureInProgress = false default: Logger.info("Default mouse event occurred for \(event.type)") } } } override func updateTrackingAreas() { super.updateTrackingAreas() if let trackingArea = self.trackingArea { removeTrackingArea(trackingArea) } let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways, .enabledDuringMouseDrag, .inVisibleRect, .activeInKeyWindow] let trackingArea = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil) addTrackingArea(trackingArea) } } ================================================ FILE: Clocker/Panel/UI/AddTableViewCell.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class AddTableViewCell: NSTableCellView { @IBOutlet var addTimezone: NSButton! override func awakeFromNib() { super.awakeFromNib() NotificationCenter.default.addObserver(self, selector: #selector(themeChanges), name: Notification.Name.themeDidChange, object: nil) if let addCell = addTimezone.cell as? NSButtonCell { addCell.highlightsBy = .contentsCellMask addCell.showsStateBy = .pushInCellMask } updateAddButton() addTimezone.setAccessibility("EmptyAddTimezone") } @objc func themeChanges() { updateAddButton() } private func updateAddButton() { addTimezone.image = Themer.shared().addImage() } } ================================================ FILE: Clocker/Panel/UI/BackgroundPanelView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa struct BackgroundPanelConstants { static let kArrowHeight: CGFloat = 4 static let kCornerRadius: CGFloat = 8 static let kBorderWidth: CGFloat = 1 } class BackgroundPanelView: NSView { private var arrowX: CGFloat = -1 private var trackingArea: NSTrackingArea? override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) NSColor.clear.set() bounds.fill(using: .copy) var rect = bounds.insetBy(dx: 1, dy: 0) rect.origin.y = BackgroundPanelConstants.kBorderWidth rect.size.height -= (BackgroundPanelConstants.kArrowHeight + (2 * BackgroundPanelConstants.kBorderWidth)) let rectPath = NSBezierPath(roundedRect: rect, xRadius: BackgroundPanelConstants.kCornerRadius, yRadius: BackgroundPanelConstants.kCornerRadius) // Append the arrow to the body if its right ege is inside // the right edge of the body (taking into account the corner // radius). let curveOffset: CGFloat = 5 let arrowMidX = frame.midX let arrowRightEdge = arrowMidX + curveOffset + BackgroundPanelConstants.kArrowHeight let bodyRightEdge = rect.maxX - BackgroundPanelConstants.kCornerRadius if arrowRightEdge < bodyRightEdge { let arrowPath = NSBezierPath() let xOrdinate = arrowMidX - BackgroundPanelConstants.kArrowHeight - curveOffset let yOrdinate = frame.height - BackgroundPanelConstants.kArrowHeight - BackgroundPanelConstants.kBorderWidth arrowPath.move(to: NSPoint(x: xOrdinate, y: yOrdinate)) arrowPath.relativeCurve(to: NSPoint(x: BackgroundPanelConstants.kArrowHeight + curveOffset, y: BackgroundPanelConstants.kBorderWidth), controlPoint1: NSPoint(x: curveOffset, y: 0), controlPoint2: NSPoint(x: BackgroundPanelConstants.kArrowHeight, y: BackgroundPanelConstants.kArrowHeight)) } Themer.shared().mainBackgroundColor().setFill() rectPath.lineWidth = 2 * BackgroundPanelConstants.kBorderWidth rectPath.stroke() rectPath.fill() } override var allowsVibrancy: Bool { return true } } ================================================ FILE: Clocker/Panel/UI/CustomSliderCell.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class CustomSliderCell: NSSliderCell { fileprivate(set) var tracking: Bool = false override func drawBar(inside rect: NSRect, flipped _: Bool) { let barRadius: CGFloat = 2.5 let value = CGFloat((doubleValue - minValue) / (maxValue - minValue)) guard let control = controlView else { return } let finalWidth = value * (control.frame.width - 8) // Left Part var leftRect = rect leftRect.size.width = finalWidth let background = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius) NSColor(calibratedRed: 67.0 / 255.0, green: 138.0 / 255.0, blue: 250.0 / 255.0, alpha: 1.0).setFill() background.fill() // Right Part let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius) Themer.shared().sliderRightColor().setFill() active.fill() } override func startTracking(at startPoint: NSPoint, in controlView: NSView) -> Bool { tracking = true return super.startTracking(at: startPoint, in: controlView) } override func stopTracking(last lastPoint: NSPoint, current stopPoint: NSPoint, in controlView: NSView, mouseIsUp flag: Bool) { super.stopTracking(last: lastPoint, current: stopPoint, in: controlView, mouseIsUp: flag) tracking = false } } ================================================ FILE: Clocker/Panel/UI/FloatingWindow.xib ================================================ ================================================ FILE: Clocker/Panel/UI/HourMarkerViewItem.xib ================================================ ================================================ FILE: Clocker/Panel/UI/NoTimezoneView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import QuartzCore class NoTimezoneView: NSView { private lazy var emoji: NSTextField = { let emoji = NSTextField(frame: NSRect(x: frame.size.width / 2 - 75, y: frame.size.height / 2 - 75, width: 150, height: 150)) emoji.wantsLayer = true emoji.stringValue = "🌏" emoji.isBordered = false emoji.isEditable = false emoji.focusRingType = .none emoji.alignment = .center emoji.font = NSFont.systemFont(ofSize: 100) emoji.backgroundColor = .clear emoji.setAccessibilityIdentifier("NoTimezoneEmoji") return emoji }() private lazy var message: NSTextField = { let messageField = NSTextField(frame: NSRect(x: frame.size.width / 2 - 250, y: frame.size.height / 2 - 275, width: 500, height: 200)) messageField.wantsLayer = true messageField.setAccessibilityIdentifier("NoTimezoneMessage") messageField.placeholderString = NSLocalizedString("No places added", comment: "Subtitle for no places added") messageField.stringValue = NSLocalizedString("No places added", comment: "Subtitle for no places added") messageField.isBordered = false messageField.isEditable = false messageField.maximumNumberOfLines = 2 messageField.focusRingType = .none messageField.alignment = .center messageField.font = NSFont(name: "Avenir", size: 24) messageField.backgroundColor = .clear messageField.textColor = .darkGray return messageField }() override func layout() { if !subviews.contains(emoji) { addSubview(emoji) addSubview(message) } resetAnimations() super.layout() } private func resetAnimations() { let function = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) let emojiAnimation = CABasicAnimation(keyPath: "transform.translation.y") emojiAnimation.toValue = -10 emojiAnimation.repeatCount = .greatestFiniteMagnitude emojiAnimation.autoreverses = true emojiAnimation.duration = 1 emojiAnimation.timingFunction = function emoji.layer?.removeAllAnimations() emoji.layer?.add(emojiAnimation, forKey: "notimezone.emoji") } } ================================================ FILE: Clocker/Panel/UI/PanelTableView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit protocol PanelTableViewDelegate: NSTableViewDelegate { func tableView(_ table: NSTableView, didHoverOver row: NSInteger) } class PanelTableView: NSTableView { weak var panelDelegate: PanelTableViewDelegate? private var enableHover: Bool = false private var trackingArea: NSTrackingArea? private(set) var hoverRow: Int = -1 override func awakeFromNib() { super.awakeFromNib() enableHover = true usesAutomaticRowHeights = true } override func updateTrackingAreas() { if let tracker = trackingArea { removeTrackingArea(tracker) } createTrackingArea() super.updateTrackingAreas() } private func createTrackingArea() { let options: NSTrackingArea.Options = [ .mouseMoved, .mouseEnteredAndExited, .activeAlways, ] let clipRect = enclosingScrollView?.contentView.bounds ?? .zero trackingArea = NSTrackingArea(rect: clipRect, options: options, owner: self, userInfo: nil) if let tracker = trackingArea { addTrackingArea(tracker) } } override func mouseEntered(with event: NSEvent) { let mousePointInWindow = event.locationInWindow let mousePoint = convert(mousePointInWindow, from: nil) var currentHoverRow = row(at: mousePoint) if currentHoverRow != hoverRow { // We've scrolled off the end of the table if currentHoverRow < 0 || currentHoverRow >= numberOfRows { currentHoverRow = -1 } setHoverRow(currentHoverRow) } } private func setHoverRow(_ row: Int) { if row != hoverRow { hoverRow = row panelDelegate?.tableView(self, didHoverOver: hoverRow) // setNeedsDisplay is deprecated in 10.14 needsDisplay = true } } override func reloadData() { super.reloadData() setHoverRow(-1) evaluateForHighlight() } private func evaluateForHighlight() { if enableHover == false { return } guard let mousePointInWindow = window?.mouseLocationOutsideOfEventStream else { return } let mousePoint = convert(mousePointInWindow, from: nil) evaluateForHighlight(at: mousePoint) } private func evaluateForHighlight(at point: NSPoint) { if enableHover == false { Logger.info("Unable to show hover button because window is occluded!") return } var hover = row(at: point) if hover != hoverRow { if hover < 0 || hover >= numberOfRows { hover = -1 } } setHoverRow(hover) } override func mouseMoved(with event: NSEvent) { let mousePointInWindow = event.locationInWindow let mousePoint = convert(mousePointInWindow, from: nil) evaluateForHighlight(at: mousePoint) } private func setEnableHover(_ enable: Bool) { if enable != enableHover { if enableHover == false { setHoverRow(-1) } enableHover = enable evaluateForHighlight() } } } ================================================ FILE: Clocker/Panel/UI/Panelr.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class Panelr: NSPanel { override var canBecomeKey: Bool { return true } override var canBecomeMain: Bool { return true } override func keyDown(with event: NSEvent) { super.keyDown(with: event) // Escape key is pressed if event.keyCode == 53 { close() } } } ================================================ FILE: Clocker/Panel/UI/TimeMarkerViewItem.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class TimeMarkerViewItem: NSCollectionViewItem { static let topConstraintIdentifier = "constrainFromTop" static let reuseIdentifier = NSUserInterfaceItemIdentifier("HourMarkerViewItem") @IBOutlet var verticalLineView: NSView! func setup(with index: Int) { for constraint in view.constraints where constraint.identifier == TimeMarkerViewItem.topConstraintIdentifier { constraint.constant = index % 4 == 0 ? 0 : 20 } verticalLineView.wantsLayer = true verticalLineView.layer?.backgroundColor = NSColor.lightGray.cgColor } override var acceptsFirstResponder: Bool { return false } } ================================================ FILE: Clocker/Panel/UI/TimezoneCellView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class TimezoneCellView: NSTableCellView { @IBOutlet var customName: NSTextField! @IBOutlet var relativeDate: NSTextField! @IBOutlet var time: NSTextField! @IBOutlet var sunriseSetTime: NSTextField! @IBOutlet var noteLabel: NSTextField! @IBOutlet var extraOptions: NSButton! @IBOutlet var sunriseImage: NSImageView! @IBOutlet var currentLocationIndicator: NSImageView! private static let minimumFontSizeForTime: Int = 10 private static let minimumFontSizeForLabel: Int = 8 var rowNumber: NSInteger = -1 var isPopoverDisplayed: Bool = false override func awakeFromNib() { if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.testingLaunchArgument) { extraOptions.isHidden = false return } sunriseSetTime.alignment = .right canDrawSubviewsIntoLayer = true extraOptions.setAccessibility("extraOptionButton") customName.setAccessibility("CustomNameLabelForCell") noteLabel.setAccessibility("NoteLabel") currentLocationIndicator.toolTip = "This row will be updated automatically if Clocker detects a system-level timezone change!" } func setTextColor(color: NSColor) { [relativeDate, customName, time, sunriseSetTime].forEach { $0?.textColor = color } noteLabel.textColor = .gray } func setupLayout() { guard let relativeFont = relativeDate.font, let sunriseFont = sunriseSetTime.font else { assertionFailure("Unable to convert to NSString") return } let relativeDateString = relativeDate.stringValue as NSString let sunriseString = sunriseSetTime.stringValue as NSString let width = relativeDateString.size(withAttributes: [NSAttributedString.Key.font: relativeFont]).width let sunriseWidth = sunriseString.size(withAttributes: [NSAttributedString.Key.font: sunriseFont]).width if relativeDateString.length > 0 { if relativeDate.isHidden { relativeDate.isHidden = false } for constraint in relativeDate.constraints where constraint.identifier == "relative-day-height" { constraint.constant = 22 } for constraint in relativeDate.constraints where constraint.identifier == "width" { constraint.constant = width + 8 } for constraint in constraints where constraint.identifier == "custom-name-top-space" { if constraint.constant != 12 { constraint.constant = 12 } } } else { relativeDate.isHidden = true for constraint in relativeDate.constraints where constraint.identifier == "relative-day-height" { constraint.constant = 0 } for constraint in constraints where constraint.identifier == "custom-name-top-space" { if constraint.constant == 12 { constraint.constant += 15 } } } for constraint in sunriseSetTime.constraints where constraint.identifier == "width" { constraint.constant = sunriseWidth + 3 } setupTheme() } private func setupTheme() { let themer = Themer.shared() setTextColor(color: themer.mainTextColor()) extraOptions.image = themer.extraOptionsImage() extraOptions.alternateImage = themer.extraOptionsHighlightedImage() currentLocationIndicator.image = themer.currentLocationImage() setupTextSize() } private func setupTextSize() { guard let userFontSize = DataStore.shared().retrieve(key: UserDefaultKeys.userFontSizePreference) as? NSNumber else { assertionFailure("User Font Size is in unexpected format") return } guard let customFont = customName.font, let timeFont = time.font else { assertionFailure("User Font Size is in unexpectedly nil") return } let newFontSize = CGFloat(TimezoneCellView.minimumFontSizeForLabel + (userFontSize.intValue * 1)) let newTimeFontSize = CGFloat(TimezoneCellView.minimumFontSizeForTime + (userFontSize.intValue * 2)) let fontManager = NSFontManager.shared customName.font = fontManager.convert(customFont, toSize: newFontSize) time.font = fontManager.convert(timeFont, toSize: newTimeFontSize) } @IBAction func showExtraOptions(_ sender: NSButton) { let isWindowFloating = DataStore.shared().shouldDisplay(ViewType.showAppInForeground) var searchView = superview while searchView != nil, searchView is PanelTableView == false { searchView = searchView?.superview } guard let panelTableView = searchView as? PanelTableView, let enclosingScroller = panelTableView.enclosingScrollView else { // We might be coming from the preview tableview! return } let visibleRect = enclosingScroller.contentView.visibleRect let range = panelTableView.rows(in: visibleRect) let count = range.length let currentRow = labs(rowNumber + 1 - count) let yCoordinate = CGFloat(currentRow * 68 + 34) let relativeRect = CGRect(x: 0, y: yCoordinate, width: frame.size.width, height: frame.size.height) if isWindowFloating == false { guard let panel = PanelController.panel() else { return } isPopoverDisplayed = panel.showNotesPopover(forRow: rowNumber, relativeTo: bounds, andButton: sender) } else { let floatingPanel = FloatingWindowController.shared() isPopoverDisplayed = floatingPanel.showNotesPopover(forRow: rowNumber, relativeTo: superview?.convert(bounds, to: nil) ?? relativeRect, andButton: sender) } Logger.log(object: nil, for: "Open Extra Options") } func setNoteLabel(hidden: Bool) { noteLabel.isHidden = hidden for constraint in noteLabel.constraints where constraint.identifier == "note-label-height" { constraint.constant = hidden ? 0 : 22 } } override func mouseDown(with event: NSEvent) { if event.clickCount == 1 { // Text is copied in the following format: Chicago - 1625185925 let clipboardCopy = "\(customName.stringValue) - \(time.stringValue)" let pasteboard = NSPasteboard.general pasteboard.declareTypes([.string], owner: nil) pasteboard.setString(clipboardCopy, forType: .string) window?.contentView?.makeToast("Copied to Clipboard".localized()) window?.endEditing(for: nil) } else if event.clickCount == 2 { // TODO: Favourite this timezone } } override func rightMouseDown(with event: NSEvent) { super.rightMouseDown(with: event) showExtraOptions(extraOptions) Logger.log(object: nil, for: "Right Click Open Options") } } ================================================ FILE: Clocker/Panel/UI/TimezoneDataSource.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreModelKit class TimezoneDataSource: NSObject { var timezones: [TimezoneData] = [] var sliderValue: Int = 0 var dataStore: DataStore init(items: [TimezoneData], store: DataStore) { sliderValue = 0 timezones = Array(items) dataStore = store super.init() } } extension TimezoneDataSource { func setSlider(value: Int) { sliderValue = value } func setItems(items: [TimezoneData]) { timezones = items } } extension TimezoneDataSource: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in _: NSTableView) -> Int { var totalTimezones = timezones.count // If totalTimezone is 0, then we can show an option to add timezones if totalTimezones == 0 { totalTimezones += 1 } return totalTimezones } func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { guard !timezones.isEmpty else { if let addCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "addCell"), owner: self) as? AddTableViewCell { return addCellView } assertionFailure("Unable to create AddTableViewCell") return nil } guard let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "timeZoneCell"), owner: self) as? TimezoneCellView else { assertionFailure("Unable to create tableviewcell") return NSView() } let currentModel = timezones[row] let operation = TimezoneDataOperations(with: currentModel, store: dataStore) cellView.sunriseSetTime.stringValue = operation.formattedSunriseTime(with: sliderValue) cellView.sunriseImage.image = currentModel.isSunriseOrSunset ? Themer.shared().sunriseImage() : Themer.shared().sunsetImage() cellView.sunriseImage.contentTintColor = currentModel.isSunriseOrSunset ? NSColor.systemYellow : NSColor.systemOrange cellView.relativeDate.stringValue = operation.date(with: sliderValue, displayType: .panel) cellView.rowNumber = row cellView.customName.stringValue = currentModel.formattedTimezoneLabel() cellView.time.stringValue = operation.time(with: sliderValue) cellView.noteLabel.toolTip = currentModel.note ?? UserDefaultKeys.emptyString cellView.currentLocationIndicator.isHidden = !currentModel.isSystemTimezone cellView.time.setAccessibilityIdentifier("ActualTime") cellView.relativeDate.setAccessibilityIdentifier("RelativeDate") if let note = currentModel.note, !note.isEmpty { cellView.noteLabel.stringValue = note cellView.setNoteLabel(hidden: false) } else if let value = operation.nextDaylightSavingsTransitionIfAvailable(with: sliderValue) { cellView.noteLabel.stringValue = value cellView.setNoteLabel(hidden: false) } else { cellView.noteLabel.stringValue = UserDefaultKeys.emptyString cellView.setNoteLabel(hidden: true) } cellView.layout(with: currentModel) cellView.setAccessibilityIdentifier(currentModel.formattedTimezoneLabel()) cellView.setAccessibilityLabel(currentModel.formattedTimezoneLabel()) return cellView } func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] { guard !timezones.isEmpty else { return [] } let windowController = FloatingWindowController.shared() if edge == .trailing { let swipeToDelete = NSTableViewRowAction(style: .destructive, title: "Delete", handler: { _, row in if self.timezones[row].isSystemTimezone { self.showAlertForDeletingAHomeRow(row, tableView) return } let indexSet = IndexSet(integer: row) tableView.removeRows(at: indexSet, withAnimation: NSTableView.AnimationOptions()) if self.dataStore.shouldDisplay(ViewType.showAppInForeground) { windowController.deleteTimezone(at: row) } else { guard let panelController = PanelController.panel() else { return } panelController.deleteTimezone(at: row) } }) if #available(OSX 11.0, *) { swipeToDelete.image = Themer.shared().filledTrashImage() } else { swipeToDelete.image = NSImage(named: NSImage.Name("Trash")) } return [swipeToDelete] } return [] } private func showAlertForDeletingAHomeRow(_ row: Int, _ tableView: NSTableView) { NSApplication.shared.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = "Confirm deleting the home row? 😅" alert.informativeText = "This row is automatically updated when Clocker detects a system timezone change. Are you sure you want to delete this?" alert.addButton(withTitle: "Yes") alert.addButton(withTitle: "No") let response = alert.runModal() if response.rawValue == 1000 { OperationQueue.main.addOperation { [weak self] in guard let sSelf = self else { return } let indexSet = IndexSet(integer: row) tableView.removeRows(at: indexSet, withAnimation: NSTableView.AnimationOptions.slideUp) if sSelf.dataStore.shouldDisplay(ViewType.showAppInForeground) { let windowController = FloatingWindowController.shared() windowController.deleteTimezone(at: row) } else { guard let panelController = PanelController.panel() else { return } panelController.deleteTimezone(at: row) } } } } } extension TimezoneDataSource: PanelTableViewDelegate { func tableView(_ table: NSTableView, didHoverOver row: NSInteger) { for rowIndex in 0 ..< table.numberOfRows { if let rowCellView = table.view(atColumn: 0, row: rowIndex, makeIfNecessary: false) as? TimezoneCellView { if row == -1 { rowCellView.extraOptions.alphaValue = 0.5 continue } rowCellView.extraOptions.alphaValue = (rowIndex == row) ? 1 : 0.5 if rowIndex == row, let hoverString = hoverStringForSelectedRow(row: row), sliderValue == 0 { rowCellView.relativeDate.stringValue = hoverString } } } } private func hoverStringForSelectedRow(row: Int) -> String? { let currentModel = timezones[row] if let timezone = TimeZone(identifier: currentModel.timezone()) { let offSet = Double(timezone.secondsFromGMT()) / 3600 let localizedName = timezone.localizedName(for: .shortDaylightSaving, locale: Locale.autoupdatingCurrent) ?? "Error" if offSet == 0.0 { return "\(localizedName)" } else { let offSetSign = offSet > 0 ? "+" : UserDefaultKeys.emptyString let offsetString = "UTC\(offSetSign)\(offSet)" return "\(localizedName) (\(offsetString))" } } return nil } } extension TimezoneCellView { func layout(with model: TimezoneData) { let shouldDisplay = DataStore.shared().shouldDisplay(.sunrise) && !sunriseSetTime.stringValue.isEmpty sunriseSetTime.isHidden = !shouldDisplay sunriseImage.isHidden = !shouldDisplay // If it's a timezone and not a place, we can't determine the sunrise/sunset time; hide the sunrise image if model.selectionType == .timezone, model.latitude == nil, model.longitude == nil { sunriseImage.isHidden = true } setupLayout() } } ================================================ FILE: Clocker/Panel/UI/Toasty.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import Foundation extension CGRect { static func center(of layer: CALayer) -> CGPoint { let parentSize = layer.frame.size return CGPoint(x: parentSize.width / 2, y: parentSize.height / 2) } static func center(of parent: NSView) -> CGPoint { let parentSize = parent.frame.size return CGPoint(x: parentSize.width / 2, y: parentSize.height / 6) } } extension String { func size(with fontSize: CGFloat) -> CGSize { let attr: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: fontSize)] let size = NSString(string: self).size(withAttributes: attr) return size } } private class HideAnimationDelegate: NSObject, CAAnimationDelegate { private weak var view: NSView? fileprivate init(view: NSView) { self.view = view } fileprivate static func delegate(forView NSView: NSView) -> CAAnimationDelegate { return HideAnimationDelegate(view: NSView) } fileprivate func animationDidStart(_: CAAnimation) { view?.layer?.opacity = 0.0 } func animationDidStop(_: CAAnimation, finished _: Bool) { view?.removeFromSuperview() view = nil } } func hideAnimation(view: NSView, style: Style) { let anim = CABasicAnimation(keyPath: "opacity") let timing = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) anim.timingFunction = timing let currentLayerTime = view.layer?.convertTime(CACurrentMediaTime(), from: nil) anim.beginTime = currentLayerTime! + CFTimeInterval(style.fadeInOutDelay) anim.duration = CFTimeInterval(style.fadeInOutDuration) anim.fromValue = 1.0 anim.toValue = 0.0 anim.isRemovedOnCompletion = false anim.delegate = HideAnimationDelegate.delegate(forView: view) view.layer?.add(anim, forKey: "hide animation") } public protocol Style { var fontSize: CGFloat { get } var horizontalMargin: CGFloat { get } var verticalMargin: CGFloat { get } var cornerRadius: CGFloat { get } var font: NSFont? { get } var backgroundColor: NSColor { get } var foregroundColor: NSColor { get } var fadeInOutDuration: CGFloat { get } var fadeInOutDelay: CGFloat { get } var labelOriginWithMargin: CGPoint { get } } public extension Style { var labelOriginWithMargin: CGPoint { return CGPoint(x: horizontalMargin, y: verticalMargin) } var fontSize: CGFloat { return 12 } var font: NSFont? { return NSFont(name: "Avenir-Light", size: fontSize) } var horizontalMargin: CGFloat { return 10 } var verticalMargin: CGFloat { return 5 } var cornerRadius: CGFloat { return 8 } var backgroundColor: NSColor { return .black } var foregroundColor: NSColor { return .white } var fadeInOutDuration: CGFloat { return 1.0 } var fadeInOutDelay: CGFloat { return 1.0 } } public struct DefaultStyle: Style { public static let shared = DefaultStyle() } private enum ToastKeys { static var ActiveToast = "TSToastActiveToastKey" } class ToastView: NSView { private let message: String private let labelSize: CGSize private let style: Style init(message: String) { self.message = message style = DefaultStyle() labelSize = message.size(with: style.fontSize) let size = CGSize( width: labelSize.width + style.horizontalMargin * 2, height: labelSize.height + style.verticalMargin * 2 ) let rect = CGRect(origin: .zero, size: size) super.init(frame: rect) wantsLayer = true setAccessibility("ToastView") } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError() } override func viewDidMoveToSuperview() { super.viewDidMoveToSuperview() if superview != nil { configure() setAccessibility("ToastView") } } private func configure() { frame = superview?.bounds ?? NSRect.zero let rect = CGRect(origin: style.labelOriginWithMargin, size: labelSize) let sizeWithMargin = CGSize( width: rect.width + style.horizontalMargin * 2, height: rect.height + style.verticalMargin * 2 ) let rectWithMargin = CGRect( origin: .zero, // position is manipulated later anyways size: sizeWithMargin ) // outside Container let container = CALayer() container.frame = rectWithMargin container.position = CGRect.center(of: superview!) container.backgroundColor = style.backgroundColor.cgColor container.cornerRadius = style.cornerRadius layer?.addSublayer(container) // inside TextLayer let text = CATextLayer() text.frame = rect text.position = CGRect.center(of: container) text.string = message text.font = style.font text.fontSize = style.fontSize text.alignmentMode = .center text.foregroundColor = style.foregroundColor.cgColor text.backgroundColor = style.backgroundColor.cgColor text.contentsScale = layer?.contentsScale ?? 0 // For Retina Display container.addSublayer(text) } } public extension NSView { func makeToast(_ message: String) { let toast = ToastView(message: message) toast.setAccessibilityEnabled(true) toast.setAccessibilityRole(.sheet) addSubview(toast) hideAnimation(view: toast, style: DefaultStyle.shared) } } ================================================ FILE: Clocker/Panel/Upcoming Events/ParentPanelController+UpcomingEvents.swift ================================================ // Copyright © 2015 Abhishek Banthia import Foundation var avenirBookFont: NSFont { if let avenirFont = NSFont(name: "Avenir-Book", size: 13) { return avenirFont } return NSFont.systemFont(ofSize: 13) } protocol UpcomingEventPanelDelegate: AnyObject { func didRemoveCalendarView() func didClickSupplementaryButton(_ sender: NSButton) } extension ParentPanelController { func setupUpcomingEventViewCollectionViewIfNeccesary() { if upcomingEventCollectionView != nil { upcomingEventsDataSource = UpcomingEventsDataSource(self, EventCenter.sharedCenter()) upcomingEventCollectionView.enclosingScrollView?.scrollerInsets = NSEdgeInsetsZero upcomingEventCollectionView.enclosingScrollView?.backgroundColor = NSColor.clear upcomingEventCollectionView.setAccessibility("UpcomingEventCollectionView") upcomingEventCollectionView.dataSource = upcomingEventsDataSource upcomingEventCollectionView.delegate = upcomingEventsDataSource upcomingEventCollectionView.enclosingScrollView?.autohidesScrollers = true } } } extension ParentPanelController: UpcomingEventPanelDelegate { func didRemoveCalendarView() { removeUpcomingEventView() } func didClickSupplementaryButton(_ sender: NSButton) { calendarButtonAction(sender) } } ================================================ FILE: Clocker/Panel/Upcoming Events/UpcomingEventViewItem.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import Foundation class UpcomingEventViewItem: NSCollectionViewItem { static let reuseIdentifier = NSUserInterfaceItemIdentifier("UpcomingEventViewItem") @IBOutlet var calendarColorView: NSView! @IBOutlet var leadingConstraint: NSLayoutConstraint! @IBOutlet var eventTitleLabel: NSTextField! @IBOutlet var eventSubtitleButton: NSButton! @IBOutlet var supplementaryButtonWidthConstraint: NSLayoutConstraint! @IBOutlet var zoomButton: NSButton! private static let SupplementaryButtonWidth: CGFloat = 24.0 private static let EventLeadingConstraint: CGFloat = 10.0 private var meetingLink: URL? private weak var panelDelegate: UpcomingEventPanelDelegate? override func viewDidLoad() { zoomButton.setButtonType(.momentaryChange) zoomButton.target = self zoomButton.action = #selector(zoomButtonAction(_:)) } override func prepareForReuse() { zoomButton.image = nil eventTitleLabel.stringValue = "" eventSubtitleButton.stringValue = "" meetingLink = nil } override var acceptsFirstResponder: Bool { return false } // MARK: Setup UI func setup(_ title: String, _ subtitle: String, _ color: NSColor, _ link: URL?, _ delegate: UpcomingEventPanelDelegate?, _ isCancelled: Bool) { if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint / 2 { leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint / 2 } panelDelegate = delegate meetingLink = link setupLabels(title, isCancelled) setupSupplementaryButton(link, cancellationState: isCancelled) setCalendarButtonTitle(buttonTitle: subtitle, cancellationState: isCancelled) calendarColorView.layer?.backgroundColor = color.cgColor } private func setupLabels(_ title: String, _ cancellationState: Bool) { var sanitizedTitle = title if (cancellationState) { let offendingString = "Canceled: " sanitizedTitle = sanitizedTitle.replacingOccurrences(of: offendingString, with: "") } let attributes: [NSAttributedString.Key: Any] = cancellationState ? [NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue, NSAttributedString.Key.strikethroughColor: NSColor.gray] : [:] let attributedString = NSAttributedString(string: sanitizedTitle, attributes: attributes) eventTitleLabel.attributedStringValue = attributedString eventTitleLabel.toolTip = title } private func setupSupplementaryButton(_ meetingURL: URL?, cancellationState: Bool) { guard meetingURL != nil, cancellationState == false else { zoomButton.image = nil supplementaryButtonWidthConstraint.constant = 0.0 return } zoomButton.isHidden = false zoomButton.image = Themer.shared().videoCallImage() if supplementaryButtonWidthConstraint.constant != UpcomingEventViewItem.SupplementaryButtonWidth { supplementaryButtonWidthConstraint.constant = UpcomingEventViewItem.SupplementaryButtonWidth } } func setupUndeterminedState(_ delegate: UpcomingEventPanelDelegate?) { panelDelegate = delegate setAlternateState(NSLocalizedString("See your next Calendar event here.", comment: "Next Event Label for no Calendar access"), NSLocalizedString("Click here to start.", comment: "Button Title for no Calendar access"), NSColor.systemBlue, Themer.shared().removeImage(), Themer.shared().removeAlternateImage()) } func setupEmptyState() { let subtitle = NSCalendar.autoupdatingCurrent.isDateInWeekend(Date()) ? "Happy Weekend.".localized() : "Great going.".localized() setAlternateState(NSLocalizedString("No upcoming events for today!", comment: "Next Event Label with no upcoming event"), subtitle, NSColor.systemGreen, nil, nil) } private func setAlternateState(_ title: String, _ buttonTitle: String, _ color: NSColor, _ image: NSImage?, _ alternateImage: NSImage?) { if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint { leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint } eventTitleLabel.stringValue = title setCalendarButtonTitle(buttonTitle: buttonTitle, cancellationState: false) calendarColorView.layer?.backgroundColor = color.cgColor zoomButton.image = image zoomButton.alternateImage = alternateImage } private func setCalendarButtonTitle(buttonTitle: String, cancellationState: Bool) { let style = NSMutableParagraphStyle() style.alignment = .left style.lineBreakMode = .byTruncatingTail if let boldFont = NSFont(name: "Avenir", size: 11) { let sanitizedString = cancellationState ? "Canceled." : buttonTitle let attributes = [NSAttributedString.Key.foregroundColor: NSColor.gray, NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.font: boldFont] let attributedString = NSAttributedString(string: sanitizedString, attributes: attributes) eventSubtitleButton.attributedTitle = attributedString eventSubtitleButton.toolTip = buttonTitle } } // MARK: Button Actions @IBAction func calendarButtonAction(_ sender: NSButton) { panelDelegate?.didClickSupplementaryButton(sender) } @objc func zoomButtonAction(_ sender: NSButton) { guard sender.image != nil else { return } if let meetingURL = meetingLink { NSWorkspace.shared.open(meetingURL) } else { panelDelegate?.didRemoveCalendarView() } } } ================================================ FILE: Clocker/Panel/Upcoming Events/UpcomingEventViewItem.xib ================================================ ================================================ FILE: Clocker/Panel/Upcoming Events/UpcomingEventsDataSource.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import Foundation class UpcomingEventsDataSource: NSObject, NSCollectionViewDataSource, NSCollectionViewDelegateFlowLayout { private var upcomingEvents: [EventInfo] = [] private var eventCenter: EventCenter! private weak var delegate: UpcomingEventPanelDelegate? private static let panelWidth: CGFloat = 300.0 init(_ panelDelegate: UpcomingEventPanelDelegate?, _ center: EventCenter) { super.init() delegate = panelDelegate eventCenter = center } func updateEventsDataSource(_ events: [EventInfo]) { upcomingEvents = events } func collectionView(_: NSCollectionView, numberOfItemsInSection _: Int) -> Int { if eventCenter.calendarAccessDenied() || eventCenter.calendarAccessNotDetermined() || upcomingEvents.isEmpty { return 1 } return upcomingEvents.count } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { guard let item = collectionView.makeItem(withIdentifier: UpcomingEventViewItem.reuseIdentifier, for: indexPath) as? UpcomingEventViewItem else { assertionFailure("Unable to make UpcomingEventViewItem") return NSCollectionViewItem() } if eventCenter.calendarAccessNotDetermined() { item.setupUndeterminedState(delegate) return item } if upcomingEvents.isEmpty { item.setupEmptyState() return item } let currentEventInfo = upcomingEvents[indexPath.item] let upcomingEventSubtitle = currentEventInfo.isAllDay ? "All-Day" : currentEventInfo.metadataForMeeting() item.setup(currentEventInfo.event.title, upcomingEventSubtitle, currentEventInfo.event.calendar.color, currentEventInfo.meetingURL, delegate, currentEventInfo.event.status == .canceled) return item } func collectionView(_ collectionView: NSCollectionView, layout _: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { if eventCenter.calendarAccessNotDetermined() { return NSSize(width: UpcomingEventsDataSource.panelWidth - 25, height: collectionView.frame.height - 15) } else if upcomingEvents.isEmpty { return NSSize(width: UpcomingEventsDataSource.panelWidth - 25, height: collectionView.frame.height - 15) } else { let currentEventInfo = upcomingEvents[indexPath.item] let bufferWidth: CGFloat = currentEventInfo.meetingURL != nil ? 60.0 : 20.0 let longerString = currentEventInfo.event.title.count >= currentEventInfo.metadataForMeeting().count ? currentEventInfo.event.title : currentEventInfo.metadataForMeeting() let attributedString = NSAttributedString(string: longerString ?? UserDefaultKeys.emptyString, attributes: [NSAttributedString.Key.font: avenirBookFont]) let maxWidth = min((attributedString.size().width + 15) + bufferWidth, UpcomingEventsDataSource.panelWidth / 2) return NSSize(width: maxWidth, height: collectionView.frame.height - 20) } } } ================================================ FILE: Clocker/Preferences/About/AboutViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit struct AboutUsConstants { static let AboutUsNibIdentifier = "CLAboutWindows" static let GitHubURL = "https://github.com/abhishekbanthia/Clocker/?ref=ClockerApp" static let PayPalURL = "https://paypal.me/abhishekbanthia1712" static let TwitterLink = "https://twitter.com/clocker_support/?ref=ClockerApp" static let TwitterFollowIntentLink = "https://twitter.com/intent/follow?screen_name=clocker_support" static let AppStoreLink = "macappstore://itunes.apple.com/us/app/clocker/id1056643111?action=write-review" static let AppStoreUpdateLink = "macappstore://itunes.apple.com/us/app/clocker/id1056643111" static let CrowdInLocalizationLink = "https://crwd.in/clocker" static let FAQsLink = "https://abhishekbanthia.com/clocker/faq" } class AboutViewController: ParentViewController { @IBOutlet var quickCommentAction: PointingHandCursorButton! @IBOutlet var privateFeedback: PointingHandCursorButton! @IBOutlet var supportClocker: PointingHandCursorButton! @IBOutlet var openSourceButton: PointingHandCursorButton! @IBOutlet var versionField: NSTextField! private var themeDidChangeNotification: NSObjectProtocol? private var feedbackWindow: AppFeedbackWindowController? override func viewDidLoad() { super.viewDidLoad() privateFeedback.setAccessibilityIdentifier("ClockerPrivateFeedback") let appDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") ?? "Clocker" let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "N/A" let longVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "N/A" versionField.stringValue = "\(appDisplayName) \(shortVersion) (\(longVersion))" setup() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setup() } } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } private func underlineTextForActionButton() { let rangesInOrder = [NSRange(location: 3, length: 16), NSRange(location: 7, length: privateFeedback.attributedTitle.length - 7), NSRange(location: 27, length: 33), NSRange(location: 42, length: 14)] let buttonsInOrder = [quickCommentAction, privateFeedback, supportClocker, openSourceButton] let localizedKeys = ["1. @clocker_support on Twitter for quick comments", "2. For Private Feedback", "You can support Clocker by leaving a review on the App Store! :)", "Help localize Clocker in your language by clicking here!"] zip(buttonsInOrder, localizedKeys).forEach { arg in let (button, title) = arg button?.title = title } zip(rangesInOrder, buttonsInOrder).forEach { arg in let (range, button) = arg setUnderline(for: button, range: range) } } private func setUnderline(for button: PointingHandCursorButton?, range: NSRange) { guard let underlinedButton = button else { return } let mutableParaghStyle = NSMutableParagraphStyle() mutableParaghStyle.alignment = .center let originalText = NSMutableAttributedString(string: underlinedButton.title) originalText.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: Int8(NSUnderlineStyle.single.rawValue)), range: range) originalText.addAttribute(NSAttributedString.Key.foregroundColor, value: Themer.shared().mainTextColor(), range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.font, value: (button?.font)!, range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: mutableParaghStyle, range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) underlinedButton.attributedTitle = originalText } @IBAction func openMyTwitter(_: Any) { guard let twitterURL = URL(string: AboutUsConstants.TwitterLink), let countryCode = Locale.autoupdatingCurrent.region?.identifier else { return } NSWorkspace.shared.open(twitterURL) // Log this let custom: [String: Any] = ["Country": countryCode] Logger.log(object: custom, for: "Opened Twitter") } @IBAction func viewSource(_: Any) { guard let sourceURL = URL(string: AboutUsConstants.AppStoreLink), let countryCode = Locale.autoupdatingCurrent.region?.identifier else { return } NSWorkspace.shared.open(sourceURL) // Log this let custom: [String: Any] = ["Country": countryCode] Logger.log(object: custom, for: "Open App Store to Review") } @IBAction func reportIssue(_: Any) { feedbackWindow = AppFeedbackWindowController.sharedWindow feedbackWindow?.appFeedbackWindowDelegate = self feedbackWindow?.showWindow(nil) NSApp.activate(ignoringOtherApps: true) view.window?.orderOut(nil) if let countryCode = Locale.autoupdatingCurrent.region?.identifier { let custom: [String: Any] = ["Country": countryCode] Logger.log(object: custom, for: "Report Issue Opened") } } @IBAction func openGitHub(_: Any) { guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink), let languageCode = Locale.preferredLanguages.first else { return } NSWorkspace.shared.open(localizationURL) // Log this let custom: [String: Any] = ["Language": languageCode] Logger.log(object: custom, for: "Opened Localization Link") } @IBOutlet var feedbackLabel: NSTextField! private func setup() { feedbackLabel.stringValue = "Feedback is always welcome:".localized() feedbackLabel.textColor = Themer.shared().mainTextColor() versionField.textColor = Themer.shared().mainTextColor() underlineTextForActionButton() } } extension AboutViewController: AppFeedbackWindowControllerDelegate { func appFeedbackWindowWillClose() { feedbackWindow = nil } func appFeedbackWindoEntryPoint() -> String { return "about_view_controller" } } ================================================ FILE: Clocker/Preferences/About/PointingHandCursorButton.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class PointingHandCursorButton: NSButton { let pointingHandCursor: NSCursor = .pointingHand override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) } override func resetCursorRects() { addCursorRect(bounds, cursor: pointingHandCursor) } } ================================================ FILE: Clocker/Preferences/App Feedback/AppFeedbackWindow.xib ================================================ ================================================ FILE: Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import FirebaseDatabase protocol AppFeedbackWindowControllerDelegate: AnyObject { func appFeedbackWindowWillClose() func appFeedbackWindoEntryPoint() -> String } extension NSNib.Name { static let appFeedbackWindowIdentifier = NSNib.Name("AppFeedbackWindow") static let onboardingWindowIdentifier = NSNib.Name("OnboardingWindow") static let welcomeViewIdentifier = NSNib.Name("WelcomeView") static let startAtLoginViewIdentifier = NSNib.Name("StartAtLoginView") } enum AppFeedbackConstants { static let CLAppFeedbackNoResponseString = "Not Provided" static let CLAppFeedbackNameProperty = "name" static let CLAppFeedbackEmailProperty = "email" static let CLAppFeedbackFeedbackProperty = "feedback" static let CLOperatingSystemVersion = "OS" static let CLClockerVersion = "Clocker version" static let CLFeedbackAlertTitle = "Thank you for helping make Clocker even better!" static let CLFeedbackAlertInformativeText = "We owe you a candy. 😇" static let CLFeedbackAlertButtonTitle = "Close" static let CLFeedbackNotEnteredErrorMessage = "Please enter some feedback." static let CLAppFeedbackDateProperty = "date" static let CLAppFeedbackUserPreferences = "Prefs" static let CLCaliforniaTimezoneIdentifier = "America/Los_Angeles" static let CLFeedbackEntryPoint = "entry_point" } class AppFeedbackWindowController: NSWindowController { @IBOutlet var nameField: NSTextField! @IBOutlet var emailField: NSTextField! @IBOutlet var feedbackTextView: NSTextView! @IBOutlet var progressIndicator: NSProgressIndicator! @IBOutlet var quickCommentsLabel: PointingHandCursorButton! public weak var appFeedbackWindowDelegate: AppFeedbackWindowControllerDelegate? private var themeDidChangeNotification: NSObjectProtocol? private var serialNumber: String? { let platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice")) guard platformExpert > 0 else { return nil } guard let serialNumber = (IORegistryEntryCreateCFProperty(platformExpert, kIOPlatformSerialNumberKey as CFString, kCFAllocatorDefault, 0).takeUnretainedValue() as? String) else { return nil } IOObjectRelease(platformExpert) return serialNumber } private var isActivityInProgress = false { didSet { progressIndicator.isHidden = !isActivityInProgress isActivityInProgress ? progressIndicator.startAnimation(nil) : progressIndicator.stopAnimation(nil) } } static var sharedWindow = AppFeedbackWindowController(windowNibName: NSNib.Name.appFeedbackWindowIdentifier) override func windowDidLoad() { super.windowDidLoad() window?.backgroundColor = Themer.shared().mainBackgroundColor() window?.titleVisibility = .hidden window?.titlebarAppearsTransparent = true progressIndicator.isHidden = true feedbackTextView.setAccessibilityIdentifier("FeedbackTextView") nameField.setAccessibilityIdentifier("NameField") emailField.setAccessibilityIdentifier("EmailField") progressIndicator.setAccessibilityIdentifier("ProgressIndicator") quickCommentsLabel.setAccessibility("QuickCommentLabel") setup() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.window?.backgroundColor = Themer.shared().mainBackgroundColor() self.setup() } } class func shared() -> AppFeedbackWindowController { return sharedWindow } override init(window: NSWindow!) { super.init(window: window) } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @IBAction func sendFeedback(_: Any) { isActivityInProgress = true if didUserEnterFeedback() == false { return } let feedbackInfo = retrieveDataForSending() Logger.info("About to send \(feedbackInfo)") sendDataToFirebase(feedbackInfo: feedbackInfo) showSucccessOnSendingInfo() } @IBAction func cancel(_: Any) { window?.close() } private func didUserEnterFeedback() -> Bool { let cleanedUpString = feedbackTextView.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if cleanedUpString.isEmpty { window?.contentView?.makeToast(AppFeedbackConstants.CLFeedbackNotEnteredErrorMessage) isActivityInProgress = false return false } return true } private func generateUserPreferences() -> String { let preferences = DataStore.shared().timezones() guard let theme = DataStore.shared().retrieve(key: UserDefaultKeys.themeKey) as? NSNumber, let displayFutureSliderKey = DataStore.shared().retrieve(key: UserDefaultKeys.themeKey) as? NSNumber, let relativeDateKey = DataStore.shared().retrieve(key: UserDefaultKeys.relativeDateKey) as? NSNumber, let country = Locale.autoupdatingCurrent.region?.identifier else { return "Error" } let selectedTimezones = preferences.compactMap { data -> String? in guard let timezoneObject = TimezoneData.customObject(from: data) else { return nil } let customString = """ Timezone: \(timezoneObject.timezone()) Name: \(timezoneObject.formattedAddress ?? "No") Favourited: \((timezoneObject.isFavourite == 1) ? "Yes" : "No") Format: \(timezoneObject.overrideFormat) System: \(timezoneObject.isSystemTimezone ? "Yes" : "No")" """ return customString } var relativeDate = "Relative" if relativeDateKey.isEqual(to: NSNumber(value: 1)) { relativeDate = "Actual Day" } else if relativeDateKey.isEqual(to: NSNumber(value: 2)) { relativeDate = "Date" } var futureSlider = "Modern" if displayFutureSliderKey.isEqual(to: NSNumber(value: 1)) { futureSlider = "Legacy" } else if theme.isEqual(to: NSNumber(value: 2)) { futureSlider = "Hidden" } return """ "Global Timezone Format: \(DataStore.shared().timezoneFormat()), "Display Future Slider": \(futureSlider), "Relative Date": \(relativeDate), "Country": \(country), "Timezones": \(selectedTimezones) """ } private func retrieveDataForSending() -> [String: String] { guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String, let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { return [:] } let name = nameField.stringValue.isEmpty ? AppFeedbackConstants.CLAppFeedbackNoResponseString : nameField.stringValue let email = emailField.stringValue.isEmpty ? AppFeedbackConstants.CLAppFeedbackNoResponseString : emailField.stringValue let appFeedbackProperty = feedbackTextView.string let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion let osVersion = "\(operatingSystem.majorVersion).\(operatingSystem.minorVersion).\(operatingSystem.patchVersion)" let versionInfo = "Clocker \(shortVersion) (\(appVersion))" return [ AppFeedbackConstants.CLAppFeedbackNameProperty: name, AppFeedbackConstants.CLAppFeedbackEmailProperty: email, AppFeedbackConstants.CLAppFeedbackFeedbackProperty: appFeedbackProperty, AppFeedbackConstants.CLOperatingSystemVersion: osVersion, AppFeedbackConstants.CLClockerVersion: versionInfo, AppFeedbackConstants.CLAppFeedbackDateProperty: todaysDate(), AppFeedbackConstants.CLAppFeedbackUserPreferences: generateUserPreferences(), AppFeedbackConstants.CLFeedbackEntryPoint: appFeedbackWindowDelegate?.appFeedbackWindoEntryPoint() ?? "Error", ] } private func todaysDate() -> String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short dateFormatter.timeZone = TimeZone(identifier: AppFeedbackConstants.CLCaliforniaTimezoneIdentifier) return dateFormatter.string(from: Date()) } var firebaseDBReference: DatabaseReference! private func sendDataToFirebase(feedbackInfo info: [String: String]) { #if DEBUG Logger.info("Sending a feedback in Debug builds will lead to a no-op") #endif guard let identifier = serialNumber else { assertionFailure("Serial Identifier was unexpectedly nil") return } #if RELEASE firebaseDBReference = Database.database().reference() firebaseDBReference.child("Feedback").child(identifier).setValue(info) #endif } private func showSucccessOnSendingInfo() { guard let feedbackWindow = window else { assertionFailure("Window property was unexpectedly nil") return } isActivityInProgress = false let alert = NSAlert() alert.messageText = AppFeedbackConstants.CLFeedbackAlertTitle alert.informativeText = AppFeedbackConstants.CLFeedbackAlertInformativeText alert.addButton(withTitle: AppFeedbackConstants.CLFeedbackAlertButtonTitle) alert.beginSheetModal(for: feedbackWindow) { _ in self.window?.close() } } @IBOutlet var contactBox: NSBox! @IBOutlet var accessoryInfo: NSTextField! private func setup() { contactBox.title = "Contact Information (Optional)".localized() accessoryInfo.stringValue = "Contact fields are optional! Your contact information will let us contact you in case we need more information or can help!".localized() let versionUpdateInstance = iVersion.sharedInstance() let string = versionUpdateInstance?.versionDetails(since: versionUpdateInstance?.applicationVersion, inDict: versionUpdateInstance?.remoteVersionsDict) if string != nil { let range = NSRange(location: 37, length: 13) quickCommentsLabel.title = "📣 An improved Clocker experience is now available!" quickCommentsLabel.tag = 0 setUnderline(for: quickCommentsLabel, range: range) } else { let range = NSRange(location: 9, length: 16) quickCommentsLabel.title = "Tweet to @Clocker_Support if you have a quick comment!" setUnderline(for: quickCommentsLabel, range: range) quickCommentsLabel.tag = 100 } [accessoryInfo].forEach { $0?.textColor = Themer.shared().mainTextColor() } contactBox.borderColor = Themer.shared().mainTextColor() feedbackTextView.backgroundColor = Themer.shared().mainBackgroundColor() nameField.backgroundColor = Themer.shared().mainBackgroundColor() emailField.backgroundColor = Themer.shared().mainBackgroundColor() } private func setUnderline(for button: PointingHandCursorButton?, range: NSRange) { guard let underlinedButton = button else { return } let mutableParaghStyle = NSMutableParagraphStyle() mutableParaghStyle.alignment = .center let originalText = NSMutableAttributedString(string: underlinedButton.title) originalText.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: Int8(NSUnderlineStyle.single.rawValue)), range: range) originalText.addAttribute(NSAttributedString.Key.foregroundColor, value: Themer.shared().mainTextColor(), range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.font, value: (button?.font)!, range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) originalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: mutableParaghStyle, range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) underlinedButton.attributedTitle = originalText } @IBAction func navigateToSupportTwitter(_ sender: NSButton) { let link = sender.tag == 100 ? AboutUsConstants.TwitterLink : AboutUsConstants.AppStoreUpdateLink guard let url = URL(string: link) else { return } NSWorkspace.shared.open(url) } } extension AppFeedbackWindowController: NSWindowDelegate { func windowWillClose(_: Notification) { performClosingCleanUp() bringPreferencesWindowToFront() } func performClosingCleanUp() { nameField.stringValue = UserDefaultKeys.emptyString emailField.stringValue = UserDefaultKeys.emptyString feedbackTextView.string = UserDefaultKeys.emptyString isActivityInProgress = false appFeedbackWindowDelegate?.appFeedbackWindowWillClose() } func bringPreferencesWindowToFront() { let windows = NSApplication.shared.windows let prefWindow = windows.first(where: { window in window.identifier == NSUserInterfaceItemIdentifier("Preferences") }) if let prefW = prefWindow { prefW.makeKeyAndOrderFront(self) NSApp.activate(ignoringOtherApps: true) } } } ================================================ FILE: Clocker/Preferences/Appearance/AppearanceViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit class AppearanceViewController: ParentViewController { @IBOutlet var timeFormat: NSPopUpButton! @IBOutlet var theme: NSPopUpButton! @IBOutlet var informationLabel: NSTextField! @IBOutlet var sliderDayRangePopup: NSPopUpButton! @IBOutlet var visualEffectView: NSVisualEffectView! @IBOutlet var menubarMode: NSSegmentedControl! @IBOutlet var includeDayInMenubarControl: NSSegmentedControl! @IBOutlet var includeDateInMenubarControl: NSSegmentedControl! @IBOutlet var includePlaceNameControl: NSSegmentedControl! @IBOutlet var appearanceTab: NSTabView! @IBOutlet var appDisplayControl: NSSegmentedControl! @IBOutlet var syncLabel: NSTextField! @IBOutlet var syncSegementedControl: NSSegmentedControl! private var themeDidChangeNotification: NSObjectProtocol? private var previewTimezones: [TimezoneData] = [] override func viewDidLoad() { super.viewDidLoad() informationLabel.stringValue = "Favourite a timezone to enable menubar display options.".localized() informationLabel.textColor = NSColor.secondaryLabelColor let chosenFormat = DataStore.shared().timezoneFormat().intValue let supportedTimeFormats = ["h:mm a (7:08 PM)", "HH:mm (19:08)", "-- With Seconds --", "h:mm:ss a (7:08:09 PM)", "HH:mm:ss (19:08:09)", "-- 12 Hour with Preceding 0 --", "hh:mm a (07:08 PM)", "hh:mm:ss a (07:08:09 PM)", "-- 12 Hour w/o AM/PM --", "hh:mm (07:08)", "hh:mm:ss (07:08:09)", "Epoch Time"] timeFormat.removeAllItems() timeFormat.addItems(withTitles: supportedTimeFormats) timeFormat.item(at: 2)?.isEnabled = false timeFormat.item(at: 5)?.isEnabled = false timeFormat.item(at: 8)?.isEnabled = false timeFormat.autoenablesItems = false timeFormat.selectItem(at: chosenFormat) timeFormat.setAccessibilityIdentifier("TimeFormatPopover") informationLabel.setAccessibilityIdentifier("InformationLabel") sliderDayRangePopup.removeAllItems() sliderDayRangePopup.addItems(withTitles: [ "1 day", "2 days", "3 days", "4 days", "5 days", "6 days", "7 days", ]) if #available(macOS 11.0, *) {} else { theme.font = NSFont.systemFont(ofSize: 13) } setup() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setup() self.animateBackgroundColorChange() self.view.needsDisplay = true // Let's make the color change permanent. self.previewPanelTableView.reloadData() } previewTimezones = [TimezoneData(with: ["customLabel": "San Francisco", "formattedAddress": "San Francisco", "place_id": "TestIdentifier", "timezoneID": "America/Los_Angeles", "nextUpdate": "", "note": "Your individual note about this location goes here!", "latitude": "37.7749295", "longitude": "-122.4194155"])] // Ensure the more beautiful tab is selected appearanceTab.selectTabViewItem(at: 0) // Setup Preview Pane previewPanelTableView.dataSource = self previewPanelTableView.delegate = self previewPanelTableView.reloadData() previewPanelTableView.selectionHighlightStyle = .none previewPanelTableView.enclosingScrollView?.hasVerticalScroller = false previewPanelTableView.enclosingScrollView?.wantsLayer = true previewPanelTableView.enclosingScrollView?.layer?.cornerRadius = 12 } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } private func animateBackgroundColorChange() { let colorAnimation = CABasicAnimation(keyPath: "backgroundColor") colorAnimation.duration = 0.25 colorAnimation.fromValue = previousBackgroundColor.cgColor colorAnimation.toValue = Themer.shared().mainBackgroundColor().cgColor view.layer?.add(colorAnimation, forKey: "backgroundColor") } override func viewWillAppear() { super.viewWillAppear() if let menubarFavourites = DataStore.shared().menubarTimezones() { visualEffectView.isHidden = menubarFavourites.isEmpty ? false : true informationLabel.isHidden = menubarFavourites.isEmpty ? false : true } if let selectedIndex = DataStore.shared().retrieve(key: UserDefaultKeys.futureSliderRange) as? NSNumber { sliderDayRangePopup.selectItem(at: selectedIndex.intValue) } let shouldDisplayCompact = DataStore.shared().shouldDisplay(.menubarCompactMode) menubarMode.setSelected(true, forSegment: shouldDisplayCompact ? 0 : 1) // True is Menubar Only and False is Menubar + Dock let appDisplayOptions = DataStore.shared().shouldDisplay(.appDisplayOptions) appDisplayControl.setSelected(true, forSegment: appDisplayOptions ? 0 : 1) // Set the Sync value from NSUbiqutousKeyValueStore let syncEnabled = NSUbiquitousKeyValueStore.default.bool(forKey: UserDefaultKeys.enableSyncKey) syncSegementedControl.setSelected(true, forSegment: syncEnabled ? 0 : 1) } @IBOutlet var timeFormatLabel: NSTextField! @IBOutlet var panelTheme: NSTextField! @IBOutlet var dayDisplayOptionsLabel: NSTextField! @IBOutlet var showSliderLabel: NSTextField! @IBOutlet var showSunriseLabel: NSTextField! @IBOutlet var largerTextLabel: NSTextField! @IBOutlet var futureSliderRangeLabel: NSTextField! @IBOutlet var includeDateLabel: NSTextField! @IBOutlet var includeDayLabel: NSTextField! @IBOutlet var includePlaceLabel: NSTextField! @IBOutlet var appDisplayLabel: NSTextField! @IBOutlet var menubarModeLabel: NSTextField! @IBOutlet var previewLabel: NSTextField! @IBOutlet var miscelleaneousLabel: NSTextField! // Panel Preview @IBOutlet var previewPanelTableView: NSTableView! private func setup() { timeFormatLabel.stringValue = "Time Format".localized() panelTheme.stringValue = "Panel Theme".localized() dayDisplayOptionsLabel.stringValue = "Day Display Options".localized() showSliderLabel.stringValue = "Time Scroller".localized() showSunriseLabel.stringValue = "Show Sunrise/Sunset".localized() largerTextLabel.stringValue = "Larger Text".localized() syncLabel.stringValue = "Enable iCloud Sync".localized() futureSliderRangeLabel.stringValue = "Future Slider Range".localized() includeDateLabel.stringValue = "Include Date".localized() includeDayLabel.stringValue = "Include Day".localized() includePlaceLabel.stringValue = "Include Place Name".localized() menubarModeLabel.stringValue = "Menubar Mode".localized() previewLabel.stringValue = "Preview".localized() miscelleaneousLabel.stringValue = "Miscellaneous".localized() [timeFormatLabel, panelTheme, dayDisplayOptionsLabel, showSliderLabel, showSunriseLabel, largerTextLabel, syncLabel, futureSliderRangeLabel, includeDayLabel, includeDateLabel, includePlaceLabel, appDisplayLabel, menubarModeLabel, previewLabel, miscelleaneousLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } previewPanelTableView.backgroundColor = Themer.shared().mainBackgroundColor() } @IBAction func timeFormatSelectionChanged(_ sender: NSPopUpButton) { let selection = NSNumber(value: sender.indexOfSelectedItem) UserDefaults.standard.set(selection, forKey: UserDefaultKeys.selectedTimeZoneFormatKey) refresh(panel: true, floating: true) if let selectedFormat = sender.selectedItem?.title, selectedFormat.contains("ss") { Logger.info("Selected format contains timezone format") guard let panelController = PanelController.panel() else { return } panelController.pauseTimer() } updateStatusItem() previewPanelTableView.reloadData() } private var previousBackgroundColor = NSColor.white @IBAction func themeChanged(_ sender: NSPopUpButton) { previousBackgroundColor = Themer.shared().mainBackgroundColor() let selectedMenuItem = sender.indexOfSelectedItem Themer.shared().set(theme: selectedMenuItem) refresh(panel: false, floating: true) guard let panelController = PanelController.panel() else { return } panelController.refreshBackgroundView() panelController.shutdownButton.image = Themer.shared().shutdownImage() panelController.preferencesButton.image = Themer.shared().preferenceImage() panelController.pinButton.image = Themer.shared().pinImage() panelController.sharingButton.image = Themer.shared().sharingImage() let defaultTimezones = panelController.defaultPreferences if defaultTimezones.isEmpty { panelController.updatePanelColor() } panelController.updateTableContent() switch selectedMenuItem { case 0: Logger.log(object: ["themeSelected": "Light"], for: "Theme") case 1: Logger.log(object: ["themeSelected": "Dark"], for: "Theme") case 2: Logger.log(object: ["themeSelected": "System"], for: "Theme") default: Logger.log(object: ["themeSelected": "System"], for: "Theme") } } private func loggingStringForRelativeDisplaySelection(_ selection: Int) -> String { switch selection { case 0: return "Relative Day" case 1: return "Actual Day" case 2: return "Actual Date Day" case 3: return "Hide" default: return "Unexpected Selection" } } @IBAction func changeRelativeDayDisplay(_ sender: NSSegmentedControl) { Logger.log(object: ["dayPreference": loggingStringForRelativeDisplaySelection(sender.selectedSegment)], for: "RelativeDate") refresh(panel: true, floating: true) previewPanelTableView.reloadData() } @IBAction func showFutureSlider(_: Any) { refresh(panel: false, floating: true) } @IBAction func showSunriseSunset(_ sender: NSSegmentedControl) { Logger.log(object: ["Is It Displayed": sender.selectedSegment == 0 ? "YES" : "NO"], for: "Sunrise Sunset") previewPanelTableView.reloadData() } @IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) { if sender.selectedSegment == 0 { Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode") NSApp.setActivationPolicy(.accessory) } else { Logger.log(object: ["Selection": "Menubar and Dock"], for: "Dock Mode") NSApp.setActivationPolicy(.regular) } } private func refresh(panel: Bool, floating: Bool) { OperationQueue.main.addOperation { if panel, DataStore.shared().shouldDisplay(ViewType.showAppInForeground) == false { guard let panelController = PanelController.panel() else { return } let futureSliderBounds = panelController.modernSlider.bounds panelController.modernSlider.setNeedsDisplay(futureSliderBounds) panelController.updateDefaultPreferences() panelController.updateTableContent() panelController.setupMenubarTimer() } if floating, DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { let floatingWindow = FloatingWindowController.shared() floatingWindow.updateTableContent() if let slider = floatingWindow.modernSlider { slider.setNeedsDisplay(floatingWindow.modernSlider.bounds) } if !panel { floatingWindow.updatePanelColor() } } } } } @IBAction func displayDayInMenubarAction(_: Any) { updateStatusItem() } @IBAction func displayDateInMenubarAction(_: Any) { updateStatusItem() } @IBAction func displayPlaceInMenubarAction(_: Any) { updateStatusItem() } private func updateStatusItem() { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } if DataStore.shared().shouldDisplay(.menubarCompactMode) { statusItem.setupStatusItem() } else { statusItem.refresh() } } @IBAction func menubarModeChanged(_ sender: NSSegmentedControl) { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } statusItem.setupStatusItem() if sender.selectedSegment == 0 { Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Compact Mode") } else { Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Standard Mode") } } @IBAction func fontSliderChanged(_: Any) { previewPanelTableView.reloadData() } @IBAction func toggleSync(_ sender: NSSegmentedControl) { NSUbiquitousKeyValueStore.default.set(sender.selectedSegment == 0, forKey: UserDefaultKeys.enableSyncKey) DataStore.shared().setupSyncNotification() } } extension AppearanceViewController: NSTableViewDataSource, NSTableViewDelegate { func numberOfRows(in _: NSTableView) -> Int { return 1 } func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { guard !previewTimezones.isEmpty else { return nil } guard let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "previewTimezoneCell"), owner: self) as? TimezoneCellView else { assertionFailure("Unable to create tableviewcell") return NSView() } let currentModel = previewTimezones[row] let operation = TimezoneDataOperations(with: currentModel, store: DataStore.shared()) cellView.sunriseSetTime.stringValue = operation.formattedSunriseTime(with: 0) cellView.sunriseImage.image = currentModel.isSunriseOrSunset ? Themer.shared().sunriseImage() : Themer.shared().sunsetImage() cellView.relativeDate.stringValue = operation.date(with: 0, displayType: .panel) cellView.rowNumber = row cellView.customName.stringValue = currentModel.formattedTimezoneLabel() cellView.time.stringValue = operation.time(with: 0) if let note = currentModel.note, !note.isEmpty { cellView.noteLabel.stringValue = note } else { cellView.noteLabel.stringValue = UserDefaultKeys.emptyString } cellView.currentLocationIndicator.isHidden = !currentModel.isSystemTimezone cellView.time.setAccessibilityIdentifier("ActualTime") cellView.layout(with: currentModel) cellView.setAccessibilityIdentifier(currentModel.formattedTimezoneLabel()) cellView.setAccessibilityLabel(currentModel.formattedTimezoneLabel()) return cellView } func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat { if let userFontSize = DataStore.shared().retrieve(key: UserDefaultKeys.userFontSizePreference) as? NSNumber, previewTimezones.count > row { let model = previewTimezones[row] let rowHeight: Int = userFontSize == 4 ? 60 : 65 if let note = model.note, !note.isEmpty { return CGFloat(rowHeight + userFontSize.intValue + 25) } return CGFloat(rowHeight + (userFontSize.intValue * 2)) } return 0 } } ================================================ FILE: Clocker/Preferences/Calendar/CalendarViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import EventKit class ClockerTextBackgroundView: NSView { override func awakeFromNib() { wantsLayer = true layer?.cornerRadius = 8.0 layer?.masksToBounds = false NotificationCenter.default.addObserver(self, selector: #selector(updateBackgroundColor), name: .themeDidChangeNotification, object: nil) } override func updateLayer() { super.updateLayer() layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor } @objc func updateBackgroundColor() { layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor } } class CalendarViewController: ParentViewController { @IBOutlet var showSegmentedControl: NSSegmentedControl! @IBOutlet var allDaysSegmentedControl: NSSegmentedControl! @IBOutlet var truncateTextField: NSTextField! @IBOutlet var noAccessView: NSVisualEffectView! @IBOutlet var informationField: NSTextField! @IBOutlet var grantAccessButton: NSButton! @IBOutlet var calendarsTableView: NSTableView! @IBOutlet var showNextMeetingInMenubarControl: NSSegmentedControl! @IBOutlet var backgroundView: NSView! @IBOutlet var nextMeetingBackgroundView: NSView! private var themeDidChangeNotification: NSObjectProtocol? private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars() override func viewDidLoad() { super.viewDidLoad() setup() NotificationCenter.default.addObserver(self, selector: #selector(calendarAccessStatusChanged), name: .calendarAccessGranted, object: nil) themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setup() } noAccessView.material = .contentBackground upcomingEventView.setAccessibility("UpcomingEventView") } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } @objc func calendarAccessStatusChanged() { verifyCalendarAccess() view.window?.windowController?.showWindow(nil) view.window?.makeKeyAndOrderFront(nil) } override func viewWillAppear() { super.viewWillAppear() verifyCalendarAccess() showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1 } private func verifyCalendarAccess() { let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted() let hasNotDeterminedCalendarAccess = EventCenter.sharedCenter().calendarAccessNotDetermined() let hasDeniedCalendarAccess = EventCenter.sharedCenter().calendarAccessDenied() noAccessView.isHidden = hasCalendarAccess if hasNotDeterminedCalendarAccess { informationField.stringValue = "Clocker is more useful when it can display events from your calendars.".localized() setGrantAccess(title: "Grant Access".localized()) } else if hasDeniedCalendarAccess { // The informationField text is taken care off in the XIB. Just set the grant button to empty because we can't do anything. setGrantAccess(title: UserDefaultKeys.emptyString) } else { calendarsTableView.reloadData() } } private func setGrantAccess(title: String) { let style = NSMutableParagraphStyle() style.alignment = .center guard let boldFont = NSFont(name: "Avenir-Medium", size: 14.0) else { return } let attributesDictionary: [NSAttributedString.Key: Any] = [ NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.font: boldFont, NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), ] let attributedString = NSAttributedString(string: title, attributes: attributesDictionary) grantAccessButton.attributedTitle = attributedString } private func onCalendarAccessDenial() { informationField.stringValue = """ Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy. """.localized() setGrantAccess(title: "Launch Preferences".localized()) // Remove upcoming event view if possible UserDefaults.standard.set("NO", forKey: UserDefaultKeys.showUpcomingEventView) } @IBAction func grantAccess(_: Any) { if grantAccessButton.title == "Grant Access".localized() { (parent as? CenteredTabViewController)?.selectedTabViewItemIndex = 3 // 3 is the Permissions View } else if grantAccessButton.title == "Launch Preferences" { NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/System Settings.app")) } } @IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) { // We need to start the menubar timer if it hasn't been started already guard let delegate = NSApplication.shared.delegate as? AppDelegate else { assertionFailure() return } let statusItemHandler = delegate.statusItemForPanel() if sender.selectedSegment == 0 { if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true { Logger.info("Timer is already in progress") updateStatusItem() return } } else { statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false) } } @IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) { var showUpcomingEventView = "YES" if sender.selectedSegment == 1 { showUpcomingEventView = "NO" } UserDefaults.standard.set(showUpcomingEventView, forKey: UserDefaultKeys.showUpcomingEventView) if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { let floatingWindow = FloatingWindowController.shared() floatingWindow.determineUpcomingViewVisibility() return } guard let panel = PanelController.panel() else { return } if sender.selectedSegment == 1 { panel.removeUpcomingEventView() Logger.log(object: ["Show": "NO"], for: "Upcoming Event View") } else { panel.showUpcomingEventView() Logger.log(object: ["Show": "YES"], for: "Upcoming Event View") } } private func updateStatusItem() { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } statusItem.refresh() } @IBOutlet var headerLabel: NSTextField! @IBOutlet var upcomingEventView: NSTextField! @IBOutlet var allDayMeetingsLabel: NSTextField! @IBOutlet var showNextMeetingLabel: NSTextField! @IBOutlet var nextMeetingAccessoryLabel: NSTextField! @IBOutlet var truncateTextLabel: NSTextField! @IBOutlet var showEventsFromLabel: NSTextField! @IBOutlet var charactersField: NSTextField! @IBOutlet var truncateAccessoryLabel: NSTextField! private func setup() { // Grant access button's text color is taken care above. headerLabel.stringValue = "Upcoming Event View Options".localized() upcomingEventView.stringValue = "Show Upcoming Event View".localized() allDayMeetingsLabel.stringValue = "Show All Day Meetings".localized() showNextMeetingLabel.stringValue = "Show Next Meeting Title in Menubar".localized() truncateTextLabel.stringValue = "Truncate menubar text longer than".localized() charactersField.stringValue = "characters".localized() showEventsFromLabel.stringValue = "Show events from".localized() truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"".localized() [headerLabel, upcomingEventView, allDayMeetingsLabel, showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel, showEventsFromLabel, charactersField, truncateAccessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } calendarsTableView.backgroundColor = Themer.shared().mainBackgroundColor() truncateTextField.backgroundColor = Themer.shared().mainBackgroundColor() } } extension CalendarViewController: NSTableViewDataSource { func numberOfRows(in _: NSTableView) -> Int { let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted() return hasCalendarAccess ? calendars.count : 0 } } extension CalendarViewController: NSTableViewDelegate { func tableView(_: NSTableView, shouldSelectRow _: Int) -> Bool { return false } func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat { guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else { return 30.0 } return 24.0 } func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { if let currentSource = calendars[row] as? String, let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell { message.sourceName.stringValue = currentSource return message } if let currentSource = calendars[row] as? CalendarInfo, let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell { calendarCell.calendarName.stringValue = currentSource.calendar.title calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off calendarCell.calendarSelected.target = self calendarCell.calendarSelected.tag = row calendarCell.calendarSelected.wantsLayer = true calendarCell.calendarSelected.action = #selector(calendarSelected(_:)) return calendarCell } return nil } @objc func calendarSelected(_ checkbox: NSButton) { let currentSelection = checkbox.tag var sourcesAndCalendars = calendars if var calInfo = sourcesAndCalendars[currentSelection] as? CalendarInfo { calInfo.selected = (checkbox.state == .on) sourcesAndCalendars[currentSelection] = calInfo } updateSelectedCalendars(sourcesAndCalendars) } private func updateSelectedCalendars(_ selection: [Any]) { var selectedCalendars: [String] = [] for obj in selection { if let calInfo = obj as? CalendarInfo, calInfo.selected { selectedCalendars.append(calInfo.calendar.calendarIdentifier) } } UserDefaults.standard.set(selectedCalendars, forKey: UserDefaultKeys.selectedCalendars) calendars = EventCenter.sharedCenter().fetchSourcesAndCalendars() EventCenter.sharedCenter().filterEvents() } } class SourceTableViewCell: NSTableCellView { @IBOutlet var sourceName: NSTextField! } class CalendarTableViewCell: NSTableCellView { @IBOutlet var calendarName: NSTextField! @IBOutlet var calendarSelected: NSButton! } ================================================ FILE: Clocker/Preferences/General/PreferencesDataSource.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit struct PreferencesDataSourceConstants { static let timezoneNameIdentifier = "formattedAddress" static let customLabelIdentifier = "label" static let availableTimezoneIdentifier = "availableTimezones" static let favoriteTimezoneIdentifier = "favouriteTimezone" } protocol PreferenceSelectionUpdates: AnyObject { func preferenceSelectionDataSourceMarkAsFavorite(_ dataObject: TimezoneData) func preferenceSelectionDataSourceUnfavourite(_ dataObject: TimezoneData) func preferenceSelectionDataSourceRefreshTimezoneTable() func preferenceSelectionDataSourceRefreshMainTableView() func preferenceSelectionDataSourceTableViewSelectionDidChange(_ status: Bool) func preferenceSelectionDataSourceTable(didClick tableColumn: NSTableColumn) } class PreferencesDataSource: NSObject { private weak var updateDelegate: PreferenceSelectionUpdates? private let store: DataStore var selectedTimezones: [Data] { return store.timezones() } init(with store: DataStore, callbackDelegate delegate: PreferenceSelectionUpdates) { self.store = store updateDelegate = delegate super.init() } } extension PreferencesDataSource: NSTableViewDelegate { func tableViewSelectionDidChange(_ notification: Notification) { if let tableView = notification.object as? NSTableView { updateDelegate?.preferenceSelectionDataSourceTableViewSelectionDidChange(tableView.selectedRow == -1) } } func tableView(_: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { let data = NSKeyedArchiver.clocker_archive(with: rowIndexes) pboard.declareTypes([.dragSession], owner: self) pboard.setData(data, forType: .dragSession) return true } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation _: NSTableView.DropOperation) -> Bool { var newOrder = selectedTimezones var destination = row if row == newOrder.count { destination -= 1 } let pBoard = info.draggingPasteboard guard let data = pBoard.data(forType: .dragSession) else { assertionFailure("Data was unexpectedly nil") return false } guard let rowIndexes = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSIndexSet.self, from: data) else { assertionFailure("Row was unexpectedly nil") return false } let first = rowIndexes.firstIndex let currentObject = newOrder[first] newOrder.remove(at: first) newOrder.insert(currentObject, at: destination) store.setTimezones(newOrder) tableView.reloadData() updateDelegate?.preferenceSelectionDataSourceRefreshMainTableView() tableView.deselectRow(tableView.selectedRow) return true } func tableView(_: NSTableView, validateDrop _: NSDraggingInfo, proposedRow _: Int, proposedDropOperation _: NSTableView.DropOperation) -> NSDragOperation { return .every } func tableView(_: NSTableView, didClick tableColumn: NSTableColumn) { updateDelegate?.preferenceSelectionDataSourceTable(didClick: tableColumn) } } extension PreferencesDataSource: NSTableViewDataSource { func numberOfRows(in _: NSTableView) -> Int { return selectedTimezones.count } func tableView(_: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { var selectedDataSource: TimezoneData? if selectedTimezones.count > row, let model = TimezoneData.customObject(from: selectedTimezones[row]) { selectedDataSource = model } if tableColumn?.identifier.rawValue == PreferencesDataSourceConstants.timezoneNameIdentifier { return handleTimezoneNameIdentifier(selectedDataSource) } if tableColumn?.identifier.rawValue == PreferencesDataSourceConstants.customLabelIdentifier { return selectedDataSource?.customLabel ?? "Error" } if tableColumn?.identifier.rawValue == PreferencesDataSourceConstants.favoriteTimezoneIdentifier { return selectedDataSource?.isFavourite ?? 0 } return nil } private func handleTimezoneNameIdentifier(_ selectedDataSource: TimezoneData?) -> Any? { guard let model = selectedDataSource else { return nil } if let address = model.formattedAddress, address.isEmpty == false { return model.formattedAddress } return model.timezone() } func tableView(_: NSTableView, setObjectValue object: Any?, for _: NSTableColumn?, row: Int) { guard !selectedTimezones.isEmpty, let dataObject = TimezoneData.customObject(from: selectedTimezones[row]) else { return } if let edit = object as? String { setNewLabel(edit, for: dataObject, at: row) } else if let isFavouriteValue = object as? NSNumber { dataObject.isFavourite = isFavouriteValue.intValue insert(timezone: dataObject, at: row) dataObject.isFavourite == 1 ? updateDelegate?.preferenceSelectionDataSourceMarkAsFavorite(dataObject) : updateDelegate?.preferenceSelectionDataSourceUnfavourite(dataObject) updateStatusItem() updateDelegate?.preferenceSelectionDataSourceRefreshTimezoneTable() } updateDelegate?.preferenceSelectionDataSourceRefreshMainTableView() } private func setNewLabel(_ label: String, for dataObject: TimezoneData, at row: Int) { let formattedValue = label.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) if selectedTimezones.count > row { Logger.log(object: [ "Old Label": dataObject.customLabel ?? "Error", "New Label": formattedValue, ], for: "Custom Label Changed") dataObject.setLabel(formattedValue) insert(timezone: dataObject, at: row) updateMenubarTitles() } else { Logger.log(object: [ "MethodName": "SetObjectValue", "Selected Timezone Count": selectedTimezones.count, "Current Row": row, ], for: "Error in selected row count") } } private func insert(timezone: TimezoneData, at index: Int) { guard let encodedObject = NSKeyedArchiver.clocker_archive(with: timezone) else { return } var newDefaults = selectedTimezones newDefaults[index] = encodedObject store.setTimezones(newDefaults) } private func updateMenubarTitles() { if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } } // TODO: This probably does not need to be used private func updateStatusItem() { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } statusItem.refresh() } } ================================================ FILE: Clocker/Preferences/General/PreferencesViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import StartupKit struct PreferencesConstants { static let noTimezoneSelectedErrorMessage = NSLocalizedString("No Timezone Selected", comment: "Message shown when the user taps on Add without selecting a timezone") static let maxTimezonesErrorMessage = NSLocalizedString("Max Timezones Selected", comment: "Max Timezones Error Message") static let maxCharactersAllowed = NSLocalizedString("Max Search Characters", comment: "Max Character Count Allowed Error Message") static let noInternetConnectivityError = "You're offline, maybe?".localized() static let tryAgainMessage = "Try again, maybe?".localized() static let offlineErrorMessage = "The Internet connection appears to be offline.".localized() static let hotKeyPathIdentifier = "values.globalPing" } class PreferencesViewController: ParentViewController { @IBOutlet private var placeholderLabel: NSTextField! @IBOutlet private var timezoneTableView: NSTableView! @IBOutlet private var availableTimezoneTableView: NSTableView! @IBOutlet private var timezonePanel: Panelr! @IBOutlet private var progressIndicator: NSProgressIndicator! @IBOutlet private var addButton: NSButton! @IBOutlet private var recorderControl: SRRecorderControl! @IBOutlet private var closeButton: NSButton! @IBOutlet private var timezoneSortButton: NSButton! @IBOutlet private var timezoneNameSortButton: NSButton! @IBOutlet private var labelSortButton: NSButton! @IBOutlet private var deleteButton: NSButton! @IBOutlet private var addTimezoneButton: NSButton! @IBOutlet private var searchField: NSSearchField! @IBOutlet private var messageLabel: NSTextField! @IBOutlet private var tableview: NSView! @IBOutlet private var additionalSortOptions: NSView! @IBOutlet var startAtLoginLabel: NSTextField! @IBOutlet var startupCheckbox: NSButton! // Sorting private var arePlacesSortedInAscendingOrder = false private var arePlacesSortedInAscendingTimezoneOrder = false private var isTimezoneSortOptionSelected = false private var isTimezoneNameSortOptionSelected = false private var isLabelOptionSelected = false private var isActivityInProgress = false { didSet { OperationQueue.main.addOperation { self.isActivityInProgress ? self.progressIndicator.startAnimation(nil) : self.progressIndicator.stopAnimation(nil) self.availableTimezoneTableView.isEnabled = !self.isActivityInProgress self.addButton.isEnabled = !self.isActivityInProgress } } } private var selectedTimeZones: [Data] { return DataStore.shared().timezones() } private var themeDidChangeNotification: NSObjectProtocol? // Selected Timezones Data Source private var selectionsDataSource: PreferencesDataSource! // Search Results Data Source Handler private var searchResultsDataSource: SearchDataSource! private lazy var startupManager = StartupManager() private var dataTask: URLSessionDataTask? = .none private lazy var notimezoneView: NoTimezoneView? = NoTimezoneView(frame: tableview.frame) private var geocodingKey: String = { guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"), let dictionary = NSDictionary(contentsOfFile: path), let apiKey = dictionary["GeocodingKey"] as? String else { assertionFailure("Unable to find the API key") return "" } return apiKey }() override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(refreshTimezoneTableView), name: NSNotification.Name.customLabelChanged, object: nil) NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification, object: self, queue: OperationQueue.main) { [weak self] _ in if let sSelf = self { sSelf.refreshTimezoneTableView() } } refreshTimezoneTableView() setup() setupShortcutObserver() darkModeChanges() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setup() } searchField.placeholderString = "Enter city, state, country or timezone name" selectionsDataSource = PreferencesDataSource(with: DataStore.shared(), callbackDelegate: self) timezoneTableView.dataSource = selectionsDataSource timezoneTableView.delegate = selectionsDataSource searchResultsDataSource = SearchDataSource(with: searchField) availableTimezoneTableView.dataSource = searchResultsDataSource availableTimezoneTableView.delegate = searchResultsDataSource } deinit { // We still need to remove observers set using NotificationCenter block: APIs if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } private func darkModeChanges() { addTimezoneButton.image = Themer.shared().addImage() deleteButton.image = Themer.shared().remove() } private func setupLocalizedText() { startAtLoginLabel.stringValue = NSLocalizedString("Start at Login", comment: "Start at Login") timezoneSortButton.title = NSLocalizedString("Sort by Time Difference", comment: "Start at Login") timezoneNameSortButton.title = NSLocalizedString("Sort by Name", comment: "Start at Login") labelSortButton.title = NSLocalizedString("Sort by Label", comment: "Start at Login") addButton.title = NSLocalizedString("Add Button Title", comment: "Button to add a location") closeButton.title = NSLocalizedString("Close Button Title", comment: "Button to close the panel") } @objc func refreshTimezoneTableView(_ shouldSelectNewlyInsertedTimezone: Bool = false) { OperationQueue.main.addOperation { self.build(shouldSelectNewlyInsertedTimezone) } } private func refreshMainTable() { OperationQueue.main.addOperation { self.refresh() } } private func refresh() { if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { updateFloatingWindow() } else { guard let panel = PanelController.panel() else { return } panel.updateDefaultPreferences() panel.updateTableContent() } } private func updateFloatingWindow() { let current = FloatingWindowController.shared() current.updateDefaultPreferences() current.updateTableContent() } private func build(_ shouldSelectLastRow: Bool = false) { if DataStore.shared().timezones() == [] { housekeeping() return } if selectedTimeZones.isEmpty == false { additionalSortOptions.isHidden = false if tableview.subviews.count > 1, let zeroView = notimezoneView, tableview.subviews.contains(zeroView) { zeroView.removeFromSuperview() timezoneTableView.enclosingScrollView?.isHidden = false } timezoneTableView.reloadData() if shouldSelectLastRow { selectNewlyInsertedTimezone() } } else { housekeeping() } cleanup() } private func housekeeping() { timezoneTableView.enclosingScrollView?.isHidden = true showNoTimezoneState() cleanup() } private func cleanup() { updateMenubarTitles() // Update the menubar titles, the custom labels might have changed. } private func updateMenubarTitles() { if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } } private func setup() { setupAccessibilityIdentifiers() deleteButton.isEnabled = false [placeholderLabel].forEach { $0.isHidden = true } messageLabel.stringValue = UserDefaultKeys.emptyString timezoneTableView.registerForDraggedTypes([.dragSession]) progressIndicator.usesThreadedAnimation = true setupLocalizedText() setupColor() startupCheckbox.integerValue = DataStore.shared().retrieve(key: UserDefaultKeys.startAtLogin) as? Int ?? 0 searchField.bezelStyle = .roundedBezel } private func setupColor() { let themer = Themer.shared() startAtLoginLabel.textColor = Themer.shared().mainTextColor() [timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach { $0?.attributedTitle = NSAttributedString(string: $0?.title ?? UserDefaultKeys.emptyString, attributes: [ NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, ]) } timezoneTableView.backgroundColor = Themer.shared().mainBackgroundColor() availableTimezoneTableView.backgroundColor = Themer.shared().textBackgroundColor() timezonePanel.backgroundColor = Themer.shared().textBackgroundColor() timezonePanel.contentView?.wantsLayer = true timezonePanel.contentView?.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor addTimezoneButton.image = themer.addImage() deleteButton.image = themer.remove() } private func setupShortcutObserver() { let defaults = NSUserDefaultsController.shared recorderControl.setAccessibilityElement(true) recorderControl.setAccessibilityIdentifier("ShortcutControl") recorderControl.setAccessibilityLabel("ShortcutControl") recorderControl.bind(NSBindingName.value, to: defaults, withKeyPath: PreferencesConstants.hotKeyPathIdentifier, options: nil) recorderControl.delegate = self } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { if let path = keyPath, path == PreferencesConstants.hotKeyPathIdentifier { let hotKeyCenter = PTHotKeyCenter.shared() let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path) hotKeyCenter?.unregisterHotKey(oldHotKey) guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else { assertionFailure("Unable to recognize shortcuts") return } let newHotKey = PTHotKey(identifier: keyPath, keyCombo: newShortcut, target: self, action: #selector(ping(_:))) hotKeyCenter?.register(newHotKey) } } @objc func ping(_ sender: NSButton) { guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return } delegate.togglePanel(sender) } private func showNoTimezoneState() { if let zeroView = notimezoneView { notimezoneView?.wantsLayer = true tableview.addSubview(zeroView) Logger.log(object: ["Showing Empty View": "YES"], for: "Showing Empty View") } additionalSortOptions.isHidden = true } private func setupAccessibilityIdentifiers() { timezoneTableView.setAccessibilityIdentifier("TimezoneTableView") availableTimezoneTableView.setAccessibilityIdentifier("AvailableTimezoneTableView") searchField.setAccessibilityIdentifier("AvailableSearchField") timezoneSortButton.setAccessibility("SortByDifference") labelSortButton.setAccessibility("SortByLabelButton") timezoneNameSortButton.setAccessibility("SortByTimezoneName") } override var acceptsFirstResponder: Bool { return true } } extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate { private func _markAsFavorite(_ dataObject: TimezoneData) { if dataObject.customLabel != nil { Logger.log(object: ["label": dataObject.customLabel ?? "Error"], for: "favouriteSelected") } if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } if let menubarTimezones = DataStore.shared().menubarTimezones(), menubarTimezones.count > 1 { showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() } } private func _unfavourite(_ dataObject: TimezoneData) { Logger.log(object: ["label": dataObject.customLabel ?? "Error"], for: "favouriteRemoved") if let appDelegate = NSApplication.shared.delegate as? AppDelegate, let menubarFavourites = DataStore.shared().menubarTimezones(), menubarFavourites.isEmpty, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { appDelegate.invalidateMenubarTimer(true) } if let appDelegate = NSApplication.shared.delegate as? AppDelegate { appDelegate.setupMenubarTimer() } } private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() { let isUITestRunning = ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.testingLaunchArgument) // If we have seen displayed the message before, abort! let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: UserDefaultKeys.longStatusBarWarningMessage) if haveWeSeenThisMessageBefore, !isUITestRunning { return } // If the user is already using the compact mode, abort. if DataStore.shared().shouldDisplay(.menubarCompactMode), !isUITestRunning { return } // Time to display the alert. NSApplication.shared.activate(ignoringOtherApps: true) let infoText = """ Multiple timezones occupy space and if macOS determines Clocker is occupying too much space, it'll hide Clocker entirely! Enable Menubar Compact Mode to fit in more timezones in less space. """ let alert = NSAlert() alert.showsSuppressionButton = true alert.messageText = "More than one location added to the menubar 😅" alert.informativeText = infoText alert.addButton(withTitle: "Enable Compact Mode") alert.addButton(withTitle: "Cancel") let response = alert.runModal() if response.rawValue == 1000 { OperationQueue.main.addOperation { UserDefaults.standard.set(0, forKey: UserDefaultKeys.menubarCompactMode) if alert.suppressionButton?.state == NSControl.StateValue.on { UserDefaults.standard.set(true, forKey: UserDefaultKeys.longStatusBarWarningMessage) } self.updateStatusBarAppearance() Logger.log(object: ["Context": ">1 Menubar Timezone in Preferences"], for: "Switched to Compact Mode") } } } } extension PreferencesViewController { @objc private func search() { let searchString = searchField.stringValue if searchString.isEmpty { dataTask?.cancel() resetSearchView() return } if dataTask?.state == .running { dataTask?.cancel() } OperationQueue.main.addOperation { if self.availableTimezoneTableView.isHidden { self.availableTimezoneTableView.isHidden = false } self.placeholderLabel.isHidden = false self.isActivityInProgress = true self.placeholderLabel.placeholderString = "Searching for \(searchString)" Logger.info(self.placeholderLabel.placeholderString ?? "") self.dataTask = NetworkManager.task(with: self.generateSearchURL(), completionHandler: { [weak self] response, error in guard let self = self else { return } OperationQueue.main.addOperation { if let errorPresent = error { self.findLocalSearchResultsForTimezones() if self.searchResultsDataSource.timezoneFilteredArray.isEmpty { self.presentError(errorPresent.localizedDescription) return } self.prepareUIForPresentingResults() return } guard let data = response, let searchResults = data.decode() else { assertionFailure("Data was unexpectedly nil") return } // let searchResults = data.decode() if searchResults.status == ResultStatus.zeroResults { Logger.info("Zero Results returned") self.findLocalSearchResultsForTimezones() self.placeholderLabel.placeholderString = self.searchResultsDataSource.timezoneFilteredArray.isEmpty ? "No results! 😔 Try entering the exact name." : UserDefaultKeys.emptyString self.reloadSearchResults() self.isActivityInProgress = false return } else if searchResults.status == ResultStatus.requestDenied && searchResults.results.isEmpty { Logger.info("Request denied!") self.findLocalSearchResultsForTimezones() self.placeholderLabel.placeholderString = self.searchResultsDataSource.timezoneFilteredArray.isEmpty ? "Update Clocker to get a faster experience 😃" : UserDefaultKeys.emptyString self.reloadSearchResults() self.isActivityInProgress = false return } self.appendResultsToFilteredArray(searchResults.results) self.findLocalSearchResultsForTimezones() self.prepareUIForPresentingResults() } }) } } private func findLocalSearchResultsForTimezones() { let lowercasedSearchString = searchField.stringValue.lowercased() searchResultsDataSource.searchTimezones(lowercasedSearchString) } private func generateSearchURL() -> String { let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" var searchString = searchField.stringValue let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines) searchString = words.joined(separator: UserDefaultKeys.emptyString) return "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(geocodingKey)&language=\(userPreferredLanguage)" } private func presentError(_ errorMessage: String) { placeholderLabel.placeholderString = errorMessage == PreferencesConstants.offlineErrorMessage ? PreferencesConstants.noInternetConnectivityError : PreferencesConstants.tryAgainMessage isActivityInProgress = false } private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) { var finalResults: [TimezoneData] = [] results.forEach { let location = $0.geometry.location let latitude = location.lat let longitude = location.lng let formattedAddress = $0.formattedAddress let totalPackage = [ "latitude": latitude, "longitude": longitude, UserDefaultKeys.timezoneName: formattedAddress, UserDefaultKeys.customLabel: formattedAddress, UserDefaultKeys.timezoneID: UserDefaultKeys.emptyString, UserDefaultKeys.placeIdentifier: $0.placeId, ] as [String: Any] finalResults.append(TimezoneData(with: totalPackage)) } searchResultsDataSource.setFilteredArrayValue(finalResults) } private func prepareUIForPresentingResults() { placeholderLabel.placeholderString = UserDefaultKeys.emptyString isActivityInProgress = false reloadSearchResults() } private func reloadSearchResults() { if searchResultsDataSource.calculateChangesets() { Logger.info("Reloading Search Results") availableTimezoneTableView.reloadData() } } private func resetSearchView() { if dataTask?.state == .running { dataTask?.cancel() } isActivityInProgress = false placeholderLabel.placeholderString = UserDefaultKeys.emptyString } private func getTimezone(for latitude: Double, and longitude: Double) { if placeholderLabel.isHidden { placeholderLabel.isHidden = false } searchField.placeholderString = "Fetching data might take some time!" placeholderLabel.placeholderString = "Retrieving timezone data" availableTimezoneTableView.isHidden = true let tuple = "\(latitude),\(longitude)" let timeStamp = Date().timeIntervalSince1970 let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)×tamp=\(timeStamp)&key=\(geocodingKey)" NetworkManager.task(with: urlString) { [weak self] response, error in guard let strongSelf = self else { return } OperationQueue.main.addOperation { if strongSelf.handleEdgeCase(for: response) == true { strongSelf.reloadSearchResults() return } if error == nil, let json = response, let timezone = json.decodeTimezone() { if strongSelf.availableTimezoneTableView.selectedRow >= 0 { strongSelf.installTimezone(timezone) } strongSelf.updateViewState() } else { OperationQueue.main.addOperation { if error?.localizedDescription == "The Internet connection appears to be offline." { strongSelf.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError } else { strongSelf.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage } strongSelf.isActivityInProgress = false } } } } } private func installTimezone(_ timezone: Timezone) { guard let dataObject = searchResultsDataSource.retrieveFilteredResultFromGoogleAPI(availableTimezoneTableView.selectedRow) else { assertionFailure("Data was unexpectedly nil") return } var filteredAddress = "Error" if let address = dataObject.formattedAddress { filteredAddress = address.filteredName() } let newTimeZone = [ UserDefaultKeys.timezoneID: timezone.timeZoneId, UserDefaultKeys.timezoneName: filteredAddress, UserDefaultKeys.placeIdentifier: dataObject.placeID!, "latitude": dataObject.latitude!, "longitude": dataObject.longitude!, "nextUpdate": UserDefaultKeys.emptyString, UserDefaultKeys.customLabel: filteredAddress, ] as [String: Any] // Mark if the timezone is same as local timezone let timezoneObject = TimezoneData(with: newTimeZone) let operationsObject = TimezoneDataOperations(with: timezoneObject, store: DataStore.shared()) operationsObject.saveObject() Logger.log(object: ["PlaceName": filteredAddress, "Timezone": timezone.timeZoneId], for: "Filtered Address") } private func resetStateAndShowDisconnectedMessage() { OperationQueue.main.addOperation { self.showMessage() } } private func showMessage() { placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError isActivityInProgress = false searchResultsDataSource.cleanupFilterArray() reloadSearchResults() } /// Returns true if there's an error. private func handleEdgeCase(for response: Data?) -> Bool { guard let json = response, let jsonUnserialized = try? JSONSerialization.jsonObject(with: json, options: .allowFragments), let unwrapped = jsonUnserialized as? [String: Any] else { setErrorPlaceholders() return false } if let status = unwrapped["status"] as? String, status == ResultStatus.zeroResults { setErrorPlaceholders() return true } return false } private func setErrorPlaceholders() { placeholderLabel.placeholderString = "No timezone found! Try entering an exact name." searchField.placeholderString = NSLocalizedString("Search Field Placeholder", comment: "Search Field Placeholder") isActivityInProgress = false } private func updateViewState() { searchResultsDataSource.cleanupFilterArray() reloadSearchResults() refreshTimezoneTableView(true) refreshMainTable() timezonePanel.close() placeholderLabel.placeholderString = UserDefaultKeys.emptyString searchField.placeholderString = NSLocalizedString("Search Field Placeholder", comment: "Search Field Placeholder") availableTimezoneTableView.isHidden = false isActivityInProgress = false } @IBAction func addTimeZone(_: NSButton) { searchResultsDataSource.cleanupFilterArray() view.window?.beginSheet(timezonePanel, completionHandler: nil) } @IBAction func addToFavorites(_: NSButton) { isActivityInProgress = true if availableTimezoneTableView.selectedRow == -1 { timezonePanel.contentView?.makeToast(PreferencesConstants.noTimezoneSelectedErrorMessage) isActivityInProgress = false return } if selectedTimeZones.count >= 100 { timezonePanel.contentView?.makeToast(PreferencesConstants.maxTimezonesErrorMessage) isActivityInProgress = false return } if searchField.stringValue.isEmpty { addTimezoneIfSearchStringIsEmpty() } else { addTimezoneIfSearchStringIsNotEmpty() } } private func addTimezoneIfSearchStringIsEmpty() { let currentRowType = searchResultsDataSource.placeForRow(availableTimezoneTableView.selectedRow) switch currentRowType { case .timezone: cleanupAfterInstallingTimezone() default: return } } private func addTimezoneIfSearchStringIsNotEmpty() { let currentRowType = searchResultsDataSource.placeForRow(availableTimezoneTableView.selectedRow) switch currentRowType { case .timezone: cleanupAfterInstallingTimezone() return case .city: cleanupAfterInstallingCity() } } private func cleanupAfterInstallingCity() { guard let dataObject = searchResultsDataSource.retrieveFilteredResultFromGoogleAPI(availableTimezoneTableView.selectedRow) else { assertionFailure("Data was unexpectedly nil") return } if messageLabel.stringValue.isEmpty { searchField.stringValue = UserDefaultKeys.emptyString guard let latitude = dataObject.latitude, let longitude = dataObject.longitude else { assertionFailure("Data was unexpectedly nil") return } getTimezone(for: latitude, and: longitude) } } private func cleanupAfterInstallingTimezone() { let data = TimezoneData() data.setLabel(UserDefaultKeys.emptyString) let currentSelection = searchResultsDataSource.retrieveSelectedTimezone(availableTimezoneTableView.selectedRow) let metaInfo = metadata(for: currentSelection) data.timezoneID = metaInfo.0.name data.formattedAddress = metaInfo.1.formattedName data.selectionType = .timezone data.isSystemTimezone = metaInfo.0.name == NSTimeZone.system.identifier let operationObject = TimezoneDataOperations(with: data, store: DataStore.shared()) operationObject.saveObject() searchResultsDataSource.cleanupFilterArray() searchResultsDataSource.timezoneFilteredArray = [] placeholderLabel.placeholderString = UserDefaultKeys.emptyString searchField.stringValue = UserDefaultKeys.emptyString reloadSearchResults() refreshTimezoneTableView(true) refreshMainTable() timezonePanel.close() searchField.placeholderString = NSLocalizedString("Search Field Placeholder", comment: "Search Field Placeholder") availableTimezoneTableView.isHidden = false isActivityInProgress = false } private func selectNewlyInsertedTimezone() { // Let's highlight the newly added row. If the number of timezones is greater than 6, the newly added timezone isn't visible. Since we hide the scrollbars as well, the user might get the impression that something is broken! if timezoneTableView.numberOfRows > 6 { timezoneTableView.scrollRowToVisible(timezoneTableView.numberOfRows - 1) } let indexSet = IndexSet(integer: IndexSet.Element(timezoneTableView.numberOfRows - 1)) timezoneTableView.selectRowIndexes(indexSet, byExtendingSelection: false) } private func metadata(for selection: TimezoneMetadata) -> (NSTimeZone, TimezoneMetadata) { if selection.formattedName == "Anywhere on Earth" { return (NSTimeZone(name: "GMT-1200")!, selection) } else if selection.formattedName == "UTC" { return (NSTimeZone(name: "GMT")!, selection) } else { return (selection.timezone, selection) } } @IBAction func closePanel(_: NSButton) { searchResultsDataSource.cleanupFilterArray() searchResultsDataSource.timezoneFilteredArray = [] searchField.stringValue = UserDefaultKeys.emptyString placeholderLabel.placeholderString = UserDefaultKeys.emptyString searchField.placeholderString = NSLocalizedString("Search Field Placeholder", comment: "Search Field Placeholder") reloadSearchResults() timezonePanel.close() isActivityInProgress = false addTimezoneButton.state = .off // The table might be hidden because of an early exit especially // if we are not able to fetch an associated timezone // For eg. Europe doesn't have an associated timezone availableTimezoneTableView.isHidden = false } @IBAction func removeFromFavourites(_: NSButton) { // If the user is editing a row, and decides to delete the row then we have a crash if timezoneTableView.editedRow != -1 || timezoneTableView.editedColumn != -1 { return } if timezoneTableView.selectedRow == -1, selectedTimeZones.count <= timezoneTableView.selectedRow { assertionFailure("Data was unexpectedly nil") return } var newDefaults = selectedTimeZones let objectsToRemove = timezoneTableView.selectedRowIndexes.map { index -> Data in selectedTimeZones[index] } newDefaults = newDefaults.filter { !objectsToRemove.contains($0) } DataStore.shared().setTimezones(newDefaults) timezoneTableView.reloadData() refreshTimezoneTableView() refreshMainTable() updateStatusBarAppearance() updateStatusItem() } // TODO: This probably does not need to be used private func updateStatusItem() { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } statusItem.refresh() } private func updateStatusBarAppearance() { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { return } statusItem.setupStatusItem() } @IBAction func filterArray(_: Any?) { searchResultsDataSource.cleanupFilterArray() if searchField.stringValue.count > 50 { isActivityInProgress = false reloadSearchResults() timezonePanel.contentView?.makeToast(PreferencesConstants.maxCharactersAllowed) return } if searchField.stringValue.isEmpty == false { dataTask?.cancel() NSObject.cancelPreviousPerformRequests(withTarget: self) perform(#selector(search), with: nil, afterDelay: 0.5) } else { resetSearchView() } reloadSearchResults() } } extension PreferencesViewController { @IBAction func loginPreferenceChanged(_ sender: NSButton) { startupManager.toggleLogin(sender.state == .on) } } // Sorting extension PreferencesViewController { @IBAction func sortOptions(_: NSButton) { additionalSortOptions.isHidden.toggle() } @IBAction func sortByTime(_ sender: NSButton) { let sortedByTime = selectedTimeZones.sorted { obj1, obj2 -> Bool in let system = NSTimeZone.system guard let object1 = TimezoneData.customObject(from: obj1), let object2 = TimezoneData.customObject(from: obj2) else { assertionFailure("Data was unexpectedly nil") return false } let timezone1 = NSTimeZone(name: object1.timezone()) let timezone2 = NSTimeZone(name: object2.timezone()) let difference1 = system.secondsFromGMT() - timezone1!.secondsFromGMT let difference2 = system.secondsFromGMT() - timezone2!.secondsFromGMT return arePlacesSortedInAscendingTimezoneOrder ? difference1 > difference2 : difference1 < difference2 } sender.image = arePlacesSortedInAscendingTimezoneOrder ? NSImage(named: NSImage.Name("NSDescendingSortIndicator"))! : NSImage(named: NSImage.Name("NSAscendingSortIndicator"))! arePlacesSortedInAscendingTimezoneOrder.toggle() DataStore.shared().setTimezones(sortedByTime) updateAfterSorting() } @IBAction func sortByLabel(_ sender: NSButton) { let sortedLabels = selectedTimeZones.sorted { obj1, obj2 -> Bool in guard let object1 = TimezoneData.customObject(from: obj1), let object2 = TimezoneData.customObject(from: obj2) else { assertionFailure("Data was unexpectedly nil") return false } return isLabelOptionSelected ? object1.customLabel! > object2.customLabel! : object1.customLabel! < object2.customLabel! } sender.image = isLabelOptionSelected ? NSImage(named: NSImage.Name("NSDescendingSortIndicator"))! : NSImage(named: NSImage.Name("NSAscendingSortIndicator"))! isLabelOptionSelected.toggle() DataStore.shared().setTimezones(sortedLabels) updateAfterSorting() } @IBAction func sortByFormattedAddress(_ sender: NSButton) { let sortedByAddress = selectedTimeZones.sorted { obj1, obj2 -> Bool in guard let object1 = TimezoneData.customObject(from: obj1), let object2 = TimezoneData.customObject(from: obj2) else { assertionFailure("Data was unexpectedly nil") return false } return isTimezoneNameSortOptionSelected ? object1.formattedAddress! > object2.formattedAddress! : object1.formattedAddress! < object2.formattedAddress! } sender.image = isTimezoneNameSortOptionSelected ? NSImage(named: NSImage.Name("NSDescendingSortIndicator"))! : NSImage(named: NSImage.Name("NSAscendingSortIndicator"))! isTimezoneNameSortOptionSelected.toggle() DataStore.shared().setTimezones(sortedByAddress) updateAfterSorting() } private func updateAfterSorting() { let newDefaults = selectedTimeZones DataStore.shared().setTimezones(newDefaults) refreshTimezoneTableView() refreshMainTable() } } extension PreferencesViewController: SRRecorderControlDelegate {} // Helpers extension PreferencesViewController { private func insert(timezone: TimezoneData, at index: Int) { guard let encodedObject = NSKeyedArchiver.clocker_archive(with: timezone) else { return } var newDefaults = selectedTimeZones newDefaults[index] = encodedObject DataStore.shared().setTimezones(newDefaults) } } extension PreferencesViewController: PreferenceSelectionUpdates { func preferenceSelectionDataSourceMarkAsFavorite(_ dataObject: TimezoneData) { _markAsFavorite(dataObject) } func preferenceSelectionDataSourceUnfavourite(_ dataObject: TimezoneData) { _unfavourite(dataObject) } func preferenceSelectionDataSourceRefreshTimezoneTable() { refreshTimezoneTableView() } func preferenceSelectionDataSourceRefreshMainTableView() { refreshMainTable() } func preferenceSelectionDataSourceTableViewSelectionDidChange(_ status: Bool) { deleteButton.isEnabled = !status } func preferenceSelectionDataSourceTable(didClick tableColumn: NSTableColumn) { if tableColumn.identifier.rawValue == "favouriteTimezone" { return } let sortedTimezones = selectedTimeZones.sorted { obj1, obj2 -> Bool in guard let object1 = TimezoneData.customObject(from: obj1), let object2 = TimezoneData.customObject(from: obj2) else { assertionFailure("Data was unexpectedly nil") return false } if tableColumn.identifier.rawValue == "formattedAddress" { return arePlacesSortedInAscendingOrder ? object1.formattedAddress! > object2.formattedAddress! : object1.formattedAddress! < object2.formattedAddress! } else { return arePlacesSortedInAscendingOrder ? object1.customLabel! > object2.customLabel! : object1.customLabel! < object2.customLabel! } } let indicatorImage = arePlacesSortedInAscendingOrder ? NSImage(named: NSImage.Name("NSDescendingSortIndicator"))! : NSImage(named: NSImage.Name("NSAscendingSortIndicator"))! timezoneTableView.setIndicatorImage(indicatorImage, in: tableColumn) arePlacesSortedInAscendingOrder.toggle() DataStore.shared().setTimezones(sortedTimezones) updateAfterSorting() } } ================================================ FILE: Clocker/Preferences/General/SearchDataSource.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreModelKit enum RowType { case city case timezone } enum SearchLocation { case onboarding case preferences } struct TimezoneMetadata { let timezone: NSTimeZone let tags: Set let formattedName: String let abbreviation: String } class SearchDataSource: NSObject { private var searchField: NSSearchField! private var finalArray: [RowType] = [] private var location: SearchLocation = .preferences private var dataTask: URLSessionDataTask? = .none private var timezoneMetadataDictionary: [String: [String]] = ["GMT+5:30": ["india", "indian", "kolkata", "calcutta", "mumbai", "delhi", "hyderabad", "noida"], "PST": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"], "PDT": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"], "UTC": ["utc", "universal"], "EST": ["florida", "new york"], "EDT": ["florida", "new york"]] private var filteredArray: [TimezoneData] = [] // Filtered results from the Google API private var timezoneArray: [TimezoneMetadata] = [] // All timezones var timezoneFilteredArray: [TimezoneMetadata] = [] // Filtered timezones list based on search input init(with searchField: NSSearchField, location: SearchLocation = .preferences) { super.init() self.searchField = searchField self.location = location setupTimezoneDatasource() calculateChangesets() } func cleanupFilterArray() { filteredArray = [] } func setFilteredArrayValue(_ newArray: [TimezoneData]) { filteredArray = newArray } func placeForRow(_ row: Int) -> RowType { return finalArray[row] } // Returns result from finalArray based on the type i.e. city or timezone func retrieveResult(_ row: Int) -> Any? { let currentRowType = finalArray[row] switch currentRowType { case .timezone: let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray return datasource[row % datasource.count] case .city: if filteredArray.count == 0 { return nil } let timezoneData = filteredArray[row % filteredArray.count] return timezoneData } } func retrieveFilteredResultFromGoogleAPI(_ index: Int) -> TimezoneData? { if index >= filteredArray.count { return nil } return filteredArray[index % filteredArray.count] } func resultsCount() -> Int { return finalArray.count } private func setupTimezoneDatasource() { timezoneArray = [] let anywhereOnEarth = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT-1200")!, tags: ["aoe", "anywhere on earth"], formattedName: "Anywhere on Earth", abbreviation: "AOE") let utcTimezone = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT")!, tags: ["utc", "gmt", "universal"], formattedName: "UTC", abbreviation: "GMT") timezoneArray.append(anywhereOnEarth) timezoneArray.append(utcTimezone) for identifier in TimeZone.knownTimeZoneIdentifiers { if let timezoneObject = TimeZone(identifier: identifier) { // Force-cast explicity since we get the identifier from `knownTimeZoneIdentifiers` let abbreviation = timezoneObject.abbreviation()! let identifier = timezoneObject.identifier var tags: Set = [abbreviation.lowercased(), identifier.lowercased()] var extraTags: [String] = [] if let tagsPresent = timezoneMetadataDictionary[abbreviation] { extraTags = tagsPresent } extraTags.forEach { tag in tags.insert(tag) } let timezoneIdentifier = NSTimeZone(name: identifier)! let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier, tags: tags, formattedName: identifier, abbreviation: abbreviation) timezoneArray.append(timezoneMetadata) } } } @discardableResult func calculateChangesets() -> Bool { var changesets: [RowType] = [] func addTimezonesIfNeeded(_ data: [TimezoneMetadata]) { data.forEach { _ in changesets.append(.timezone) } } if searchField.stringValue.isEmpty, location == .preferences { addTimezonesIfNeeded(timezoneArray) } else { filteredArray.forEach { _ in changesets.append(.city) } addTimezonesIfNeeded(timezoneFilteredArray) } if changesets != finalArray { finalArray = changesets return true } return false } func searchTimezones(_ searchString: String) { timezoneFilteredArray = [] timezoneFilteredArray = timezoneArray.filter { timezoneMetadata -> Bool in let tags = timezoneMetadata.tags for tag in tags where tag.contains(searchString.lowercased()) { return true } return false } } func retrieveSelectedTimezone(_ selectedIndex: Int) -> TimezoneMetadata { return searchField.stringValue.isEmpty == false ? timezoneFilteredArray[selectedIndex % timezoneFilteredArray.count] : timezoneArray[selectedIndex] } } extension SearchDataSource: NSTableViewDataSource { func numberOfRows(in _: NSTableView) -> Int { return finalArray.count } func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { let currentRowType = finalArray[row] switch currentRowType { case .timezone: return timezoneCell(tableView, currentRowType, row) case .city: return cityCell(tableView, currentRowType, row) } } } extension SearchDataSource: NSTableViewDelegate { func tableView(_: NSTableView, heightOfRow _: Int) -> CGFloat { return 30 } } extension SearchDataSource { private func timezoneCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray guard !datasource.isEmpty else { return nil } message.sourceName.stringValue = datasource[row % datasource.count].formattedName + " (\(datasource[row % datasource.count].abbreviation))" return message } return nil } private func cityCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { if let cityCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { let timezoneData = filteredArray[row % filteredArray.count] cityCell.sourceName.stringValue = timezoneData.formattedAddress ?? "Error" return cityCell } return nil } } class SearchResultTableViewCell: NSTableCellView { @IBOutlet var sourceName: NSTextField! } class HeaderTableViewCell: NSTableCellView { @IBOutlet var headerField: NSTextField! } ================================================ FILE: Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import EventKit class MenubarTitleProvider: NSObject { private let store: DataStore private let eventCenter: EventCenter init(with dataStore: DataStore, eventStore: EventCenter) { store = dataStore eventCenter = eventStore super.init() } func titleForMenubar() -> String? { let filteredEvents = eventCenter.eventsForDate let autoupdatingCalendar = eventCenter.autoupdatingCalendar if let nextEvent = checkForUpcomingEvents(filteredEvents, calendar: autoupdatingCalendar) { return nextEvent } guard let menubarTitles = store.menubarTimezones() else { return nil } // If the menubar is in compact mode, we don't need any of the below calculations; exit early if store.shouldDisplay(.menubarCompactMode) { return nil } if menubarTitles.isEmpty == false { let titles = menubarTitles.map { data -> String? in let timezone = TimezoneData.customObject(from: data) let operationsObject = TimezoneDataOperations(with: timezone!, store: store) return "\(operationsObject.menuTitle().trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines))" } let titlesStringified = titles.compactMap { $0 } return titlesStringified.joined(separator: " ") } return nil } func checkForUpcomingEvents(_ filteredEvents: [Date: [EventInfo]], calendar: Calendar) -> String? { if store.shouldDisplay(.showMeetingInMenubar) { guard let events = filteredEvents[calendar.startOfDay(for: Date())] else { return nil } for eventInfo in events { let event = eventInfo.event let acceptableCriteria = event.startDate.timeIntervalSinceNow > -300 if acceptableCriteria, !eventInfo.isAllDay, eventInfo.event.status != EKEventStatus.canceled { let timeForEventToStart = event.startDate.timeIntervalSinceNow / 60 if timeForEventToStart > 30 { Logger.info("Our next event: \(event.title ?? "Error") starts in \(timeForEventToStart) mins") continue } return format(event: event) } } } return nil } internal func format(event: EKEvent) -> String { guard let truncateLength = store.retrieve(key: UserDefaultKeys.truncateTextLength) as? NSNumber, let eventTitle = event.title, event.title.isEmpty == false else { return UserDefaultKeys.emptyString } let seconds = event.startDate.timeIntervalSinceNow var menubarText: String = UserDefaultKeys.emptyString if eventTitle.count > truncateLength.intValue { let truncateIndex = eventTitle.index(eventTitle.startIndex, offsetBy: truncateLength.intValue) let truncatedTitle = String(eventTitle[..= 1 { let suffix = String(format: " in %0.fm", minutes) menubarText.append(suffix) } else { menubarText.append(" starts now.") } return menubarText } } ================================================ FILE: Clocker/Preferences/Menu Bar/StatusContainerView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit func compactWidth(for timezone: TimezoneData, with store: DataStore) -> Int { var totalWidth = 55 let timeFormat = timezone.timezoneFormat(store.timezoneFormat()) if store.shouldShowDayInMenubar() { totalWidth += 12 } if timeFormat == DateFormat.twelveHour || timeFormat == DateFormat.twelveHourWithSeconds || timeFormat == DateFormat.twelveHourWithZero || timeFormat == DateFormat.twelveHourWithSeconds { totalWidth += 20 } else if timeFormat == DateFormat.twentyFourHour || timeFormat == DateFormat.twentyFourHourWithSeconds { totalWidth += 0 } if timezone.shouldShowSeconds(store.timezoneFormat()) { // Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM totalWidth += 15 } if store.shouldShowDateInMenubar() { totalWidth += 20 } return totalWidth } // Test with Sat 12:46 AM let bufferWidth: CGFloat = 9.5 let upcomingEventBufferWidth: CGFloat = 32.5 protocol StatusItemViewConforming { /// Mark that we need to refresh the text we're showing in the menubar func statusItemViewSetNeedsDisplay() /// Status Item Views can be used to represent different information (like time in location, or an upcoming meeting). Distinguish between different status items view through this identifier func statusItemViewIdentifier() -> String } /// Observe for User Default changes for timezones in App Delegate and reconstruct the Status View if neccesary /// We'll inject the menubar timezones into Status Container View which'll pass it to StatusItemView /// The benefit of doing so is reducing time-spent calculating menubar timezones and deserialization through `TimezoneData.customObject` /// Also inject, `shouldDisplaySecondsInMenubar` /// class StatusContainerView: NSView { private var previousX: Int = 0 private let store: DataStore private lazy var paragraphStyle: NSMutableParagraphStyle = { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center paragraphStyle.lineBreakMode = .byTruncatingTail // Better readability for p,q,y,g in the status bar. let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" let lineHeight = userPreferredLanguage.contains("en") ? 0.92 : 1 paragraphStyle.lineHeightMultiple = CGFloat(lineHeight) return paragraphStyle }() override func awakeFromNib() { super.awakeFromNib() wantsLayer = true layer?.backgroundColor = NSColor.clear.cgColor } init(with timezones: [Data], store: DataStore, showUpcomingEventView: Bool, bufferContainerWidth: Int) { self.store = store func addSubviews() { if showUpcomingEventView, let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())], events.isEmpty == false, let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events) { let calculatedWidth = bestWidth(for: upcomingEvent) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let calendarItemView = UpcomingEventStatusItemView(frame: frame) calendarItemView.dataObject = upcomingEvent addSubview(calendarItemView) previousX += calculatedWidth } timezones.forEach { if let timezoneObject = TimezoneData.customObject(from: $0) { addTimezone(timezoneObject) } } } let timeBasedAttributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] func containerWidth(for timezones: [Data], event: EventInfo?) -> CGFloat { var compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in if let timezoneObject = TimezoneData.customObject(from: timezone) { let precalculatedWidth = Double(compactWidth(for: timezoneObject, with: store)) let operationObject = TimezoneDataOperations(with: timezoneObject, store: store) let calculatedSubtitleSize = compactModeTimeFont.size(for: operationObject.compactMenuSubtitle(), width: precalculatedWidth, 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 return result + max(calculatedTitleSize.width, calculatedSubtitleSize.width) + bufferWidth + secondsBuffer } return result + CGFloat(bufferContainerWidth) } if showUpcomingEventView { let calculateMeetingHeaderSize = compactModeTimeFont.size(for: upcomingEvent?.event.title ?? "", width: 70, attributes: timeBasedAttributes) let calculatedMeetingSubtitleSize = compactModeTimeFont.size(for: upcomingEvent?.metadataForMeeting() ?? "", width: 55, attributes: timeBasedAttributes) compressedWidth += CGFloat(min(calculateMeetingHeaderSize.width, calculatedMeetingSubtitleSize.width) + bufferWidth + upcomingEventBufferWidth) } let calculatedWidth = min(compressedWidth, CGFloat(timezones.count * bufferContainerWidth)) return calculatedWidth } let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())] let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events ?? []) let statusItemWidth = containerWidth(for: timezones, event: upcomingEvent) let frame = NSRect(x: 0, y: 0, width: statusItemWidth, height: 30) super.init(frame: frame) addSubviews() } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } func addTimezone(_ timezone: TimezoneData) { let calculatedWidth = bestWidth(for: timezone) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let statusItemView = StatusItemView(frame: frame) statusItemView.dataObject = timezone addSubview(statusItemView) previousX += calculatedWidth } private func bestWidth(for timezone: TimezoneData) -> Int { var textColor = hasDarkAppearance ? NSColor.white : NSColor.black if #available(OSX 11.0, *) { textColor = NSColor.white } let timeBasedAttributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: paragraphStyle, ] let operation = TimezoneDataOperations(with: timezone, store: store) let bestSize = compactModeTimeFont.size(for: operation.compactMenuSubtitle(), width: Double(compactWidth(for: timezone, with: store)), 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) } private func bestWidth(for eventInfo: EventInfo) -> Int { var textColor = hasDarkAppearance ? NSColor.white : NSColor.black if #available(OSX 11.0, *) { textColor = NSColor.white } let timeBasedAttributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] let bestSize = compactModeTimeFont.size(for: eventInfo.metadataForMeeting(), width: 55, // Default for a location based status view attributes: timeBasedAttributes) let bestTitleSize = compactModeTimeFont.size(for: eventInfo.event.title, width: 70, // Little more buffer since meeting titles tend to be longer attributes: timeBasedAttributes) return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) } func updateTime() { if subviews.isEmpty { assertionFailure("Subviews count should > 0") } for view in subviews { if let conformingView = view as? StatusItemViewConforming { conformingView.statusItemViewSetNeedsDisplay() } } // See if frame's width needs any adjustment adjustWidthIfNeccessary() } private func adjustWidthIfNeccessary() { var newWidth: CGFloat = 0 subviews.forEach { if let statusItem = $0 as? StatusItemView, statusItem.isHidden == false { // Determine what's the best width required to display the current string. let newBestWidth = CGFloat(bestWidth(for: statusItem.dataObject)) // Let's note if the current width is too small/correct newWidth += statusItem.frame.size.width != newBestWidth ? newBestWidth : statusItem.frame.size.width statusItem.frame = CGRect(x: statusItem.frame.origin.x, y: statusItem.frame.origin.y, width: newBestWidth, height: statusItem.frame.size.height) } else if let upcomingEventView = $0 as? UpcomingEventStatusItemView, upcomingEventView.isHidden == false { let newBestWidth = CGFloat(bestWidth(for: upcomingEventView.dataObject)) // Let's note if the current width is too small/correct newWidth += $0.frame.size.width != newBestWidth ? newBestWidth : upcomingEventView.frame.size.width upcomingEventView.frame = CGRect(x: upcomingEventView.frame.origin.x, y: upcomingEventView.frame.origin.y, width: newBestWidth, height: upcomingEventView.frame.size.height) } } if newWidth != frame.size.width, newWidth > frame.size.width + 2.0 { Logger.info("Correcting our width to \(newWidth) and the previous width was \(frame.size.width)") // NSView move animation NSAnimationContext.runAnimationGroup({ context in context.duration = 0.2 context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) let newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: newWidth, height: frame.size.height) // The view will animate to the new origin self.animator().frame = newFrame }) {} } } } ================================================ FILE: Clocker/Preferences/Menu Bar/StatusItemHandler.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit private enum MenubarState { case compactText case standardText case icon } class StatusItemHandler: NSObject { var hasActiveIcon: Bool = false var menubarTimer: Timer? var statusItem: NSStatusItem = { let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem.button?.toolTip = "Clocker" (statusItem.button?.cell as? NSButtonCell)?.highlightsBy = NSCell.StyleMask(rawValue: 0) return statusItem }() private lazy var menubarTitleHandler = MenubarTitleProvider(with: self.store, eventStore: EventCenter.sharedCenter()) private var statusContainerView: StatusContainerView? private var nsCalendar = Calendar.autoupdatingCurrent private lazy var units: Set = Set([.era, .year, .month, .day, .hour, .minute]) private var userNotificationsDidChangeNotif: NSObjectProtocol? private let store: DataStore // Current State might be set twice when the user first launches an app. // First, when StatusItemHandler() is instantiated in AppDelegate // Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet. // TODO: Make sure it's set just once. private var currentState: MenubarState = .standardText { didSet { // Do some cleanup switch oldValue { case .compactText: statusItem.button?.subviews = [] statusContainerView = nil case .standardText: statusItem.button?.title = UserDefaultKeys.emptyString case .icon: statusItem.button?.image = nil } // Now setup for the new menubar state switch currentState { case .compactText: setupForCompactTextMode() case .standardText: setupForStandardTextMode() case .icon: setClockerIcon() } Logger.info("Status Bar Current State changed: \(currentState)\n") } } init(with dataStore: DataStore) { store = dataStore super.init() setupStatusItem() setupNotificationObservers() } func setupStatusItem() { // Let's figure out the initial menubar state var menubarState = MenubarState.icon let shouldTextBeDisplayed = store.menubarTimezones()?.isEmpty ?? true if !shouldTextBeDisplayed || store.shouldDisplay(.showMeetingInMenubar) { if store.shouldDisplay(.menubarCompactMode) { menubarState = .compactText } else { menubarState = .standardText } } // Initial state has been figured out. Time to set it! currentState = menubarState func setSelector() { statusItem.button?.action = #selector(menubarIconClicked(_:)) } statusItem.button?.target = self statusItem.autosaveName = NSStatusItem.AutosaveName("ClockerStatusItem") setSelector() } private func setupNotificationObservers() { let center = NotificationCenter.default let mainQueue = OperationQueue.main center.addObserver(self, selector: #selector(updateMenubar), name: NSWorkspace.didWakeNotification, object: nil) DistributedNotificationCenter.default.addObserver(self, selector: #selector(respondToInterfaceStyleChange), name: .interfaceStyleDidChange, object: nil) userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification, object: self, queue: mainQueue) { _ in self.setupStatusItem() } NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: OperationQueue.main) { _ in self.menubarTimer?.invalidate() } NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: OperationQueue.main) { _ in self.setupStatusItem() } } deinit { if let userNotifsDidChange = userNotificationsDidChangeNotif { NotificationCenter.default.removeObserver(userNotifsDidChange) } } private func constructCompactView(with upcomingEventView: Bool = false) { statusItem.button?.subviews = [] statusContainerView = nil let menubarTimezones = store.menubarTimezones() ?? [] if menubarTimezones.isEmpty { currentState = .icon return } statusContainerView = StatusContainerView(with: menubarTimezones, store: store, showUpcomingEventView: upcomingEventView, bufferContainerWidth: bufferCalculatedWidth()) statusContainerView?.wantsLayer = true statusItem.button?.addSubview(statusContainerView!) statusItem.button?.frame = statusContainerView!.bounds // For OS < 11, we need to fix the sizing (width) on the button's window // Otherwise, we won't be able to see the menu bar option at all. if let window = statusItem.button?.window { let currentFrame = window.frame let newFrame = NSRect(x: currentFrame.origin.x, y: currentFrame.origin.y, width: statusItem.button?.bounds.size.width ?? 0, height: currentFrame.size.height) window.setFrame(newFrame, display: true) } statusItem.button?.subviews.first?.window?.backgroundColor = NSColor.clear } // This is called when the Apple interface style pre-Mojave is changed. // In High Sierra and before, we could have a dark or light menubar and dock // Our icon is template, so it changes automatically; so is our standard status bar text // Only need to handle the compact mode! @objc func respondToInterfaceStyleChange() { if store.shouldDisplay(.menubarCompactMode) { updateCompactMenubar() } } @objc func setHasActiveIcon(_ value: Bool) { hasActiveIcon = value } @objc func menubarIconClicked(_ sender: NSStatusBarButton) { guard let mainDelegate = NSApplication.shared.delegate as? AppDelegate else { return } mainDelegate.togglePanel(sender) } @objc func updateMenubar() { guard let fireDate = calculateFireDate() else { return } let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() menubarTimer = Timer(fire: fireDate, interval: 0, repeats: false, block: { [weak self] _ in if let strongSelf = self { strongSelf.refresh() } }) // Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20 guard let runLoopTimer = menubarTimer else { Logger.info("Timer is unexpectedly nil") return } RunLoop.main.add(runLoopTimer, forMode: .common) } private func shouldDisplaySecondsInMenubar() -> Bool { let syncedTimezones = store.menubarTimezones() ?? [] let timezonesSupportingSeconds = syncedTimezones.filter { data in if let timezoneObj = TimezoneData.customObject(from: data) { return timezoneObj.shouldShowSeconds(store.timezoneFormat()) } return false } return timezonesSupportingSeconds.isEmpty == false } private func calculateFireDate() -> Date? { let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() let menubarFavourites = store.menubarTimezones() if !units.contains(.second), shouldDisplaySeconds { units.insert(.second) } var components = nsCalendar.dateComponents(units, from: Date()) // We want to update every second only when there's a timezone present! if shouldDisplaySeconds, let seconds = components.second, let favourites = menubarFavourites, !favourites.isEmpty { components.second = seconds + 1 } else if let minutes = components.minute { components.minute = minutes + 1 } else { Logger.info("Unable to create date components for the menubar timewr") return nil } guard let fireDate = nsCalendar.date(from: components) else { Logger.info("Unable to form Fire Date") return nil } return fireDate } func updateCompactMenubar() { let filteredEvents = EventCenter.sharedCenter().filteredEvents let calendar = EventCenter.sharedCenter().autoupdatingCalendar let upcomingEvent = menubarTitleHandler.checkForUpcomingEvents(filteredEvents, calendar: calendar) if upcomingEvent != nil { // Iterate and see if we're showing the calendar item view let upcomingEventView = retrieveUpcomingEventStatusView() // If not, reconstruct Status Container View with another view if upcomingEventView == nil { constructCompactView(with: true) } } if let upcomingEventView = retrieveUpcomingEventStatusView(), upcomingEvent == nil { upcomingEventView.removeFromSuperview() constructCompactView() // So that Status Container View reclaims the space } // This will internally call `statusItemViewSetNeedsDisplay` on all subviews ensuring all text in the menubar is up-to-date. statusContainerView?.updateTime() } private func removeUpcomingStatusItemView() { NSAnimationContext.runAnimationGroup({ context in context.duration = 0.2 let upcomingEventView = retrieveUpcomingEventStatusView() upcomingEventView?.removeFromSuperview() }) { [weak self] in if let sSelf = self { sSelf.constructCompactView() } } } func refresh() { if currentState == .compactText { updateCompactMenubar() updateMenubar() } else if currentState == .standardText, let title = menubarTitleHandler.titleForMenubar() { // Need setting button's image to nil // Especially if we have showUpcomingEvents turned to true and menubar timezones are empty statusItem.button?.image = nil let attributes = [NSAttributedString.Key.font: NSFont.monospacedDigitSystemFont(ofSize: 13.0, weight: NSFont.Weight.regular), NSAttributedString.Key.baselineOffset: 0.1] as [NSAttributedString.Key: Any] statusItem.button?.attributedTitle = NSAttributedString(string: title, attributes: attributes) updateMenubar() } else { setClockerIcon() menubarTimer?.invalidate() } } private func setupForStandardTextMode() { Logger.info("Initializing menubar timer") // Let's invalidate the previous timer menubarTimer?.invalidate() menubarTimer = nil setupForStandardText() updateMenubar() } func invalidateTimer(showIcon show: Bool, isSyncing sync: Bool) { // Check if user is not showing // 1. Timezones // 2. Upcoming Event let menubarFavourites = store.menubarTimezones() ?? [] if menubarFavourites.isEmpty, store.shouldDisplay(.showMeetingInMenubar) == false { Logger.info("Invalidating menubar timer!") invalidation() if show { currentState = .icon } } else if sync { Logger.info("Invalidating menubar timer for sync purposes!") invalidation() if show { setClockerIcon() } } else { Logger.info("Not stopping menubar timer!") } } private func invalidation() { menubarTimer?.invalidate() } private func setClockerIcon() { if statusItem.button?.subviews.isEmpty == false { statusItem.button?.subviews = [] } if statusItem.button?.image?.name() == NSImage.Name.menubarIcon { return } statusItem.button?.title = UserDefaultKeys.emptyString statusItem.button?.image = NSImage(named: .menubarIcon) statusItem.button?.imagePosition = .imageOnly statusItem.button?.toolTip = "Clocker" } private func setupForStandardText() { var menubarText = UserDefaultKeys.emptyString if let menubarTitle = menubarTitleHandler.titleForMenubar() { menubarText = menubarTitle } else if store.shouldDisplay(.showMeetingInMenubar) { // Don't have any meeting to show } else { // We have no favourites to display and no meetings to show. // That means we should display our icon! } guard !menubarText.isEmpty else { setClockerIcon() return } let attributes = [NSAttributedString.Key.font: NSFont.monospacedDigitSystemFont(ofSize: 13.0, weight: NSFont.Weight.regular), NSAttributedString.Key.baselineOffset: 0.1] as [NSAttributedString.Key: Any] statusItem.button?.attributedTitle = NSAttributedString(string: menubarText, attributes: attributes) statusItem.button?.image = nil statusItem.button?.imagePosition = .imageLeft } private func setupForCompactTextMode() { // Let's invalidate the previous timer menubarTimer?.invalidate() menubarTimer = nil let filteredEvents = EventCenter.sharedCenter().filteredEvents let calendar = EventCenter.sharedCenter().autoupdatingCalendar let checkForUpcomingEvents = menubarTitleHandler.checkForUpcomingEvents(filteredEvents, calendar: calendar) constructCompactView(with: checkForUpcomingEvents != nil) updateMenubar() } private func retrieveUpcomingEventStatusView() -> NSView? { let upcomingEventView = statusContainerView?.subviews.first(where: { statusItemView in if let upcomingEventView = statusItemView as? StatusItemViewConforming { return upcomingEventView.statusItemViewIdentifier() == "upcoming_event_view" } return false }) 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 } } ================================================ FILE: Clocker/Preferences/Menu Bar/StatusItemView.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreModelKit var defaultTimeParagraphStyle: NSMutableParagraphStyle { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center paragraphStyle.lineBreakMode = .byTruncatingTail return paragraphStyle } var defaultParagraphStyle: NSMutableParagraphStyle { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center paragraphStyle.lineBreakMode = .byTruncatingTail // Better readability for p,q,y,g in the status bar. let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" let lineHeight = userPreferredLanguage.contains("en") ? 0.92 : 1 paragraphStyle.lineHeightMultiple = CGFloat(lineHeight) return paragraphStyle } var compactModeTimeFont: NSFont { return NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .regular) } extension NSView { var hasDarkAppearance: Bool { switch effectiveAppearance.name { case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark: return true default: return false } } } class StatusItemView: NSView { // MARK: Private variables private let locationView = NSTextField(labelWithString: "Hello") private let timeView = NSTextField(labelWithString: "Mon 19:14 PM") private var operationsObject: TimezoneDataOperations { return TimezoneDataOperations(with: dataObject, store: DataStore.shared()) } private lazy var paragraphStyle: NSMutableParagraphStyle = { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center paragraphStyle.lineBreakMode = .byTruncatingTail // Better readability for p,q,y,g in the status bar. let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" let lineHeight = userPreferredLanguage.contains("en") ? 0.92 : 1 paragraphStyle.lineHeightMultiple = CGFloat(lineHeight) return paragraphStyle }() private var timeAttributes: [NSAttributedString.Key: AnyObject] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let attributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: paragraphStyle, ] return attributes } private var textFontAttributes: [NSAttributedString.Key: Any] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let textFontAttributes = [ NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: paragraphStyle, ] return textFontAttributes } // MARK: Public var dataObject: TimezoneData! { didSet { initialSetup() } } override init(frame frameRect: NSRect) { super.init(frame: frameRect) [timeView, locationView].forEach { $0.wantsLayer = true $0.applyDefaultStyle() $0.translatesAutoresizingMaskIntoConstraints = false addSubview($0) } timeView.disableWrapping() var topAnchorConstant: CGFloat = 7.0 if #available(macOS 11.0, *) { topAnchorConstant = 0.0 } NSLayoutConstraint.activate([ locationView.leadingAnchor.constraint(equalTo: leadingAnchor), locationView.trailingAnchor.constraint(equalTo: trailingAnchor), locationView.topAnchor.constraint(equalTo: topAnchor, constant: topAnchorConstant), locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35), ]) NSLayoutConstraint.activate([ timeView.leadingAnchor.constraint(equalTo: leadingAnchor), timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), timeView.topAnchor.constraint(equalTo: locationView.bottomAnchor), timeView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) } @available(OSX 10.14, *) override func viewDidChangeEffectiveAppearance() { super.viewDidChangeEffectiveAppearance() statusItemViewSetNeedsDisplay() } private func initialSetup() { locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes) } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension StatusItemView: StatusItemViewConforming { func statusItemViewSetNeedsDisplay() { locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes) } func statusItemViewIdentifier() -> String { return "location_view" } } ================================================ FILE: Clocker/Preferences/Menu Bar/UpcomingEventStatusItemView.swift ================================================ // Copyright © 2015 Abhishek Banthia import AppKit import Foundation class UpcomingEventStatusItemView: NSView { static let containerWidth = 70 private let nextEventField = NSTextField(labelWithString: "Next Event") private let etaField = NSTextField(labelWithString: "5 mins") var dataObject: EventInfo! { didSet { initialSetup() } } private var timeAttributes: [NSAttributedString.Key: AnyObject] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let attributes = [ NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultTimeParagraphStyle, ] return attributes } private var textFontAttributes: [NSAttributedString.Key: Any] { let textColor = hasDarkAppearance ? NSColor.white : NSColor.black let textFontAttributes = [ NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, ] return textFontAttributes } override init(frame frameRect: NSRect) { super.init(frame: frameRect) [etaField, nextEventField].forEach { $0.wantsLayer = true $0.applyDefaultStyle() $0.translatesAutoresizingMaskIntoConstraints = false addSubview($0) } etaField.disableWrapping() var topAnchorConstant: CGFloat = 7 if #available(macOS 11.0, *) { topAnchorConstant = 0 } NSLayoutConstraint.activate([ nextEventField.leadingAnchor.constraint(equalTo: leadingAnchor), nextEventField.trailingAnchor.constraint(equalTo: trailingAnchor), nextEventField.topAnchor.constraint(equalTo: topAnchor, constant: topAnchorConstant), nextEventField.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35), ]) NSLayoutConstraint.activate([ etaField.leadingAnchor.constraint(equalTo: leadingAnchor), etaField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), etaField.topAnchor.constraint(equalTo: nextEventField.bottomAnchor), etaField.bottomAnchor.constraint(equalTo: bottomAnchor), ]) } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } private func initialSetup() { nextEventField.attributedStringValue = NSAttributedString(string: dataObject.event.title, attributes: textFontAttributes) etaField.attributedStringValue = NSAttributedString(string: dataObject.metadataForMeeting(), attributes: timeAttributes) } @available(OSX 10.14, *) override func viewDidChangeEffectiveAppearance() { super.viewDidChangeEffectiveAppearance() statusItemViewSetNeedsDisplay() } func updateWithNextEventInfo(_ metadata: String) { nextEventField.attributedStringValue = NSAttributedString(string: "Next Event", attributes: textFontAttributes) etaField.attributedStringValue = NSAttributedString(string: metadata, attributes: timeAttributes) } } extension UpcomingEventStatusItemView: StatusItemViewConforming { func statusItemViewSetNeedsDisplay() { nextEventField.attributedStringValue = NSAttributedString(string: dataObject.event.title, attributes: textFontAttributes) etaField.attributedStringValue = NSAttributedString(string: dataObject.metadataForMeeting(), attributes: timeAttributes) } func statusItemViewIdentifier() -> String { return "upcoming_event_view" } } ================================================ FILE: Clocker/Preferences/OneWindowController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class CenteredTabViewController: NSTabViewController { override func viewDidLoad() { super.viewDidLoad() // Setup localized tab labels tabViewItems.forEach { item in if let identifier = item.identifier as? String { item.label = NSLocalizedString(identifier, comment: "Tab View Item Label for \(identifier)") } } } } class OneWindowController: NSWindowController { private var themeDidChangeNotification: NSObjectProtocol? override func windowDidLoad() { super.windowDidLoad() setup() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in NSAnimationContext.runAnimationGroup { context in context.duration = 1 context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) self.window?.animator().backgroundColor = Themer.shared().mainBackgroundColor() } self.setupToolbarImages() } } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } private func setup() { setupWindow() setupToolbarImages() } private func setupWindow() { window?.titlebarAppearsTransparent = true window?.backgroundColor = Themer.shared().mainBackgroundColor() window?.identifier = NSUserInterfaceItemIdentifier("Preferences") } private func setupToolbarImages() { guard let tabViewController = contentViewController as? CenteredTabViewController else { return } let themer = Themer.shared() var identifierTOImageMapping: [String: NSImage] = ["Appearance Tab": themer.appearanceTabImage(), "Calendar Tab": themer.calendarTabImage(), "Permissions Tab": themer.privacyTabImage()] if let prefsTabImage = themer.generalTabImage() { identifierTOImageMapping["Preferences Tab"] = prefsTabImage } if let aboutTabImage = themer.aboutTabImage() { identifierTOImageMapping["About Tab"] = aboutTabImage } tabViewController.tabViewItems.forEach { tabViewItem in let identity = (tabViewItem.identifier as? String) ?? "" if identifierTOImageMapping[identity] != nil { tabViewItem.image = identifierTOImageMapping[identity] } } } // MARK: Public func openPermissionsPane() { openPreferenceTab(at: 3) NSApp.activate(ignoringOtherApps: true) } // Action mapped to the + button in the PanelController. We should always open the General Pane when the + button is clicked. func openGeneralPane() { openPreferenceTab(at: 0) NSApp.activate(ignoringOtherApps: true) } private func openPreferenceTab(at index: Int) { guard let window = window else { return } if !window.isMainWindow || !window.isVisible { showWindow(nil) } guard let tabViewController = contentViewController as? CenteredTabViewController else { return } tabViewController.selectedTabViewItemIndex = index } } ================================================ FILE: Clocker/Preferences/ParentViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa class ParentViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() if let view = view as? ParentView { view.wantsLayer = true } preferredContentSize = NSSize(width: view.frame.size.width, height: view.frame.size.height) } } class ParentView: NSView { override func updateLayer() { super.updateLayer() layer?.backgroundColor = Themer.shared().mainBackgroundColor().cgColor if let controller = window?.windowController?.contentViewController as? PermissionsViewController { [controller.calendarContainerView, controller.remindersContainerView].forEach { $0?.applyShadow() } } } } ================================================ FILE: Clocker/Preferences/Permissions/PermissionsViewController.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit class PermissionsViewController: ParentViewController { @IBOutlet var calendarContainerView: NSView! @IBOutlet var remindersContainerView: NSView! @IBOutlet private var calendarButton: NSButton! @IBOutlet private var remindersButton: NSButton! @IBOutlet private var calendarActivity: NSProgressIndicator! @IBOutlet private var remindersActivity: NSProgressIndicator! @IBOutlet private var reminderHeaderLabel: NSTextField! @IBOutlet private var reminderDetailLabel: NSTextField! @IBOutlet private var calendarHeaderLabel: NSTextField! @IBOutlet private var calendarDetailLabel: NSTextField! @IBOutlet private var privacyLabel: NSTextField! @IBOutlet private var headerLabel: NSTextField! private var themeDidChangeNotification: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() [calendarContainerView, remindersContainerView].forEach { $0?.applyShadow() } setupLocalizedText() themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in self.setupLocalizedText() [self.calendarContainerView, self.remindersContainerView].forEach { $0?.applyShadow() } } } override func viewWillAppear() { super.viewDidLoad() setup() } deinit { if let themeDidChangeNotif = themeDidChangeNotification { NotificationCenter.default.removeObserver(themeDidChangeNotif) } } private func setup() { setupButtons() } private func setupLocalizedText() { headerLabel.stringValue = NSLocalizedString("Permissions", comment: "Permissions Tab Titles") reminderHeaderLabel.stringValue = NSLocalizedString("Reminders Access", comment: "Reminders Permission Title") reminderDetailLabel.stringValue = NSLocalizedString("Reminders Detail", comment: "Reminders Detail Text") calendarHeaderLabel.stringValue = NSLocalizedString("Calendar Access", comment: "Calendar Permission Title") calendarDetailLabel.stringValue = NSLocalizedString("Calendar Detail", comment: "Calendar Detail Text") privacyLabel.stringValue = NSLocalizedString("Privacy Text", comment: "Text explaining options can be changed in the future through System Preferences") [calendarHeaderLabel, calendarDetailLabel, privacyLabel, reminderDetailLabel, reminderHeaderLabel, headerLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } } private func setupButtons() { calendarButton.setAccessibility("CalendarGrantAccessButton") remindersButton.setAccessibility("RemindersGrantAccessButton") /* if LocationController.sharedInstance.locationAccessGranted() { locationButton.title = "Granted" } else if LocationController.sharedInstance.locationAccessDenied() { locationButton.title = "Denied" } else if LocationController.sharedInstance.locationAccessNotDetermined() { locationButton.title = "Grant" } else { locationButton.title = "Unexpected" } */ if EventCenter.sharedCenter().calendarAccessGranted() { calendarButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") } else if EventCenter.sharedCenter().calendarAccessDenied() { calendarButton.title = NSLocalizedString("Denied Button Text", comment: "Denied Button Text") } else if EventCenter.sharedCenter().calendarAccessNotDetermined() { calendarButton.title = NSLocalizedString("Grant Button Text", comment: "Grant Button Text") } else { calendarButton.title = "Unexpected".localized() } if EventCenter.sharedCenter().reminderAccessGranted() { remindersButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") } else if EventCenter.sharedCenter().reminderAccessDenied() { remindersButton.title = NSLocalizedString("Denied Button Text", comment: "Denied Button Text") } else if EventCenter.sharedCenter().reminderAccessNotDetermined() { remindersButton.title = NSLocalizedString("Grant Button Text", comment: "Grant Button Text") } else { remindersButton.title = "Unexpected".localized() } } @IBAction func locationAction(_: Any) { /* let locationCenter = LocationController.sharedInstance if locationCenter.locationAccessNotDetermined() { locationCenter.determineAndRequestLocationAuthorization() } else if locationCenter.locationAccessGranted() { OperationQueue.main.addOperation { self.locationButton.title = "Granted" } } else if locationCenter.locationAccessDenied() { OperationQueue.main.addOperation { self.locationButton.title = "Denied" } }*/ } @IBAction func calendarAction(_: Any) { let eventCenter = EventCenter.sharedCenter() if eventCenter.calendarAccessNotDetermined() { calendarActivity.startAnimation(nil) eventCenter.requestAccess(to: .event, completionHandler: { granted in OperationQueue.main.addOperation { self.calendarActivity.stopAnimation(nil) } if granted { OperationQueue.main.addOperation { self.view.window?.orderBack(nil) NSApp.activate(ignoringOtherApps: true) self.calendarButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") // Used to update CalendarViewController's view NotificationCenter.default.post(name: .calendarAccessGranted, object: nil) } } else { Logger.log(object: ["Calendar Access Not Granted": "YES"], for: "Calendar Access Not Granted") } }) } else if eventCenter.calendarAccessGranted() { calendarButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") } else { calendarButton.title = NSLocalizedString("Denied Button Text", comment: "Denied Button Text") } } @IBAction func remindersAction(_: NSButton) { let eventCenter = EventCenter.sharedCenter() if eventCenter.reminderAccessNotDetermined() { remindersActivity.startAnimation(nil) eventCenter.requestAccess(to: .reminder, completionHandler: { granted in OperationQueue.main.addOperation { self.remindersActivity.stopAnimation(nil) } if granted { OperationQueue.main.addOperation { self.view.window?.orderBack(nil) NSApp.activate(ignoringOtherApps: true) self.remindersButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") } } else { Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted") } }) } else if eventCenter.reminderAccessGranted() { remindersButton.title = NSLocalizedString("Granted Button Text", comment: "Granted Button Text") } else { remindersButton.title = NSLocalizedString("Denied Button Text", comment: "Denied Button Text") } } } extension NSView { func applyShadow() { wantsLayer = true layer?.masksToBounds = true layer?.cornerRadius = 12 layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor } } extension NSButton { func setBackgroundColor(color: NSColor) { wantsLayer = true layer?.backgroundColor = color.cgColor } } ================================================ FILE: Clocker/Preferences/Preferences.storyboard ================================================ When selected, your upcoming meeting title will appear in the menubar 30m before it starts. All Day Events won't be shown in the menubar! addToFavorites: ================================================ FILE: Clocker/Preferences/StartupManager.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import ServiceManagement struct StartupManager { func toggleLogin(_ shouldStartAtLogin: Bool) { if !SMLoginItemSetEnabled("com.abhishek.ClockerHelper" as CFString, shouldStartAtLogin) { Logger.log(object: ["Successful": "NO"], for: "Start Clocker Login") addClockerToLoginItemsManually() } else { Logger.log(object: ["Successful": "YES"], for: "Start Clocker Login") } } private func addClockerToLoginItemsManually() { NSApplication.shared.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = "Clocker is unable to set to start at login. 😅" alert.informativeText = "You can manually set it to start at startup by adding Clocker to your login items." alert.addButton(withTitle: "Add Manually") alert.addButton(withTitle: "Cancel") let response = alert.runModal() if response.rawValue == 1000 { OperationQueue.main.addOperation { let prefPane = "/System/Library/PreferencePanes/Accounts.prefPane" NSWorkspace.shared.openFile(prefPane) } } } } ================================================ FILE: Clocker/Releases/appcast.xml ================================================ Clocker Changelog https://github.com/n0shake/Clocker/files/5613815/Clocker.app.zip Most recent changes en Version 20.10.03 10.12 https://github.com/n0shake/Clocker/blob/master/Clocker/Releases/changelog.md Sun Dec 07 14:59:58 GMT 2020 +0000 ================================================ FILE: Clocker/Releases/changelog.md ================================================ # Version History ## 20.10.03 Nov 15, 2020 - Big Sur compatibility updates - German, Russian translations now look better! ## 20.10.02 Oct 27, 2020 - Clocker won't by default show up in the App Switcher mode unless specified - DST Transition alerts will now be available in the main panel - Editable state of the date picker next to Slider is now prominently highlighted - Fixed: Changing hour format of an individual timezone affecting multiple timezones - New translated strings! ## 20.10.01 Oct 26, 2020 - DST Transition alerts will now be available in the main panel - Editable state of the date picker next to Slider is now prominently highlighted - Fixed: Changing hour format of an individual timezone affecting multiple timezones - New translated strings! ## 20.07.01 Jul 15, 2020 - Preview mode in Preferences - New Icons! - Fixes an annoying light mode bug where the menu-item appears translucent ## 20.05.01 May 8, 2020 - Fixes to reduce memory footprint of the app - UI Fixes ## 1.6.21 Apr 26, 2020 - Trackpad Support for Future Slider - Fixes to reduce my API usage costs ## 1.6.2 Jan 19, 2020 - Bug fixes and improvements! ## 1.6.19 Nov 3, 2019 Font and window size changes to accommodate smaller MacBooks! ## 1.6.18 Oct 20, 2019 Some tweaks to reduce my Google Cloud API billing costs! ## 1.6.17 Sep 30, 2019 - Formatted address for the auto-updating home row reflects the timezone user is in. - New translated strings for German, Russian and Chinese simplified. More languages added. - Fix for crash which occurred on changing menubar display options ## 1.6.16 Sep 23, 2019 Bug fixes and localization improvements! ## 1.6.15 Sep 16, 2019 - Hindi language users can now use Clocker in their native language - You can now signup to help convert Clocker to your preferred language by emailing me at abhishekbanthia@me.com - Fixed a bug where the time was incorrectly displayed while adding a timezone ## 1.6.14 Sep 9, 2019 Tryin squash some bugs and making things a little faster. ## 1.6.13 Sep 3, 2019 Now, you can add dates in the compact menubar style. - Fix for timezones in compact mode getting cut off - Fix for invisible location text in the menubar while transitioning to Dark/Light mode ## 1.6.12 Jul 24, 2019 Bug fixes! ## 1.6.11 Jul 20, 2019 - Fixes the seconds always displaying bug! ## 1.6.10 Jul 18, 2019 - New icons. Shoutout to Anojan (@anojan__10) for working on this. - Unified City and Timezone Search - Bug fixes and improvements ## 1.6.09 Apr 9, 2019 - Added Anywhere on Earth timezone - Fixed data calculation bug - Under the hood changes ## 1.6.08Mar 31, 2019 Squashed some bugs: - Fix jumpiness for timezones overlapping in the menubar - Fixed width if the next event label was too long - Fix if system doesn't have Avenir font ## 1.6.07 Dec 12, 2018 - New Compact Mode to squeeze in more time zones in your menubar - New Onboarding Flow - Show icon in the dock - Switch between light, dark or your macOS default theme mode - Design overhaul to the Calendar view - Right-click opens extra options view - Dock menu options for Quick toggle or Opening preferences - and much more! ## 1.6.06 Sep 25, 2018 - Support for 10.14 Mojave! ## 1.6.05 Sep 11, 2018 - Bug fixes and improvements ## 1.6.04 Sep 9, 2018 - Fixes reminders integration - Fixes status bar update issues - Dark Mode support ## 1.6.03 Sep 3, 2018 - Fixes 12-24 hour issue - Fixes menubar icon bug ## 1.6.02 Aug 19, 2018 - Fixes issue with incorrect time calculations. ================================================ FILE: Clocker/StartupKit/.gitignore ================================================ .DS_Store /.build /Packages /*.xcodeproj xcuserdata/ ================================================ FILE: Clocker/StartupKit/Package.swift ================================================ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "StartupKit", platforms: [ .macOS(.v10_12), ], products: [ .library( name: "StartupKit", targets: ["StartupKit"] ), ], dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), ], targets: [ .target( name: "StartupKit", dependencies: [] ), ] ) ================================================ FILE: Clocker/StartupKit/README.md ================================================ # StartupKit Handles the interaction with the ServiceManagement framework. ================================================ FILE: Clocker/StartupKit/Sources/StartupKit/StartupManager.swift ================================================ // Copyright © 2015 Abhishek Banthia import Cocoa import ServiceManagement public struct StartupManager { public init() {} public func toggleLogin(_ shouldStartAtLogin: Bool) { if !SMLoginItemSetEnabled("com.abhishek.ClockerHelper" as CFString, shouldStartAtLogin) { addClockerToLoginItemsManually() } } private func addClockerToLoginItemsManually() { NSApplication.shared.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = "Clocker is unable to set to start at login. 😅" alert.informativeText = "You can manually set it to start at startup by adding Clocker to your login items." alert.addButton(withTitle: "Add Manually") alert.addButton(withTitle: "Cancel") let response = alert.runModal() if response.rawValue == 1000 { OperationQueue.main.addOperation { // Reference: https://gist.github.com/rmcdongit/f66ff91e0dad78d4d6346a75ded4b751?permalink_comment_id=4286386#gistcomment-4286386 let prefPane = "x-apple.systempreferences:com.apple.LoginItems-Settings.extension" if let prefPaneURL = URL(string: prefPane) { NSWorkspace.shared.open(prefPaneURL) } } } } } ================================================ FILE: Clocker/release.py ================================================ import sys import os # Increment the version + build number # Change scheme to Release # Build and Analyze # Notarize the version # Zip and upload it as a Github release # Update appcast.xml def handle_command_execution(command, return_value): if return_value != 0: print(command + " failed with return value of "+str(return_value)) def increment_build_number(): # Print the build number. Use the -terse to limit the output to just the buld number check_version_command = "agvtool what-version" handle_command_execution(check_version_command, os.system(check_version_command)) # Bump the build number bump_version_command = "agvtool next-version -increment-minor-version" handle_command_execution(bump_version_command, os.system(bump_version_command)) def increment_version_to(new_version): check_version_command = "agvtool new-marketing-version "+new_version handle_command_execution(check_version_command, os.system(check_version_command)) def clean_build_analyze_release_config(): build_command = "xcodebuild -scheme Clocker -project Clocker.xcodeproj/ -configuration Release clean build analyze -quiet" handle_command_execution(build_command, os.system(build_command)) def test_release_config(): build_command = "xcodebuild -scheme Clocker -project Clocker.xcodeproj/ test" handle_command_execution(build_command, os.system(build_command)) def main(): if len(sys.argv) < 2: print("Doh! Enter the new release version") return increment_version_to(sys.argv[1]) increment_build_number() clean_build_analyze_release_config() test_release_config() main() ================================================ FILE: Clocker/run ================================================ #!/bin/sh # Copyright 2019 Google # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # run # # This script is meant to be run as a Run Script in the "Build Phases" section # of your Xcode project. It sends debug symbols to symbolicate stacktraces, # sends build events to track versions, and onboards apps for Crashlytics. # # This script calls upload-symbols twice: # # 1) First it calls upload-symbols synchronously in "validation" mode. If the # script finds issues with the build environment, it will report errors to Xcode. # In validation mode it exits before doing any time consuming work. # # 2) Then it calls upload-symbols in the background to actually send the build # event and upload symbols. It does this in the background so that it doesn't # slow down your builds. If an error happens here, you won't see it in Xcode. # # You can find the output for the background execution in Console.app, by # searching for "upload-symbols". # # If you want verbose output, you can pass the --debug flag to this script # # Only run for Release/Distribution builds if [[ "$CONFIGURATION" != "Release" && "$CONFIGURATION" != "Distribution" ]]; then exit 0 fi # Figure out where we're being called from DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) # Build up the arguments list, passing through any flags added, and quoting # every argument in case there are spaces in any of the the paths. ARGUMENTS='' for i in "$@"; do ARGUMENTS="$ARGUMENTS \"$i\"" done VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate" UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase" # Quote the path to handle folders with special characters COMMAND_PATH="\"$DIR/upload-symbols\" " # Ensure params are as expected, run in sync mode to validate, # and cause a build error if validation fails eval $COMMAND_PATH$VALIDATE_ARGUMENTS return_code=$? if [[ $return_code != 0 ]]; then exit $return_code fi # Verification passed, convert and upload dSYMs in the background to prevent # build delays # # Note: Validation is performed again at this step before upload # # Note: Output can still be found in Console.app, by searching for # "upload-symbols" # eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 & ================================================ FILE: Readme.md ================================================

download localization app support

**Clocker** is an macOS menubar utility designed to help you keep track of your friends in different time zones. It's written using ~~Objective-C~~ Swift 5 and is completely ad-free. If you'd like to help translate Clocker in your language, [here's the invite link](https://crwd.in/clocker). If you'd like to donate, you can do so [here](https://www.paypal.me/AbhishekBanthia). You can find detailed reviews [here](https://www.podfeet.com/blog/2020/07/clocker/) and [slightly older here](https://lifehacker.com/clocker-crams-a-world-clock-into-your-menu-bar-1794709422). Need any help? Open an issue! ## Install ```shell brew install --cask clocker ``` # Contributing **Clocker** is open for pull requests. # License Copyright (c) 2022 **Abhishek Banthia**. Released under the MIT License. ================================================ FILE: swiftlint-install.sh ================================================ #!/bin/bash # Installs the SwiftLint package. # Tries to get the precompiled .pkg file from Github, but if that # fails just recompiles from source. set -e SWIFTLINT_PKG_PATH="/tmp/SwiftLint.pkg" SWIFTLINT_PKG_URL="https://github.com/realm/SwiftLint/releases/download/0.9.1/SwiftLint.pkg" wget --output-document=$SWIFTLINT_PKG_PATH $SWIFTLINT_PKG_URL if [ -f $SWIFTLINT_PKG_PATH ]; then echo "SwiftLint package exists! Installing it..." sudo installer -pkg $SWIFTLINT_PKG_PATH -target / else echo "SwiftLint package doesn't exist. Compiling from source..." && git clone https://github.com/realm/SwiftLint.git /tmp/SwiftLint && cd /tmp/SwiftLint && git submodule update --init --recursive && sudo make install fi