Abhishek Banthia
3 years ago
60 changed files with 963 additions and 610 deletions
@ -0,0 +1,38 @@
|
||||
// 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 |
||||
} |
||||
} |
||||
} |
||||
|
||||
extension ParentPanelController: UpcomingEventPanelDelegate { |
||||
func didRemoveCalendarView() { |
||||
removeUpcomingEventView() |
||||
} |
||||
|
||||
func didClickSupplementaryButton(_ sender: NSButton) { |
||||
calendarButtonAction(sender) |
||||
} |
||||
} |
@ -0,0 +1,131 @@
|
||||
// Copyright © 2015 Abhishek Banthia |
||||
|
||||
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.target = self |
||||
zoomButton.action = #selector(zoomButtonAction(_:)) |
||||
} |
||||
|
||||
override func prepareForReuse() { |
||||
zoomButton.image = nil |
||||
eventTitleLabel.stringValue = "" |
||||
eventSubtitleButton.stringValue = "" |
||||
} |
||||
|
||||
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) |
||||
setCalendarButtonTitle(buttonTitle: subtitle) |
||||
calendarColorView.layer?.backgroundColor = color.cgColor |
||||
} |
||||
|
||||
private func setupLabels(_ title: String, _ cancellationState: Bool) { |
||||
let attributes: [NSAttributedString.Key: Any] = cancellationState ? [NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue, |
||||
NSAttributedString.Key.strikethroughColor: NSColor.gray] : [:] |
||||
let attributedString = NSAttributedString(string: title, attributes: attributes) |
||||
eventTitleLabel.attributedStringValue = attributedString |
||||
|
||||
eventTitleLabel.toolTip = title |
||||
} |
||||
|
||||
private func setupSupplementaryButton(_ meetingURL: URL?) { |
||||
guard meetingURL != nil 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()) |
||||
} |
||||
|
||||
func setupEmptyState() { |
||||
setAlternateState(NSLocalizedString("No upcoming events for today!", comment: "Next Event Label with no upcoming event"), |
||||
NSLocalizedString("Great going.", comment: "Button Title for no upcoming event"), |
||||
NSColor.systemGreen, |
||||
nil) |
||||
} |
||||
|
||||
private func setAlternateState(_ title: String, _ buttonTitle: String, _ color: NSColor, _ image: NSImage? = nil) { |
||||
if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint { |
||||
leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint |
||||
} |
||||
|
||||
eventTitleLabel.stringValue = title |
||||
setCalendarButtonTitle(buttonTitle: buttonTitle) |
||||
calendarColorView.layer?.backgroundColor = color.cgColor |
||||
zoomButton.image = image |
||||
} |
||||
|
||||
private func setCalendarButtonTitle(buttonTitle: String) { |
||||
let style = NSMutableParagraphStyle() |
||||
style.alignment = .left |
||||
style.lineBreakMode = .byTruncatingTail |
||||
|
||||
if let boldFont = NSFont(name: "Avenir", size: 11) { |
||||
let attributes = [NSAttributedString.Key.foregroundColor: NSColor.gray, NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.font: boldFont] |
||||
let attributedString = NSAttributedString(string: buttonTitle, attributes: attributes) |
||||
eventSubtitleButton.attributedTitle = attributedString |
||||
eventSubtitleButton.toolTip = attributedString.string |
||||
} |
||||
} |
||||
|
||||
// MARK: Button Actions |
||||
|
||||
@IBAction func calendarButtonAction(_ sender: NSButton) { |
||||
panelDelegate?.didClickSupplementaryButton(sender) |
||||
} |
||||
|
||||
@objc func zoomButtonAction(_: Any) { |
||||
if let meetingURL = meetingLink { |
||||
NSWorkspace.shared.open(meetingURL) |
||||
} else { |
||||
panelDelegate?.didRemoveCalendarView() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> |
||||
<dependencies> |
||||
<deployment identifier="macosx"/> |
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/> |
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |
||||
</dependencies> |
||||
<objects> |
||||
<customObject id="-2" userLabel="File's Owner"/> |
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/> |
||||
<customView id="Hz6-mo-xeY"> |
||||
<rect key="frame" x="0.0" y="0.0" width="250" height="75"/> |
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> |
||||
<subviews> |
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="aM6-TI-1os" customClass="ClockerTextBackgroundView" customModule="Clocker" customModuleProvider="target"> |
||||
<rect key="frame" x="0.0" y="0.0" width="250" height="75"/> |
||||
<subviews> |
||||
<button focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Khp-8n-4FA"> |
||||
<rect key="frame" x="16" y="21" width="198" height="18"/> |
||||
<constraints> |
||||
<constraint firstAttribute="height" constant="18" id="asu-Dd-XhW"/> |
||||
</constraints> |
||||
<buttonCell key="cell" type="bevel" title="All Day Event" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingHead" truncatesLastVisibleLine="YES" state="on" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="JOt-0Q-EAr"> |
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |
||||
<font key="font" size="10" name="Avenir-Heavy"/> |
||||
</buttonCell> |
||||
<connections> |
||||
<action selector="calendarButtonAction:" target="p0M-E4-898" id="Gua-lA-sNh"/> |
||||
</connections> |
||||
</button> |
||||
<customView wantsLayer="YES" focusRingType="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bbu-oG-vAb"> |
||||
<rect key="frame" x="5" y="21" width="2" height="34"/> |
||||
<constraints> |
||||
<constraint firstAttribute="width" constant="2" id="Qhx-45-1Hc"/> |
||||
<constraint firstAttribute="height" constant="34" id="xeh-m2-Fhh"/> |
||||
</constraints> |
||||
</customView> |
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fTt-gC-72b"> |
||||
<rect key="frame" x="15" y="33" width="201" height="24"/> |
||||
<constraints> |
||||
<constraint firstAttribute="height" constant="24" id="BA4-1v-xiZ"/> |
||||
</constraints> |
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="left" title="Nandita Jaiswal's 35th birthday" placeholderString="" id="Dtv-o3-gqd"> |
||||
<font key="font" size="12" name="Avenir-Book"/> |
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
||||
<color key="backgroundColor" red="1" green="0.5" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> |
||||
</textFieldCell> |
||||
</textField> |
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="qRe-Ao-1fr"> |
||||
<rect key="frame" x="216" y="26" width="24" height="24"/> |
||||
<constraints> |
||||
<constraint firstAttribute="width" constant="24" id="ZRs-Vt-FOM"/> |
||||
<constraint firstAttribute="height" constant="24" id="ouc-t4-DO2"/> |
||||
</constraints> |
||||
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="Appearance" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="8Sv-kp-Zff"> |
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |
||||
<font key="font" metaFont="system"/> |
||||
</buttonCell> |
||||
</button> |
||||
</subviews> |
||||
<constraints> |
||||
<constraint firstItem="qRe-Ao-1fr" firstAttribute="leading" secondItem="fTt-gC-72b" secondAttribute="trailing" constant="2" id="161-lb-rqc"/> |
||||
<constraint firstItem="fTt-gC-72b" firstAttribute="top" secondItem="Bbu-oG-vAb" secondAttribute="top" constant="-2" id="FeN-R0-t1A"/> |
||||
<constraint firstItem="Khp-8n-4FA" firstAttribute="bottom" secondItem="Bbu-oG-vAb" secondAttribute="bottom" id="LQD-54-bUh"/> |
||||
<constraint firstItem="Bbu-oG-vAb" firstAttribute="leading" secondItem="aM6-TI-1os" secondAttribute="leading" constant="5" id="Myg-8W-znN"/> |
||||
<constraint firstAttribute="trailing" secondItem="qRe-Ao-1fr" secondAttribute="trailing" constant="10" id="Myk-Ff-8WP"/> |
||||
<constraint firstItem="Khp-8n-4FA" firstAttribute="trailing" secondItem="fTt-gC-72b" secondAttribute="trailing" id="URW-gt-WbF"/> |
||||
<constraint firstItem="Khp-8n-4FA" firstAttribute="leading" secondItem="fTt-gC-72b" secondAttribute="leading" constant="-1" id="VyL-Kt-2aT"/> |
||||
<constraint firstItem="fTt-gC-72b" firstAttribute="leading" secondItem="Bbu-oG-vAb" secondAttribute="trailing" constant="10" id="arv-Oe-yih"/> |
||||
<constraint firstItem="qRe-Ao-1fr" firstAttribute="centerY" secondItem="aM6-TI-1os" secondAttribute="centerY" id="vdC-gb-1wa"/> |
||||
<constraint firstItem="Bbu-oG-vAb" firstAttribute="centerY" secondItem="aM6-TI-1os" secondAttribute="centerY" id="wkl-Vo-0kU"/> |
||||
</constraints> |
||||
</customView> |
||||
</subviews> |
||||
<constraints> |
||||
<constraint firstItem="aM6-TI-1os" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="2AJ-xz-nf9"/> |
||||
<constraint firstItem="aM6-TI-1os" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="D6T-PQ-RdE"/> |
||||
<constraint firstAttribute="bottom" secondItem="aM6-TI-1os" secondAttribute="bottom" id="DmC-7T-lrT"/> |
||||
<constraint firstAttribute="trailing" secondItem="aM6-TI-1os" secondAttribute="trailing" id="aCZ-sV-Mnd"/> |
||||
</constraints> |
||||
<point key="canvasLocation" x="147" y="238.5"/> |
||||
</customView> |
||||
<customObject id="p0M-E4-898" customClass="UpcomingEventViewItem" customModule="Clocker" customModuleProvider="target"> |
||||
<connections> |
||||
<outlet property="calendarColorView" destination="Bbu-oG-vAb" id="tBe-V1-qmC"/> |
||||
<outlet property="eventSubtitleButton" destination="Khp-8n-4FA" id="mXV-EZ-2AW"/> |
||||
<outlet property="eventTitleLabel" destination="fTt-gC-72b" id="bOh-LO-tiH"/> |
||||
<outlet property="leadingConstraint" destination="Myg-8W-znN" id="OUR-Na-THS"/> |
||||
<outlet property="supplementaryButtonWidthConstraint" destination="ZRs-Vt-FOM" id="S8K-B3-ZLb"/> |
||||
<outlet property="view" destination="Hz6-mo-xeY" id="9S0-wg-csq"/> |
||||
<outlet property="zoomButton" destination="qRe-Ao-1fr" id="0IC-Fx-l3Q"/> |
||||
</connections> |
||||
</customObject> |
||||
</objects> |
||||
<resources> |
||||
<image name="Appearance" width="350" height="350"/> |
||||
</resources> |
||||
</document> |
@ -0,0 +1,64 @@
|
||||
// Copyright © 2015 Abhishek Banthia |
||||
|
||||
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 = 350.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 { |
||||
let item = collectionView.makeItem(withIdentifier: UpcomingEventViewItem.reuseIdentifier, for: indexPath) as! UpcomingEventViewItem |
||||
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 attributedString = NSAttributedString(string: currentEventInfo.event.title, attributes: [NSAttributedString.Key.font: avenirBookFont]) |
||||
let maxWidth = min(attributedString.size().width + bufferWidth, UpcomingEventsDataSource.panelWidth / 2) |
||||
return NSSize(width: maxWidth, height: collectionView.frame.height - 20) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue