Sfoglia il codice sorgente

Add Zoom-specific join mode inputs and content switching.

This updates the Join Meetings UI so Zoom shows Join with ID/Join with URL tabs and renders the correct form when each mode is selected.

Made-with: Cursor
huzaifahayat12 2 settimane fa
parent
commit
5e4352d3df
2 ha cambiato i file con 235 aggiunte e 7 eliminazioni
  1. 7 5
      meetings_app/Base.lproj/Main.storyboard
  2. 228 2
      meetings_app/ViewController.swift

+ 7 - 5
meetings_app/Base.lproj/Main.storyboard

@@ -1,7 +1,9 @@
1
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
-<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11134" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="24765" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
3 3
     <dependencies>
4
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
4
+        <deployment identifier="macosx"/>
5
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24765"/>
6
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
5 7
     </dependencies>
6 8
     <scenes>
7 9
         <!--Application-->
@@ -673,7 +675,7 @@
673 675
                         <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
674 676
                     </connections>
675 677
                 </application>
676
-                <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
678
+                <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="meetings_app" customModuleProvider="target"/>
677 679
                 <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
678 680
                 <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
679 681
             </objects>
@@ -703,7 +705,7 @@
703 705
         <!--View Controller-->
704 706
         <scene sceneID="hIz-AP-VOD">
705 707
             <objects>
706
-                <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
708
+                <viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="meetings_app" customModuleProvider="target" sceneMemberID="viewController">
707 709
                     <view key="view" id="m2S-Jp-Qdl">
708 710
                         <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
709 711
                         <autoresizingMask key="autoresizingMask"/>

+ 228 - 2
meetings_app/ViewController.swift

@@ -22,6 +22,11 @@ private enum MeetingProvider: Int {
22 22
     case zoho = 3
23 23
 }
24 24
 
