Эх сурвалжийг харах

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 3 сар өмнө
parent
commit
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 {
     case settings = 4
 }
 
-private enum MeetingProvider: Int {
-    case meet = 0
-    case zoom = 1
-    case teams = 2
-    case zoho = 3
-}
-
 private enum ZoomJoinMode: Int {
     case id = 0
     case url = 1
@@ -50,13 +43,10 @@ final class ViewController: NSViewController {
 
     private var mainContentHost: NSView?
     private var sidebarRowViews: [SidebarPage: NSView] = [:]
-    private var tabViews: [MeetingProvider: NSView] = [:]
     private var selectedSidebarPage: SidebarPage = .joinMeetings
-    private var selectedMeetingProvider: MeetingProvider = .meet
     private var selectedZoomJoinMode: ZoomJoinMode = .id
     private var pageCache: [SidebarPage: NSView] = [:]
     private var sidebarPageByView = [ObjectIdentifier: SidebarPage]()
-    private var meetingProviderByView = [ObjectIdentifier: MeetingProvider]()
     private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
     private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
     private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
@@ -169,28 +159,13 @@ private extension ViewController {
         showSidebarPage(page)
     }
 
-    @objc private func meetingTabClicked(_ sender: NSClickGestureRecognizer) {
-        guard let view = sender.view,
-              let provider = meetingProviderByView[ObjectIdentifier(view)],
-              provider != selectedMeetingProvider else { return }
-        selectedMeetingProvider = provider
-        if provider == .zoom {
-            selectedZoomJoinMode = .id
-        }
-        updateTabAppearance()
-        if selectedSidebarPage == .joinMeetings {
-            pageCache[.joinMeetings] = nil
-            showSidebarPage(.joinMeetings)
-        }
-    }
-
     @objc private func zoomJoinModeClicked(_ sender: NSClickGestureRecognizer) {
         guard let view = sender.view,
               let mode = zoomJoinModeByView[ObjectIdentifier(view)],
               mode != selectedZoomJoinMode else { return }
         selectedZoomJoinMode = mode
         updateZoomJoinModeAppearance()
-        if selectedSidebarPage == .joinMeetings, selectedMeetingProvider == .zoom {
+        if selectedSidebarPage == .joinMeetings {
             pageCache[.joinMeetings] = nil
             showSidebarPage(.joinMeetings)
         }
@@ -420,12 +395,6 @@ private extension ViewController {
         }
     }
 
-    private func updateTabAppearance() {
-        for (provider, tab) in tabViews {
-            applyTabStyle(tab, provider: provider, logoTemplate: logoTemplateForMeetingProvider(provider))
-        }
-    }
-
     private func logoTemplateForSidebarPage(_ page: SidebarPage) -> Bool {
         switch page {
         case .photo, .tutorials: return false
@@ -433,13 +402,6 @@ private extension ViewController {
         }
     }
 
-    private func logoTemplateForMeetingProvider(_ provider: MeetingProvider) -> Bool {
-        switch provider {
-        case .teams: return false
-        case .meet, .zoom, .zoho: return true
-        }
-    }
-
     func makeSidebar() -> NSView {
         let sidebar = NSView()
         sidebar.translatesAutoresizingMaskIntoConstraints = false
@@ -580,19 +542,11 @@ private extension ViewController {
         contentStack.spacing = 14
         contentStack.alignment = .leading
 
+        let joinActions = meetJoinActionsRow()
         contentStack.addArrangedSubview(textLabel("Join Meetings", font: typography.pageTitle, color: palette.textPrimary))
-        contentStack.addArrangedSubview(meetingTypeTabs())
-        if selectedMeetingProvider == .zoom {
-            contentStack.addArrangedSubview(zoomJoinModeTabs())
-            if selectedZoomJoinMode == .id {
-                contentStack.addArrangedSubview(zoomMeetingIDSection())
-            } else {
-                contentStack.addArrangedSubview(meetingUrlSection())
-            }
-        } else {
-            contentStack.addArrangedSubview(joinWithURLHeading())
-            contentStack.addArrangedSubview(meetingUrlSection())
-        }
+        contentStack.addArrangedSubview(meetJoinSectionRow())
+        contentStack.addArrangedSubview(joinActions)
+        contentStack.setCustomSpacing(26, after: joinActions)
         contentStack.addArrangedSubview(scheduleHeader())
         contentStack.addArrangedSubview(textLabel("Tuesday, 14 Apr", font: typography.dateHeading, color: palette.textSecondary))
         contentStack.addArrangedSubview(scheduleCardsRow())
@@ -608,6 +562,123 @@ private extension ViewController {
         return panel
     }
 
+    func meetJoinSectionRow() -> NSView {
+        let row = NSStackView()
+        row.translatesAutoresizingMaskIntoConstraints = false
+        row.orientation = .horizontal
+        row.spacing = 12
+        row.alignment = .top
+        row.distribution = .fillEqually
+        row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
+        row.heightAnchor.constraint(equalToConstant: 140).isActive = true
+
+        let instant = HoverSurfaceView()
+        instant.translatesAutoresizingMaskIntoConstraints = false
+        instant.wantsLayer = true
+        instant.layer?.cornerRadius = 14
+        instant.layer?.backgroundColor = palette.sectionCard.cgColor
+        styleSurface(instant, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
+
+        let iconWrap = roundedContainer(cornerRadius: 12, color: NSColor.clear)
+        iconWrap.translatesAutoresizingMaskIntoConstraints = false
+        iconWrap.widthAnchor.constraint(equalToConstant: 58).isActive = true
+        iconWrap.heightAnchor.constraint(equalToConstant: 58).isActive = true
+        iconWrap.layer?.borderWidth = 0
+        let meetLogoImage = NSImage(named: "MeetLogo") ?? NSImage()
+        meetLogoImage.isTemplate = false
+        let meetLogo = NSImageView(image: meetLogoImage)
+        meetLogo.translatesAutoresizingMaskIntoConstraints = false
+        meetLogo.imageScaling = .scaleProportionallyDown
+        meetLogo.contentTintColor = nil
+        iconWrap.addSubview(meetLogo)
+
+        let instantTitle = textLabel("New Instant Meet", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
+        let instantSub = textLabel("Start instant Meet in more section with\nGoogle Meet meet.", font: NSFont.systemFont(ofSize: 16 / 2, weight: .medium), color: palette.textSecondary)
+        instantSub.maximumNumberOfLines = 2
+        instant.addSubview(iconWrap)
+        instant.addSubview(instantTitle)
+        instant.addSubview(instantSub)
+
+        let codeCard = HoverSurfaceView()
+        codeCard.translatesAutoresizingMaskIntoConstraints = false
+        codeCard.wantsLayer = true
+        codeCard.layer?.cornerRadius = 14
+        codeCard.layer?.backgroundColor = palette.sectionCard.cgColor
+        styleSurface(codeCard, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
+        let codeTitle = textLabel("Join with Link", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
+        let codeInputShell = roundedContainer(cornerRadius: 8, color: palette.inputBackground)
+        codeInputShell.translatesAutoresizingMaskIntoConstraints = false
+        codeInputShell.heightAnchor.constraint(equalToConstant: 52).isActive = true
+        styleSurface(codeInputShell, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
+        let codeField = NSTextField(string: "")
+        codeField.translatesAutoresizingMaskIntoConstraints = false
+        codeField.isEditable = true
+        codeField.isBordered = false
+        codeField.drawsBackground = false
+        codeField.focusRingType = .none
+        codeField.font = NSFont.systemFont(ofSize: 36 / 2, weight: .regular)
+        codeField.textColor = palette.textPrimary
+        codeField.placeholderString = "Enter Link"
+        codeInputShell.addSubview(codeField)
+        codeCard.addSubview(codeTitle)
+        codeCard.addSubview(codeInputShell)
+
+        NSLayoutConstraint.activate([
+            meetLogo.centerXAnchor.constraint(equalTo: iconWrap.centerXAnchor),
+            meetLogo.centerYAnchor.constraint(equalTo: iconWrap.centerYAnchor),
+            meetLogo.widthAnchor.constraint(equalToConstant: 46),
+            meetLogo.heightAnchor.constraint(equalToConstant: 46),
+
+            iconWrap.leadingAnchor.constraint(equalTo: instant.leadingAnchor, constant: 18),
+            iconWrap.topAnchor.constraint(equalTo: instant.topAnchor, constant: 22),
+            instantTitle.leadingAnchor.constraint(equalTo: iconWrap.trailingAnchor, constant: 14),
+            instantTitle.topAnchor.constraint(equalTo: instant.topAnchor, constant: 24),
+            instantSub.leadingAnchor.constraint(equalTo: instantTitle.leadingAnchor),
+            instantSub.topAnchor.constraint(equalTo: instantTitle.bottomAnchor, constant: 6),
+            instantSub.trailingAnchor.constraint(lessThanOrEqualTo: instant.trailingAnchor, constant: -16),
+
+            codeTitle.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
+            codeTitle.topAnchor.constraint(equalTo: codeCard.topAnchor, constant: 22),
+            codeInputShell.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
+            codeInputShell.trailingAnchor.constraint(equalTo: codeCard.trailingAnchor, constant: -18),
+            codeInputShell.topAnchor.constraint(equalTo: codeTitle.bottomAnchor, constant: 12),
+            codeField.leadingAnchor.constraint(equalTo: codeInputShell.leadingAnchor, constant: 14),
+            codeField.trailingAnchor.constraint(equalTo: codeInputShell.trailingAnchor, constant: -14),
+            codeField.centerYAnchor.constraint(equalTo: codeInputShell.centerYAnchor)
+        ])
+
+        let baseColor = palette.sectionCard
+        let hoverColor = baseColor.blended(withFraction: 0.10, of: NSColor.white) ?? baseColor
+        instant.onHoverChanged = { hovering in
+            instant.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
+        }
+        codeCard.onHoverChanged = { hovering in
+            codeCard.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
+        }
+        instant.onHoverChanged?(false)
+        codeCard.onHoverChanged?(false)
+
+        row.addArrangedSubview(instant)
+        row.addArrangedSubview(codeCard)
+        return row
+    }
+
+    func meetJoinActionsRow() -> NSView {
+        let row = NSStackView()
+        row.translatesAutoresizingMaskIntoConstraints = false
+        row.orientation = .horizontal
+        row.spacing = 12
+        row.alignment = .centerY
+        row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
+
+        let spacer = NSView()
+        spacer.translatesAutoresizingMaskIntoConstraints = false
+        row.addArrangedSubview(spacer)
+        row.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
+        row.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
+        return row
+    }
+
     func makePaywallContent() -> NSView {
         paywallPlanViews.removeAll()
         premiumPlanByView.removeAll()
@@ -1170,51 +1241,6 @@ private extension ViewController {
         return container
     }
 
-    func meetingTypeTabs() -> NSView {
-        let wrapper = NSView()
-        wrapper.translatesAutoresizingMaskIntoConstraints = false
-
-        let shell = roundedContainer(cornerRadius: 24, color: palette.tabBarBackground)
-        shell.translatesAutoresizingMaskIntoConstraints = false
-        shell.heightAnchor.constraint(equalToConstant: 48).isActive = true
-
-        let stack = NSStackView()
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        stack.orientation = .horizontal
-        stack.distribution = .fillEqually
-        stack.spacing = 4
-
-        let meetTab = topTab("Meet", icon: "􀤆", provider: .meet, logoImageName: "MeetLogo")
-        stack.addArrangedSubview(meetTab)
-        tabViews[.meet] = meetTab
-        let zoomTab = topTab("Zoom", icon: "􀤉", provider: .zoom, logoImageName: "ZoomLogo", logoPointSize: 34)
-        stack.addArrangedSubview(zoomTab)
-        tabViews[.zoom] = zoomTab
-        let teamsTab = topTab("Teams", icon: "􀉨", provider: .teams, logoImageName: "TeamsLogo", logoPointSize: 26, logoHeightMultiplier: 62.0 / 50.0, logoTemplate: false)
-        stack.addArrangedSubview(teamsTab)
-        tabViews[.teams] = teamsTab
-        let zohoTab = topTab("Zoho", icon: "􀯶", provider: .zoho, logoImageName: "ZohoLogo", logoPointSize: 28)
-        stack.addArrangedSubview(zohoTab)
-        tabViews[.zoho] = zohoTab
-
-        shell.addSubview(stack)
-        wrapper.addSubview(shell)
-        NSLayoutConstraint.activate([
-            wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: 780),
-            shell.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
-            shell.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
-            shell.topAnchor.constraint(equalTo: wrapper.topAnchor),
-            shell.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor),
-            // More horizontal breathing room inside the bar.
-            stack.leadingAnchor.constraint(equalTo: shell.leadingAnchor, constant: 64),
-            stack.trailingAnchor.constraint(equalTo: shell.trailingAnchor, constant: -44),
-            stack.topAnchor.constraint(equalTo: shell.topAnchor, constant: 6),
-            stack.bottomAnchor.constraint(equalTo: shell.bottomAnchor, constant: -6)
-        ])
-
-        return wrapper
-    }
-
     func meetingUrlSection() -> NSView {
         let wrapper = NSView()
         wrapper.translatesAutoresizingMaskIntoConstraints = false
@@ -1430,6 +1456,44 @@ private final class HoverTrackingView: RowHitTestView {
     }
 }
 
+/// Hover tracking without overriding hit-testing; keeps controls like text fields interactive.
+private final class HoverSurfaceView: NSView {
+    var onHoverChanged: ((Bool) -> Void)?
+
+    private var trackingAreaRef: NSTrackingArea?
+    private var isHovering = false {
+        didSet {
+            guard isHovering != oldValue else { return }
+            onHoverChanged?(isHovering)
+        }
+    }
+
+    override func updateTrackingAreas() {
+        super.updateTrackingAreas()
+        if let trackingAreaRef {
+            removeTrackingArea(trackingAreaRef)
+        }
+        let options: NSTrackingArea.Options = [
+            .activeInKeyWindow,
+            .inVisibleRect,
+            .mouseEnteredAndExited
+        ]
+        let area = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil)
+        addTrackingArea(area)
+        trackingAreaRef = area
+    }
+
+    override func mouseEntered(with event: NSEvent) {
+        super.mouseEntered(with: event)
+        isHovering = true
+    }
+
+    override func mouseExited(with event: NSEvent) {
+        super.mouseExited(with: event)
+        isHovering = false
+    }
+}
+
 private final class SettingsMenuViewController: NSViewController {
     private let palette: Palette
     private let typography: Typography
@@ -1748,79 +1812,6 @@ private extension ViewController {
         }
     }
 
-    func topTab(_ title: String, icon: String, provider: MeetingProvider, logoImageName: String? = nil, logoPointSize: CGFloat = 26, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true) -> NSView {
-        let tab = HoverTrackingView()
-        tab.wantsLayer = true
-        tab.layer?.cornerRadius = 19
-        tab.layer?.backgroundColor = NSColor.clear.cgColor
-        tab.translatesAutoresizingMaskIntoConstraints = false
-        meetingProviderByView[ObjectIdentifier(tab)] = provider
-
-        let leadingView: NSView
-        if let name = logoImageName, let logo = NSImage(named: name) {
-            let imageView = NSImageView(image: logo)
-            imageView.translatesAutoresizingMaskIntoConstraints = false
-            imageView.imageScaling = .scaleProportionallyDown
-            imageView.imageAlignment = .alignCenter
-            imageView.isEditable = false
-            if logoTemplate {
-                imageView.contentTintColor = palette.textPrimary
-            }
-            leadingView = imageView
-        } else {
-            leadingView = textLabel(icon, font: typography.tabIcon, color: palette.textPrimary)
-        }
-
-        let titleLabel = textLabel(title, font: typography.tabTitle, color: palette.textPrimary)
-
-        tab.addSubview(leadingView)
-        tab.addSubview(titleLabel)
-
-        var constraints: [NSLayoutConstraint] = [
-            leadingView.leadingAnchor.constraint(equalTo: tab.leadingAnchor, constant: 18),
-            leadingView.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
-            titleLabel.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 8),
-            titleLabel.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
-            titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: tab.trailingAnchor, constant: -18)
-        ]
-        if logoImageName != nil {
-            constraints.append(contentsOf: [
-                leadingView.widthAnchor.constraint(equalToConstant: logoPointSize),
-                leadingView.heightAnchor.constraint(equalToConstant: logoPointSize * logoHeightMultiplier)
-            ])
-        }
-        NSLayoutConstraint.activate(constraints)
-
-        applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate)
-        tab.onHoverChanged = { [weak self, weak tab] hovering in
-            guard let self, let tab else { return }
-            self.applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate, hovering: hovering)
-        }
-
-        let click = NSClickGestureRecognizer(target: self, action: #selector(meetingTabClicked(_:)))
-        tab.addGestureRecognizer(click)
-
-        return tab
-    }
-
-    func applyTabStyle(_ tab: NSView, provider: MeetingProvider, logoTemplate: Bool, hovering: Bool = false) {
-        let selected = (provider == selectedMeetingProvider)
-        let hoverColor = NSColor(calibratedWhite: 1, alpha: 0.07)
-        tab.layer?.backgroundColor = (selected ? palette.primaryBlue : (hovering ? hoverColor : NSColor.clear)).cgColor
-        guard tab.subviews.count >= 2 else { return }
-        let leading = tab.subviews[0]
-        let title = tab.subviews[1] as? NSTextField
-        let textColor = palette.textPrimary
-        title?.textColor = textColor
-        if let imageView = leading as? NSImageView {
-            if logoTemplate {
-                imageView.contentTintColor = textColor
-            }
-        } else if let iconField = leading as? NSTextField {
-            iconField.textColor = textColor
-        }
-    }
-
     func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
         let button = HoverTrackingView()
         button.wantsLayer = true