Browse Source

Initial attempt at showing next meeting event in compact menubar mode.

pull/113/head
Abhishek 3 years ago
parent
commit
a44f0f7291
  1. 1
      Clocker/Preferences/Calendar/CalendarViewController.swift
  2. 3
      Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift
  3. 64
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  4. 40
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift

1
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -84,7 +84,6 @@ class CalendarViewController: ParentViewController {
verifyCalendarAccess() verifyCalendarAccess()
showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1 showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1
showNextMeetingInMenubarControl.isEnabled = DataStore.shared().shouldDisplay(.menubarCompactMode) ? false : true
} }
private func verifyCalendarAccess() { private func verifyCalendarAccess() {

3
Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift

@ -34,7 +34,7 @@ class MenubarTitleProvider: NSObject {
return nil return nil
} }
private func checkForUpcomingEvents() -> String? { func checkForUpcomingEvents() -> String? {
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
let filteredDates = EventCenter.sharedCenter().eventsForDate let filteredDates = EventCenter.sharedCenter().eventsForDate
let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar
@ -48,7 +48,6 @@ class MenubarTitleProvider: NSObject {
if timeForEventToStart > 30 { if timeForEventToStart > 30 {
Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins")
continue continue
} }

64
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -19,6 +19,10 @@ func bufferCalculatedWidth() -> Int {
totalWidth += 20 totalWidth += 20
} }
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
totalWidth += 100
}
return totalWidth return totalWidth
} }
@ -55,6 +59,14 @@ func compactWidth(for timezone: TimezoneData) -> Int {
// Test with Sat 12:46 AM // Test with Sat 12:46 AM
let bufferWidth: CGFloat = 9.5 let bufferWidth: CGFloat = 9.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
}
class StatusContainerView: NSView { class StatusContainerView: NSView {
private var previousX: Int = 0 private var previousX: Int = 0
@ -64,8 +76,20 @@ class StatusContainerView: NSView {
layer?.backgroundColor = NSColor.clear.cgColor layer?.backgroundColor = NSColor.clear.cgColor
} }
init(with timezones: [Data]) { init(with timezones: [Data], showUpcomingEventView: Bool) {
func addSubviews() { 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 { timezones.forEach {
if let timezoneObject = TimezoneData.customObject(from: $0) { if let timezoneObject = TimezoneData.customObject(from: $0) {
addTimezone(timezoneObject) addTimezone(timezoneObject)
@ -80,7 +104,7 @@ class StatusContainerView: NSView {
] ]
func containerWidth(for timezones: [Data]) -> CGFloat { func containerWidth(for timezones: [Data]) -> CGFloat {
let compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in var compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in
if let timezoneObject = TimezoneData.customObject(from: timezone) { if let timezoneObject = TimezoneData.customObject(from: timezone) {
let precalculatedWidth = Double(compactWidth(for: timezoneObject)) let precalculatedWidth = Double(compactWidth(for: timezoneObject))
@ -95,6 +119,10 @@ class StatusContainerView: NSView {
return result + CGFloat(bufferCalculatedWidth()) return result + CGFloat(bufferCalculatedWidth())
} }
if showUpcomingEventView {
compressedWidth += 70
}
let calculatedWidth = min(compressedWidth, let calculatedWidth = min(compressedWidth,
CGFloat(timezones.count * bufferCalculatedWidth())) CGFloat(timezones.count * bufferCalculatedWidth()))
return calculatedWidth return calculatedWidth
@ -146,11 +174,41 @@ class StatusContainerView: NSView {
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) 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(eventInfo.metadataForMeeting(),
55, // Default for a location based status view
attributes: timeBasedAttributes)
let bestTitleSize = compactModeTimeFont.size(eventInfo.event.title,
70, // Little more buffer since meeting titles tend to be longer
attributes: timeBasedAttributes)
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)
}
func updateTime() { func updateTime() {
if subviews.isEmpty { if subviews.isEmpty {
assertionFailure("Subviews count should > 0") 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 // See if frame's width needs any adjustment
adjustWidthIfNeccessary() adjustWidthIfNeccessary()
} }
@ -170,8 +228,6 @@ class StatusContainerView: NSView {
y: statusItem.frame.origin.y, y: statusItem.frame.origin.y,
width: newBestWidth, width: newBestWidth,
height: statusItem.frame.size.height) height: statusItem.frame.size.height)
statusItem.updateTimeInMenubar()
} }
} }

40
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -24,7 +24,7 @@ class StatusItemHandler: NSObject {
private var menubarTitleHandler = MenubarTitleProvider() private var menubarTitleHandler = MenubarTitleProvider()
private var parentView: StatusContainerView? private var statusContainerView: StatusContainerView?
private var nsCalendar = Calendar.autoupdatingCurrent private var nsCalendar = Calendar.autoupdatingCurrent
@ -42,7 +42,7 @@ class StatusItemHandler: NSObject {
switch oldValue { switch oldValue {
case .compactText: case .compactText:
statusItem.view = nil statusItem.view = nil
parentView = nil statusContainerView = nil
case .standardText: case .standardText:
statusItem.button?.title = CLEmptyString statusItem.button?.title = CLEmptyString
case .icon: case .icon:
@ -134,8 +134,8 @@ class StatusItemHandler: NSObject {
} }
} }
private func constructCompactView() { private func constructCompactView(with upcomingEventView: Bool = false) {
parentView = nil statusContainerView = nil
let menubarTimezones = DataStore.shared().menubarTimezones() ?? [] let menubarTimezones = DataStore.shared().menubarTimezones() ?? []
if menubarTimezones.isEmpty { if menubarTimezones.isEmpty {
@ -143,8 +143,9 @@ class StatusItemHandler: NSObject {
return return
} }
parentView = StatusContainerView(with: menubarTimezones) statusContainerView = StatusContainerView(with: menubarTimezones,
statusItem.view = parentView showUpcomingEventView: upcomingEventView)
statusItem.view = statusContainerView
statusItem.view?.window?.backgroundColor = NSColor.clear statusItem.view?.window?.backgroundColor = NSColor.clear
} }
@ -241,7 +242,20 @@ class StatusItemHandler: NSObject {
} }
func updateCompactMenubar() { func updateCompactMenubar() {
parentView?.updateTime() if let upcomingEvent = menubarTitleHandler.checkForUpcomingEvents() {
print("Need to construct upcoming event view \(upcomingEvent)")
// 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)
}
} else {
let upcomingEventView = retrieveUpcomingEventStatusView()
upcomingEventView?.removeFromSuperview()
}
// This will internally call `statusItemViewSetNeedsDisplay` on all subviews ensuring all text in the menubar is up-to-date.
statusContainerView?.updateTime()
} }
func refresh() { func refresh() {
@ -347,7 +361,17 @@ class StatusItemHandler: NSObject {
menubarTimer?.invalidate() menubarTimer?.invalidate()
menubarTimer = nil menubarTimer = nil
constructCompactView() constructCompactView(with: menubarTitleHandler.checkForUpcomingEvents() != nil)
updateMenubar() 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
}
} }

Loading…
Cancel
Save