25
+private enum ZoomJoinMode: Int {
26
+    case id = 0
27
+    case url = 1
28
+}
29
+
25 30
 private enum SettingsAction: Int {
26 31
     case restore = 0
27 32
     case rateUs = 1
@@ -48,9 +53,12 @@ final class ViewController: NSViewController {
48 53
     private var tabViews: [MeetingProvider: NSView] = [:]
49 54
     private var selectedSidebarPage: SidebarPage = .joinMeetings
50 55
     private var selectedMeetingProvider: MeetingProvider = .meet
56
+    private var selectedZoomJoinMode: ZoomJoinMode = .id
51 57
     private var pageCache: [SidebarPage: NSView] = [:]
52 58
     private var sidebarPageByView = [ObjectIdentifier: SidebarPage]()
53 59
     private var meetingProviderByView = [ObjectIdentifier: MeetingProvider]()
60
+    private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
61
+    private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
54 62
     private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
55 63
     private weak var centeredTitleLabel: NSTextField?
56 64
     private weak var paywallWindow: NSWindow?
@@ -166,7 +174,26 @@ private extension ViewController {
166 174
               let provider = meetingProviderByView[ObjectIdentifier(view)],
167 175
               provider != selectedMeetingProvider else { return }
168 176
         selectedMeetingProvider = provider
177
+        if provider == .zoom {
178
+            selectedZoomJoinMode = .id
179
+        }
169 180
         updateTabAppearance()
181
+        if selectedSidebarPage == .joinMeetings {
182
+            pageCache[.joinMeetings] = nil
183
+            showSidebarPage(.joinMeetings)
184
+        }
185
+    }
186
+
187
+    @objc private func zoomJoinModeClicked(_ sender: NSClickGestureRecognizer) {
188
+        guard let view = sender.view,
189
+              let mode = zoomJoinModeByView[ObjectIdentifier(view)],
190
+              mode != selectedZoomJoinMode else { return }
191
+        selectedZoomJoinMode = mode
192
+        updateZoomJoinModeAppearance()
193
+        if selectedSidebarPage == .joinMeetings, selectedMeetingProvider == .zoom {
194
+            pageCache[.joinMeetings] = nil
195
+            showSidebarPage(.joinMeetings)
196
+        }
170 197
     }
171 198
 
172 199
     @objc private func premiumButtonClicked(_ sender: NSClickGestureRecognizer) {
@@ -555,8 +582,17 @@ private extension ViewController {
555 582
 
556 583
         contentStack.addArrangedSubview(textLabel("Join Meetings", font: typography.pageTitle, color: palette.textPrimary))
557 584
         contentStack.addArrangedSubview(meetingTypeTabs())
558
-        contentStack.addArrangedSubview(joinWithURLHeading())
559
-        contentStack.addArrangedSubview(meetingUrlSection())
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
+        }
560 596
         contentStack.addArrangedSubview(scheduleHeader())
561 597
         contentStack.addArrangedSubview(textLabel("Tuesday, 14 Apr", font: typography.dateHeading, color: palette.textSecondary))
562 598
         contentStack.addArrangedSubview(scheduleCardsRow())
@@ -911,6 +947,196 @@ private extension ViewController {
911 947
         return card
912 948
     }
913 949
 
950
+    func zoomJoinModeTabs() -> NSView {
951
+        let row = NSStackView()
952
+        row.translatesAutoresizingMaskIntoConstraints = false
953
+        row.orientation = .horizontal
954
+        row.alignment = .centerY
955
+        row.spacing = 28
956
+        row.widthAnchor.constraint(greaterThanOrEqualToConstant: 780).isActive = true
957
+
958
+        let idTab = joinModeTab("Join with ID", mode: .id)
959
+        let urlTab = joinModeTab("Join with URL", mode: .url)
960
+        row.addArrangedSubview(idTab)
961
+        row.addArrangedSubview(urlTab)
962
+        let spacer = NSView()
963
+        spacer.translatesAutoresizingMaskIntoConstraints = false
964
+        row.addArrangedSubview(spacer)
965
+
966
+        zoomJoinModeViews[.id] = idTab
967
+        zoomJoinModeViews[.url] = urlTab
968
+        updateZoomJoinModeAppearance()
969
+        return row
970
+    }
971
+
972
+    func joinModeTab(_ title: String, mode: ZoomJoinMode) -> NSView {
973
+        let tab = HoverTrackingView()
974
+        tab.translatesAutoresizingMaskIntoConstraints = false
975
+        tab.wantsLayer = true
976
+        tab.layer?.cornerRadius = 6
977
+        tab.layer?.backgroundColor = NSColor.clear.cgColor
978
+        tab.heightAnchor.constraint(equalToConstant: 30).isActive = true
979
+        zoomJoinModeByView[ObjectIdentifier(tab)] = mode
980
+
981
+        let label = textLabel(title, font: NSFont.systemFont(ofSize: 33 / 2, weight: .medium), color: palette.textPrimary)
982
+        tab.addSubview(label)
983
+        NSLayoutConstraint.activate([
984
+            label.leadingAnchor.constraint(equalTo: tab.leadingAnchor, constant: 4),
985
+            label.trailingAnchor.constraint(equalTo: tab.trailingAnchor, constant: -4),
986
+            label.topAnchor.constraint(equalTo: tab.topAnchor, constant: 4),
987
+            label.bottomAnchor.constraint(equalTo: tab.bottomAnchor, constant: -6)
988
+        ])
989
+
990
+        let click = NSClickGestureRecognizer(target: self, action: #selector(zoomJoinModeClicked(_:)))
991
+        tab.addGestureRecognizer(click)
992
+        return tab
993
+    }
994
+
995
+    func updateZoomJoinModeAppearance() {
996
+        for (mode, tab) in zoomJoinModeViews {
997
+            let selected = (mode == selectedZoomJoinMode)
998
+            let textColor = selected ? palette.textPrimary : palette.textSecondary
999
+            let label = tab.subviews.first { $0 is NSTextField } as? NSTextField
1000
+            label?.textColor = textColor
1001
+
1002
+            // Keep the active tab visually underlined like the reference.
1003
+            if selected {
1004
+                if tab.subviews.contains(where: { $0.identifier?.rawValue == "modeUnderline" }) == false {
1005
+                    let underline = NSView()
1006
+                    underline.identifier = NSUserInterfaceItemIdentifier("modeUnderline")
1007
+                    underline.translatesAutoresizingMaskIntoConstraints = false
1008
+                    underline.wantsLayer = true
1009
+                    underline.layer?.backgroundColor = palette.primaryBlue.cgColor
1010
+                    tab.addSubview(underline)
1011
+                    NSLayoutConstraint.activate([
1012
+                        underline.leadingAnchor.constraint(equalTo: tab.leadingAnchor),
1013
+                        underline.trailingAnchor.constraint(equalTo: tab.trailingAnchor),
1014
+                        underline.bottomAnchor.constraint(equalTo: tab.bottomAnchor),
1015
+                        underline.heightAnchor.constraint(equalToConstant: 2)
1016
+                    ])
1017
+                }
1018
+            } else {
1019
+                tab.subviews
1020
+                    .filter { $0.identifier?.rawValue == "modeUnderline" }
1021
+                    .forEach { $0.removeFromSuperview() }
1022
+            }
1023
+        }
1024
+    }
1025
+
1026
+    func joinWithIDHeading() -> NSView {
1027
+        let container = NSView()
1028
+        container.translatesAutoresizingMaskIntoConstraints = false
1029
+
1030
+        let title = textLabel("Join with ID", font: typography.joinWithURLTitle, color: palette.textPrimary)
1031
+        title.alignment = .left
1032
+        title.setContentHuggingPriority(.defaultHigh, for: .horizontal)
1033
+        title.setContentCompressionResistancePriority(.required, for: .horizontal)
1034
+
1035
+        let bar = NSView()
1036
+        bar.translatesAutoresizingMaskIntoConstraints = false
1037
+        bar.wantsLayer = true
1038
+        bar.layer?.backgroundColor = palette.primaryBlue.cgColor
1039
+        bar.heightAnchor.constraint(equalToConstant: 3).isActive = true
1040
+
1041
+        container.addSubview(title)
1042
+        container.addSubview(bar)
1043
+
1044
+        NSLayoutConstraint.activate([
1045
+            title.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1046
+            title.topAnchor.constraint(equalTo: container.topAnchor),
1047
+
1048
+            bar.leadingAnchor.constraint(equalTo: title.leadingAnchor),
1049
+            bar.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 6),
1050
+            bar.widthAnchor.constraint(equalTo: title.widthAnchor),
1051
+            bar.bottomAnchor.constraint(equalTo: container.bottomAnchor),
1052
+
1053
+            container.trailingAnchor.constraint(equalTo: title.trailingAnchor)
1054
+        ])
1055
+
1056
+        return container
1057
+    }
1058
+
1059
+    func zoomMeetingIDSection() -> NSView {
1060
+        let wrapper = NSView()
1061
+        wrapper.translatesAutoresizingMaskIntoConstraints = false
1062
+
1063
+        let fieldsRow = NSStackView()
1064
+        fieldsRow.translatesAutoresizingMaskIntoConstraints = false
1065
+        fieldsRow.orientation = .horizontal
1066
+        fieldsRow.alignment = .top
1067
+        fieldsRow.distribution = .fillEqually
1068
+        fieldsRow.spacing = 12
1069
+        fieldsRow.addArrangedSubview(zoomInputField(title: "Meeting ID", placeholder: "Enter meeting ID..."))
1070
+        fieldsRow.addArrangedSubview(zoomInputField(title: "Meeting Passcode", placeholder: "Enter meeting passcode..."))
1071
+
1072
+        let actions = NSStackView()
1073
+        actions.orientation = .horizontal
1074
+        actions.spacing = 10
1075
+        actions.translatesAutoresizingMaskIntoConstraints = false
1076
+        actions.alignment = .centerY
1077
+        actions.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
1078
+        actions.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
1079
+
1080
+        wrapper.addSubview(fieldsRow)
1081
+        wrapper.addSubview(actions)
1082
+
1083
+        NSLayoutConstraint.activate([
1084
+            wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: 780),
1085
+
1086
+            fieldsRow.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
1087
+            fieldsRow.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
1088
+            fieldsRow.topAnchor.constraint(equalTo: wrapper.topAnchor),
1089
+
1090
+            actions.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
1091
+            actions.topAnchor.constraint(equalTo: fieldsRow.bottomAnchor, constant: 14),
1092
+            actions.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor)
1093
+        ])
1094
+
1095
+        return wrapper
1096
+    }
1097
+
1098
+    func zoomInputField(title: String, placeholder: String) -> NSView {
1099
+        let wrapper = NSView()
1100
+        wrapper.translatesAutoresizingMaskIntoConstraints = false
1101
+
1102
+        let heading = textLabel(title, font: typography.fieldLabel, color: palette.textPrimary)
1103
+        let textFieldContainer = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
1104
+        textFieldContainer.translatesAutoresizingMaskIntoConstraints = false
1105
+        textFieldContainer.heightAnchor.constraint(equalToConstant: 40).isActive = true
1106
+        styleSurface(textFieldContainer, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
1107
+
1108
+        let field = NSTextField(string: "")
1109
+        field.translatesAutoresizingMaskIntoConstraints = false
1110
+        field.isEditable = true
1111
+        field.isSelectable = true
1112
+        field.isBordered = false
1113
+        field.drawsBackground = false
1114
+        field.placeholderString = placeholder
1115
+        field.font = typography.inputPlaceholder
1116
+        field.textColor = palette.textPrimary
1117
+        field.focusRingType = .none
1118
+        textFieldContainer.addSubview(field)
1119
+
1120
+        wrapper.addSubview(heading)
1121
+        wrapper.addSubview(textFieldContainer)
1122
+
1123
+        NSLayoutConstraint.activate([
1124
+            heading.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
1125
+            heading.topAnchor.constraint(equalTo: wrapper.topAnchor),
1126
+
1127
+            textFieldContainer.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
1128
+            textFieldContainer.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
1129
+            textFieldContainer.topAnchor.constraint(equalTo: heading.bottomAnchor, constant: 10),
1130
+            textFieldContainer.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor),
1131
+
1132
+            field.leadingAnchor.constraint(equalTo: textFieldContainer.leadingAnchor, constant: 12),
1133
+            field.trailingAnchor.constraint(equalTo: textFieldContainer.trailingAnchor, constant: -12),
1134
+            field.centerYAnchor.constraint(equalTo: textFieldContainer.centerYAnchor)
1135
+        ])
1136
+
1137
+        return wrapper
1138
+    }
1139
+
914 1140
     func joinWithURLHeading() -> NSView {
915 1141
         let container = NSView()
916 1142
         container.translatesAutoresizingMaskIntoConstraints = false