From 72ccfd1e738da435311efc13451ef48f117afa6d Mon Sep 17 00:00:00 2001 From: bgjooon Date: Sun, 2 Jun 2024 15:55:59 -0700 Subject: [PATCH 1/5] feat: add support for zm.page links --- .../zm_page_icon.imageset/Contents.json | 12 ++++++++++++ .../zm_page_icon.imageset/zm_page_icon.svg | 1 + MeetingBar/MeetingServices.swift | 6 ++++++ MeetingBarTests/MeetingServicesTests.swift | 3 ++- MeetingBarTests/Meetingbar-sample-data.ics | 13 +++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/Contents.json create mode 100644 MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/zm_page_icon.svg diff --git a/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/Contents.json b/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/Contents.json new file mode 100644 index 00000000..ecca3e20 --- /dev/null +++ b/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "zm_page_icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/zm_page_icon.svg b/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/zm_page_icon.svg new file mode 100644 index 00000000..6ec823a7 --- /dev/null +++ b/MeetingBar/Assets.xcassets/vendor-icons/zm_page_icon.imageset/zm_page_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MeetingBar/MeetingServices.swift b/MeetingBar/MeetingServices.swift index d15fa324..e83e652d 100644 --- a/MeetingBar/MeetingServices.swift +++ b/MeetingBar/MeetingServices.swift @@ -76,6 +76,7 @@ enum MeetingServices: String, Codable, CaseIterable { case pumble = "Pumble" case suitConference = "Suit Conference" case doxyMe = "Doxy.me" + case zmPage = "zm.page" case other = "Other" var localizedValue: String { @@ -321,6 +322,7 @@ struct LinksRegex { let pumble = try! NSRegularExpression(pattern: #"https?://meet\.pumble\.com/[a-z-]+"#) let suitConference = try! NSRegularExpression(pattern: #"https?://([a-z0-9.]+)?conference\.istesuit\.com/[^\s]*+"#) let doxyMe = try! NSRegularExpression(pattern: #"https://([a-z0-9.]+)?doxy\.me/[^\s]*"#) + let zmPage = try! NSRegularExpression(pattern: #"https?://([a-zA-Z0-9.]+)\.zm\.page"#) } func getRegexForMeetingService(_ service: MeetingServices) -> NSRegularExpression? { @@ -607,6 +609,10 @@ func getIconForMeetingService(_ meetingService: MeetingServices?) -> NSImage { image = NSImage(named: "doxy_me_icon")! image.size = NSSize(width: 16, height: 16) + case .some(.zmPage): + image = NSImage(named: "zm_page_icon")! + image.size = NSSize(width: 16, height: 16) + // tested and verified case .none: image = NSImage(named: "no_online_session")! diff --git a/MeetingBarTests/MeetingServicesTests.swift b/MeetingBarTests/MeetingServicesTests.swift index 6fdf4d44..42063cd7 100644 --- a/MeetingBarTests/MeetingServicesTests.swift +++ b/MeetingBarTests/MeetingServicesTests.swift @@ -41,7 +41,8 @@ let meetings = [ MeetingLink(service: .tuple, url: URL(string: "https://tuple.app/c/V1StGXR8_Z5jdHi6B")!), MeetingLink(service: .pumble, url: URL(string: "https://meet.pumble.com/vly-hggs-xsn")!), MeetingLink(service: .suitConference, url: URL(string: "https://turkcell.conference.istesuit.com/username")!), - MeetingLink(service: .doxyMe, url: URL(string: "https://bbc.doxy.me/dr.who")!) + MeetingLink(service: .doxyMe, url: URL(string: "https://bbc.doxy.me/dr.who")!), + MeetingLink(service: .zmPage, url: URL(string: "https://meetingbar.zm.page")!) ] class MeetingServicesTests: XCTestCase { diff --git a/MeetingBarTests/Meetingbar-sample-data.ics b/MeetingBarTests/Meetingbar-sample-data.ics index 6b31fa3c..2e8385b0 100644 --- a/MeetingBarTests/Meetingbar-sample-data.ics +++ b/MeetingBarTests/Meetingbar-sample-data.ics @@ -409,4 +409,17 @@ DTSTART;TZID=Europe/Berlin:20201225T203000 SEQUENCE:1 DESCRIPTION:https://go.teamviewer.com/v15/m212323 END:VEVENT +BEGIN:VEVENT +CREATED:20240601T165407Z +UID:F012139F-5ED4-4617-8943-EBD0460D79ED +DTEND;TZID=Europe/Berlin:20240601T213000 +TRANSP:OPAQUE +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +SUMMARY:ZM Page Test +LAST-MODIFIED:20240601T165434Z +DTSTAMP:20240601T165435Z +DTSTART;TZID=Europe/Berlin:20240601T203000 +SEQUENCE:1 +DESCRIPTION:https://meetingbar.zm.page +END:VEVENT END:VCALENDAR From 931b3e386a12ff7766b619ce58631e7340e06024 Mon Sep 17 00:00:00 2001 From: Wojciech Teichert Date: Thu, 13 Jun 2024 18:53:44 +0000 Subject: [PATCH 2/5] Translated using Weblate (Polish) Currently translated at 100.0% (269 of 269 strings) Translation: MeetingBar/App Translate-URL: https://hosted.weblate.org/projects/meetingbar/app/pl/ --- .../Resources /Localization /pl.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MeetingBar/Resources /Localization /pl.lproj/Localizable.strings b/MeetingBar/Resources /Localization /pl.lproj/Localizable.strings index e1ec7be5..37fd4486 100644 --- a/MeetingBar/Resources /Localization /pl.lproj/Localizable.strings +++ b/MeetingBar/Resources /Localization /pl.lproj/Localizable.strings @@ -266,8 +266,8 @@ // MARK: - Shared -"shared_send_notification_toggle" = "Wyślij powiadomienie aby dołączyć do następnego spotkania"; -"shared_fullscreen_notification_toggle" = "Show a fullscreen notification"; +"shared_send_notification_toggle" = "Wyślij powiadomienie systemowe"; +"shared_fullscreen_notification_toggle" = "Pokaż pełnoekranowe powiadomienie"; "general_when_event_starts" = "gdy wydarzenie się rozpocznie"; "general_one_minute_before" = "1 minutę przed"; "general_three_minute_before" = "3 minuty przed"; From 871ff366ddad02396b963fc05c03f4328ee53064 Mon Sep 17 00:00:00 2001 From: leits Date: Fri, 14 Jun 2024 22:50:52 +0300 Subject: [PATCH 3/5] Store notification ids in Constants --- MeetingBar/AppDelegate.swift | 5 +++-- MeetingBar/Constants.swift | 5 +++++ MeetingBar/Notifications.swift | 23 +++++++++-------------- MeetingBar/StatusBarItemController.swift | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/MeetingBar/AppDelegate.swift b/MeetingBar/AppDelegate.swift index 945030e7..40fc0a89 100644 --- a/MeetingBar/AppDelegate.swift +++ b/MeetingBar/AppDelegate.swift @@ -121,7 +121,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } // Reschedule next notification with updated event name visibility - removePendingNotificationRequests() + removePendingNotificationRequests(withID: notificationIDs.event_starts) + removePendingNotificationRequests(withID: notificationIDs.event_ends) if let nextEvent = getNextEvent(events: self.statusBarItem.events) { scheduleEventNotification(nextEvent) } @@ -167,7 +168,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele scheduleEventNotification(nextEvent) } } else { - removePendingNotificationRequests() + removePendingNotificationRequests(withID: notificationIDs.event_starts) } } } diff --git a/MeetingBar/Constants.swift b/MeetingBar/Constants.swift index 2b20b92f..65683588 100644 --- a/MeetingBar/Constants.swift +++ b/MeetingBar/Constants.swift @@ -177,6 +177,11 @@ enum WindowTitles { static let changelog = "windows_title_changelog".loco() } +enum notificationIDs { + static let event_starts = "NEXT_EVENT" + static let event_ends = "EVENT_ENDS" +} + let eventStartScriptPlaceholder = """ # the method to be called with the following parameters for the next meeting. # diff --git a/MeetingBar/Notifications.swift b/MeetingBar/Notifications.swift index ca8fb29d..bec9315c 100644 --- a/MeetingBar/Notifications.swift +++ b/MeetingBar/Notifications.swift @@ -60,7 +60,7 @@ func registerNotificationCategories() { notificationCenter.getNotificationCategories { _ in } } -func sendUserNotification(_ title: String, _ text: String, _ categoryIdentier: String? = nil) { +func sendUserNotification(_ title: String, _ text: String) { requestNotificationAuthorization() // By the apple best practices let center = UNUserNotificationCenter.current() @@ -69,13 +69,7 @@ func sendUserNotification(_ title: String, _ text: String, _ categoryIdentier: S content.title = title content.body = text - let identifier: String - if let categoryIdentier = categoryIdentier { - content.categoryIdentifier = categoryIdentier - identifier = categoryIdentier - } else { - identifier = UUID().uuidString - } + let identifier = UUID().uuidString let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) @@ -149,7 +143,7 @@ func scheduleEventNotification(_ event: MBEvent) { return } - removePendingNotificationRequests() + removePendingNotificationRequests(withID: notificationIDs.event_starts) let center = UNUserNotificationCenter.current() @@ -179,7 +173,7 @@ func scheduleEventNotification(_ event: MBEvent) { content.threadIdentifier = "meetingbar" let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) - let request = UNNotificationRequest(identifier: "NEXT_EVENT", content: content, trigger: trigger) + let request = UNNotificationRequest(identifier: notificationIDs.event_starts, content: content, trigger: trigger) center.add(request) { error in if let error = error { NSLog("%@", "request \(request.identifier) could not be added because of error \(error)") @@ -189,7 +183,7 @@ func scheduleEventNotification(_ event: MBEvent) { func snoozeEventNotification(_ event: MBEvent, _ interval: NotificationEventTimeAction) { requestNotificationAuthorization() // By the apple best practices - removePendingNotificationRequests() + removePendingNotificationRequests(withID: notificationIDs.event_starts) let now = Date() let center = UNUserNotificationCenter.current() @@ -216,7 +210,7 @@ func snoozeEventNotification(_ event: MBEvent, _ interval: NotificationEventTime } let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) - let request = UNNotificationRequest(identifier: "NEXT_EVENT", content: content, trigger: trigger) + let request = UNNotificationRequest(identifier: notificationIDs.event_starts, content: content, trigger: trigger) center.add(request) { error in if let error = error { NSLog("%@", "request \(request) could not be added because of error \(error)") @@ -226,9 +220,10 @@ func snoozeEventNotification(_ event: MBEvent, _ interval: NotificationEventTime } } -func removePendingNotificationRequests() { +func removePendingNotificationRequests(withID: String) { let center = UNUserNotificationCenter.current() - center.removeAllPendingNotificationRequests() + center.removePendingNotificationRequests(withIdentifiers: [withID]) +// center.removeAllPendingNotificationRequests() } func removeDeliveredNotifications() { diff --git a/MeetingBar/StatusBarItemController.swift b/MeetingBar/StatusBarItemController.swift index a2e096f9..bc587547 100644 --- a/MeetingBar/StatusBarItemController.swift +++ b/MeetingBar/StatusBarItemController.swift @@ -134,7 +134,7 @@ class StatusBarItemController { switch nextEventState { case .none: if Defaults[.joinEventNotification] { - removePendingNotificationRequests() + removePendingNotificationRequests(withID: notificationIDs.event_starts) removeDeliveredNotifications() } title = "🏁" From 02a706f94ab2d8cdc4ed832530f6270da6f6b018 Mon Sep 17 00:00:00 2001 From: leits Date: Fri, 14 Jun 2024 22:51:13 +0300 Subject: [PATCH 4/5] Add event end notificaiton picker --- MeetingBar/Constants.swift | 7 +++++++ MeetingBar/Extensions/DefaultsKeys.swift | 3 +++ MeetingBar/Views/Preferences/AdvancedTab.swift | 1 + MeetingBar/Views/Shared.swift | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/MeetingBar/Constants.swift b/MeetingBar/Constants.swift index 65683588..e0222442 100644 --- a/MeetingBar/Constants.swift +++ b/MeetingBar/Constants.swift @@ -139,6 +139,13 @@ enum TimeBeforeEvent: Int, Defaults.Serializable, Codable { case fiveMinuteBefore = 300 } +enum TimeBeforeEventEnd: Int, Defaults.Serializable, Codable { + case atEnd = 5 + case minuteBefore = 60 + case threeMinuteBefore = 180 + case fiveMinuteBefore = 300 +} + enum UtilsRegex { static let outlookSafeLinkRegex = try! NSRegularExpression(pattern: #"https://[\S]+\.safelinks\.protection\.outlook\.com/[\S]+url=([\S]*)"#) static let linkDetection = try! NSRegularExpression(pattern: #"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"#, options: .caseInsensitive) diff --git a/MeetingBar/Extensions/DefaultsKeys.swift b/MeetingBar/Extensions/DefaultsKeys.swift index 0c75b428..1a0b35f0 100644 --- a/MeetingBar/Extensions/DefaultsKeys.swift +++ b/MeetingBar/Extensions/DefaultsKeys.swift @@ -25,6 +25,9 @@ extension Defaults.Keys { static let joinEventNotification = Key("joinEventNotification", default: true) static let joinEventNotificationTime = Key("joinEventNotificationTime", default: .atStart) + static let endOfEventNotification = Key("endOfEventNotification", default: true) + static let endOfEventNotificationTime = Key("endOfEventNotificationTime", default: .atEnd) + static let fullscreenNotification = Key("fullscreenNotification", default: false) static let fullscreenNotificationTime = Key("fullscreenNotificationTime", default: .atStart) static let processedEventsForFullscreenNotification = Key<[ProcessedEvent]>("processedEventsForFullscreenNotification", default: []) diff --git a/MeetingBar/Views/Preferences/AdvancedTab.swift b/MeetingBar/Views/Preferences/AdvancedTab.swift index 15d90e73..d79c59eb 100644 --- a/MeetingBar/Views/Preferences/AdvancedTab.swift +++ b/MeetingBar/Views/Preferences/AdvancedTab.swift @@ -13,6 +13,7 @@ import Defaults struct AdvancedTab: View { var body: some View { VStack(alignment: .leading) { + endEventNotificationPicker() AutomaticEventJoinPicker() Divider() ScriptSection() diff --git a/MeetingBar/Views/Shared.swift b/MeetingBar/Views/Shared.swift index f073d5f4..6b3b3f34 100644 --- a/MeetingBar/Views/Shared.swift +++ b/MeetingBar/Views/Shared.swift @@ -78,6 +78,24 @@ struct JoinEventNotificationPicker: View { } } } +struct endEventNotificationPicker: View { + @Default(.endOfEventNotification) var endOfEventNotification + @Default(.endOfEventNotificationTime) var endOfEventNotificationTime + + var body: some View { + HStack { + Toggle("Sent a notification when event ends", isOn: $endOfEventNotification) + Picker("", selection: $endOfEventNotificationTime) { + Text("when event ends").tag(TimeBeforeEventEnd.atEnd) + Text("1 minute before").tag(TimeBeforeEventEnd.minuteBefore) + Text("3 minute before").tag(TimeBeforeEventEnd.threeMinuteBefore) + Text("5 minute before".loco()).tag(TimeBeforeEventEnd.fiveMinuteBefore) + }.frame(width: 220, alignment: .leading).labelsHidden().disabled(!endOfEventNotification) + Text("βeta").font(.caption).foregroundColor(.orange) + } + + } +} func checkNotificationSettings() -> (Bool, Bool) { var noAlertStyle = false From 13d27ac87b7b6106d13b531e65b266d22ae04725 Mon Sep 17 00:00:00 2001 From: leits Date: Mon, 17 Jun 2024 20:10:17 +0300 Subject: [PATCH 5/5] Add event ends notification --- MeetingBar/Notifications.swift | 122 ++++++++++++++++++++++++--------- MeetingBar/Views/Shared.swift | 2 +- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/MeetingBar/Notifications.swift b/MeetingBar/Notifications.swift index bec9315c..4587c9c5 100644 --- a/MeetingBar/Notifications.swift +++ b/MeetingBar/Notifications.swift @@ -133,50 +133,104 @@ func displayAlert(title: String, text: String) { } func scheduleEventNotification(_ event: MBEvent) { + if !Defaults[.joinEventNotification] && !Defaults[.endOfEventNotification] { + return + } + requestNotificationAuthorization() // By the apple best practices let now = Date() - let notificationTime = Double(Defaults[.joinEventNotificationTime].rawValue) - let timeInterval = event.startDate.timeIntervalSince(now) - notificationTime - if timeInterval < 0.5 { - return - } + // Event start notification + if Defaults[.joinEventNotification] { + let notificationTime = Double(Defaults[.joinEventNotificationTime].rawValue) + let timeInterval = event.startDate.timeIntervalSince(now) - notificationTime - removePendingNotificationRequests(withID: notificationIDs.event_starts) + if timeInterval < 0.5 { + return + } - let center = UNUserNotificationCenter.current() + removePendingNotificationRequests(withID: notificationIDs.event_starts) - let content = UNMutableNotificationContent() - if Defaults[.hideMeetingTitle] { - content.title = "general_meeting".loco() - } else { - content.title = event.title - } - if #available(macOS 12.0, *) { - content.interruptionLevel = .timeSensitive - } + let center = UNUserNotificationCenter.current() + + let content = UNMutableNotificationContent() + if Defaults[.hideMeetingTitle] { + content.title = "general_meeting".loco() + } else { + content.title = event.title + } + if #available(macOS 12.0, *) { + content.interruptionLevel = .timeSensitive + } - switch Defaults[.joinEventNotificationTime] { - case .atStart: - content.body = "notifications_event_start_soon_body".loco() - case .minuteBefore: - content.body = "notifications_event_start_one_minute_body".loco() - case .threeMinuteBefore: - content.body = "notifications_event_start_three_minutes_body".loco() - case .fiveMinuteBefore: - content.body = "notifications_event_start_five_minutes_body".loco() + switch Defaults[.joinEventNotificationTime] { + case .atStart: + content.body = "notifications_event_start_soon_body".loco() + case .minuteBefore: + content.body = "notifications_event_start_one_minute_body".loco() + case .threeMinuteBefore: + content.body = "notifications_event_start_three_minutes_body".loco() + case .fiveMinuteBefore: + content.body = "notifications_event_start_five_minutes_body".loco() + } + content.categoryIdentifier = "EVENT" + content.sound = UNNotificationSound.default + content.userInfo = ["eventID": event.ID] + content.threadIdentifier = "meetingbar" + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) + let request = UNNotificationRequest(identifier: notificationIDs.event_starts, content: content, trigger: trigger) + center.add(request) { error in + if let error = error { + NSLog("%@", "request \(request.identifier) could not be added because of error \(error)") + } + } } - content.categoryIdentifier = "EVENT" - content.sound = UNNotificationSound.default - content.userInfo = ["eventID": event.ID] - content.threadIdentifier = "meetingbar" - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) - let request = UNNotificationRequest(identifier: notificationIDs.event_starts, content: content, trigger: trigger) - center.add(request) { error in - if let error = error { - NSLog("%@", "request \(request.identifier) could not be added because of error \(error)") + // Event end notification + if Defaults[.endOfEventNotification] { + let notificationTime = Double(Defaults[.endOfEventNotificationTime].rawValue) + let timeInterval = event.endDate.timeIntervalSince(now) - notificationTime + + if timeInterval < 0.5 { + return + } + + let center = UNUserNotificationCenter.current() + + let content = UNMutableNotificationContent() + if Defaults[.hideMeetingTitle] { + content.title = "general_meeting".loco() + } else { + content.title = event.title + } + if #available(macOS 12.0, *) { + content.interruptionLevel = .timeSensitive + } + + switch Defaults[.endOfEventNotificationTime] { + // TODO: notification localization + case .atEnd: + content.body = "Event ends soon" + case .minuteBefore: + content.body = "Event ends in one minute" + case .threeMinuteBefore: + content.body = "Event ends in three minutes" + case .fiveMinuteBefore: + content.body = "Event ends in five minutes" + } +// content.categoryIdentifier = "EVENT" + content.sound = UNNotificationSound.default + content.userInfo = ["eventID": event.ID] + content.threadIdentifier = "meetingbar" + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) + let request = UNNotificationRequest(identifier: notificationIDs.event_starts, content: content, trigger: trigger) + center.add(request) { error in + if let error = error { + NSLog("%@", "request \(request.identifier) could not be added because of error \(error)") + } } } } diff --git a/MeetingBar/Views/Shared.swift b/MeetingBar/Views/Shared.swift index 6b3b3f34..5f198130 100644 --- a/MeetingBar/Views/Shared.swift +++ b/MeetingBar/Views/Shared.swift @@ -89,7 +89,7 @@ struct endEventNotificationPicker: View { Text("when event ends").tag(TimeBeforeEventEnd.atEnd) Text("1 minute before").tag(TimeBeforeEventEnd.minuteBefore) Text("3 minute before").tag(TimeBeforeEventEnd.threeMinuteBefore) - Text("5 minute before".loco()).tag(TimeBeforeEventEnd.fiveMinuteBefore) + Text("5 minute before").tag(TimeBeforeEventEnd.fiveMinuteBefore) }.frame(width: 220, alignment: .leading).labelsHidden().disabled(!endOfEventNotification) Text("βeta").font(.caption).foregroundColor(.orange) }