|
@@ -334,6 +334,10 @@ final class ViewController: NSViewController {
|
|
|
private var scheduleGoogleAuthHostPadHeightConstraint: NSLayoutConstraint?
|
|
private var scheduleGoogleAuthHostPadHeightConstraint: NSLayoutConstraint?
|
|
|
private var scheduleGoogleAuthButtonWidthConstraint: NSLayoutConstraint?
|
|
private var scheduleGoogleAuthButtonWidthConstraint: NSLayoutConstraint?
|
|
|
private var scheduleGoogleAuthButtonHeightConstraint: NSLayoutConstraint?
|
|
private var scheduleGoogleAuthButtonHeightConstraint: NSLayoutConstraint?
|
|
|
|
|
+ private weak var settingsReminderMasterSwitch: NSSwitch?
|
|
|
|
|
+ private weak var settingsReminder1DaySwitch: NSSwitch?
|
|
|
|
|
+ private weak var settingsReminder12HoursSwitch: NSSwitch?
|
|
|
|
|
+ private weak var settingsReminder1HourSwitch: NSSwitch?
|
|
|
/// Circular avatar size when signed in (top-right, Google-style).
|
|
/// Circular avatar size when signed in (top-right, Google-style).
|
|
|
private let scheduleGoogleSignedInAvatarSize: CGFloat = 36
|
|
private let scheduleGoogleSignedInAvatarSize: CGFloat = 36
|
|
|
private var scheduleGoogleAuthHovering = false
|
|
private var scheduleGoogleAuthHovering = false
|
|
@@ -1465,8 +1469,13 @@ private extension ViewController {
|
|
|
if hasCompletedInitialStoreKitSync {
|
|
if hasCompletedInitialStoreKitSync {
|
|
|
scheduleRatingPromptAfterPremiumUpgrade()
|
|
scheduleRatingPromptAfterPremiumUpgrade()
|
|
|
}
|
|
}
|
|
|
|
|
+ MeetingReminderManager.shared.requestPermissionIfNeeded { [weak self] _ in
|
|
|
|
|
+ guard let self else { return }
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: self.scheduleCachedMeetings)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if hadPremiumAccess && !hasPremiumAccess {
|
|
if hadPremiumAccess && !hasPremiumAccess {
|
|
|
|
|
+ MeetingReminderManager.shared.cancelAllReminders()
|
|
|
showPaywall()
|
|
showPaywall()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1833,6 +1842,15 @@ private extension ViewController {
|
|
|
stack.setCustomSpacing(24, after: shareButton)
|
|
stack.setCustomSpacing(24, after: shareButton)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if storeKitCoordinator.hasPremiumAccess {
|
|
|
|
|
+ let notificationsTitle = textLabel("Notifications", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
|
|
+ stack.addArrangedSubview(notificationsTitle)
|
|
|
|
|
+ let remindersSection = makeSettingsRemindersSection()
|
|
|
|
|
+ stack.addArrangedSubview(remindersSection)
|
|
|
|
|
+ remindersSection.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ stack.setCustomSpacing(24, after: remindersSection)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
let legalTitle = textLabel("Help & Legal", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
let legalTitle = textLabel("Help & Legal", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
stack.addArrangedSubview(legalTitle)
|
|
stack.addArrangedSubview(legalTitle)
|
|
|
let privacyButton = makeSettingsActionButton(icon: "🔒", title: "Privacy Policy", action: .privacyPolicy)
|
|
let privacyButton = makeSettingsActionButton(icon: "🔒", title: "Privacy Policy", action: .privacyPolicy)
|
|
@@ -1901,6 +1919,130 @@ private extension ViewController {
|
|
|
return row
|
|
return row
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func makeSettingsRemindersSection() -> NSView {
|
|
|
|
|
+ let container = NSStackView()
|
|
|
|
|
+ container.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ container.orientation = .vertical
|
|
|
|
|
+ container.spacing = 1
|
|
|
|
|
+ container.alignment = .leading
|
|
|
|
|
+
|
|
|
|
|
+ let masterRow = makeReminderToggleRow(
|
|
|
|
|
+ icon: "🔔",
|
|
|
|
|
+ title: "Reminders",
|
|
|
|
|
+ state: ReminderPreferences.remindersEnabled,
|
|
|
|
|
+ action: #selector(settingsReminderMasterToggled(_:)),
|
|
|
|
|
+ isSubRow: false
|
|
|
|
|
+ )
|
|
|
|
|
+ container.addArrangedSubview(masterRow)
|
|
|
|
|
+ masterRow.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ let subAlpha: CGFloat = ReminderPreferences.remindersEnabled ? 1.0 : 0.4
|
|
|
|
|
+
|
|
|
|
|
+ let day1Row = makeReminderToggleRow(
|
|
|
|
|
+ icon: "📅",
|
|
|
|
|
+ title: "1 Day Before",
|
|
|
|
|
+ state: ReminderPreferences.remind1Day,
|
|
|
|
|
+ action: #selector(settingsReminder1DayToggled(_:)),
|
|
|
|
|
+ isSubRow: true
|
|
|
|
|
+ )
|
|
|
|
|
+ day1Row.alphaValue = subAlpha
|
|
|
|
|
+ container.addArrangedSubview(day1Row)
|
|
|
|
|
+ day1Row.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ let hours12Row = makeReminderToggleRow(
|
|
|
|
|
+ icon: "🕛",
|
|
|
|
|
+ title: "12 Hours Before",
|
|
|
|
|
+ state: ReminderPreferences.remind12Hours,
|
|
|
|
|
+ action: #selector(settingsReminder12HoursToggled(_:)),
|
|
|
|
|
+ isSubRow: true
|
|
|
|
|
+ )
|
|
|
|
|
+ hours12Row.alphaValue = subAlpha
|
|
|
|
|
+ container.addArrangedSubview(hours12Row)
|
|
|
|
|
+ hours12Row.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ let hour1Row = makeReminderToggleRow(
|
|
|
|
|
+ icon: "⏰",
|
|
|
|
|
+ title: "1 Hour Before",
|
|
|
|
|
+ state: ReminderPreferences.remind1Hour,
|
|
|
|
|
+ action: #selector(settingsReminder1HourToggled(_:)),
|
|
|
|
|
+ isSubRow: true
|
|
|
|
|
+ )
|
|
|
|
|
+ hour1Row.alphaValue = subAlpha
|
|
|
|
|
+ container.addArrangedSubview(hour1Row)
|
|
|
|
|
+ hour1Row.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ settingsReminderMasterSwitch = masterRow.subviews.compactMap { $0 as? NSSwitch }.first
|
|
|
|
|
+ settingsReminder1DaySwitch = day1Row.subviews.compactMap { $0 as? NSSwitch }.first
|
|
|
|
|
+ settingsReminder12HoursSwitch = hours12Row.subviews.compactMap { $0 as? NSSwitch }.first
|
|
|
|
|
+ settingsReminder1HourSwitch = hour1Row.subviews.compactMap { $0 as? NSSwitch }.first
|
|
|
|
|
+
|
|
|
|
|
+ return container
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func makeReminderToggleRow(icon: String, title: String, state: Bool, action: Selector, isSubRow: Bool) -> NSView {
|
|
|
|
|
+ let row = roundedContainer(cornerRadius: 10, color: isSubRow ? palette.inputBackground.withAlphaComponent(0.6) : palette.inputBackground)
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.heightAnchor.constraint(equalToConstant: 48).isActive = true
|
|
|
|
|
+ styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+
|
|
|
|
|
+ let iconLabel = textLabel(icon, font: NSFont.systemFont(ofSize: isSubRow ? 15 : 17, weight: .medium), color: palette.textPrimary)
|
|
|
|
|
+ let titleLabel = textLabel(title, font: NSFont.systemFont(ofSize: isSubRow ? 14 : 15, weight: isSubRow ? .regular : .semibold), color: palette.textPrimary)
|
|
|
|
|
+ if isSubRow {
|
|
|
|
|
+ titleLabel.textColor = palette.textSecondary
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let toggle = NSSwitch()
|
|
|
|
|
+ toggle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ toggle.state = state ? .on : .off
|
|
|
|
|
+ toggle.target = self
|
|
|
|
|
+ toggle.action = action
|
|
|
|
|
+
|
|
|
|
|
+ row.addSubview(iconLabel)
|
|
|
|
|
+ row.addSubview(titleLabel)
|
|
|
|
|
+ row.addSubview(toggle)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ iconLabel.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: isSubRow ? 30 : 14),
|
|
|
|
|
+ iconLabel.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
|
+ titleLabel.leadingAnchor.constraint(equalTo: iconLabel.trailingAnchor, constant: 10),
|
|
|
|
|
+ titleLabel.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
|
+ toggle.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -14),
|
|
|
|
|
+ toggle.centerYAnchor.constraint(equalTo: row.centerYAnchor)
|
|
|
|
|
+ ])
|
|
|
|
|
+ return row
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsReminderMasterToggled(_ sender: NSSwitch) {
|
|
|
|
|
+ let enabled = sender.state == .on
|
|
|
|
|
+ ReminderPreferences.remindersEnabled = enabled
|
|
|
|
|
+ let subAlpha: CGFloat = enabled ? 1.0 : 0.4
|
|
|
|
|
+ settingsReminder1DaySwitch?.superview?.alphaValue = subAlpha
|
|
|
|
|
+ settingsReminder12HoursSwitch?.superview?.alphaValue = subAlpha
|
|
|
|
|
+ settingsReminder1HourSwitch?.superview?.alphaValue = subAlpha
|
|
|
|
|
+ if enabled {
|
|
|
|
|
+ MeetingReminderManager.shared.requestPermissionIfNeeded { [weak self] _ in
|
|
|
|
|
+ guard let self else { return }
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: self.scheduleCachedMeetings)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ MeetingReminderManager.shared.cancelAllReminders()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsReminder1DayToggled(_ sender: NSSwitch) {
|
|
|
|
|
+ ReminderPreferences.remind1Day = sender.state == .on
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: scheduleCachedMeetings)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsReminder12HoursToggled(_ sender: NSSwitch) {
|
|
|
|
|
+ ReminderPreferences.remind12Hours = sender.state == .on
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: scheduleCachedMeetings)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsReminder1HourToggled(_ sender: NSSwitch) {
|
|
|
|
|
+ ReminderPreferences.remind1Hour = sender.state == .on
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: scheduleCachedMeetings)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private func makeSettingsGoogleAccountRow() -> NSView {
|
|
private func makeSettingsGoogleAccountRow() -> NSView {
|
|
|
let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -6503,6 +6645,7 @@ private extension ViewController {
|
|
|
renderScheduleCards(into: stack, meetings: [])
|
|
renderScheduleCards(into: stack, meetings: [])
|
|
|
}
|
|
}
|
|
|
scheduleCachedMeetings = []
|
|
scheduleCachedMeetings = []
|
|
|
|
|
+ MeetingReminderManager.shared.cancelAllReminders()
|
|
|
applySchedulePageFiltersAndRender()
|
|
applySchedulePageFiltersAndRender()
|
|
|
if calendarPageGridStack != nil {
|
|
if calendarPageGridStack != nil {
|
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
@@ -6526,6 +6669,9 @@ private extension ViewController {
|
|
|
renderScheduleCards(into: stack, meetings: filtered)
|
|
renderScheduleCards(into: stack, meetings: filtered)
|
|
|
}
|
|
}
|
|
|
scheduleCachedMeetings = meetings
|
|
scheduleCachedMeetings = meetings
|
|
|
|
|
+ if storeKitCoordinator.hasPremiumAccess {
|
|
|
|
|
+ MeetingReminderManager.shared.scheduleReminders(for: meetings)
|
|
|
|
|
+ }
|
|
|
applySchedulePageFiltersAndRender()
|
|
applySchedulePageFiltersAndRender()
|
|
|
if calendarPageGridStack != nil {
|
|
if calendarPageGridStack != nil {
|
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
@@ -6545,6 +6691,7 @@ private extension ViewController {
|
|
|
renderScheduleCards(into: stack, meetings: [])
|
|
renderScheduleCards(into: stack, meetings: [])
|
|
|
}
|
|
}
|
|
|
scheduleCachedMeetings = []
|
|
scheduleCachedMeetings = []
|
|
|
|
|
+ MeetingReminderManager.shared.cancelAllReminders()
|
|
|
applySchedulePageFiltersAndRender()
|
|
applySchedulePageFiltersAndRender()
|
|
|
if calendarPageGridStack != nil {
|
|
if calendarPageGridStack != nil {
|
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
calendarPageMonthLabel?.stringValue = calendarMonthTitleText(for: calendarPageMonthAnchor)
|
|
@@ -6648,6 +6795,7 @@ private extension ViewController {
|
|
|
|
|
|
|
|
private func performGoogleSignOut() {
|
|
private func performGoogleSignOut() {
|
|
|
do {
|
|
do {
|
|
|
|
|
+ MeetingReminderManager.shared.cancelAllReminders()
|
|
|
try googleOAuth.signOut()
|
|
try googleOAuth.signOut()
|
|
|
applyGoogleProfile(nil)
|
|
applyGoogleProfile(nil)
|
|
|
updateGoogleAuthButtonTitle()
|
|
updateGoogleAuthButtonTitle()
|