|
|
@@ -422,6 +422,16 @@ final class ViewController: NSViewController {
|
|
422
|
422
|
case customRange = 4
|
|
423
|
423
|
}
|
|
424
|
424
|
|
|
|
425
|
+ /// Saved meeting list filters on AI Companion (uses each recording’s `endedAt` day in the current calendar).
|
|
|
426
|
+ private enum AiCompanionRecordingsFilter: Int {
|
|
|
427
|
+ case all = 0
|
|
|
428
|
+ case today = 1
|
|
|
429
|
+ case week = 2
|
|
|
430
|
+ case previousWeek = 3
|
|
|
431
|
+ case previousMonth = 4
|
|
|
432
|
+ case customRange = 5
|
|
|
433
|
+ }
|
|
|
434
|
+
|
|
425
|
435
|
private var scheduleFilter: ScheduleFilter = .all
|
|
426
|
436
|
private weak var scheduleDateHeadingLabel: NSTextField?
|
|
427
|
437
|
private weak var scheduleCardsStack: NSStackView?
|
|
|
@@ -449,6 +459,14 @@ final class ViewController: NSViewController {
|
|
449
|
459
|
private var googleAccountPopover: NSPopover?
|
|
450
|
460
|
private var scheduleCachedMeetings: [ScheduledMeeting] = []
|
|
451
|
461
|
private var aiCompanionLocalRecordings: [MeetingRecordingSummary] = []
|
|
|
462
|
+ private var aiCompanionRecordingsFilter: AiCompanionRecordingsFilter = .today
|
|
|
463
|
+ private var aiCompanionFilterFromDate: Date = Calendar.current.startOfDay(for: Date())
|
|
|
464
|
+ private var aiCompanionFilterToDate: Date = Calendar.current.startOfDay(for: Date())
|
|
|
465
|
+ private var aiCompanionRecordingsRangeErrorMessage: String?
|
|
|
466
|
+ private weak var aiCompanionRecordingsFilterDropdown: NSPopUpButton?
|
|
|
467
|
+ private weak var aiCompanionFilterFromDatePicker: NSDatePicker?
|
|
|
468
|
+ private weak var aiCompanionFilterToDatePicker: NSDatePicker?
|
|
|
469
|
+ private weak var aiCompanionRecordingsRangeErrorLabel: NSTextField?
|
|
452
|
470
|
private var activeMeetingRecordingSession: ActiveMeetingRecordingSession?
|
|
453
|
471
|
private var activeMeetingAudioRecorder: AVAudioRecorder?
|
|
454
|
472
|
private var activeMeetingSystemAudioStopper: (() async -> Void)?
|
|
|
@@ -2824,17 +2842,102 @@ private extension ViewController {
|
|
2824
|
2842
|
contentStack.addArrangedSubview(emptyLabel)
|
|
2825
|
2843
|
emptyLabel.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2826
|
2844
|
} else {
|
|
|
2845
|
+ let filterRow = NSStackView()
|
|
|
2846
|
+ filterRow.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2847
|
+ filterRow.userInterfaceLayoutDirection = .leftToRight
|
|
|
2848
|
+ filterRow.orientation = .horizontal
|
|
|
2849
|
+ filterRow.alignment = .centerY
|
|
|
2850
|
+ filterRow.spacing = 18
|
|
|
2851
|
+ filterRow.distribution = .fill
|
|
|
2852
|
+
|
|
|
2853
|
+ let filterDropdown = makeAiCompanionRecordingsFilterDropdown()
|
|
|
2854
|
+ aiCompanionRecordingsFilterDropdown = filterDropdown
|
|
|
2855
|
+ filterRow.addArrangedSubview(filterDropdown)
|
|
|
2856
|
+ filterRow.setCustomSpacing(20, after: filterDropdown)
|
|
|
2857
|
+
|
|
|
2858
|
+ let (fromShell, fromPicker) = makeFilterStyleDatePicker(
|
|
|
2859
|
+ date: aiCompanionFilterFromDate,
|
|
|
2860
|
+ changeAction: #selector(aiCompanionFilterDatePickerChanged(_:))
|
|
|
2861
|
+ )
|
|
|
2862
|
+ aiCompanionFilterFromDatePicker = fromPicker
|
|
|
2863
|
+ filterRow.addArrangedSubview(fromShell)
|
|
|
2864
|
+ filterRow.setCustomSpacing(16, after: fromShell)
|
|
|
2865
|
+
|
|
|
2866
|
+ let (toShell, toPicker) = makeFilterStyleDatePicker(
|
|
|
2867
|
+ date: aiCompanionFilterToDate,
|
|
|
2868
|
+ changeAction: #selector(aiCompanionFilterDatePickerChanged(_:))
|
|
|
2869
|
+ )
|
|
|
2870
|
+ aiCompanionFilterToDatePicker = toPicker
|
|
|
2871
|
+ filterRow.addArrangedSubview(toShell)
|
|
|
2872
|
+ NSLayoutConstraint.activate([
|
|
|
2873
|
+ fromShell.widthAnchor.constraint(equalTo: toShell.widthAnchor),
|
|
|
2874
|
+ fromShell.widthAnchor.constraint(greaterThanOrEqualToConstant: 152)
|
|
|
2875
|
+ ])
|
|
|
2876
|
+
|
|
|
2877
|
+ let filterRowSpacer = NSView()
|
|
|
2878
|
+ filterRowSpacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2879
|
+ filterRowSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
2880
|
+ filterRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
2881
|
+ filterRow.addArrangedSubview(filterRowSpacer)
|
|
|
2882
|
+
|
|
|
2883
|
+ let applyFilterButton = makeSchedulePagePillButton(title: "Apply", action: #selector(aiCompanionRecordingsApplyPressed(_:)))
|
|
|
2884
|
+ filterRow.addArrangedSubview(applyFilterButton)
|
|
|
2885
|
+ filterRow.setCustomSpacing(22, after: applyFilterButton)
|
|
|
2886
|
+ let resetFilterButton = makeSchedulePagePillButton(title: "Reset", action: #selector(aiCompanionRecordingsResetPressed(_:)))
|
|
|
2887
|
+ filterRow.addArrangedSubview(resetFilterButton)
|
|
|
2888
|
+ filterRow.setCustomSpacing(22, after: resetFilterButton)
|
|
|
2889
|
+ filterRow.addArrangedSubview(
|
|
|
2890
|
+ makeScheduleRefreshButton(
|
|
|
2891
|
+ action: #selector(aiCompanionRecordingsRefreshPressed(_:)),
|
|
|
2892
|
+ accessibilityDescription: "Refresh saved meetings"
|
|
|
2893
|
+ )
|
|
|
2894
|
+ )
|
|
|
2895
|
+
|
|
|
2896
|
+ contentStack.addArrangedSubview(filterRow)
|
|
|
2897
|
+ filterRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2898
|
+
|
|
|
2899
|
+ let rangeError = textLabel(
|
|
|
2900
|
+ aiCompanionRecordingsRangeErrorMessage ?? "",
|
|
|
2901
|
+ font: NSFont.systemFont(ofSize: 12, weight: .semibold),
|
|
|
2902
|
+ color: .systemRed
|
|
|
2903
|
+ )
|
|
|
2904
|
+ rangeError.alignment = .left
|
|
|
2905
|
+ rangeError.isHidden = aiCompanionRecordingsRangeErrorMessage == nil
|
|
|
2906
|
+ rangeError.maximumNumberOfLines = 3
|
|
|
2907
|
+ rangeError.lineBreakMode = .byWordWrapping
|
|
|
2908
|
+ aiCompanionRecordingsRangeErrorLabel = rangeError
|
|
|
2909
|
+ contentStack.addArrangedSubview(rangeError)
|
|
|
2910
|
+ rangeError.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2911
|
+
|
|
2827
|
2912
|
let previousMeetingsLabel = textLabel("Previous meetings", font: NSFont.systemFont(ofSize: 14, weight: .semibold), color: palette.textPrimary)
|
|
2828
|
2913
|
previousMeetingsLabel.alignment = .left
|
|
2829
|
2914
|
contentStack.addArrangedSubview(previousMeetingsLabel)
|
|
2830
|
2915
|
previousMeetingsLabel.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2831
|
2916
|
contentStack.setCustomSpacing(10, after: previousMeetingsLabel)
|
|
2832
|
2917
|
|
|
2833
|
|
- for recording in aiCompanionLocalRecordings {
|
|
2834
|
|
- let card = aiCompanionMeetingCard(recording)
|
|
2835
|
|
- contentStack.addArrangedSubview(card)
|
|
2836
|
|
- card.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2918
|
+ let displayedRecordings = filteredAiCompanionRecordings()
|
|
|
2919
|
+ if displayedRecordings.isEmpty {
|
|
|
2920
|
+ let emptyFiltered = textLabel(
|
|
|
2921
|
+ aiCompanionRecordingsRangeErrorMessage == nil
|
|
|
2922
|
+ ? "No meetings match the selected filters."
|
|
|
2923
|
+ : "Adjust the date range and tap Apply.",
|
|
|
2924
|
+ font: typography.fieldLabel,
|
|
|
2925
|
+ color: palette.textMuted
|
|
|
2926
|
+ )
|
|
|
2927
|
+ emptyFiltered.alignment = .left
|
|
|
2928
|
+ emptyFiltered.maximumNumberOfLines = 2
|
|
|
2929
|
+ emptyFiltered.lineBreakMode = .byWordWrapping
|
|
|
2930
|
+ contentStack.addArrangedSubview(emptyFiltered)
|
|
|
2931
|
+ emptyFiltered.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2932
|
+ } else {
|
|
|
2933
|
+ for recording in displayedRecordings {
|
|
|
2934
|
+ let card = aiCompanionMeetingCard(recording)
|
|
|
2935
|
+ contentStack.addArrangedSubview(card)
|
|
|
2936
|
+ card.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2937
|
+ }
|
|
2837
|
2938
|
}
|
|
|
2939
|
+
|
|
|
2940
|
+ refreshAiCompanionRecordingsFilterChrome()
|
|
2838
|
2941
|
}
|
|
2839
|
2942
|
|
|
2840
|
2943
|
content.addSubview(contentStack)
|
|
|
@@ -2859,6 +2962,119 @@ private extension ViewController {
|
|
2859
|
2962
|
return panel
|
|
2860
|
2963
|
}
|
|
2861
|
2964
|
|
|
|
2965
|
+ private func makeAiCompanionRecordingsFilterDropdown() -> NSPopUpButton {
|
|
|
2966
|
+ let button = HoverPopUpButton(frame: .zero, pullsDown: false)
|
|
|
2967
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2968
|
+ button.autoenablesItems = false
|
|
|
2969
|
+ button.isBordered = false
|
|
|
2970
|
+ button.bezelStyle = .regularSquare
|
|
|
2971
|
+ button.wantsLayer = true
|
|
|
2972
|
+ button.layer?.cornerRadius = 8
|
|
|
2973
|
+ button.layer?.masksToBounds = true
|
|
|
2974
|
+ button.layer?.backgroundColor = palette.inputBackground.cgColor
|
|
|
2975
|
+ button.layer?.borderColor = palette.inputBorder.cgColor
|
|
|
2976
|
+ button.layer?.borderWidth = 1
|
|
|
2977
|
+ button.font = typography.filterText
|
|
|
2978
|
+ button.contentTintColor = palette.textSecondary
|
|
|
2979
|
+ button.target = self
|
|
|
2980
|
+ button.action = #selector(aiCompanionRecordingsFilterChanged(_:))
|
|
|
2981
|
+ button.heightAnchor.constraint(equalToConstant: 34).isActive = true
|
|
|
2982
|
+ button.widthAnchor.constraint(equalToConstant: 268).isActive = true
|
|
|
2983
|
+
|
|
|
2984
|
+ button.removeAllItems()
|
|
|
2985
|
+ button.addItems(withTitles: ["All", "Today", "This week", "Previous week", "Previous month", "Custom range"])
|
|
|
2986
|
+ button.selectItem(at: aiCompanionRecordingsFilter.rawValue)
|
|
|
2987
|
+ if let menu = button.menu {
|
|
|
2988
|
+ for (index, item) in menu.items.enumerated() {
|
|
|
2989
|
+ item.tag = index
|
|
|
2990
|
+ }
|
|
|
2991
|
+ }
|
|
|
2992
|
+
|
|
|
2993
|
+ let baseColor = palette.inputBackground
|
|
|
2994
|
+ let baseBorder = palette.inputBorder
|
|
|
2995
|
+ let hoverBlend = darkModeEnabled ? NSColor.white : NSColor.black
|
|
|
2996
|
+ let hoverColor = baseColor.blended(withFraction: 0.10, of: hoverBlend) ?? baseColor
|
|
|
2997
|
+ let hoverBorder = baseBorder.blended(withFraction: 0.16, of: hoverBlend) ?? baseBorder
|
|
|
2998
|
+ button.onHoverChanged = { [weak button] hovering in
|
|
|
2999
|
+ button?.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
|
|
|
3000
|
+ button?.layer?.borderColor = (hovering ? hoverBorder : baseBorder).cgColor
|
|
|
3001
|
+ }
|
|
|
3002
|
+ button.onHoverChanged?(false)
|
|
|
3003
|
+ return button
|
|
|
3004
|
+ }
|
|
|
3005
|
+
|
|
|
3006
|
+ private func aiCompanionHasValidCustomFilterRange() -> Bool {
|
|
|
3007
|
+ let calendar = Calendar.current
|
|
|
3008
|
+ let start = calendar.startOfDay(for: aiCompanionFilterFromDate)
|
|
|
3009
|
+ let end = calendar.startOfDay(for: aiCompanionFilterToDate)
|
|
|
3010
|
+ return start <= end
|
|
|
3011
|
+ }
|
|
|
3012
|
+
|
|
|
3013
|
+ private func filteredAiCompanionRecordings() -> [MeetingRecordingSummary] {
|
|
|
3014
|
+ let calendar = Calendar.current
|
|
|
3015
|
+ let now = Date()
|
|
|
3016
|
+ let recordings = aiCompanionLocalRecordings
|
|
|
3017
|
+
|
|
|
3018
|
+ func previousCalendarWeekBounds(reference: Date) -> (start: Date, end: Date)? {
|
|
|
3019
|
+ guard let thisWeek = calendar.dateInterval(of: .weekOfYear, for: reference),
|
|
|
3020
|
+ let prevStart = calendar.date(byAdding: .weekOfYear, value: -1, to: thisWeek.start)
|
|
|
3021
|
+ else { return nil }
|
|
|
3022
|
+ return (prevStart, thisWeek.start)
|
|
|
3023
|
+ }
|
|
|
3024
|
+
|
|
|
3025
|
+ func previousCalendarMonthBounds(reference: Date) -> (start: Date, end: Date)? {
|
|
|
3026
|
+ guard let thisMonth = calendar.dateInterval(of: .month, for: reference),
|
|
|
3027
|
+ let prevStart = calendar.date(byAdding: .month, value: -1, to: thisMonth.start)
|
|
|
3028
|
+ else { return nil }
|
|
|
3029
|
+ return (prevStart, thisMonth.start)
|
|
|
3030
|
+ }
|
|
|
3031
|
+
|
|
|
3032
|
+ switch aiCompanionRecordingsFilter {
|
|
|
3033
|
+ case .all:
|
|
|
3034
|
+ return recordings
|
|
|
3035
|
+ case .today:
|
|
|
3036
|
+ let dayStart = calendar.startOfDay(for: now)
|
|
|
3037
|
+ guard let nextDay = calendar.date(byAdding: .day, value: 1, to: dayStart) else { return [] }
|
|
|
3038
|
+ return recordings.filter { $0.endedAt >= dayStart && $0.endedAt < nextDay }
|
|
|
3039
|
+ case .week:
|
|
|
3040
|
+ guard let thisWeek = calendar.dateInterval(of: .weekOfYear, for: now) else { return [] }
|
|
|
3041
|
+ return recordings.filter { $0.endedAt >= thisWeek.start && $0.endedAt < thisWeek.end }
|
|
|
3042
|
+ case .previousWeek:
|
|
|
3043
|
+ guard let bounds = previousCalendarWeekBounds(reference: now) else { return [] }
|
|
|
3044
|
+ return recordings.filter { $0.endedAt >= bounds.start && $0.endedAt < bounds.end }
|
|
|
3045
|
+ case .previousMonth:
|
|
|
3046
|
+ guard let bounds = previousCalendarMonthBounds(reference: now) else { return [] }
|
|
|
3047
|
+ return recordings.filter { $0.endedAt >= bounds.start && $0.endedAt < bounds.end }
|
|
|
3048
|
+ case .customRange:
|
|
|
3049
|
+ guard aiCompanionHasValidCustomFilterRange() else { return [] }
|
|
|
3050
|
+ let start = calendar.startOfDay(for: aiCompanionFilterFromDate)
|
|
|
3051
|
+ let inclusiveEndDay = calendar.startOfDay(for: aiCompanionFilterToDate)
|
|
|
3052
|
+ guard let endExclusive = calendar.date(byAdding: .day, value: 1, to: inclusiveEndDay) else { return [] }
|
|
|
3053
|
+ return recordings.filter { $0.endedAt >= start && $0.endedAt < endExclusive }
|
|
|
3054
|
+ }
|
|
|
3055
|
+ }
|
|
|
3056
|
+
|
|
|
3057
|
+ private func refreshAiCompanionRecordingsFilterChrome() {
|
|
|
3058
|
+ let isCustom = aiCompanionRecordingsFilter == .customRange
|
|
|
3059
|
+ aiCompanionFilterFromDatePicker?.isEnabled = isCustom
|
|
|
3060
|
+ aiCompanionFilterToDatePicker?.isEnabled = isCustom
|
|
|
3061
|
+ let dim: CGFloat = isCustom ? 1.0 : 0.65
|
|
|
3062
|
+ aiCompanionFilterFromDatePicker?.superview?.alphaValue = dim
|
|
|
3063
|
+ aiCompanionFilterToDatePicker?.superview?.alphaValue = dim
|
|
|
3064
|
+ }
|
|
|
3065
|
+
|
|
|
3066
|
+ private func setAiCompanionRecordingsRangeError(_ message: String?) {
|
|
|
3067
|
+ aiCompanionRecordingsRangeErrorMessage = message
|
|
|
3068
|
+ aiCompanionRecordingsRangeErrorLabel?.stringValue = message ?? ""
|
|
|
3069
|
+ aiCompanionRecordingsRangeErrorLabel?.isHidden = message == nil
|
|
|
3070
|
+ }
|
|
|
3071
|
+
|
|
|
3072
|
+ private func redrawAiCompanionPageIfNeeded() {
|
|
|
3073
|
+ guard selectedSidebarPage == .aiCompanion else { return }
|
|
|
3074
|
+ pageCache[.aiCompanion] = nil
|
|
|
3075
|
+ showSidebarPage(.aiCompanion)
|
|
|
3076
|
+ }
|
|
|
3077
|
+
|
|
2862
|
3078
|
private func aiCompanionActiveRecordingCard(session: ActiveMeetingRecordingSession) -> NSView {
|
|
2863
|
3079
|
let card = roundedContainer(cornerRadius: 14, color: palette.sectionCard)
|
|
2864
|
3080
|
card.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -6191,7 +6407,7 @@ private extension ViewController {
|
|
6191
|
6407
|
}
|
|
6192
|
6408
|
|
|
6193
|
6409
|
/// Rounded shell matching `makeSchedulePageFilterDropdown` (34pt, 8pt corner, border + hover). Both pickers use the same field+stepper style inside the shell.
|
|
6194
|
|
- private func makeScheduleDatePicker(date: Date) -> (NSView, NSDatePicker) {
|
|
|
6410
|
+ private func makeFilterStyleDatePicker(date: Date, changeAction: Selector) -> (NSView, NSDatePicker) {
|
|
6195
|
6411
|
let shell = HoverSurfaceView()
|
|
6196
|
6412
|
shell.translatesAutoresizingMaskIntoConstraints = false
|
|
6197
|
6413
|
shell.wantsLayer = true
|
|
|
@@ -6229,7 +6445,7 @@ private extension ViewController {
|
|
6229
|
6445
|
picker.textColor = palette.textSecondary
|
|
6230
|
6446
|
picker.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
6231
|
6447
|
picker.target = self
|
|
6232
|
|
- picker.action = #selector(schedulePageDatePickerChanged(_:))
|
|
|
6448
|
+ picker.action = changeAction
|
|
6233
|
6449
|
|
|
6234
|
6450
|
shell.addSubview(picker)
|
|
6235
|
6451
|
NSLayoutConstraint.activate([
|
|
|
@@ -6242,6 +6458,10 @@ private extension ViewController {
|
|
6242
|
6458
|
return (shell, picker)
|
|
6243
|
6459
|
}
|
|
6244
|
6460
|
|
|
|
6461
|
+ private func makeScheduleDatePicker(date: Date) -> (NSView, NSDatePicker) {
|
|
|
6462
|
+ makeFilterStyleDatePicker(date: date, changeAction: #selector(schedulePageDatePickerChanged(_:)))
|
|
|
6463
|
+ }
|
|
|
6464
|
+
|
|
6245
|
6465
|
private func makeSchedulePagePillButton(title: String, action: Selector) -> NSButton {
|
|
6246
|
6466
|
let button = makeSchedulePillButton(title: title)
|
|
6247
|
6467
|
button.target = self
|
|
|
@@ -6475,9 +6695,12 @@ private extension ViewController {
|
|
6475
|
6695
|
return button
|
|
6476
|
6696
|
}
|
|
6477
|
6697
|
|
|
6478
|
|
- private func makeScheduleRefreshButton() -> NSButton {
|
|
|
6698
|
+ private func makeScheduleRefreshButton(
|
|
|
6699
|
+ action: Selector = #selector(scheduleReloadButtonPressed(_:)),
|
|
|
6700
|
+ accessibilityDescription: String = "Refresh meetings"
|
|
|
6701
|
+ ) -> NSButton {
|
|
6479
|
6702
|
let diameter: CGFloat = 30
|
|
6480
|
|
- let button = HoverButton(title: "", target: self, action: #selector(scheduleReloadButtonPressed(_:)))
|
|
|
6703
|
+ let button = HoverButton(title: "", target: self, action: action)
|
|
6481
|
6704
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
6482
|
6705
|
button.isBordered = false
|
|
6483
|
6706
|
button.bezelStyle = .regularSquare
|
|
|
@@ -6489,7 +6712,7 @@ private extension ViewController {
|
|
6489
|
6712
|
button.layer?.borderWidth = 1
|
|
6490
|
6713
|
button.setButtonType(.momentaryChange)
|
|
6491
|
6714
|
button.contentTintColor = palette.textSecondary
|
|
6492
|
|
- button.image = NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: "Refresh meetings")
|
|
|
6715
|
+ button.image = NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: accessibilityDescription)
|
|
6493
|
6716
|
button.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 14, weight: .semibold)
|
|
6494
|
6717
|
button.imagePosition = .imageOnly
|
|
6495
|
6718
|
button.imageScaling = .scaleProportionallyDown
|
|
|
@@ -8954,6 +9177,56 @@ private extension ViewController {
|
|
8954
|
9177
|
applySchedulePageFiltersAndRender()
|
|
8955
|
9178
|
}
|
|
8956
|
9179
|
|
|
|
9180
|
+ @objc private func aiCompanionRecordingsFilterChanged(_ sender: NSPopUpButton) {
|
|
|
9181
|
+ guard let selectedItem = sender.selectedItem,
|
|
|
9182
|
+ let filter = AiCompanionRecordingsFilter(rawValue: selectedItem.tag) else { return }
|
|
|
9183
|
+ aiCompanionRecordingsFilter = filter
|
|
|
9184
|
+ if filter != .customRange {
|
|
|
9185
|
+ setAiCompanionRecordingsRangeError(nil)
|
|
|
9186
|
+ }
|
|
|
9187
|
+ refreshAiCompanionRecordingsFilterChrome()
|
|
|
9188
|
+ redrawAiCompanionPageIfNeeded()
|
|
|
9189
|
+ }
|
|
|
9190
|
+
|
|
|
9191
|
+ @objc private func aiCompanionFilterDatePickerChanged(_ sender: NSDatePicker) {
|
|
|
9192
|
+ aiCompanionFilterFromDate = aiCompanionFilterFromDatePicker?.dateValue ?? aiCompanionFilterFromDate
|
|
|
9193
|
+ aiCompanionFilterToDate = aiCompanionFilterToDatePicker?.dateValue ?? aiCompanionFilterToDate
|
|
|
9194
|
+ }
|
|
|
9195
|
+
|
|
|
9196
|
+ @objc private func aiCompanionRecordingsApplyPressed(_ sender: NSButton) {
|
|
|
9197
|
+ aiCompanionFilterFromDate = aiCompanionFilterFromDatePicker?.dateValue ?? aiCompanionFilterFromDate
|
|
|
9198
|
+ aiCompanionFilterToDate = aiCompanionFilterToDatePicker?.dateValue ?? aiCompanionFilterToDate
|
|
|
9199
|
+ if let selectedItem = aiCompanionRecordingsFilterDropdown?.selectedItem,
|
|
|
9200
|
+ let filter = AiCompanionRecordingsFilter(rawValue: selectedItem.tag) {
|
|
|
9201
|
+ aiCompanionRecordingsFilter = filter
|
|
|
9202
|
+ }
|
|
|
9203
|
+ if aiCompanionRecordingsFilter == .customRange && !aiCompanionHasValidCustomFilterRange() {
|
|
|
9204
|
+ setAiCompanionRecordingsRangeError("Start date must be on or before end date.")
|
|
|
9205
|
+ } else {
|
|
|
9206
|
+ setAiCompanionRecordingsRangeError(nil)
|
|
|
9207
|
+ }
|
|
|
9208
|
+ refreshAiCompanionRecordingsFilterChrome()
|
|
|
9209
|
+ redrawAiCompanionPageIfNeeded()
|
|
|
9210
|
+ }
|
|
|
9211
|
+
|
|
|
9212
|
+ @objc private func aiCompanionRecordingsResetPressed(_ sender: NSButton) {
|
|
|
9213
|
+ aiCompanionRecordingsFilter = .today
|
|
|
9214
|
+ aiCompanionRecordingsFilterDropdown?.selectItem(at: AiCompanionRecordingsFilter.today.rawValue)
|
|
|
9215
|
+ let today = Calendar.current.startOfDay(for: Date())
|
|
|
9216
|
+ aiCompanionFilterFromDate = today
|
|
|
9217
|
+ aiCompanionFilterToDate = today
|
|
|
9218
|
+ aiCompanionFilterFromDatePicker?.dateValue = today
|
|
|
9219
|
+ aiCompanionFilterToDatePicker?.dateValue = today
|
|
|
9220
|
+ setAiCompanionRecordingsRangeError(nil)
|
|
|
9221
|
+ refreshAiCompanionRecordingsFilterChrome()
|
|
|
9222
|
+ redrawAiCompanionPageIfNeeded()
|
|
|
9223
|
+ }
|
|
|
9224
|
+
|
|
|
9225
|
+ @objc private func aiCompanionRecordingsRefreshPressed(_ sender: NSButton) {
|
|
|
9226
|
+ loadAiCompanionLocalRecordings()
|
|
|
9227
|
+ redrawAiCompanionPageIfNeeded()
|
|
|
9228
|
+ }
|
|
|
9229
|
+
|
|
8957
|
9230
|
@objc func schedulePageAddMeetingPressed(_ sender: NSButton) {
|
|
8958
|
9231
|
guard requireGoogleLoginForCalendarScheduling() else { return }
|
|
8959
|
9232
|
|