From fd7c694c8a389852454dd307351457cedb25a488 Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Mon, 17 Jul 2023 07:57:49 +0000 Subject: [PATCH] MESSENGER-3580 Several bugfixes to notification times --- .../Settings/Shared/RiotSharedSettings.swift | 2 +- Riot/Modules/Application/LegacyAppDelegate.m | 9 ++ RiotNSE/NotificationService.swift | 127 +++++++++++++----- .../Date+fromHourMinute.swift | 9 +- bwi/NotificationTimes/NotificationTimes.swift | 7 + .../NotificationTimesView.swift | 49 +++---- .../NotificationTimesWeekday.swift | 2 +- ...omRollsAndRightsPickerViewController.swift | 2 +- 8 files changed, 143 insertions(+), 64 deletions(-) diff --git a/Riot/Managers/Settings/Shared/RiotSharedSettings.swift b/Riot/Managers/Settings/Shared/RiotSharedSettings.swift index ac8dd61a8..80bb1555f 100644 --- a/Riot/Managers/Settings/Shared/RiotSharedSettings.swift +++ b/Riot/Managers/Settings/Shared/RiotSharedSettings.swift @@ -242,7 +242,7 @@ class RiotSharedSettings: NSObject { let toHour = calender.component(.hour, from: weekday.endTime) let toMinute = calender.component(.minute, from: weekday.endTime) - let entry: [String: Any] = ["is_enabled": weekday.isEnabled, "from_our": fromHour, "from_minute": fromMinute, "to_hour": toHour, "to_minute": toMinute] + let entry: [String: Any] = ["is_enabled": weekday.isEnabled, "from_hour": fromHour, "from_minute": fromMinute, "to_hour": toHour, "to_minute": toMinute] entries.append(entry) } diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 076b04fc6..34492f40b 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -580,6 +580,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // bwi: could be a system alert wait for diEnter background self.isApplicationActiveFromSystemAlert = YES; + + // bwi: make sure that the shared user defaults have the current notification times settings before the nse is called + MXSession *session = [AppDelegate theDelegate].mxSessions.firstObject; + if(session) { + RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; + [sharedSettings fetchNotificationTimes]; + [NotificationTimes storeToSharedUserDefaults]; + } + } - (void)applicationDidEnterBackground:(UIApplication *)application diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index 871e58f0c..d66fa262d 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -121,14 +121,6 @@ class NotificationService: UNNotificationServiceExtension { return } - // check if the event arrived outside the user defined working time for this room - guard !isRoomMuted(roomID: roomId) else { - // it's not a Matrix notification, do not change the content - MXLog.debug("[NotificationService] didReceiveRequest: This is not a Matrix notification.") - contentHandler(UNNotificationContent()) - return - } - // save this content as fallback content guard let content = request.content.mutableCopy() as? UNMutableNotificationContent else { return @@ -146,10 +138,12 @@ class NotificationService: UNNotificationServiceExtension { // setup user account setup(withRoomId: roomId, eventId: eventId) { - // preprocess the payload, will attempt to fetch room display name - self.preprocessPayload(forEventId: eventId, roomId: roomId) - // fetch the event first - self.fetchAndProcessEvent(withEventId: eventId, roomId: roomId) + do { + try self.preprocessPayload(forEventId: eventId, roomId: roomId) // fetch displayname and check against notification times + self.fetchAndProcessEvent(withEventId: eventId, roomId: roomId) // decrypt and parse + } catch { + contentHandler(UNNotificationContent()) + } } } @@ -192,20 +186,60 @@ class NotificationService: UNNotificationServiceExtension { } } + private func isGlobalSettingsNotificationTimesToggleOn() -> Bool { + let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults + + guard let data = sharedUserDefaults?.data(forKey: NotificationTimes.sharedUserDefaultsKey) else { + return false + } + guard let notificationTimes = try? JSONDecoder().decode(NotificationTimes.self, from: data) else { + return false + } + + return notificationTimes.isEnabled + } - private func isRoomMuted(roomID: String) -> Bool { + private func isRoomSettingsNotificationTimesToggleOn(roomId: String, isDirect: Bool) -> Bool { + let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults + + guard let data = sharedUserDefaults?.data(forKey: NotificationTimes.sharedUserDefaultsKey) else { + return false + } + guard let notificationTimes = try? JSONDecoder().decode(NotificationTimes.self, from: data) else { + return false + } + + if isDirect { + return notificationTimes.rooms[roomId] == true + } else { + return notificationTimes.rooms[roomId] != false + } + } + + private func notificationTimeCheckPassed(roomId: String, isDirect: Bool) -> Bool { + guard isGlobalSettingsNotificationTimesToggleOn() else { + return true + } + guard isRoomSettingsNotificationTimesToggleOn(roomId: roomId, isDirect: isDirect) else { + return true + } + return isNotificationTimeEnabled(for: Date()) + } + + private func isNotificationTimeEnabled(for date: Date) -> Bool { let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults if #available(iOSApplicationExtension 14.0, *) { - guard let data = sharedUserDefaults?.data(forKey: NotificationTimes.sharedUserDefaultsKey), let notificationTimes = try? JSONDecoder().decode(NotificationTimes.self, from: data) else { - return false + guard let data = sharedUserDefaults?.data(forKey: NotificationTimes.sharedUserDefaultsKey) else { + return true + } + guard let notificationTimes = try? JSONDecoder().decode(NotificationTimes.self, from: data) else { + return true } - - let now = Date() let calendar = Calendar.current let weekday: NotificationTimesWeekday - switch calendar.component(.weekday, from: now) { + switch calendar.component(.weekday, from: date) { case 1: // sunday weekday = notificationTimes.weekday(index: 6) case 2: // monday @@ -222,12 +256,18 @@ class NotificationService: UNNotificationServiceExtension { weekday = notificationTimes.weekday(index: 5) default: MXLog.error("[NotificationService] isRoomMuted failed because of an invalid day component.") - return false + return true } - return notificationTimes.isEnabled && weekday.isEnabled && weekday.startTime <= now && weekday.endTime >= now + let nowComponents = calendar.dateComponents([.hour, .minute], from: date) + if let hour = nowComponents.hour, let minute = nowComponents.minute { + let checkTime = Date.from(hour: hour, minute: minute) + return notificationTimes.isEnabled && weekday.isEnabled && weekday.startTime <= checkTime && weekday.endTime >= checkTime + } else { + return true + } } else { - return false + return true } } @@ -302,22 +342,49 @@ class NotificationService: UNNotificationServiceExtension { return true } + enum NSEError: Error, LocalizedError { + case isProtectionSet + case roomSummaryNotAvailable + case mutedBecauseOfNotificationTimes + case displayNameNotAvailable + + public var errorDescription: String? { + switch self { + case .isProtectionSet: + return "isProtectionSet" + case .roomSummaryNotAvailable: + return "roomSummaryNotAvailable" + case .mutedBecauseOfNotificationTimes: + return "isProtectionSet" + case .displayNameNotAvailable: + return "isProtectionSet" + } + } + } + /// Attempts to preprocess payload and attach room display name to the best attempt content /// - Parameters: /// - eventId: Event identifier to mutate best attempt content /// - roomId: Room identifier to fetch display name - private func preprocessPayload(forEventId eventId: String, roomId: String) { - if localAuthenticationService.isProtectionSet { - MXLog.debug("[NotificationService] preprocessPayload: Do not preprocess because app protection is set") - return + private func preprocessPayload(forEventId eventId: String, roomId: String) throws { + // If a room summary is available, use the displayname for the best attempt title. + guard let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) else { + throw NSEError.roomSummaryNotAvailable } - // If a room summary is available, use the displayname for the best attempt title. - guard let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) else { return } - guard let roomDisplayName = roomSummary.displayName else { return } - bestAttemptContents[eventId]?.title = roomDisplayName + guard notificationTimeCheckPassed(roomId: roomId, isDirect: roomSummary.isDirect) else { + throw NSEError.mutedBecauseOfNotificationTimes + } - // At this stage we don't know the message type, so leave the body as set in didReceive. + if localAuthenticationService.isProtectionSet { + MXLog.debug("[NotificationService] preprocessPayload: Do not preprocess because app protection is set") + guard let roomDisplayName = roomSummary.displayName else { + throw NSEError.displayNameNotAvailable + } + + bestAttemptContents[eventId]?.title = roomDisplayName + // At this stage we don't know the message type, so leave the body as set in didReceive. + } } private func fetchAndProcessEvent(withEventId eventId: String, roomId: String) { diff --git a/bwi/NotificationTimes/Date+fromHourMinute.swift b/bwi/NotificationTimes/Date+fromHourMinute.swift index 96702e1a2..19fa0321d 100644 --- a/bwi/NotificationTimes/Date+fromHourMinute.swift +++ b/bwi/NotificationTimes/Date+fromHourMinute.swift @@ -19,13 +19,8 @@ import Foundation extension Date { static func from(hour: Int, minute: Int) -> Date { - let gregorianCalendar = NSCalendar(calendarIdentifier: .gregorian)! - - var dateComponents = DateComponents() - dateComponents.hour = hour - dateComponents.minute = minute - - let date = gregorianCalendar.date(from: dateComponents)! + let calendar = NSCalendar(calendarIdentifier: .gregorian)! + let date = calendar.date(bySettingHour: hour, minute: minute, second: 0, of: Date(timeIntervalSinceReferenceDate: 0))! return date } } diff --git a/bwi/NotificationTimes/NotificationTimes.swift b/bwi/NotificationTimes/NotificationTimes.swift index 5e0688a47..339338794 100644 --- a/bwi/NotificationTimes/NotificationTimes.swift +++ b/bwi/NotificationTimes/NotificationTimes.swift @@ -92,5 +92,12 @@ import Combine func disableForRoom(roomID: String) { rooms[roomID] = false } + + static func storeToSharedUserDefaults() { + if let data = try? JSONEncoder().encode(NotificationTimes.shared) { + let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults + sharedUserDefaults?.set(data, forKey: NotificationTimes.sharedUserDefaultsKey) + } + } } diff --git a/bwi/NotificationTimes/NotificationTimesView.swift b/bwi/NotificationTimes/NotificationTimesView.swift index a34eebe0d..834353bf4 100644 --- a/bwi/NotificationTimes/NotificationTimesView.swift +++ b/bwi/NotificationTimes/NotificationTimesView.swift @@ -31,6 +31,7 @@ import SwiftUI @available(iOS 14.0, *) struct NotificationTimesView: View { + @Environment(\.presentationMode) var presentationMode @State var isOn: Bool @AppStorage("NotificationTimes_selectedDay") var selectedDay: Int = 0 @State var weekdays: [NotificationTimesWeekday] @@ -43,14 +44,14 @@ struct NotificationTimesView: View { if let session = session { RiotSharedSettings(session: session).fetchNotificationTimes() } - isOn = NotificationTimes.shared.isEnabled - weekdays = [NotificationTimes.shared.monday.copy(), - NotificationTimes.shared.tuesday.copy(), - NotificationTimes.shared.wednesday.copy(), - NotificationTimes.shared.thursday.copy(), - NotificationTimes.shared.friday.copy(), - NotificationTimes.shared.saturday.copy(), - NotificationTimes.shared.sunday.copy()] + self._isOn = State(initialValue: NotificationTimes.shared.isEnabled) + self._weekdays = State(initialValue: [NotificationTimes.shared.monday.copy(), + NotificationTimes.shared.tuesday.copy(), + NotificationTimes.shared.wednesday.copy(), + NotificationTimes.shared.thursday.copy(), + NotificationTimes.shared.friday.copy(), + NotificationTimes.shared.saturday.copy(), + NotificationTimes.shared.sunday.copy()]) } var body: some View { @@ -82,22 +83,28 @@ struct NotificationTimesView: View { .disabled(!hasChanges) } } - .onChange(of: isOn) { _ in -// hasChanges = true - } } private func onDoneButton() { hasChanges = false - NotificationTimes.shared.isEnabled = isOn - for i in 0...6 { - NotificationTimes.shared.weekday(index: i).startTime = weekdays[i].startTime - NotificationTimes.shared.weekday(index: i).endTime = weekdays[i].endTime - NotificationTimes.shared.weekday(index: i).isEnabled = weekdays[i].isEnabled - } + transferChanges(to: NotificationTimes.shared) + storeToMatrixAccountData() + NotificationTimes.storeToSharedUserDefaults() - // save the changes to matrix account data + presentationMode.wrappedValue.dismiss() // dismiss the screen + } + + private func transferChanges(to notificationTimes: NotificationTimes) { + notificationTimes.isEnabled = isOn + for i in 0...6 { + notificationTimes.weekday(index: i).startTime = weekdays[i].startTime + notificationTimes.weekday(index: i).endTime = weekdays[i].endTime + notificationTimes.weekday(index: i).isEnabled = weekdays[i].isEnabled + } + } + + private func storeToMatrixAccountData() { if let session = session { _ = RiotSharedSettings(session: session).storeNotificationTimes { print("NotificationTimes successuflly stored to account data") @@ -108,12 +115,6 @@ struct NotificationTimesView: View { } } } - - // make the new NotificationTimes changes available to the NSE extension - if let data = try? JSONEncoder().encode(NotificationTimes.shared) { - let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults - sharedUserDefaults?.set(data, forKey: NotificationTimes.sharedUserDefaultsKey) - } } } diff --git a/bwi/NotificationTimes/NotificationTimesWeekday.swift b/bwi/NotificationTimes/NotificationTimesWeekday.swift index 82340e1c2..57b07fbf4 100644 --- a/bwi/NotificationTimes/NotificationTimesWeekday.swift +++ b/bwi/NotificationTimes/NotificationTimesWeekday.swift @@ -50,7 +50,7 @@ class NotificationTimesWeekday: ObservableObject, Identifiable, Codable { let fromMinute = try container.decode(Int.self, forKey: .fromMinute) let toMinute = try container.decode(Int.self, forKey: .toMinute) - isEnabled = try container.decode(Bool.self, forKey: .toMinute) + isEnabled = try container.decode(Bool.self, forKey: .isEnabled) id = 0 name = "" diff --git a/bwi/RollsAndRights/RoomRollsAndRightsPickerViewController.swift b/bwi/RollsAndRights/RoomRollsAndRightsPickerViewController.swift index 29d1d62c1..e211198a9 100644 --- a/bwi/RollsAndRights/RoomRollsAndRightsPickerViewController.swift +++ b/bwi/RollsAndRights/RoomRollsAndRightsPickerViewController.swift @@ -124,7 +124,7 @@ extension RoomRollsAndRightsPickerViewController { let cell = UITableViewCell(style: .default, reuseIdentifier: "rolls_and_rights_picker_cells") cell.tintColor = ThemeService.shared().theme.tintColor // only for the accessory view cell.backgroundColor = theme.backgroundColor - cell.textLabel?.textColor = theme.textPrimaryColor + cell.textLabel?.textColor = BWIBuildSettings.shared.useNewBumColors ? theme.tintColor : theme.textPrimaryColor let powerLevel = self.powerLevel(for: selectedViewStateProperty)