Pārlūkot izejas kodu

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 4 dienas atpakaļ
vecāks
revīzija
c1c13789fe
1 mainītis faili ar 55 papildinājumiem un 18 dzēšanām
  1. 55 18
      zoom_app/ViewController.swift

+ 55 - 18
zoom_app/ViewController.swift

@@ -194,9 +194,10 @@ class ViewController: NSViewController {
194 194
     private weak var scheduleTopicField: NSTextField?
195 195
     private weak var scheduleDateField: NSTextField?
196 196
     private weak var scheduleTimeField: NSTextField?
197
-    private weak var scheduleTimeZoneCombo: NSComboBox?
197
+    private weak var scheduleTimeZonePopup: NSPopUpButton?
198 198
     private weak var scheduleDurationField: NSTextField?
199 199
     private weak var scheduleSubmitButton: NSButton?
200
+    private var scheduleTimeZoneDisplayToIdentifier: [String: String] = [:]
200 201
     private weak var joinURLField: NSTextField?
201 202
     private weak var joinMeetingIDField: NSTextField?
202 203
     private weak var joinPasscodeField: NSTextField?
@@ -670,19 +671,26 @@ class ViewController: NSViewController {
670 671
         let timeHint = makeLabel("12-hour time in the timezone below · example 2:30 PM", size: 11, color: mutedText, weight: .regular, centered: false)
671 672
 
672 673
         let tzLabel = makeLabel("Timezone", size: 12, color: secondaryText, weight: .medium, centered: false)
673
-        let tzCombo = NSComboBox()
674
+        let tzCombo = NSPopUpButton()
674 675
         tzCombo.translatesAutoresizingMaskIntoConstraints = false
675 676
         tzCombo.font = .systemFont(ofSize: 14, weight: .regular)
676
-        tzCombo.textColor = primaryText
677
-        tzCombo.backgroundColor = palette.inputBackground
678
-        tzCombo.isSelectable = true
679
-        tzCombo.completes = true
680
-        tzCombo.numberOfVisibleItems = 10
681
-        tzCombo.addItems(withObjectValues: TimeZone.knownTimeZoneIdentifiers.sorted())
682
-        tzCombo.stringValue = TimeZone.current.identifier
683
-        tzCombo.toolTip = "IANA timezone (type to filter). Meeting start is interpreted in this zone."
684
-        scheduleTimeZoneCombo = tzCombo
685
-        let tzHint = makeLabel("Type to search, e.g. America/New_York or Europe/London", size: 11, color: mutedText, weight: .regular, centered: false)
677
+        tzCombo.wantsLayer = true
678
+        tzCombo.layer?.cornerRadius = 10
679
+        tzCombo.layer?.borderWidth = 1
680
+        tzCombo.layer?.borderColor = palette.inputBorder.cgColor
681
+        tzCombo.layer?.backgroundColor = palette.inputBackground.cgColor
682
+        let tzOptions = makeTimeZoneDisplayOptions(referenceDate: defaultStart)
683
+        scheduleTimeZoneDisplayToIdentifier = Dictionary(uniqueKeysWithValues: tzOptions.map { ($0.display, $0.identifier) })
684
+        tzCombo.removeAllItems()
685
+        tzCombo.addItems(withTitles: tzOptions.map(\.display))
686
+        if let selected = tzOptions.first(where: { $0.identifier == TimeZone.current.identifier }) {
687
+            tzCombo.selectItem(withTitle: selected.display)
688
+        } else {
689
+            tzCombo.selectItem(at: 0)
690
+        }
691
+        tzCombo.toolTip = "Timezone with GMT offset. Meeting start is interpreted in this zone."
692
+        scheduleTimeZonePopup = tzCombo
693
+        let tzHint = makeLabel("Includes GMT offset, e.g. GMT+05:00 - Asia/Karachi", size: 11, color: mutedText, weight: .regular, centered: false)
686 694
 
687 695
         let tzStack = NSStackView(views: [tzLabel, tzCombo, tzHint])
688 696
         tzStack.orientation = .vertical
@@ -789,7 +797,7 @@ class ViewController: NSViewController {
789 797
 
790 798
             topicBox.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
791 799
             tzCombo.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
792
-            tzCombo.heightAnchor.constraint(equalToConstant: 46),
800
+            tzCombo.heightAnchor.constraint(equalToConstant: 34),
793 801
             dateTimeRow.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
794 802
             durationBox.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
795 803
             buttons.widthAnchor.constraint(equalTo: innerStack.widthAnchor),
@@ -811,11 +819,13 @@ class ViewController: NSViewController {
811 819
             showSimpleAlert(title: "Topic required", message: "Enter a name for your meeting.")
812 820
             return
813 821
         }
814
-        let tzId = scheduleTimeZoneCombo?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
815
-        if tzId.isEmpty == false, TimeZone(identifier: tzId) == nil {
822
+        let tzId = scheduleTimeZonePopup?.titleOfSelectedItem?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
823
+        if tzId.isEmpty == false,
824
+           scheduleTimeZoneDisplayToIdentifier[tzId] == nil,
825
+           TimeZone(identifier: tzId) == nil {
816 826
             showSimpleAlert(
817 827
                 title: "Timezone",
818
-                message: "Enter a valid IANA timezone (pick from the list or type to search), for example America/New_York."
828
+                message: "Choose a timezone from the list (with GMT offset), for example GMT+05:00 - Asia/Karachi."
819 829
             )
820 830
             return
821 831
         }
@@ -896,18 +906,45 @@ class ViewController: NSViewController {
896 906
         scheduleTopicField = nil
897 907
         scheduleDateField = nil
898 908
         scheduleTimeField = nil
899
-        scheduleTimeZoneCombo = nil
909
+        scheduleTimeZonePopup = nil
900 910
         scheduleDurationField = nil
901 911
         scheduleSubmitButton = nil
912
+        scheduleTimeZoneDisplayToIdentifier = [:]
902 913
     }
903 914
 
904 915
     /// Resolves the schedule panel timezone (IANA id from combo, or system default).
905 916
     private func resolvedScheduleTimeZoneForMeeting() -> TimeZone {
906
-        let raw = scheduleTimeZoneCombo?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
917
+        let raw = scheduleTimeZonePopup?.titleOfSelectedItem?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
918
+        if let identifier = scheduleTimeZoneDisplayToIdentifier[raw],
919
+           let mapped = TimeZone(identifier: identifier) {
920
+            return mapped
921
+        }
907 922
         guard raw.isEmpty == false, let tz = TimeZone(identifier: raw) else { return TimeZone.current }
908 923
         return tz
909 924
     }
910 925
 
926
+    private struct TimeZoneDisplayOption {
927
+        let identifier: String
928
+        let display: String
929
+    }
930
+
931
+    private func makeTimeZoneDisplayOptions(referenceDate: Date) -> [TimeZoneDisplayOption] {
932
+        TimeZone.knownTimeZoneIdentifiers.compactMap { identifier in
933
+            guard let tz = TimeZone(identifier: identifier) else { return nil }
934
+            let seconds = tz.secondsFromGMT(for: referenceDate)
935
+            let sign = seconds >= 0 ? "+" : "-"
936
+            let absSeconds = abs(seconds)
937
+            let hours = absSeconds / 3600
938
+            let minutes = (absSeconds % 3600) / 60
939
+            let offset = String(format: "GMT%@%02d:%02d", sign, hours, minutes)
940
+            return TimeZoneDisplayOption(identifier: identifier, display: "\(offset) - \(identifier)")
941
+        }
942
+        .sorted { lhs, rhs in
943
+            if lhs.display == rhs.display { return lhs.identifier < rhs.identifier }
944
+            return lhs.display < rhs.display
945
+        }
946
+    }
947
+
911 948
     private struct ScheduleMeetingDraft {
912 949
         let startDate: Date
913 950
         let startTimeWithOffset: String