|
|
@@ -8,19 +8,311 @@
|
|
8
|
8
|
import Cocoa
|
|
9
|
9
|
|
|
10
|
10
|
class ViewController: NSViewController {
|
|
|
11
|
+ private let sidebarWidth: CGFloat = 78
|
|
|
12
|
+ // Dark-mode values copied from classroom_app Palette(isDarkMode: true).
|
|
|
13
|
+ private let appBackground = NSColor(calibratedRed: 10 / 255, green: 11 / 255, blue: 12 / 255, alpha: 1)
|
|
|
14
|
+ private let sidebarBackground = NSColor(calibratedRed: 16 / 255, green: 17 / 255, blue: 19 / 255, alpha: 1)
|
|
|
15
|
+ private let sidebarActiveBackground = NSColor(calibratedRed: 22 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1)
|
|
|
16
|
+ private let cardBackground = NSColor(calibratedRed: 20 / 255, green: 21 / 255, blue: 24 / 255, alpha: 1)
|
|
|
17
|
+ private let separatorColor = NSColor(calibratedRed: 26 / 255, green: 27 / 255, blue: 30 / 255, alpha: 1)
|
|
|
18
|
+ private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
|
|
|
19
|
+ private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
|
|
|
20
|
+ private let secondaryText = NSColor(calibratedWhite: 0.78, alpha: 1)
|
|
|
21
|
+ private let mutedText = NSColor(calibratedWhite: 0.66, alpha: 1)
|
|
11
|
22
|
|
|
12
|
23
|
override func viewDidLoad() {
|
|
13
|
24
|
super.viewDidLoad()
|
|
|
25
|
+ setupUI()
|
|
|
26
|
+ }
|
|
|
27
|
+
|
|
|
28
|
+ override func viewDidAppear() {
|
|
|
29
|
+ super.viewDidAppear()
|
|
|
30
|
+
|
|
|
31
|
+ // Keep a practical default size for this desktop mock.
|
|
|
32
|
+ view.window?.setContentSize(NSSize(width: 960, height: 700))
|
|
|
33
|
+ view.window?.title = "zoom Workplace"
|
|
|
34
|
+ }
|
|
|
35
|
+
|
|
|
36
|
+ private func setupUI() {
|
|
|
37
|
+ view.wantsLayer = true
|
|
|
38
|
+ view.layer?.backgroundColor = appBackground.cgColor
|
|
|
39
|
+
|
|
|
40
|
+ let sidebar = makeSidebar()
|
|
|
41
|
+ let content = makeContent()
|
|
|
42
|
+
|
|
|
43
|
+ view.addSubview(sidebar)
|
|
|
44
|
+ view.addSubview(content)
|
|
|
45
|
+
|
|
|
46
|
+ sidebar.translatesAutoresizingMaskIntoConstraints = false
|
|
|
47
|
+ content.translatesAutoresizingMaskIntoConstraints = false
|
|
|
48
|
+
|
|
|
49
|
+ NSLayoutConstraint.activate([
|
|
|
50
|
+ sidebar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
51
|
+ sidebar.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
52
|
+ sidebar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
53
|
+ sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
|
|
|
54
|
+
|
|
|
55
|
+ content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
|
|
|
56
|
+ content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
57
|
+ content.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
58
|
+ content.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
|
59
|
+ ])
|
|
|
60
|
+ }
|
|
|
61
|
+
|
|
|
62
|
+ private func makeSidebar() -> NSView {
|
|
|
63
|
+ let sidebar = NSView()
|
|
|
64
|
+ sidebar.wantsLayer = true
|
|
|
65
|
+ sidebar.layer?.backgroundColor = sidebarBackground.cgColor
|
|
|
66
|
+
|
|
|
67
|
+ let title = NSTextField(labelWithString: "Home")
|
|
|
68
|
+ title.font = .systemFont(ofSize: 11, weight: .medium)
|
|
|
69
|
+ title.textColor = primaryText
|
|
|
70
|
+ title.alignment = .center
|
|
|
71
|
+
|
|
|
72
|
+ let homeBox = NSBox()
|
|
|
73
|
+ homeBox.boxType = .custom
|
|
|
74
|
+ homeBox.borderType = .noBorder
|
|
|
75
|
+ homeBox.cornerRadius = 12
|
|
|
76
|
+ homeBox.fillColor = sidebarActiveBackground
|
|
|
77
|
+
|
|
|
78
|
+ let homeIcon = NSTextField(labelWithString: "⌂")
|
|
|
79
|
+ homeIcon.font = .systemFont(ofSize: 20, weight: .medium)
|
|
|
80
|
+ homeIcon.textColor = .white
|
|
|
81
|
+ homeIcon.alignment = .center
|
|
14
|
82
|
|
|
15
|
|
- // Do any additional setup after loading the view.
|
|
|
83
|
+ homeBox.contentView?.addSubview(homeIcon)
|
|
|
84
|
+ homeIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
85
|
+ NSLayoutConstraint.activate([
|
|
|
86
|
+ homeIcon.centerXAnchor.constraint(equalTo: homeBox.contentView!.centerXAnchor),
|
|
|
87
|
+ homeIcon.centerYAnchor.constraint(equalTo: homeBox.contentView!.centerYAnchor)
|
|
|
88
|
+ ])
|
|
|
89
|
+
|
|
|
90
|
+ let sidebarItems = ["Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"]
|
|
|
91
|
+ var itemViews: [NSView] = []
|
|
|
92
|
+
|
|
|
93
|
+ for item in sidebarItems {
|
|
|
94
|
+ let container = NSView()
|
|
|
95
|
+ let icon = NSTextField(labelWithString: "◻︎")
|
|
|
96
|
+ icon.font = .systemFont(ofSize: 15, weight: .regular)
|
|
|
97
|
+ icon.textColor = secondaryText
|
|
|
98
|
+ icon.alignment = .center
|
|
|
99
|
+
|
|
|
100
|
+ let label = NSTextField(labelWithString: item)
|
|
|
101
|
+ label.font = .systemFont(ofSize: 12, weight: .regular)
|
|
|
102
|
+ label.textColor = secondaryText
|
|
|
103
|
+ label.alignment = .center
|
|
|
104
|
+
|
|
|
105
|
+ container.addSubview(icon)
|
|
|
106
|
+ container.addSubview(label)
|
|
|
107
|
+ icon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
108
|
+ label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
109
|
+
|
|
|
110
|
+ NSLayoutConstraint.activate([
|
|
|
111
|
+ icon.topAnchor.constraint(equalTo: container.topAnchor),
|
|
|
112
|
+ icon.centerXAnchor.constraint(equalTo: container.centerXAnchor),
|
|
|
113
|
+ label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 5),
|
|
|
114
|
+ label.centerXAnchor.constraint(equalTo: container.centerXAnchor),
|
|
|
115
|
+ label.bottomAnchor.constraint(equalTo: container.bottomAnchor)
|
|
|
116
|
+ ])
|
|
|
117
|
+ itemViews.append(container)
|
|
|
118
|
+ }
|
|
|
119
|
+
|
|
|
120
|
+ let stack = NSStackView(views: itemViews)
|
|
|
121
|
+ stack.orientation = .vertical
|
|
|
122
|
+ stack.spacing = 16
|
|
|
123
|
+ stack.alignment = .centerX
|
|
|
124
|
+ stack.distribution = .gravityAreas
|
|
|
125
|
+
|
|
|
126
|
+ sidebar.addSubview(homeBox)
|
|
|
127
|
+ sidebar.addSubview(title)
|
|
|
128
|
+ sidebar.addSubview(stack)
|
|
|
129
|
+
|
|
|
130
|
+ homeBox.translatesAutoresizingMaskIntoConstraints = false
|
|
|
131
|
+ title.translatesAutoresizingMaskIntoConstraints = false
|
|
|
132
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
133
|
+
|
|
|
134
|
+ NSLayoutConstraint.activate([
|
|
|
135
|
+ homeBox.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 42),
|
|
|
136
|
+ homeBox.centerXAnchor.constraint(equalTo: sidebar.centerXAnchor),
|
|
|
137
|
+ homeBox.widthAnchor.constraint(equalToConstant: 56),
|
|
|
138
|
+ homeBox.heightAnchor.constraint(equalToConstant: 56),
|
|
|
139
|
+
|
|
|
140
|
+ title.topAnchor.constraint(equalTo: homeBox.bottomAnchor, constant: 8),
|
|
|
141
|
+ title.centerXAnchor.constraint(equalTo: sidebar.centerXAnchor),
|
|
|
142
|
+
|
|
|
143
|
+ stack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 30),
|
|
|
144
|
+ stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor),
|
|
|
145
|
+ stack.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor)
|
|
|
146
|
+ ])
|
|
|
147
|
+
|
|
|
148
|
+ return sidebar
|
|
16
|
149
|
}
|
|
17
|
150
|
|
|
18
|
|
- override var representedObject: Any? {
|
|
19
|
|
- didSet {
|
|
20
|
|
- // Update the view, if already loaded.
|
|
|
151
|
+ private func makeContent() -> NSView {
|
|
|
152
|
+ let content = NSView()
|
|
|
153
|
+
|
|
|
154
|
+ let back = NSTextField(labelWithString: "‹ Back")
|
|
|
155
|
+ back.font = .systemFont(ofSize: 34, weight: .regular)
|
|
|
156
|
+ back.textColor = accentBlue
|
|
|
157
|
+
|
|
|
158
|
+ let logo = NSTextField(labelWithString: "zoom\nWorkplace")
|
|
|
159
|
+ logo.font = .systemFont(ofSize: 24, weight: .bold)
|
|
|
160
|
+ logo.textColor = primaryText
|
|
|
161
|
+ logo.alignment = .center
|
|
|
162
|
+ logo.maximumNumberOfLines = 2
|
|
|
163
|
+
|
|
|
164
|
+ let domain = NSTextField(labelWithString: "us05web.zoom.us")
|
|
|
165
|
+ domain.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
|
166
|
+ domain.textColor = primaryText
|
|
|
167
|
+ domain.alignment = .center
|
|
|
168
|
+
|
|
|
169
|
+ let emailField = NSTextField()
|
|
|
170
|
+ emailField.placeholderString = "Email or phone number"
|
|
|
171
|
+ emailField.font = .systemFont(ofSize: 20, weight: .regular)
|
|
|
172
|
+ emailField.textColor = .white
|
|
|
173
|
+ emailField.focusRingType = .none
|
|
|
174
|
+ emailField.wantsLayer = true
|
|
|
175
|
+ emailField.layer?.cornerRadius = 10
|
|
|
176
|
+ emailField.layer?.borderWidth = 1.5
|
|
|
177
|
+ emailField.layer?.borderColor = accentBlue.cgColor
|
|
|
178
|
+ emailField.layer?.backgroundColor = cardBackground.cgColor
|
|
|
179
|
+
|
|
|
180
|
+ let nextButton = NSButton(title: "Next", target: nil, action: nil)
|
|
|
181
|
+ nextButton.font = .systemFont(ofSize: 20, weight: .semibold)
|
|
|
182
|
+ nextButton.bezelStyle = .regularSquare
|
|
|
183
|
+ nextButton.isBordered = false
|
|
|
184
|
+ nextButton.wantsLayer = true
|
|
|
185
|
+ nextButton.layer?.cornerRadius = 10
|
|
|
186
|
+ nextButton.layer?.backgroundColor = cardBackground.cgColor
|
|
|
187
|
+ nextButton.contentTintColor = mutedText
|
|
|
188
|
+
|
|
|
189
|
+ let divider = NSBox()
|
|
|
190
|
+ divider.boxType = .separator
|
|
|
191
|
+ divider.borderColor = separatorColor
|
|
|
192
|
+
|
|
|
193
|
+ let signInText = NSTextField(labelWithString: "or sign in with")
|
|
|
194
|
+ signInText.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
195
|
+ signInText.textColor = secondaryText
|
|
|
196
|
+ signInText.alignment = .center
|
|
|
197
|
+
|
|
|
198
|
+ let ssoButton = makeSocialButton(icon: "🔑", text: "SSO")
|
|
|
199
|
+ let googleButton = makeSocialButton(icon: "G", text: "Google")
|
|
|
200
|
+ let appleButton = makeSocialButton(icon: "", text: "Apple")
|
|
|
201
|
+ let facebookButton = makeSocialButton(icon: "f", text: "Facebook")
|
|
|
202
|
+ let microsoftButton = makeSocialButton(icon: "■", text: "Microsoft")
|
|
|
203
|
+
|
|
|
204
|
+ let socialStack = NSStackView(views: [ssoButton, googleButton, appleButton, facebookButton, microsoftButton])
|
|
|
205
|
+ socialStack.orientation = .horizontal
|
|
|
206
|
+ socialStack.spacing = 14
|
|
|
207
|
+ socialStack.distribution = .fillEqually
|
|
|
208
|
+
|
|
|
209
|
+ let signup = NSTextField(labelWithString: "Don't have an account? Sign up")
|
|
|
210
|
+ signup.font = .systemFont(ofSize: 15, weight: .regular)
|
|
|
211
|
+ signup.textColor = primaryText
|
|
|
212
|
+ signup.alignment = .center
|
|
|
213
|
+
|
|
|
214
|
+ let footer = NSTextField(labelWithString: "Help Terms Privacy")
|
|
|
215
|
+ footer.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
216
|
+ footer.textColor = NSColor(calibratedRed: 72 / 255, green: 129 / 255, blue: 218 / 255, alpha: 1)
|
|
|
217
|
+ footer.alignment = .center
|
|
|
218
|
+
|
|
|
219
|
+ content.addSubview(back)
|
|
|
220
|
+ content.addSubview(logo)
|
|
|
221
|
+ content.addSubview(domain)
|
|
|
222
|
+ content.addSubview(emailField)
|
|
|
223
|
+ content.addSubview(nextButton)
|
|
|
224
|
+ content.addSubview(divider)
|
|
|
225
|
+ content.addSubview(signInText)
|
|
|
226
|
+ content.addSubview(socialStack)
|
|
|
227
|
+ content.addSubview(signup)
|
|
|
228
|
+ content.addSubview(footer)
|
|
|
229
|
+
|
|
|
230
|
+ [back, logo, domain, emailField, nextButton, divider, signInText, socialStack, signup, footer].forEach {
|
|
|
231
|
+ $0.translatesAutoresizingMaskIntoConstraints = false
|
|
21
|
232
|
}
|
|
|
233
|
+
|
|
|
234
|
+ NSLayoutConstraint.activate([
|
|
|
235
|
+ back.topAnchor.constraint(equalTo: content.topAnchor, constant: 26),
|
|
|
236
|
+ back.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 34),
|
|
|
237
|
+
|
|
|
238
|
+ logo.topAnchor.constraint(equalTo: content.topAnchor, constant: 118),
|
|
|
239
|
+ logo.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
240
|
+
|
|
|
241
|
+ domain.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: 12),
|
|
|
242
|
+ domain.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
243
|
+
|
|
|
244
|
+ emailField.topAnchor.constraint(equalTo: domain.bottomAnchor, constant: 30),
|
|
|
245
|
+ emailField.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
246
|
+ emailField.widthAnchor.constraint(equalToConstant: 520),
|
|
|
247
|
+ emailField.heightAnchor.constraint(equalToConstant: 52),
|
|
|
248
|
+
|
|
|
249
|
+ nextButton.topAnchor.constraint(equalTo: emailField.bottomAnchor, constant: 20),
|
|
|
250
|
+ nextButton.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
251
|
+ nextButton.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
252
|
+ nextButton.heightAnchor.constraint(equalToConstant: 52),
|
|
|
253
|
+
|
|
|
254
|
+ divider.topAnchor.constraint(equalTo: nextButton.bottomAnchor, constant: 28),
|
|
|
255
|
+ divider.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
256
|
+ divider.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
257
|
+
|
|
|
258
|
+ signInText.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
259
|
+ signInText.centerYAnchor.constraint(equalTo: divider.centerYAnchor),
|
|
|
260
|
+
|
|
|
261
|
+ socialStack.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 18),
|
|
|
262
|
+ socialStack.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
263
|
+ socialStack.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
264
|
+
|
|
|
265
|
+ signup.topAnchor.constraint(equalTo: socialStack.bottomAnchor, constant: 14),
|
|
|
266
|
+ signup.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
267
|
+
|
|
|
268
|
+ footer.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -16),
|
|
|
269
|
+ footer.centerXAnchor.constraint(equalTo: content.centerXAnchor)
|
|
|
270
|
+ ])
|
|
|
271
|
+
|
|
|
272
|
+ return content
|
|
22
|
273
|
}
|
|
23
|
274
|
|
|
|
275
|
+ private func makeSocialButton(icon: String, text: String) -> NSView {
|
|
|
276
|
+ let wrapper = NSView()
|
|
|
277
|
+ let iconLabel = NSTextField(labelWithString: icon)
|
|
|
278
|
+ iconLabel.font = .systemFont(ofSize: 22, weight: .medium)
|
|
|
279
|
+ iconLabel.alignment = .center
|
|
|
280
|
+ iconLabel.textColor = primaryText
|
|
|
281
|
+
|
|
|
282
|
+ let iconBox = NSBox()
|
|
|
283
|
+ iconBox.boxType = .custom
|
|
|
284
|
+ iconBox.cornerRadius = 12
|
|
|
285
|
+ iconBox.borderType = .noBorder
|
|
|
286
|
+ iconBox.fillColor = cardBackground
|
|
24
|
287
|
|
|
|
288
|
+ let textLabel = NSTextField(labelWithString: text)
|
|
|
289
|
+ textLabel.font = .systemFont(ofSize: 12, weight: .regular)
|
|
|
290
|
+ textLabel.textColor = secondaryText
|
|
|
291
|
+ textLabel.alignment = .center
|
|
|
292
|
+
|
|
|
293
|
+ wrapper.addSubview(iconBox)
|
|
|
294
|
+ wrapper.addSubview(textLabel)
|
|
|
295
|
+ iconBox.contentView?.addSubview(iconLabel)
|
|
|
296
|
+
|
|
|
297
|
+ iconBox.translatesAutoresizingMaskIntoConstraints = false
|
|
|
298
|
+ iconLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
299
|
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
300
|
+
|
|
|
301
|
+ NSLayoutConstraint.activate([
|
|
|
302
|
+ iconBox.topAnchor.constraint(equalTo: wrapper.topAnchor),
|
|
|
303
|
+ iconBox.centerXAnchor.constraint(equalTo: wrapper.centerXAnchor),
|
|
|
304
|
+ iconBox.widthAnchor.constraint(equalToConstant: 52),
|
|
|
305
|
+ iconBox.heightAnchor.constraint(equalToConstant: 52),
|
|
|
306
|
+
|
|
|
307
|
+ iconLabel.centerXAnchor.constraint(equalTo: iconBox.contentView!.centerXAnchor),
|
|
|
308
|
+ iconLabel.centerYAnchor.constraint(equalTo: iconBox.contentView!.centerYAnchor),
|
|
|
309
|
+
|
|
|
310
|
+ textLabel.topAnchor.constraint(equalTo: iconBox.bottomAnchor, constant: 6),
|
|
|
311
|
+ textLabel.centerXAnchor.constraint(equalTo: wrapper.centerXAnchor),
|
|
|
312
|
+ textLabel.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor)
|
|
|
313
|
+ ])
|
|
|
314
|
+
|
|
|
315
|
+ return wrapper
|
|
|
316
|
+ }
|
|
25
|
317
|
}
|
|
26
|
318
|
|