Browse Source

Add accidentally deleted files.

pull/92/head
Abhishek 4 years ago
parent
commit
f690f28e06
  1. 32
      Clocker/Clocker.xcodeproj/project.pbxproj
  2. 60
      Clocker/Preferences/Menu Bar/MenubarHandler.swift
  3. 181
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  4. 354
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift
  5. 132
      Clocker/Preferences/Menu Bar/StatusItemView.swift

32
Clocker/Clocker.xcodeproj/project.pbxproj

@ -7,15 +7,15 @@
objects = {
/* Begin PBXBuildFile section */
3508CC942599FFEC000E3530 /* MenubarHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC932599FFEC000E3530 /* MenubarHandler.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 */; };
35190E47255F53F5006E9C85 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F3C2259D892002FA5C6 /* Logger.swift */; };
357391872507277500D30819 /* HourMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* HourMarkerViewItem.swift */; };
357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357391862507277500D30819 /* HourMarkerViewItem.xib */; };
3595FAD0227F88BC0044A12A /* UserDefaults + KVOExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */; };
35C11E2124873A550031F18C /* VersionUpdateHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */; };
35C36EE422595EFD002FA5C6 /* StatusContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE022595EFD002FA5C6 /* StatusContainerView.swift */; };
35C36EE522595EFD002FA5C6 /* MenubarHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE122595EFD002FA5C6 /* MenubarHandler.swift */; };
35C36EE622595EFD002FA5C6 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE222595EFD002FA5C6 /* StatusItemView.swift */; };
35C36EE722595EFD002FA5C6 /* StatusItemHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE322595EFD002FA5C6 /* StatusItemHandler.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 */; };
@ -225,6 +225,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
3508CC932599FFEC000E3530 /* MenubarHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenubarHandler.swift; sourceTree = "<group>"; };
3508CC99259A0001000E3530 /* StatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
3508CC9E259A000E000E3530 /* StatusItemHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemHandler.swift; sourceTree = "<group>"; };
3508CCA9259A0027000E3530 /* StatusContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = "<group>"; };
352AF497232E07B400D96FA7 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
352AF499232E07B400D96FA7 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = "<group>"; };
3545C52A22612BCC00121E25 /* TimezoneDataEqualityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimezoneDataEqualityTests.swift; sourceTree = "<group>"; };
@ -234,10 +238,6 @@
357391862507277500D30819 /* HourMarkerViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HourMarkerViewItem.xib; sourceTree = "<group>"; };
3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults + KVOExtensions.swift"; sourceTree = "<group>"; };
35C11E2024873A550031F18C /* VersionUpdateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateHandler.swift; sourceTree = "<group>"; };
35C36EE022595EFD002FA5C6 /* StatusContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = "<group>"; };
35C36EE122595EFD002FA5C6 /* MenubarHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenubarHandler.swift; sourceTree = "<group>"; };
35C36EE222595EFD002FA5C6 /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
35C36EE322595EFD002FA5C6 /* StatusItemHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemHandler.swift; sourceTree = "<group>"; };
35C36EE822595F13002FA5C6 /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = "<group>"; };
35C36EE922595F13002FA5C6 /* OnboardingWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingWelcomeViewController.swift; sourceTree = "<group>"; };
35C36EEA22595F13002FA5C6 /* WelcomeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeView.xib; sourceTree = "<group>"; };
@ -483,10 +483,10 @@
35C36EDF22595D9C002FA5C6 /* Menu Bar */ = {
isa = PBXGroup;
children = (
35C36EE122595EFD002FA5C6 /* MenubarHandler.swift */,
35C36EE022595EFD002FA5C6 /* StatusContainerView.swift */,
35C36EE322595EFD002FA5C6 /* StatusItemHandler.swift */,
35C36EE222595EFD002FA5C6 /* StatusItemView.swift */,
3508CC932599FFEC000E3530 /* MenubarHandler.swift */,
3508CC99259A0001000E3530 /* StatusItemView.swift */,
3508CC9E259A000E000E3530 /* StatusItemHandler.swift */,
3508CCA9259A0027000E3530 /* StatusContainerView.swift */,
);
path = "Menu Bar";
sourceTree = "<group>";
@ -1214,6 +1214,7 @@
9AB6F15D2259D08300A44663 /* iVersion.m in Sources */,
35C36EF622595F14002FA5C6 /* OnboardingController.swift in Sources */,
9AB6F1622259D1B000A44663 /* PreferencesDataSource.swift in Sources */,
3508CC9A259A0001000E3530 /* StatusItemView.swift in Sources */,
35C36F672259DF4C002FA5C6 /* RateController.swift in Sources */,
35C36F472259D892002FA5C6 /* Reach.swift in Sources */,
35C36EF222595F14002FA5C6 /* OnboardingWelcomeViewController.swift in Sources */,
@ -1224,16 +1225,15 @@
35C36F412259D892002FA5C6 /* Themer.swift in Sources */,
35C36F452259D892002FA5C6 /* Strings.swift in Sources */,
35C36EF722595F14002FA5C6 /* FinalOnboardingViewController.swift in Sources */,
35C36EE622595EFD002FA5C6 /* StatusItemView.swift in Sources */,
35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */,
9A5951BD1C1D0A8D009C17AA /* CommonStrings.m in Sources */,
357391872507277500D30819 /* HourMarkerViewItem.swift in Sources */,
35C36EE422595EFD002FA5C6 /* StatusContainerView.swift in Sources */,
35C36F782259E1D0002FA5C6 /* Foundation + Additions.swift in Sources */,
35C36F16225961DA002FA5C6 /* Date+Inits.swift in Sources */,
35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */,
35C36F942259EB87002FA5C6 /* CLTimezoneData.m in Sources */,
35C36F5D2259DD96002FA5C6 /* TimezoneDataOperations.swift in Sources */,
3508CC942599FFEC000E3530 /* MenubarHandler.swift in Sources */,
35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */,
35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */,
35C36F622259DE67002FA5C6 /* NotesPopover.swift in Sources */,
@ -1246,7 +1246,6 @@
35C36F702259E185002FA5C6 /* BackgroundPanelView.swift in Sources */,
9AB6F1582259CFFC00A44663 /* AboutViewController.swift in Sources */,
35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */,
35C36EE722595EFD002FA5C6 /* StatusItemHandler.swift in Sources */,
35C36F1B225961DA002FA5C6 /* Date+Format.swift in Sources */,
35C36F372259D7C3002FA5C6 /* AddTableViewCell.swift in Sources */,
35C36F12225961DA002FA5C6 /* Date+Components.swift in Sources */,
@ -1255,7 +1254,6 @@
35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */,
35C36F5E2259DD96002FA5C6 /* TimezoneData.swift in Sources */,
35C36F19225961DA002FA5C6 /* Enums.swift in Sources */,
35C36EE522595EFD002FA5C6 /* MenubarHandler.swift in Sources */,
35C36F5A2259DD8A002FA5C6 /* Panelr.swift in Sources */,
35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */,
35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */,
@ -1267,6 +1265,7 @@
35C36F572259DD8A002FA5C6 /* TimezoneDataSource.swift in Sources */,
35C36F462259D892002FA5C6 /* DataStore.swift in Sources */,
9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */,
3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */,
C2CCCD8220619C4C00F2DFC2 /* LocationController.swift in Sources */,
35C36F4B2259D971002FA5C6 /* UnderlinedButton.swift in Sources */,
9AB6F1562259CF3900A44663 /* CalendarViewController.swift in Sources */,
@ -1278,6 +1277,7 @@
9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */,
9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */,
35C36F1C225961DA002FA5C6 /* TimePeriodChain.swift in Sources */,
3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */,
35C36F11225961DA002FA5C6 /* TimePeriodGroup.swift in Sources */,
35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */,
35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */,

60
Clocker/Preferences/Menu Bar/MenubarHandler.swift

@ -0,0 +1,60 @@
// 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
}
}

181
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -0,0 +1,181 @@
// 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()
}
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)
}
}
}

354
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -0,0 +1,354 @@
// 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<Calendar.Component> = 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.view = nil
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() {
parentView = nil
let menubarTimezones = retrieveSyncedMenubarTimezones()
if menubarTimezones.isEmpty {
currentState = .icon
return
}
parentView = StatusContainerView(with: menubarTimezones)
statusItem.view = parentView
statusItem.view?.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.view != nil {
statusItem.view = nil
}
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()
}
}

132
Clocker/Preferences/Menu Bar/StatusItemView.swift

@ -0,0 +1,132 @@
// 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 = NSTextField(labelWithString: "Hello")
private let timeView: NSTextField = 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)
}
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)
}
}
Loading…
Cancel
Save