MESSENGER-3580 Several bugfixes to notification times

This commit is contained in:
Arnfried Griesert
2023-07-17 07:57:49 +00:00
parent 6a2101c858
commit fd7c694c8a
8 changed files with 143 additions and 64 deletions

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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 = ""

View File

@@ -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)