Sfoglia il codice sorgente

Polish Schedule page layout, filters, and card grid behavior.

Schedule content now pins with geometric anchors and LTR stacks, uses overlay scrollers and asymmetric insets, left-aligns the title row, and sizes card rows without empty columns so one or two meetings fill the width. Filter spacing, control widths, and card dimensions were increased for a fuller main panel.

Made-with: Cursor
huzaifahayat12 1 settimana fa
parent
commit
767faa4f31
1 ha cambiato i file con 114 aggiunte e 50 eliminazioni
  1. 114 50
      meetings_app/ViewController.swift

+ 114 - 50
meetings_app/ViewController.swift

@@ -330,8 +330,12 @@ final class ViewController: NSViewController {
330 330
     private var schedulePageVisibleCount: Int = 0
331 331
     private let schedulePageBatchSize: Int = 20
332 332
     private let schedulePageCardsPerRow: Int = 3
333
-    private let schedulePageCardWidth: CGFloat = 240
334
-    private let schedulePageCardSpacing: CGFloat = 12
333
+    private let schedulePageCardSpacing: CGFloat = 20
334
+    private let schedulePageCardHeight: CGFloat = 182
335
+    private let schedulePageStackSpacing: CGFloat = 16
336
+    private let schedulePageLeadingInset: CGFloat = 28
337
+    /// Tighter than leading so cards/filters use full width; overlay scrollbar avoids a dead right gutter.
338
+    private let schedulePageTrailingInset: CGFloat = 12
335 339
     private var schedulePageScrollObservation: NSObjectProtocol?
336 340
     private weak var schedulePageDateHeadingLabel: NSTextField?
337 341
     private weak var schedulePageFilterDropdown: NSPopUpButton?
@@ -1736,20 +1740,25 @@ private extension ViewController {
1736 1740
     func makeSchedulePageContent() -> NSView {
1737 1741
         let panel = NSView()
1738 1742
         panel.translatesAutoresizingMaskIntoConstraints = false
1743
+        panel.userInterfaceLayoutDirection = .leftToRight
1739 1744
 
1740 1745
         let contentStack = NSStackView()
1741 1746
         contentStack.translatesAutoresizingMaskIntoConstraints = false
1747
+        contentStack.userInterfaceLayoutDirection = .leftToRight
1742 1748
         contentStack.orientation = .vertical
1743
-        contentStack.spacing = 12
1744
-        contentStack.alignment = .leading
1749
+        contentStack.spacing = schedulePageStackSpacing
1750
+        contentStack.alignment = .width
1745 1751
 
1746
-        contentStack.addArrangedSubview(schedulePageHeader())
1752
+        let header = schedulePageHeader()
1753
+        contentStack.addArrangedSubview(header)
1747 1754
 
1748 1755
         let heading = textLabel(schedulePageInitialHeadingText(), font: typography.dateHeading, color: palette.textSecondary)
1756
+        heading.alignment = .left
1749 1757
         schedulePageDateHeadingLabel = heading
1750 1758
         contentStack.addArrangedSubview(heading)
1751 1759
 
1752 1760
         let rangeError = textLabel("", font: NSFont.systemFont(ofSize: 12, weight: .semibold), color: .systemRed)
1761
+        rangeError.alignment = .left
1753 1762
         rangeError.isHidden = true
1754 1763
         schedulePageRangeErrorLabel = rangeError
1755 1764
         contentStack.addArrangedSubview(rangeError)
@@ -1760,11 +1769,14 @@ private extension ViewController {
1760 1769
         panel.addSubview(contentStack)
1761 1770
 
1762 1771
         NSLayoutConstraint.activate([
1763
-            contentStack.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 28),
1764
-            contentStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
1772
+            contentStack.leftAnchor.constraint(equalTo: panel.leftAnchor, constant: schedulePageLeadingInset),
1773
+            contentStack.rightAnchor.constraint(equalTo: panel.rightAnchor, constant: -schedulePageTrailingInset),
1765 1774
             contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 6),
1766
-            cardsContainer.leadingAnchor.constraint(equalTo: contentStack.leadingAnchor),
1767
-            cardsContainer.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -16)
1775
+            contentStack.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -16),
1776
+            header.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
1777
+            heading.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
1778
+            rangeError.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
1779
+            cardsContainer.widthAnchor.constraint(equalTo: contentStack.widthAnchor)
1768 1780
         ])
