// 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() floatingWindow.modernSlider.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 } }