Kaynağa Gözat

Refocus home UI on Google Meet-only join flow.

Remove multi-provider tabs, redesign the join section with instant meet/link cards and hover behavior, and refresh Meet logo assets for improved visual quality.

Made-with: Cursor
huzaifahayat12 2 hafta önce
ebeveyn
işleme
c9a8af292e

BIN
meetings_app/Assets.xcassets/MeetLogo.imageset/meet_logo.png


BIN
meetings_app/Assets.xcassets/MeetLogo.imageset/meet_logo@2x.png


BIN
meetings_app/Assets.xcassets/MeetLogo.imageset/meet_logo@3x.png


+ 160 - 169
meetings_app/ViewController.swift

@@ -15,13 +15,6 @@ private enum SidebarPage: Int {
15 15
     case settings = 4
16 16
 }
17 17
 
18
-private enum MeetingProvider: Int {
19
-    case meet = 0
20
-    case zoom = 1
21
-    case teams = 2
22
-    case zoho = 3
23
-}
24
-
25 18
 private enum ZoomJoinMode: Int {
26 19
     case id = 0
27 20
     case url = 1
@@ -50,13 +43,10 @@ final class ViewController: NSViewController {
50 43
 
51 44
     private var mainContentHost: NSView?
52 45
     private var sidebarRowViews: [SidebarPage: NSView] = [:]
53
-    private var tabViews: [MeetingProvider: NSView] = [:]
54 46
     private var selectedSidebarPage: SidebarPage = .joinMeetings
55
-    private var selectedMeetingProvider: MeetingProvider = .meet
56 47
     private var selectedZoomJoinMode: ZoomJoinMode = .id
57 48
     private var pageCache: [SidebarPage: NSView] = [:]
58 49
     private var sidebarPageByView = [ObjectIdentifier: SidebarPage]()
59
-    private var meetingProviderByView = [ObjectIdentifier: MeetingProvider]()
60 50
     private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
61 51
     private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
62 52
     private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
@@ -169,28 +159,13 @@ private extension ViewController {
169 159
         showSidebarPage(page)
170 160
     }
171 161
 
172
-    @objc private func meetingTabClicked(_ sender: NSClickGestureRecognizer) {
173
-        guard let view = sender.view,
174
-              let provider = meetingProviderByView[ObjectIdentifier(view)],
175
-              provider != selectedMeetingProvider else { return }
176
-        selectedMeetingProvider = provider
177
-        if provider == .zoom {
178
-            selectedZoomJoinMode = .id
179
-        }
180
-        updateTabAppearance()
181
-        if selectedSidebarPage == .joinMeetings {
182
-            pageCache[.joinMeetings] = nil
183
-            showSidebarPage(.joinMeetings)
184
-        }
185
-    }
186
-
187 162
     @objc private func zoomJoinModeClicked(_ sender: NSClickGestureRecognizer) {
188 163
         guard let view = sender.view,
189 164
               let mode = zoomJoinModeByView[ObjectIdentifier(view)],
190 165
               mode != selectedZoomJoinMode else { return }
191 166
         selectedZoomJoinMode = mode
192 167
         updateZoomJoinModeAppearance()
193
-        if selectedSidebarPage == .joinMeetings, selectedMeetingProvider == .zoom {
168
+        if selectedSidebarPage == .joinMeetings {
194 169
             pageCache[.joinMeetings] = nil
195 170
             showSidebarPage(.joinMeetings)
196 171
         }
@@ -420,12 +395,6 @@ private extension ViewController {
420 395
         }
421 396
     }
422 397
 
423
-    private func updateTabAppearance() {
424
-        for (provider, tab) in tabViews {
425
-            applyTabStyle(tab, provider: provider, logoTemplate: logoTemplateForMeetingProvider(provider))
426
-        }
427
-    }
428
-
429 398
     private func logoTemplateForSidebarPage(_ page: SidebarPage) -> Bool {
430 399
         switch page {
431 400
         case .photo, .tutorials: return false
@@ -433,13 +402,6 @@ private extension ViewController {
433 402
         }
434 403
     }
435 404
 
436
-    private func logoTemplateForMeetingProvider(_ provider: MeetingProvider) -> Bool {
437
-        switch provider {
438
-        case .teams: return false
439
-        case .meet, .zoom, .zoho: return true
440
-        }
441
-    }
442
-
443 405
     func makeSidebar() -> NSView {
444 406
         let sidebar = NSView()
445 407
         sidebar.translatesAutoresizingMaskIntoConstraints = false
@@ -580,19 +542,11 @@ private extension ViewController {
580 542
         contentStack.spacing = 14
581 543
         contentStack.alignment = .leading
582 544
 
545
+        let joinActions = meetJoinActionsRow()
583 546
         contentStack.addArrangedSubview(textLabel("Join Meetings", font: typography.pageTitle, color: palette.textPrimary))
584
-        contentStack.addArrangedSubview(meetingTypeTabs())
585
-        if selectedMeetingProvider == .zoom {
586
-            contentStack.addArrangedSubview(zoomJoinModeTabs())
587
-            if selectedZoomJoinMode == .id {
588
-                contentStack.addArrangedSubview(zoomMeetingIDSection())
589
-            } else {
590
-                contentStack.addArrangedSubview(meetingUrlSection())
591
-            }
592
-        } else {
593
-            contentStack.addArrangedSubview(joinWithURLHeading())
594
-            contentStack.addArrangedSubview(meetingUrlSection())
595
-        }
547
+        contentStack.addArrangedSubview(meetJoinSectionRow())
548
+        contentStack.addArrangedSubview(joinActions)
549
+        contentStack.setCustomSpacing(26, after: joinActions)
596 550
         contentStack.addArrangedSubview(scheduleHeader())
597 551
         contentStack.addArrangedSubview(textLabel("Tuesday, 14 Apr", font: typography.dateHeading, color: palette.textSecondary))
598 552
         contentStack.addArrangedSubview(scheduleCardsRow())
@@ -608,6 +562,123 @@ private extension ViewController {
608 562
         return panel
609 563
     }
610 564
 
565
+    func meetJoinSectionRow() -> NSView {
566
+        let row = NSStackView()
567
+        row.translatesAutoresizingMaskIntoConstraints = false
568
+        row.orientation = .horizontal
569
+        row.spacing = 12
570
+        row.alignment = .top
571
+        row.distribution = .fillEqually
572
+        row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
573
+        row.heightAnchor.constraint(equalToConstant: 140).isActive = true
574
+
575
+        let instant = HoverSurfaceView()
576
+        instant.translatesAutoresizingMaskIntoConstraints = false
577
+        instant.wantsLayer = true
578
+        instant.layer?.cornerRadius = 14
579
+        instant.layer?.backgroundColor = palette.sectionCard.cgColor
580
+        styleSurface(instant, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
581
+
582
+        let iconWrap = roundedContainer(cornerRadius: 12, color: NSColor.clear)
583
+        iconWrap.translatesAutoresizingMaskIntoConstraints = false
584
+        iconWrap.widthAnchor.constraint(equalToConstant: 58).isActive = true
585
+        iconWrap.heightAnchor.constraint(equalToConstant: 58).isActive = true
586
+        iconWrap.layer?.borderWidth = 0
587
+        let meetLogoImage = NSImage(named: "MeetLogo") ?? NSImage()
588
+        meetLogoImage.isTemplate = false
589
+        let meetLogo = NSImageView(image: meetLogoImage)
590
+        meetLogo.translatesAutoresizingMaskIntoConstraints = false
591
+        meetLogo.imageScaling = .scaleProportionallyDown
592
+        meetLogo.contentTintColor = nil
593
+        iconWrap.addSubview(meetLogo)
594
+
595
+        let instantTitle = textLabel("New Instant Meet", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
596
+        let instantSub = textLabel("Start instant Meet in more section with\nGoogle Meet meet.", font: NSFont.systemFont(ofSize: 16 / 2, weight: .medium), color: palette.textSecondary)
597
+        instantSub.maximumNumberOfLines = 2
598
+        instant.addSubview(iconWrap)
599
+        instant.addSubview(instantTitle)
600
+        instant.addSubview(instantSub)
601
+
602
+        let codeCard = HoverSurfaceView()
603
+        codeCard.translatesAutoresizingMaskIntoConstraints = false
604
+        codeCard.wantsLayer = true
605
+        codeCard.layer?.cornerRadius = 14
606
+        codeCard.layer?.backgroundColor = palette.sectionCard.cgColor
607
+        styleSurface(codeCard, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
608
+        let codeTitle = textLabel("Join with Link", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
609
+        let codeInputShell = roundedContainer(cornerRadius: 8, color: palette.inputBackground)
610
+        codeInputShell.translatesAutoresizingMaskIntoConstraints = false
611
+        codeInputShell.heightAnchor.constraint(equalToConstant: 52).isActive = true
612
+        styleSurface(codeInputShell, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
613
+        let codeField = NSTextField(string: "")
614
+        codeField.translatesAutoresizingMaskIntoConstraints = false
615
+        codeField.isEditable = true
616
+        codeField.isBordered = false
617
+        codeField.drawsBackground = false
618
+        codeField.focusRingType = .none
619
+        codeField.font = NSFont.systemFont(ofSize: 36 / 2, weight: .regular)
620
+        codeField.textColor = palette.textPrimary
621
+        codeField.placeholderString = "Enter Link"
622
+        codeInputShell.addSubview(codeField)
623
+        codeCard.addSubview(codeTitle)
624
+        codeCard.addSubview(codeInputShell)
625
+
626
+        NSLayoutConstraint.activate([
627
+            meetLogo.centerXAnchor.constraint(equalTo: iconWrap.centerXAnchor),
628
+            meetLogo.centerYAnchor.constraint(equalTo: iconWrap.centerYAnchor),
629
+            meetLogo.widthAnchor.constraint(equalToConstant: 46),
630
+            meetLogo.heightAnchor.constraint(equalToConstant: 46),
631
+
632
+            iconWrap.leadingAnchor.constraint(equalTo: instant.leadingAnchor, constant: 18),
633
+            iconWrap.topAnchor.constraint(equalTo: instant.topAnchor, constant: 22),
634
+            instantTitle.leadingAnchor.constraint(equalTo: iconWrap.trailingAnchor, constant: 14),
635
+            instantTitle.topAnchor.constraint(equalTo: instant.topAnchor, constant: 24),
636
+            instantSub.leadingAnchor.constraint(equalTo: instantTitle.leadingAnchor),
637
+            instantSub.topAnchor.constraint(equalTo: instantTitle.bottomAnchor, constant: 6),
638
+            instantSub.trailingAnchor.constraint(lessThanOrEqualTo: instant.trailingAnchor, constant: -16),
639
+
640
+            codeTitle.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
641
+            codeTitle.topAnchor.constraint(equalTo: codeCard.topAnchor, constant: 22),
642
+            codeInputShell.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
643
+            codeInputShell.trailingAnchor.constraint(equalTo: codeCard.trailingAnchor, constant: -18),
644
+            codeInputShell.topAnchor.constraint(equalTo: codeTitle.bottomAnchor, constant: 12),
645
+            codeField.leadingAnchor.constraint(equalTo: codeInputShell.leadingAnchor, constant: 14),
646
+            codeField.trailingAnchor.constraint(equalTo: codeInputShell.trailingAnchor, constant: -14),
647
+            codeField.centerYAnchor.constraint(equalTo: codeInputShell.centerYAnchor)
648
+        ])
649
+
650
+        let baseColor = palette.sectionCard
651
+        let hoverColor = baseColor.blended(withFraction: 0.10, of: NSColor.white) ?? baseColor
652
+        instant.onHoverChanged = { hovering in
653
+            instant.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
654
+        }
655
+        codeCard.onHoverChanged = { hovering in
656
+            codeCard.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
657
+        }
658
+        instant.onHoverChanged?(false)
659
+        codeCard.onHoverChanged?(false)
660
+
661
+        row.addArrangedSubview(instant)
662
+        row.addArrangedSubview(codeCard)
663
+        return row
664
+    }
665
+
666
+    func meetJoinActionsRow() -> NSView {
667
+        let row = NSStackView()
668
+        row.translatesAutoresizingMaskIntoConstraints = false
669
+        row.orientation = .horizontal
670
+        row.spacing = 12
671
+        row.alignment = .centerY
672
+        row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
673
+
674
+        let spacer = NSView()
675
+        spacer.translatesAutoresizingMaskIntoConstraints = false
676
+        row.addArrangedSubview(spacer)
677
+        row.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
678
+        row.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
679
+        return row
680
+    }
681
+
611 682
     func makePaywallContent() -> NSView {
612 683
         paywallPlanViews.removeAll()
613 684
         premiumPlanByView.removeAll()
@@ -1170,51 +1241,6 @@ private extension ViewController {
1170 1241
         return container
1171 1242
     }
1172 1243
 
1173
-    func meetingTypeTabs() -> NSView {
1174
-        let wrapper = NSView()
1175
-        wrapper.translatesAutoresizingMaskIntoConstraints = false
1176
-
1177
-        let shell = roundedContainer(cornerRadius: 24, color: palette.tabBarBackground)
1178
-        shell.translatesAutoresizingMaskIntoConstraints = false
1179
-        shell.heightAnchor.constraint(equalToConstant: 48).isActive = true
1180
-
1181
-        let stack = NSStackView()
1182
-        stack.translatesAutoresizingMaskIntoConstraints = false
1183
-        stack.orientation = .horizontal
1184
-        stack.distribution = .fillEqually
1185
-        stack.spacing = 4
1186
-
1187
-        let meetTab = topTab("Meet", icon: "􀤆", provider: .meet, logoImageName: "MeetLogo")
1188
-        stack.addArrangedSubview(meetTab)
1189
-        tabViews[.meet] = meetTab
1190
-        let zoomTab = topTab("Zoom", icon: "􀤉", provider: .zoom, logoImageName: "ZoomLogo", logoPointSize: 34)
1191
-        stack.addArrangedSubview(zoomTab)
1192
-        tabViews[.zoom] = zoomTab
1193
-        let teamsTab = topTab("Teams", icon: "􀉨", provider: .teams, logoImageName: "TeamsLogo", logoPointSize: 26, logoHeightMultiplier: 62.0 / 50.0, logoTemplate: false)
1194
-        stack.addArrangedSubview(teamsTab)
1195
-        tabViews[.teams] = teamsTab
1196
-        let zohoTab = topTab("Zoho", icon: "􀯶", provider: .zoho, logoImageName: "ZohoLogo", logoPointSize: 28)
1197
-        stack.addArrangedSubview(zohoTab)
1198
-        tabViews[.zoho] = zohoTab
1199
-
1200
-        shell.addSubview(stack)
1201
-        wrapper.addSubview(shell)
1202
-        NSLayoutConstraint.activate([
1203
-            wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: 780),
1204
-            shell.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
1205
-            shell.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
1206
-            shell.topAnchor.constraint(equalTo: wrapper.topAnchor),
1207
-            shell.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor),
1208
-            // More horizontal breathing room inside the bar.
1209
-            stack.leadingAnchor.constraint(equalTo: shell.leadingAnchor, constant: 64),
1210
-            stack.trailingAnchor.constraint(equalTo: shell.trailingAnchor, constant: -44),
1211
-            stack.topAnchor.constraint(equalTo: shell.topAnchor, constant: 6),
1212
-            stack.bottomAnchor.constraint(equalTo: shell.bottomAnchor, constant: -6)
1213
-        ])
1214
-
1215
-        return wrapper
1216
-    }
1217
-
1218 1244
     func meetingUrlSection() -> NSView {
1219 1245
         let wrapper = NSView()
1220 1246
         wrapper.translatesAutoresizingMaskIntoConstraints = false
@@ -1430,6 +1456,44 @@ private final class HoverTrackingView: RowHitTestView {
1430 1456
     }
1431 1457
 }
1432 1458
 
1459
+/// Hover tracking without overriding hit-testing; keeps controls like text fields interactive.
1460
+private final class HoverSurfaceView: NSView {
1461
+    var onHoverChanged: ((Bool) -> Void)?
1462
+
1463
+    private var trackingAreaRef: NSTrackingArea?
1464
+    private var isHovering = false {
1465
+        didSet {
1466
+            guard isHovering != oldValue else { return }
1467
+            onHoverChanged?(isHovering)
1468
+        }
1469
+    }
1470
+
1471
+    override func updateTrackingAreas() {
1472
+        super.updateTrackingAreas()
1473
+        if let trackingAreaRef {
1474
+            removeTrackingArea(trackingAreaRef)
1475
+        }
1476
+        let options: NSTrackingArea.Options = [
1477
+            .activeInKeyWindow,
1478
+            .inVisibleRect,
1479
+            .mouseEnteredAndExited
1480
+        ]
1481
+        let area = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil)
1482
+        addTrackingArea(area)
1483
+        trackingAreaRef = area
1484
+    }
1485
+
1486
+    override func mouseEntered(with event: NSEvent) {
1487
+        super.mouseEntered(with: event)
1488
+        isHovering = true
1489
+    }
1490
+
1491
+    override func mouseExited(with event: NSEvent) {
1492
+        super.mouseExited(with: event)
1493
+        isHovering = false
1494
+    }
1495
+}
1496
+
1433 1497
 private final class SettingsMenuViewController: NSViewController {
1434 1498
     private let palette: Palette
1435 1499
     private let typography: Typography
@@ -1748,79 +1812,6 @@ private extension ViewController {
1748 1812
         }
1749 1813
     }
1750 1814
 
1751
-    func topTab(_ title: String, icon: String, provider: MeetingProvider, logoImageName: String? = nil, logoPointSize: CGFloat = 26, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true) -> NSView {
1752
-        let tab = HoverTrackingView()
1753
-        tab.wantsLayer = true
1754
-        tab.layer?.cornerRadius = 19
1755
-        tab.layer?.backgroundColor = NSColor.clear.cgColor
1756
-        tab.translatesAutoresizingMaskIntoConstraints = false
1757
-        meetingProviderByView[ObjectIdentifier(tab)] = provider
1758
-
1759
-        let leadingView: NSView
1760
-        if let name = logoImageName, let logo = NSImage(named: name) {
1761
-            let imageView = NSImageView(image: logo)
1762
-            imageView.translatesAutoresizingMaskIntoConstraints = false
1763
-            imageView.imageScaling = .scaleProportionallyDown
1764
-            imageView.imageAlignment = .alignCenter
1765
-            imageView.isEditable = false
1766
-            if logoTemplate {
1767
-                imageView.contentTintColor = palette.textPrimary
1768
-            }
1769
-            leadingView = imageView
1770
-        } else {
1771
-            leadingView = textLabel(icon, font: typography.tabIcon, color: palette.textPrimary)
1772
-        }
1773
-
1774
-        let titleLabel = textLabel(title, font: typography.tabTitle, color: palette.textPrimary)
1775
-
1776
-        tab.addSubview(leadingView)
1777
-        tab.addSubview(titleLabel)
1778
-
1779
-        var constraints: [NSLayoutConstraint] = [
1780
-            leadingView.leadingAnchor.constraint(equalTo: tab.leadingAnchor, constant: 18),
1781
-            leadingView.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
1782
-            titleLabel.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 8),
1783
-            titleLabel.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
1784
-            titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: tab.trailingAnchor, constant: -18)
1785
-        ]
1786
-        if logoImageName != nil {
1787
-            constraints.append(contentsOf: [
1788
-                leadingView.widthAnchor.constraint(equalToConstant: logoPointSize),
1789
-                leadingView.heightAnchor.constraint(equalToConstant: logoPointSize * logoHeightMultiplier)
1790
-            ])
1791
-        }
1792
-        NSLayoutConstraint.activate(constraints)
1793
-
1794
-        applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate)
1795
-        tab.onHoverChanged = { [weak self, weak tab] hovering in
1796
-            guard let self, let tab else { return }
1797
-            self.applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate, hovering: hovering)
1798
-        }
1799
-
1800
-        let click = NSClickGestureRecognizer(target: self, action: #selector(meetingTabClicked(_:)))
1801
-        tab.addGestureRecognizer(click)
1802
-
1803
-        return tab
1804
-    }
1805
-
1806
-    func applyTabStyle(_ tab: NSView, provider: MeetingProvider, logoTemplate: Bool, hovering: Bool = false) {
1807
-        let selected = (provider == selectedMeetingProvider)
1808
-        let hoverColor = NSColor(calibratedWhite: 1, alpha: 0.07)
1809
-        tab.layer?.backgroundColor = (selected ? palette.primaryBlue : (hovering ? hoverColor : NSColor.clear)).cgColor
1810
-        guard tab.subviews.count >= 2 else { return }
1811
-        let leading = tab.subviews[0]
1812
-        let title = tab.subviews[1] as? NSTextField
1813
-        let textColor = palette.textPrimary
1814
-        title?.textColor = textColor
1815
-        if let imageView = leading as? NSImageView {
1816
-            if logoTemplate {
1817
-                imageView.contentTintColor = textColor
1818
-            }
1819
-        } else if let iconField = leading as? NSTextField {
1820
-            iconField.textColor = textColor
1821
-        }
1822
-    }
1823
-
1824 1815
     func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
1825 1816
         let button = HoverTrackingView()
1826 1817
         button.wantsLayer = true