You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

248 lines
9.8 KiB

// 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.noteLabel.isHidden = false
} else if let value = operation.nextDaylightSavingsTransitionIfAvailable(with: sliderValue) {
cellView.noteLabel.stringValue = value
cellView.noteLabel.isHidden = false
} else {
cellView.noteLabel.stringValue = UserDefaultKeys.emptyString
cellView.noteLabel.isHidden = true
}
cellView.layout(with: currentModel)
cellView.setAccessibilityIdentifier(currentModel.formattedTimezoneLabel())
cellView.setAccessibilityLabel(currentModel.formattedTimezoneLabel())
return cellView
}
func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat {
guard !timezones.isEmpty else {
return 100
}
if let userFontSize = dataStore.retrieve(key: UserDefaultKeys.userFontSizePreference) as? NSNumber,
timezones.count > row,
let relativeDisplay = dataStore.retrieve(key: UserDefaultKeys.relativeDateKey) as? NSNumber
{
let model = timezones[row]
let shouldShowSunrise = dataStore.shouldDisplay(.sunrise)
var rowHeight: Int = userFontSize == 4 ? 60 : 65
if relativeDisplay.intValue == 3 {
rowHeight -= 5
}
if shouldShowSunrise, model.selectionType == .city {
rowHeight += 8
}
if let note = model.note, !note.isEmpty {
rowHeight += userFontSize.intValue + 15
} else if TimezoneDataOperations(with: model, store: dataStore).nextDaylightSavingsTransitionIfAvailable(with: sliderValue) != nil {
rowHeight += userFontSize.intValue + 15
}
if model.isSystemTimezone {
rowHeight += 2
}
rowHeight += (userFontSize.intValue * 2)
return CGFloat(rowHeight)
}
return 1
}
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()
}
}