|
|
@@ -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
|