|
|
@@ -15,8 +15,9 @@ private enum SidebarPage: Int {
|
|
15
|
15
|
case joinMeetings = 0
|
|
16
|
16
|
case photo = 1
|
|
17
|
17
|
case enrolled = 2
|
|
18
|
|
- case video = 3
|
|
19
|
|
- case settings = 4
|
|
|
18
|
+ case teaching = 3
|
|
|
19
|
+ case video = 4
|
|
|
20
|
+ case settings = 5
|
|
20
|
21
|
}
|
|
21
|
22
|
|
|
22
|
23
|
private enum ZoomJoinMode: Int {
|
|
|
@@ -289,6 +290,7 @@ final class ViewController: NSViewController {
|
|
289
|
290
|
private var lastKnownPremiumAccess = false
|
|
290
|
291
|
private var displayedScheduleTodos: [ClassroomTodoItem] = []
|
|
291
|
292
|
private var enrolledCachedCourses: [ClassroomCourse] = []
|
|
|
293
|
+ private var teachingCachedCourses: [ClassroomCourse] = []
|
|
292
|
294
|
private var appUsageSessionStartDate: Date?
|
|
293
|
295
|
private var hasObservedAppLifecycleForUsage = false
|
|
294
|
296
|
private var premiumUpgradeRatingPromptWorkItem: DispatchWorkItem?
|
|
|
@@ -360,9 +362,11 @@ final class ViewController: NSViewController {
|
|
360
|
362
|
private weak var schedulePageCardsScrollView: NSScrollView?
|
|
361
|
363
|
private weak var enrolledPageHeadingLabel: NSTextField?
|
|
362
|
364
|
private weak var enrolledPageCardsStack: NSStackView?
|
|
363
|
|
- private let enrolledSingleCardWidth: CGFloat = 320
|
|
|
365
|
+ private weak var teachingPageHeadingLabel: NSTextField?
|
|
|
366
|
+ private weak var teachingPageCardsStack: NSStackView?
|
|
364
|
367
|
private var enrolledClassDetailsPopover: NSPopover?
|
|
365
|
368
|
private var enrolledCourseByCardID: [String: ClassroomCourse] = [:]
|
|
|
369
|
+ private var teachingCourseByCardID: [String: ClassroomCourse] = [:]
|
|
366
|
370
|
|
|
367
|
371
|
// MARK: - Calendar page (custom month UI)
|
|
368
|
372
|
private var calendarPageMonthAnchor: Date = Calendar.current.startOfDay(for: Date())
|
|
|
@@ -1270,6 +1274,7 @@ private extension ViewController {
|
|
1270
|
1274
|
pageCache[.joinMeetings] = nil
|
|
1271
|
1275
|
pageCache[.photo] = nil
|
|
1272
|
1276
|
pageCache[.enrolled] = nil
|
|
|
1277
|
+ pageCache[.teaching] = nil
|
|
1273
|
1278
|
pageCache[.video] = nil
|
|
1274
|
1279
|
pageCache[.settings] = nil
|
|
1275
|
1280
|
showSidebarPage(selectedSidebarPage)
|
|
|
@@ -1518,6 +1523,8 @@ private extension ViewController {
|
|
1518
|
1523
|
built = makeSchedulePageContent()
|
|
1519
|
1524
|
case .enrolled:
|
|
1520
|
1525
|
built = makeEnrolledPageContent()
|
|
|
1526
|
+ case .teaching:
|
|
|
1527
|
+ built = makeTeachingPageContent()
|
|
1521
|
1528
|
case .video:
|
|
1522
|
1529
|
built = makeCalendarPageContent()
|
|
1523
|
1530
|
case .settings:
|
|
|
@@ -1878,6 +1885,8 @@ private extension ViewController {
|
|
1878
|
1885
|
title = "Schedule"
|
|
1879
|
1886
|
case .enrolled:
|
|
1880
|
1887
|
title = "Enrolled"
|
|
|
1888
|
+ case .teaching:
|
|
|
1889
|
+ title = "Teaching"
|
|
1881
|
1890
|
case .video:
|
|
1882
|
1891
|
title = "Calendar"
|
|
1883
|
1892
|
case .settings:
|
|
|
@@ -1921,7 +1930,7 @@ private extension ViewController {
|
|
1921
|
1930
|
private func logoTemplateForSidebarPage(_ page: SidebarPage) -> Bool {
|
|
1922
|
1931
|
switch page {
|
|
1923
|
1932
|
case .photo: return false
|
|
1924
|
|
- case .joinMeetings, .enrolled, .video, .settings: return true
|
|
|
1933
|
+ case .joinMeetings, .enrolled, .teaching, .video, .settings: return true
|
|
1925
|
1934
|
}
|
|
1926
|
1935
|
}
|
|
1927
|
1936
|
|
|
|
@@ -1982,12 +1991,15 @@ private extension ViewController {
|
|
1982
|
1991
|
menuStack.addArrangedSubview(joinRow)
|
|
1983
|
1992
|
sidebarRowViews[.joinMeetings] = joinRow
|
|
1984
|
1993
|
menuStack.addArrangedSubview(sidebarSectionTitle("Planning"))
|
|
1985
|
|
- let photoRow = sidebarItem("Schedule", icon: "", page: .photo, systemSymbolName: "clock.badge.checkmark")
|
|
|
1994
|
+ let photoRow = sidebarItem("To-Do", icon: "", page: .photo, systemSymbolName: "clock.badge.checkmark")
|
|
1986
|
1995
|
menuStack.addArrangedSubview(photoRow)
|
|
1987
|
1996
|
sidebarRowViews[.photo] = photoRow
|
|
1988
|
1997
|
let enrolledRow = sidebarItem("Enrolled", icon: "", page: .enrolled, systemSymbolName: "person.3.sequence.fill")
|
|
1989
|
1998
|
menuStack.addArrangedSubview(enrolledRow)
|
|
1990
|
1999
|
sidebarRowViews[.enrolled] = enrolledRow
|
|
|
2000
|
+ let teachingRow = sidebarItem("Teaching", icon: "", page: .teaching, systemSymbolName: "person.2.badge.gearshape.fill")
|
|
|
2001
|
+ menuStack.addArrangedSubview(teachingRow)
|
|
|
2002
|
+ sidebarRowViews[.teaching] = teachingRow
|
|
1991
|
2003
|
let videoRow = sidebarItem("Calendar", icon: "", page: .video, systemSymbolName: "calendar")
|
|
1992
|
2004
|
menuStack.addArrangedSubview(videoRow)
|
|
1993
|
2005
|
sidebarRowViews[.video] = videoRow
|
|
|
@@ -2328,6 +2340,109 @@ private extension ViewController {
|
|
2328
|
2340
|
return panel
|
|
2329
|
2341
|
}
|
|
2330
|
2342
|
|
|
|
2343
|
+ func makeTeachingPageContent() -> NSView {
|
|
|
2344
|
+ let panel = NSView()
|
|
|
2345
|
+ panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2346
|
+ panel.userInterfaceLayoutDirection = .leftToRight
|
|
|
2347
|
+
|
|
|
2348
|
+ let contentStack = NSStackView()
|
|
|
2349
|
+ contentStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2350
|
+ contentStack.userInterfaceLayoutDirection = .leftToRight
|
|
|
2351
|
+ contentStack.orientation = .vertical
|
|
|
2352
|
+ contentStack.spacing = 14
|
|
|
2353
|
+ contentStack.alignment = .width
|
|
|
2354
|
+ contentStack.distribution = .fill
|
|
|
2355
|
+
|
|
|
2356
|
+ let titleRow = NSStackView()
|
|
|
2357
|
+ titleRow.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2358
|
+ titleRow.userInterfaceLayoutDirection = .leftToRight
|
|
|
2359
|
+ titleRow.orientation = .horizontal
|
|
|
2360
|
+ titleRow.alignment = .centerY
|
|
|
2361
|
+ titleRow.distribution = .fill
|
|
|
2362
|
+ titleRow.spacing = 10
|
|
|
2363
|
+
|
|
|
2364
|
+ let titleLabel = textLabel("Teaching Classes", font: typography.pageTitle, color: palette.textPrimary)
|
|
|
2365
|
+ titleLabel.alignment = .left
|
|
|
2366
|
+ titleLabel.maximumNumberOfLines = 1
|
|
|
2367
|
+ titleLabel.lineBreakMode = .byTruncatingTail
|
|
|
2368
|
+ titleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
|
2369
|
+ titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
|
2370
|
+
|
|
|
2371
|
+ let spacer = NSView()
|
|
|
2372
|
+ spacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2373
|
+ spacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
2374
|
+ spacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
2375
|
+
|
|
|
2376
|
+ let refreshButton = makeScheduleRefreshButton()
|
|
|
2377
|
+ refreshButton.target = self
|
|
|
2378
|
+ refreshButton.action = #selector(teachingPageRefreshPressed(_:))
|
|
|
2379
|
+
|
|
|
2380
|
+ titleRow.addArrangedSubview(titleLabel)
|
|
|
2381
|
+ titleRow.addArrangedSubview(spacer)
|
|
|
2382
|
+ titleRow.addArrangedSubview(refreshButton)
|
|
|
2383
|
+
|
|
|
2384
|
+ let heading = textLabel(teachingPageInitialHeadingText(), font: typography.dateHeading, color: palette.textSecondary)
|
|
|
2385
|
+ heading.alignment = .left
|
|
|
2386
|
+ heading.maximumNumberOfLines = 2
|
|
|
2387
|
+ heading.lineBreakMode = .byWordWrapping
|
|
|
2388
|
+ teachingPageHeadingLabel = heading
|
|
|
2389
|
+
|
|
|
2390
|
+ let scroll = NSScrollView()
|
|
|
2391
|
+ scroll.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2392
|
+ scroll.userInterfaceLayoutDirection = .leftToRight
|
|
|
2393
|
+ scroll.drawsBackground = false
|
|
|
2394
|
+ scroll.hasHorizontalScroller = false
|
|
|
2395
|
+ scroll.hasVerticalScroller = true
|
|
|
2396
|
+ scroll.autohidesScrollers = true
|
|
|
2397
|
+ scroll.borderType = .noBorder
|
|
|
2398
|
+ scroll.scrollerStyle = .overlay
|
|
|
2399
|
+ scroll.automaticallyAdjustsContentInsets = false
|
|
|
2400
|
+ let clip = TopAlignedClipView()
|
|
|
2401
|
+ clip.drawsBackground = false
|
|
|
2402
|
+ scroll.contentView = clip
|
|
|
2403
|
+
|
|
|
2404
|
+ let stack = NSStackView()
|
|
|
2405
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2406
|
+ stack.userInterfaceLayoutDirection = .leftToRight
|
|
|
2407
|
+ stack.orientation = .vertical
|
|
|
2408
|
+ stack.spacing = 14
|
|
|
2409
|
+ stack.alignment = .leading
|
|
|
2410
|
+ teachingPageCardsStack = stack
|
|
|
2411
|
+ scroll.documentView = stack
|
|
|
2412
|
+
|
|
|
2413
|
+ NSLayoutConstraint.activate([
|
|
|
2414
|
+ stack.leadingAnchor.constraint(equalTo: scroll.contentView.leadingAnchor),
|
|
|
2415
|
+ stack.trailingAnchor.constraint(equalTo: scroll.contentView.trailingAnchor),
|
|
|
2416
|
+ stack.topAnchor.constraint(equalTo: scroll.contentView.topAnchor),
|
|
|
2417
|
+ stack.widthAnchor.constraint(equalTo: scroll.contentView.widthAnchor)
|
|
|
2418
|
+ ])
|
|
|
2419
|
+
|
|
|
2420
|
+ contentStack.addArrangedSubview(titleRow)
|
|
|
2421
|
+ contentStack.setCustomSpacing(10, after: titleRow)
|
|
|
2422
|
+ contentStack.addArrangedSubview(heading)
|
|
|
2423
|
+ contentStack.setCustomSpacing(12, after: heading)
|
|
|
2424
|
+ contentStack.addArrangedSubview(scroll)
|
|
|
2425
|
+ panel.addSubview(contentStack)
|
|
|
2426
|
+
|
|
|
2427
|
+ NSLayoutConstraint.activate([
|
|
|
2428
|
+ contentStack.leftAnchor.constraint(equalTo: panel.leftAnchor, constant: 28),
|
|
|
2429
|
+ contentStack.rightAnchor.constraint(equalTo: panel.rightAnchor, constant: -28),
|
|
|
2430
|
+ contentStack.topAnchor.constraint(equalTo: panel.topAnchor),
|
|
|
2431
|
+ contentStack.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -16),
|
|
|
2432
|
+ titleRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
|
|
|
2433
|
+ heading.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
|
|
|
2434
|
+ scroll.widthAnchor.constraint(equalTo: contentStack.widthAnchor),
|
|
|
2435
|
+ scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 420)
|
|
|
2436
|
+ ])
|
|
|
2437
|
+
|
|
|
2438
|
+ renderTeachingClassCards([])
|
|
|
2439
|
+ Task { [weak self] in
|
|
|
2440
|
+ await self?.loadTeachingClasses()
|
|
|
2441
|
+ }
|
|
|
2442
|
+
|
|
|
2443
|
+ return panel
|
|
|
2444
|
+ }
|
|
|
2445
|
+
|
|
2331
|
2446
|
func makeCalendarPageContent() -> NSView {
|
|
2332
|
2447
|
let panel = NSView()
|
|
2333
|
2448
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -6461,12 +6576,22 @@ private extension ViewController {
|
|
6461
|
6576
|
googleOAuth.loadTokens() == nil ? "Connect Google to see enrolled classes" : "Loading classes…"
|
|
6462
|
6577
|
}
|
|
6463
|
6578
|
|
|
|
6579
|
+ private func teachingPageInitialHeadingText() -> String {
|
|
|
6580
|
+ googleOAuth.loadTokens() == nil ? "Connect Google to see teaching classes" : "Loading classes…"
|
|
|
6581
|
+ }
|
|
|
6582
|
+
|
|
6464
|
6583
|
@objc func enrolledPageRefreshPressed(_ sender: NSButton) {
|
|
6465
|
6584
|
Task { [weak self] in
|
|
6466
|
6585
|
await self?.loadEnrolledClasses()
|
|
6467
|
6586
|
}
|
|
6468
|
6587
|
}
|
|
6469
|
6588
|
|
|
|
6589
|
+ @objc func teachingPageRefreshPressed(_ sender: NSButton) {
|
|
|
6590
|
+ Task { [weak self] in
|
|
|
6591
|
+ await self?.loadTeachingClasses()
|
|
|
6592
|
+ }
|
|
|
6593
|
+ }
|
|
|
6594
|
+
|
|
6470
|
6595
|
@objc func scheduleFilterDropdownChanged(_ sender: NSPopUpButton) {
|
|
6471
|
6596
|
guard let selectedItem = sender.selectedItem,
|
|
6472
|
6597
|
let filter = ScheduleFilter(rawValue: selectedItem.tag) else { return }
|
|
|
@@ -6578,6 +6703,12 @@ private extension ViewController {
|
|
6578
|
6703
|
return "\(courses.count) enrolled class\(courses.count == 1 ? "" : "es")"
|
|
6579
|
6704
|
}
|
|
6580
|
6705
|
|
|
|
6706
|
+ private func teachingPageHeadingText(for courses: [ClassroomCourse]) -> String {
|
|
|
6707
|
+ if googleOAuth.loadTokens() == nil { return "Connect Google to see teaching classes" }
|
|
|
6708
|
+ if courses.isEmpty { return "No active teaching classes" }
|
|
|
6709
|
+ return "\(courses.count) teaching class\(courses.count == 1 ? "" : "es")"
|
|
|
6710
|
+ }
|
|
|
6711
|
+
|
|
6581
|
6712
|
private func openURL(_ url: URL) {
|
|
6582
|
6713
|
NSWorkspace.shared.open(url)
|
|
6583
|
6714
|
}
|
|
|
@@ -6616,16 +6747,51 @@ private extension ViewController {
|
|
6616
|
6747
|
return
|
|
6617
|
6748
|
}
|
|
6618
|
6749
|
|
|
6619
|
|
- let isSingleCard = (courses.count == 1)
|
|
6620
|
6750
|
for course in courses {
|
|
6621
|
6751
|
let card = enrolledClassCardButton(course: course)
|
|
6622
|
6752
|
stack.addArrangedSubview(card)
|
|
6623
|
|
- if isSingleCard {
|
|
6624
|
|
- card.widthAnchor.constraint(equalToConstant: enrolledSingleCardWidth).isActive = true
|
|
6625
|
|
- card.widthAnchor.constraint(lessThanOrEqualTo: stack.widthAnchor).isActive = true
|
|
6626
|
|
- } else {
|
|
6627
|
|
- card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
6628
|
|
- }
|
|
|
6753
|
+ card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
6754
|
+ }
|
|
|
6755
|
+ }
|
|
|
6756
|
+
|
|
|
6757
|
+ private func renderTeachingClassCards(_ courses: [ClassroomCourse]) {
|
|
|
6758
|
+ guard let stack = teachingPageCardsStack else { return }
|
|
|
6759
|
+ teachingCourseByCardID.removeAll()
|
|
|
6760
|
+
|
|
|
6761
|
+ stack.arrangedSubviews.forEach { view in
|
|
|
6762
|
+ stack.removeArrangedSubview(view)
|
|
|
6763
|
+ view.removeFromSuperview()
|
|
|
6764
|
+ }
|
|
|
6765
|
+
|
|
|
6766
|
+ if courses.isEmpty {
|
|
|
6767
|
+ let empty = roundedContainer(cornerRadius: 12, color: palette.sectionCard)
|
|
|
6768
|
+ empty.translatesAutoresizingMaskIntoConstraints = false
|
|
|
6769
|
+ empty.heightAnchor.constraint(equalToConstant: 128).isActive = true
|
|
|
6770
|
+ styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
6771
|
+
|
|
|
6772
|
+ let emptyLabel = textLabel(
|
|
|
6773
|
+ googleOAuth.loadTokens() == nil ? "Connect to load classes" : "No teaching classes found",
|
|
|
6774
|
+ font: typography.cardSubtitle,
|
|
|
6775
|
+ color: palette.textSecondary
|
|
|
6776
|
+ )
|
|
|
6777
|
+ emptyLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
6778
|
+ emptyLabel.alignment = .left
|
|
|
6779
|
+ empty.addSubview(emptyLabel)
|
|
|
6780
|
+
|
|
|
6781
|
+ NSLayoutConstraint.activate([
|
|
|
6782
|
+ emptyLabel.leadingAnchor.constraint(equalTo: empty.leadingAnchor, constant: 18),
|
|
|
6783
|
+ emptyLabel.trailingAnchor.constraint(equalTo: empty.trailingAnchor, constant: -18),
|
|
|
6784
|
+ emptyLabel.centerYAnchor.constraint(equalTo: empty.centerYAnchor)
|
|
|
6785
|
+ ])
|
|
|
6786
|
+ stack.addArrangedSubview(empty)
|
|
|
6787
|
+ empty.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
6788
|
+ return
|
|
|
6789
|
+ }
|
|
|
6790
|
+
|
|
|
6791
|
+ for course in courses {
|
|
|
6792
|
+ let card = teachingClassCardButton(course: course)
|
|
|
6793
|
+ stack.addArrangedSubview(card)
|
|
|
6794
|
+ card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
6629
|
6795
|
}
|
|
6630
|
6796
|
}
|
|
6631
|
6797
|
|
|
|
@@ -6664,6 +6830,12 @@ private extension ViewController {
|
|
6664
|
6830
|
enrolledClassCardClicked(view: sender, course: course)
|
|
6665
|
6831
|
}
|
|
6666
|
6832
|
|
|
|
6833
|
+ @objc private func teachingCardButtonPressed(_ sender: NSButton) {
|
|
|
6834
|
+ guard let courseID = sender.identifier?.rawValue,
|
|
|
6835
|
+ let course = teachingCourseByCardID[courseID] else { return }
|
|
|
6836
|
+ enrolledClassCardClicked(view: sender, course: course)
|
|
|
6837
|
+ }
|
|
|
6838
|
+
|
|
6667
|
6839
|
private func enrolledClassCard(course: ClassroomCourse) -> NSView {
|
|
6668
|
6840
|
let card = roundedContainer(cornerRadius: 14, color: palette.sectionCard)
|
|
6669
|
6841
|
card.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -6713,6 +6885,35 @@ private extension ViewController {
|
|
6713
|
6885
|
return card
|
|
6714
|
6886
|
}
|
|
6715
|
6887
|
|
|
|
6888
|
+ private func teachingClassCardButton(course: ClassroomCourse) -> NSView {
|
|
|
6889
|
+ let hit = HoverButton(title: "", target: self, action: #selector(teachingCardButtonPressed(_:)))
|
|
|
6890
|
+ hit.translatesAutoresizingMaskIntoConstraints = false
|
|
|
6891
|
+ hit.isBordered = false
|
|
|
6892
|
+ hit.bezelStyle = .regularSquare
|
|
|
6893
|
+ hit.wantsLayer = true
|
|
|
6894
|
+ hit.identifier = NSUserInterfaceItemIdentifier(course.id)
|
|
|
6895
|
+ teachingCourseByCardID[course.id] = course
|
|
|
6896
|
+ hit.heightAnchor.constraint(greaterThanOrEqualToConstant: 124).isActive = true
|
|
|
6897
|
+
|
|
|
6898
|
+ let card = enrolledClassCard(course: course)
|
|
|
6899
|
+ hit.addSubview(card)
|
|
|
6900
|
+ NSLayoutConstraint.activate([
|
|
|
6901
|
+ card.leadingAnchor.constraint(equalTo: hit.leadingAnchor),
|
|
|
6902
|
+ card.trailingAnchor.constraint(equalTo: hit.trailingAnchor),
|
|
|
6903
|
+ card.topAnchor.constraint(equalTo: hit.topAnchor),
|
|
|
6904
|
+ card.bottomAnchor.constraint(equalTo: hit.bottomAnchor)
|
|
|
6905
|
+ ])
|
|
|
6906
|
+ hit.onHoverChanged = { [weak self, weak card] hovering in
|
|
|
6907
|
+ guard let self, let card else { return }
|
|
|
6908
|
+ let base = self.palette.sectionCard
|
|
|
6909
|
+ let hoverBlend = self.darkModeEnabled ? NSColor.white : NSColor.black
|
|
|
6910
|
+ let hover = base.blended(withFraction: 0.10, of: hoverBlend) ?? base
|
|
|
6911
|
+ card.layer?.backgroundColor = (hovering ? hover : base).cgColor
|
|
|
6912
|
+ }
|
|
|
6913
|
+ hit.onHoverChanged?(false)
|
|
|
6914
|
+ return hit
|
|
|
6915
|
+ }
|
|
|
6916
|
+
|
|
6716
|
6917
|
private func enrolledClassCardClicked(view: NSView, course: ClassroomCourse) {
|
|
6717
|
6918
|
Task { [weak self, weak view] in
|
|
6718
|
6919
|
guard let self, let view else { return }
|
|
|
@@ -6751,6 +6952,7 @@ private extension ViewController {
|
|
6751
|
6952
|
}
|
|
6752
|
6953
|
}
|
|
6753
|
6954
|
|
|
|
6955
|
+
|
|
6754
|
6956
|
private func showEnrolledClassDetailsPopover(details: ClassroomClassDetails, defaultAuthorName: String, relativeTo anchor: NSView) {
|
|
6755
|
6957
|
enrolledClassDetailsPopover?.performClose(nil)
|
|
6756
|
6958
|
let popover = NSPopover()
|
|
|
@@ -7230,6 +7432,48 @@ private extension ViewController {
|
|
7230
|
7432
|
}
|
|
7231
|
7433
|
}
|
|
7232
|
7434
|
|
|
|
7435
|
+ private func loadTeachingClasses() async {
|
|
|
7436
|
+ do {
|
|
|
7437
|
+ if googleOAuth.loadTokens() == nil {
|
|
|
7438
|
+ await MainActor.run {
|
|
|
7439
|
+ teachingCachedCourses = []
|
|
|
7440
|
+ teachingPageHeadingLabel?.stringValue = "Connect Google to see teaching classes"
|
|
|
7441
|
+ renderTeachingClassCards([])
|
|
|
7442
|
+ }
|
|
|
7443
|
+ return
|
|
|
7444
|
+ }
|
|
|
7445
|
+
|
|
|
7446
|
+ let token = try await googleOAuth.validAccessToken(presentingWindow: view.window)
|
|
|
7447
|
+ let courses = try await classroomClient.fetchTeachingCourses(accessToken: token)
|
|
|
7448
|
+
|
|
|
7449
|
+ await MainActor.run {
|
|
|
7450
|
+ teachingCachedCourses = courses
|
|
|
7451
|
+ teachingPageHeadingLabel?.stringValue = teachingPageHeadingText(for: courses)
|
|
|
7452
|
+ renderTeachingClassCards(courses)
|
|
|
7453
|
+ }
|
|
|
7454
|
+ } catch {
|
|
|
7455
|
+ await MainActor.run {
|
|
|
7456
|
+ if errorRequiresReconsentForClassroomScopes(error) {
|
|
|
7457
|
+ _ = try? googleOAuth.signOut()
|
|
|
7458
|
+ applyGoogleProfile(nil)
|
|
|
7459
|
+ updateGoogleAuthButtonTitle()
|
|
|
7460
|
+ teachingCachedCourses = []
|
|
|
7461
|
+ teachingPageHeadingLabel?.stringValue = "Reconnect Google to enable Classroom permissions"
|
|
|
7462
|
+ renderTeachingClassCards([])
|
|
|
7463
|
+ showSimpleAlert(
|
|
|
7464
|
+ title: "Reconnect Google",
|
|
|
7465
|
+ message: "We added Google Classroom permissions. Please connect your Google account again so Google can grant access to your classes."
|
|
|
7466
|
+ )
|
|
|
7467
|
+ return
|
|
|
7468
|
+ }
|
|
|
7469
|
+ teachingCachedCourses = []
|
|
|
7470
|
+ teachingPageHeadingLabel?.stringValue = "Couldn’t load teaching classes"
|
|
|
7471
|
+ renderTeachingClassCards([])
|
|
|
7472
|
+ showSimpleError("Couldn’t load teaching classes.", error: error)
|
|
|
7473
|
+ }
|
|
|
7474
|
+ }
|
|
|
7475
|
+ }
|
|
|
7476
|
+
|
|
7233
|
7477
|
func showScheduleHelp() {
|
|
7234
|
7478
|
let alert = NSAlert()
|
|
7235
|
7479
|
alert.messageText = "Google Classroom to-do"
|
|
|
@@ -7249,6 +7493,7 @@ private extension ViewController {
|
|
7249
|
7493
|
self.pageCache[.joinMeetings] = nil
|
|
7250
|
7494
|
self.pageCache[.photo] = nil
|
|
7251
|
7495
|
self.pageCache[.enrolled] = nil
|
|
|
7496
|
+ self.pageCache[.teaching] = nil
|
|
7252
|
7497
|
self.showSidebarPage(self.selectedSidebarPage)
|
|
7253
|
7498
|
}
|
|
7254
|
7499
|
await self.loadSchedule()
|
|
|
@@ -7278,6 +7523,7 @@ private extension ViewController {
|
|
7278
|
7523
|
self.pageCache[.joinMeetings] = nil
|
|
7279
|
7524
|
self.pageCache[.photo] = nil
|
|
7280
|
7525
|
self.pageCache[.enrolled] = nil
|
|
|
7526
|
+ self.pageCache[.teaching] = nil
|
|
7281
|
7527
|
self.pageCache[.video] = nil
|
|
7282
|
7528
|
self.pageCache[.settings] = nil
|
|
7283
|
7529
|
self.showSidebarPage(self.selectedSidebarPage)
|
|
|
@@ -7330,6 +7576,7 @@ private extension ViewController {
|
|
7330
|
7576
|
pageCache[.joinMeetings] = nil
|
|
7331
|
7577
|
pageCache[.photo] = nil
|
|
7332
|
7578
|
pageCache[.enrolled] = nil
|
|
|
7579
|
+ pageCache[.teaching] = nil
|
|
7333
|
7580
|
pageCache[.video] = nil
|
|
7334
|
7581
|
pageCache[.settings] = nil
|
|
7335
|
7582
|
showSidebarPage(selectedSidebarPage)
|