瀏覽代碼

Fix timezone picker focus rendering and show GMT offset labels.

This replaces the editable timezone combo box with a stable dropdown and displays GMT offset plus IANA zone names to keep selection readable and avoid text baseline shifts when focused.

Made-with: Cursor
huzaifahayat12 3 月之前
父節點
當前提交
c1c13789fe
共有 1 個文件被更改,包括 55 次插入18 次删除
  1. 55 18
      zoom_app/ViewController.swift

+ 55 - 18
zoom_app/ViewController.swift

@@ -194,9 +194,10 @@ class ViewController: NSViewController {
     private weak var scheduleTopicField: NSTextField?
     private weak var scheduleDateField: NSTextField?
     private weak var scheduleTimeField: NSTextField?
-    private weak var scheduleTimeZoneCombo: NSComboBox?
+    private weak var scheduleTimeZonePopup: NSPopUpButton?
     private weak var scheduleDurationField: NSTextField?
     private weak var scheduleSubmitButton: NSButton?
+    private var scheduleTimeZoneDisplayToIdentifier: [String: String] = [:]
     private weak var joinURLField: NSTextField?
     private weak var joinMeetingIDField: NSTextField?
     private weak var joinPasscodeField: NSTextField?
@@ -670,19 +671,26 @@ class ViewController: NSViewController {
         let timeHint = makeLabel("12-hour time in the timezone below · example 2:30 PM", size: 11, color: mutedText, weight: .regular, centered: false)
 
         let tzLabel = makeLabel("Timezone", size: 12, color: secondaryText, weight: .medium, centered: false)
-        let tzCombo = NSComboBox()
+        let tzCombo = NSPopUpButton()
         tzCombo.translatesAutoresizingMaskIntoConstraints = false
         tzCombo.font = .systemFont(ofSize: 14, weight: .regular)
-        tzCombo.textColor = primaryText
-        tzCombo.backgroundColor = palette.inputBackground
-        tzCombo.isSelectable = true
-        tzCombo.completes = true
-        tzCombo.numberOfVisibleItems = 10
-        tzCombo.addItems(withObjectValues: TimeZone.knownTimeZoneIdentifiers.sorted())
-        tzCombo.stringValue = TimeZone.current.identifier
-        tzCombo.toolTip = "IANA timezone (type to filter). Meeting start is interpreted in this zone."
-        scheduleTimeZoneCombo = tzCombo
-        let tzHint = makeLabel("Type to search, e.g. America/New_York or Europe/London", size: 11, color: mutedText, weight: .regular, centered: false)
+        tzCombo.wantsLayer = true
+        tzCombo.layer?.cornerRadius = 10
+        tzCombo.layer?.borderWidth = 1
+        tzCombo.layer?.borderColor = palette.inputBorder.cgColor
+        tzCombo.layer?.backgroundColor = palette.inputBackground.cgColor
+        let tzOptions = makeTimeZoneDisplayOptions(referenceDate: defaultStart)
+        scheduleTimeZoneDisplayToIdentifier = Dictionary(uniqueKeysWithValues: tzOptions.map { ($0.display, $0.identifier) })
+        tzCombo.removeAllItems()
+        tzCombo.addItems(withTitles: tzOptions.map(\.display))
+        if let selected = tzOptions.first(where: { $0.identifier == TimeZone.current.identifier }) {
+            tzCombo.selectItem(withTitle: selected.display)
+        } else {
+            tzCombo.selectItem(at: 0)
+        }
+        tzCombo.toolTip = "Timezone with GMT offset. Meeting start is interpreted in this zone."
+        scheduleTimeZonePopup = tzCombo
+        let tzHint = makeLabel("Includes GMT offset, e.g. GMT+05:00 - Asia/Karachi", size: 11, color: mutedText, weight: .regular, centered: false)
 
         let tzStack = NSStackView(views: [tzLabel, tzCombo, tzHint])
         tzStack.orientation = .vertical
@@ -789,7 +797,7 @@ class ViewController: NSViewController {
 
             topicBox.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
             tzCombo.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
-            tzCombo.heightAnchor.constraint(equalToConstant: 46),
+            tzCombo.heightAnchor.constraint(equalToConstant: 34),
             dateTimeRow.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
             durationBox.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
             buttons.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
@@ -811,11 +819,13 @@ class ViewController: NSViewController {
             showSimpleAlert(title: "Topic required", message: "Enter a name for your meeting.")
             return
         }
-        let tzId = scheduleTimeZoneCombo?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-        if tzId.isEmpty == false, TimeZone(identifier: tzId) == nil {
+        let tzId = scheduleTimeZonePopup?.titleOfSelectedItem?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+        if tzId.isEmpty == false,
+           scheduleTimeZoneDisplayToIdentifier[tzId] == nil,
+           TimeZone(identifier: tzId) == nil {
             showSimpleAlert(
                 title: "Timezone",
-                message: "Enter a valid IANA timezone (pick from the list or type to search), for example America/New_York."
+                message: "Choose a timezone from the list (with GMT offset), for example GMT+05:00 - Asia/Karachi."
             )
             return
         }
@@ -896,18 +906,45 @@ class ViewController: NSViewController {
         scheduleTopicField = nil
         scheduleDateField = nil
         scheduleTimeField = nil
-        scheduleTimeZoneCombo = nil
+        scheduleTimeZonePopup = nil
         scheduleDurationField = nil
         scheduleSubmitButton = nil
+        scheduleTimeZoneDisplayToIdentifier = [:]
     }
 
     /// Resolves the schedule panel timezone (IANA id from combo, or system default).
     private func resolvedScheduleTimeZoneForMeeting() -> TimeZone {
-        let raw = scheduleTimeZoneCombo?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+        let raw = scheduleTimeZonePopup?.titleOfSelectedItem?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+        if let identifier = scheduleTimeZoneDisplayToIdentifier[raw],
+           let mapped = TimeZone(identifier: identifier) {
+            return mapped
+        }
         guard raw.isEmpty == false, let tz = TimeZone(identifier: raw) else { return TimeZone.current }
         return tz
     }
 
+    private struct TimeZoneDisplayOption {
+        let identifier: String
+        let display: String
+    }
+
+    private func makeTimeZoneDisplayOptions(referenceDate: Date) -> [TimeZoneDisplayOption] {
+        TimeZone.knownTimeZoneIdentifiers.compactMap { identifier in
+            guard let tz = TimeZone(identifier: identifier) else { return nil }
+            let seconds = tz.secondsFromGMT(for: referenceDate)
+            let sign = seconds >= 0 ? "+" : "-"
+            let absSeconds = abs(seconds)
+            let hours = absSeconds / 3600
+            let minutes = (absSeconds % 3600) / 60
+            let offset = String(format: "GMT%@%02d:%02d", sign, hours, minutes)
+            return TimeZoneDisplayOption(identifier: identifier, display: "\(offset) - \(identifier)")
+        }
+        .sorted { lhs, rhs in
+            if lhs.display == rhs.display { return lhs.identifier < rhs.identifier }
+            return lhs.display < rhs.display
+        }
+    }
+
     private struct ScheduleMeetingDraft {
         let startDate: Date
         let startTimeWithOffset: String