1769 1781
 
1770 1782
         Task { [weak self] in
@@ -2658,37 +2670,81 @@ private extension ViewController {
2658 2670
     private func schedulePageHeader() -> NSView {
2659 2671
         let container = NSStackView()
2660 2672
         container.translatesAutoresizingMaskIntoConstraints = false
2673
+        container.userInterfaceLayoutDirection = .leftToRight
2661 2674
         container.orientation = .vertical
2662
-        container.spacing = 10
2663
-        container.alignment = .leading
2675
+        container.spacing = 14
2676
+        container.alignment = .width
2677
+
2678
+        let titleRow = NSStackView()
2679
+        titleRow.translatesAutoresizingMaskIntoConstraints = false
2680
+        titleRow.userInterfaceLayoutDirection = .leftToRight
2681
+        titleRow.orientation = .horizontal
2682
+        titleRow.alignment = .centerY
2683
+        titleRow.distribution = .fill
2684
+        titleRow.spacing = 0
2664 2685
 
2665 2686
         let titleLabel = textLabel("Schedule", font: typography.pageTitle, color: palette.textPrimary)
2666
-        container.addArrangedSubview(titleLabel)
2687
+        titleLabel.alignment = .left
2688
+        titleLabel.userInterfaceLayoutDirection = .leftToRight
2689
+        titleLabel.maximumNumberOfLines = 1
2690
+        titleLabel.lineBreakMode = .byTruncatingTail
2691
+        titleLabel.setContentHuggingPriority(.required, for: .horizontal)
2692
+        titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
2693
+
2694
+        let titleRowSpacer = NSView()
2695
+        titleRowSpacer.translatesAutoresizingMaskIntoConstraints = false
2696
+        titleRowSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
2697
+        titleRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
2698
+
2699
+        titleRow.addArrangedSubview(titleLabel)
2700
+        titleRow.addArrangedSubview(titleRowSpacer)
2701
+        container.addArrangedSubview(titleRow)
2667 2702
 
2668 2703
         let filterRow = NSStackView()
2669 2704
         filterRow.translatesAutoresizingMaskIntoConstraints = false
2705
+        filterRow.userInterfaceLayoutDirection = .leftToRight
2670 2706
         filterRow.orientation = .horizontal
2671 2707
         filterRow.alignment = .centerY
2672
-        filterRow.spacing = 10
2708
+        filterRow.spacing = 18
2709
+        filterRow.distribution = .fill
2673 2710
 
2674 2711
         let filterDropdown = makeSchedulePageFilterDropdown()
2675 2712
         schedulePageFilterDropdown = filterDropdown
2676 2713
         filterRow.addArrangedSubview(filterDropdown)
2714
+        filterRow.setCustomSpacing(20, after: filterDropdown)
2677 2715
 
2678 2716
         let fromPicker = makeScheduleDatePicker(date: schedulePageFromDate)
2679 2717
         schedulePageFromDatePicker = fromPicker
2680 2718
         filterRow.addArrangedSubview(fromPicker)
2719
+        filterRow.setCustomSpacing(16, after: fromPicker)
2681 2720
 
2682 2721
         let toPicker = makeScheduleDatePicker(date: schedulePageToDate)
2683 2722
         schedulePageToDatePicker = toPicker
2684 2723
         filterRow.addArrangedSubview(toPicker)
2724
+        NSLayoutConstraint.activate([
2725
+            fromPicker.widthAnchor.constraint(equalTo: toPicker.widthAnchor),
2726
+            fromPicker.widthAnchor.constraint(greaterThanOrEqualToConstant: 152)
2727
+        ])
2685 2728
 
2686
-        filterRow.addArrangedSubview(makeSchedulePagePillButton(title: "Apply", action: #selector(schedulePageApplyDateRangePressed(_:))))
2687
-        filterRow.addArrangedSubview(makeSchedulePagePillButton(title: "Reset", action: #selector(schedulePageResetFiltersPressed(_:))))
2729
+        let filterRowSpacer = NSView()
2730
+        filterRowSpacer.translatesAutoresizingMaskIntoConstraints = false
2731
+        filterRowSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
2732
+        filterRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
2733
+        filterRow.addArrangedSubview(filterRowSpacer)
2734
+
2735
+        let applyButton = makeSchedulePagePillButton(title: "Apply", action: #selector(schedulePageApplyDateRangePressed(_:)))
2736
+        filterRow.addArrangedSubview(applyButton)
2737
+        filterRow.setCustomSpacing(22, after: applyButton)
2738
+        let resetButton = makeSchedulePagePillButton(title: "Reset", action: #selector(schedulePageResetFiltersPressed(_:)))
2739
+        filterRow.addArrangedSubview(resetButton)
2740
+        filterRow.setCustomSpacing(22, after: resetButton)
2688 2741
         filterRow.addArrangedSubview(makeScheduleRefreshButton())
2689 2742
 
2690 2743
         container.addArrangedSubview(filterRow)
2691
-        container.widthAnchor.constraint(greaterThanOrEqualToConstant: 780).isActive = true
2744
+        NSLayoutConstraint.activate([
2745
+            titleRow.widthAnchor.constraint(equalTo: container.widthAnchor),
2746
+            filterRow.widthAnchor.constraint(equalTo: container.widthAnchor)
2747
+        ])
2692 2748
         refreshSchedulePageDateFilterUI()
2693 2749
         return container
2694 2750
     }
@@ -2710,7 +2766,7 @@ private extension ViewController {
2710 2766
         button.target = self
2711 2767
         button.action = #selector(schedulePageFilterDropdownChanged(_:))
2712 2768
         button.heightAnchor.constraint(equalToConstant: 34).isActive = true
2713
-        button.widthAnchor.constraint(equalToConstant: 190).isActive = true
2769
+        button.widthAnchor.constraint(equalToConstant: 228).isActive = true
2714 2770
 
2715 2771
         button.removeAllItems()
2716 2772
         button.addItems(withTitles: ["All", "Today", "This week", "This month", "Custom range"])
@@ -2742,7 +2798,7 @@ private extension ViewController {
2742 2798
         picker.dateValue = date
2743 2799
         picker.font = typography.filterText
2744 2800
         picker.heightAnchor.constraint(equalToConstant: 34).isActive = true
2745
-        picker.widthAnchor.constraint(equalToConstant: 156).isActive = true
2801
+        picker.setContentHuggingPriority(.defaultLow, for: .horizontal)
2746 2802
         picker.target = self
2747 2803
         picker.action = #selector(schedulePageDatePickerChanged(_:))
2748 2804
         return picker
@@ -2752,7 +2808,7 @@ private extension ViewController {
2752 2808
         let button = makeSchedulePillButton(title: title)
2753 2809
         button.target = self
2754 2810
         button.action = action
2755
-        button.widthAnchor.constraint(equalToConstant: 92).isActive = true
2811
+        button.widthAnchor.constraint(equalToConstant: 100).isActive = true
2756 2812
         return button
2757 2813
     }
2758 2814
 
@@ -2764,24 +2820,27 @@ private extension ViewController {
2764 2820
 
2765 2821
         let wrapper = NSView()
2766 2822
         wrapper.translatesAutoresizingMaskIntoConstraints = false
2823
+        wrapper.userInterfaceLayoutDirection = .leftToRight
2767 2824
 
2768 2825
         let scroll = NSScrollView()
2769 2826
         scroll.translatesAutoresizingMaskIntoConstraints = false
2827
+        scroll.userInterfaceLayoutDirection = .leftToRight
2770 2828
         scroll.drawsBackground = false
2771 2829
         scroll.hasHorizontalScroller = false
2772 2830
         scroll.hasVerticalScroller = true
2773 2831
         scroll.autohidesScrollers = true
2774 2832
         scroll.borderType = .noBorder
2775
-        let viewportWidth = (schedulePageCardWidth * CGFloat(schedulePageCardsPerRow)) + (schedulePageCardSpacing * CGFloat(schedulePageCardsPerRow - 1))
2776
-        scroll.widthAnchor.constraint(equalToConstant: viewportWidth).isActive = true
2833
+        scroll.scrollerStyle = .overlay
2834
+        scroll.automaticallyAdjustsContentInsets = false
2777 2835
         schedulePageCardsScrollView = scroll
2778 2836
         wrapper.addSubview(scroll)
2779 2837
 
2780 2838
         let stack = NSStackView()
2781 2839
         stack.translatesAutoresizingMaskIntoConstraints = false
2840
+        stack.userInterfaceLayoutDirection = .leftToRight
2782 2841
         stack.orientation = .vertical
2783
-        stack.spacing = 12
2784
-        stack.alignment = .leading
2842
+        stack.spacing = schedulePageCardSpacing
2843
+        stack.alignment = .width
2785 2844
         schedulePageCardsStack = stack
2786 2845
         scroll.documentView = stack
2787 2846
 
@@ -3035,16 +3094,21 @@ private extension ViewController {
3035 3094
         return wrapper
3036 3095
     }
3037 3096
 
3038
-    func scheduleCard(meeting: ScheduledMeeting) -> NSView {
3097
+    func scheduleCard(meeting: ScheduledMeeting, useFlexibleWidth: Bool = false, contentHeight: CGFloat = 150) -> NSView {
3039 3098
         let cardWidth: CGFloat = 240
3040 3099
 
3041 3100
         let card = roundedContainer(cornerRadius: 12, color: palette.sectionCard)
3042 3101
         styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: true)
3043 3102
         card.translatesAutoresizingMaskIntoConstraints = false
3044
-        card.widthAnchor.constraint(equalToConstant: cardWidth).isActive = true
3045
-        card.heightAnchor.constraint(equalToConstant: 150).isActive = true
3046
-        card.setContentHuggingPriority(.required, for: .horizontal)
3047
-        card.setContentCompressionResistancePriority(.required, for: .horizontal)
3103
+        card.heightAnchor.constraint(equalToConstant: contentHeight).isActive = true
3104
+        if useFlexibleWidth {
3105
+            card.setContentHuggingPriority(.defaultLow, for: .horizontal)
3106
+            card.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
3107
+        } else {
3108
+            card.widthAnchor.constraint(equalToConstant: cardWidth).isActive = true
3109
+            card.setContentHuggingPriority(.required, for: .horizontal)
3110
+            card.setContentCompressionResistancePriority(.required, for: .horizontal)
3111
+        }
3048 3112
 
3049 3113
         let icon = roundedContainer(cornerRadius: 8, color: palette.meetingBadge)
3050 3114
         icon.translatesAutoresizingMaskIntoConstraints = false
@@ -3089,7 +3153,7 @@ private extension ViewController {
3089 3153
         card.addSubview(time)
3090 3154
         card.addSubview(duration)
3091 3155
 
3092
-        NSLayoutConstraint.activate([
3156
+        var titleConstraints: [NSLayoutConstraint] = [
3093 3157
             icon.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 10),
3094 3158
             icon.topAnchor.constraint(equalTo: card.topAnchor, constant: 10),
3095 3159
 
@@ -3098,9 +3162,12 @@ private extension ViewController {
3098 3162
 
3099 3163
             title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 6),
3100 3164
             title.centerYAnchor.constraint(equalTo: icon.centerYAnchor),
3101
-            title.trailingAnchor.constraint(lessThanOrEqualTo: dayChip.leadingAnchor, constant: -8),
3102
-            title.widthAnchor.constraint(lessThanOrEqualToConstant: 130),
3103
-
3165
+            title.trailingAnchor.constraint(lessThanOrEqualTo: dayChip.leadingAnchor, constant: -8)
3166
+        ]
3167
+        if !useFlexibleWidth {
3168
+            titleConstraints.append(title.widthAnchor.constraint(lessThanOrEqualToConstant: 130))
3169
+        }
3170
+        NSLayoutConstraint.activate(titleConstraints + [
3104 3171
             subtitle.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 10),
3105 3172
             subtitle.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 10),
3106 3173
             subtitle.trailingAnchor.constraint(lessThanOrEqualTo: card.trailingAnchor, constant: -10),
@@ -3119,10 +3186,15 @@ private extension ViewController {
3119 3186
         hit.isBordered = false
3120 3187
         hit.bezelStyle = .regularSquare
3121 3188
         hit.identifier = NSUserInterfaceItemIdentifier(meeting.meetURL.absoluteString)
3122
-        hit.widthAnchor.constraint(equalToConstant: cardWidth).isActive = true
3123
-        hit.heightAnchor.constraint(equalToConstant: 150).isActive = true
3124
-        hit.setContentHuggingPriority(.required, for: .horizontal)
3125
-        hit.setContentCompressionResistancePriority(.required, for: .horizontal)
3189
+        hit.heightAnchor.constraint(equalToConstant: contentHeight).isActive = true
3190
+        if useFlexibleWidth {
3191
+            hit.setContentHuggingPriority(.defaultLow, for: .horizontal)
3192
+            hit.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
3193
+        } else {
3194
+            hit.widthAnchor.constraint(equalToConstant: cardWidth).isActive = true
3195
+            hit.setContentHuggingPriority(.required, for: .horizontal)
3196
+            hit.setContentCompressionResistancePriority(.required, for: .horizontal)
3197
+        }
3126 3198
         hit.addSubview(card)
3127 3199
         NSLayoutConstraint.activate([
3128 3200
             card.leadingAnchor.constraint(equalTo: hit.leadingAnchor),
@@ -4437,9 +4509,8 @@ private extension ViewController {
4437 4509
         if visibleMeetings.isEmpty {
4438 4510
             let empty = roundedContainer(cornerRadius: 10, color: palette.sectionCard)
4439 4511
             empty.translatesAutoresizingMaskIntoConstraints = false
4440
-            empty.heightAnchor.constraint(equalToConstant: 120).isActive = true
4441
-            let viewportWidth = (schedulePageCardWidth * CGFloat(schedulePageCardsPerRow)) + (schedulePageCardSpacing * CGFloat(schedulePageCardsPerRow - 1))
4442
-            empty.widthAnchor.constraint(equalToConstant: viewportWidth).isActive = true
4512
+            empty.heightAnchor.constraint(equalToConstant: 140).isActive = true
4513
+            empty.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
4443 4514
             styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
4444 4515
             let label = textLabel(googleOAuth.loadTokens() == nil ? "Connect to load schedule" : "No meetings for selected filters", font: typography.cardSubtitle, color: palette.textSecondary)
4445 4516
             label.translatesAutoresizingMaskIntoConstraints = false
@@ -4456,24 +4527,17 @@ private extension ViewController {
4456 4527
         while index < visibleMeetings.count {
4457 4528
             let row = NSStackView()
4458 4529
             row.translatesAutoresizingMaskIntoConstraints = false
4530
+            row.userInterfaceLayoutDirection = .leftToRight
4459 4531
             row.orientation = .horizontal
4460 4532
             row.alignment = .top
4461 4533
             row.spacing = schedulePageCardSpacing
4462
-            row.distribution = .fill
4534
+            row.distribution = .fillEqually
4463 4535
             let rowEnd = min(index + schedulePageCardsPerRow, visibleMeetings.count)
4464 4536
             for meeting in visibleMeetings[index..<rowEnd] {
4465
-                row.addArrangedSubview(scheduleCard(meeting: meeting))
4466
-            }
4467
-            if rowEnd - index < schedulePageCardsPerRow {
4468
-                for _ in 0..<(schedulePageCardsPerRow - (rowEnd - index)) {
4469
-                    let spacer = NSView()
4470
-                    spacer.translatesAutoresizingMaskIntoConstraints = false
4471
-                    spacer.widthAnchor.constraint(equalToConstant: schedulePageCardWidth).isActive = true
4472
-                    spacer.heightAnchor.constraint(equalToConstant: 150).isActive = true
4473
-                    row.addArrangedSubview(spacer)
4474
-                }
4537
+                row.addArrangedSubview(scheduleCard(meeting: meeting, useFlexibleWidth: true, contentHeight: schedulePageCardHeight))
4475 4538
             }
4476 4539
             stack.addArrangedSubview(row)
4540
+            row.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
4477 4541
             index = rowEnd
4478 4542
         }
4479 4543