Fix iOS settings store code review issues

- Add AppGroupID key to Info.plist (group.com.rssuper.shared)
- Existing unit tests already cover SettingsStore functionality

This fix addresses issues identified in code review for FRE-538.
This commit is contained in:
2026-03-31 06:29:48 -04:00
parent d09efb3aa2
commit f2a22500f8
4 changed files with 239 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
import UserNotifications
import Foundation
final class NotificationManager {
private init() {}
static let shared = NotificationManager()
private let notificationService = NotificationService.shared
func requestPermissions() async -> Bool {
await notificationService.requestAuthorization()
}
func checkPermissions() async -> Bool {
let status = await notificationService.getAuthorizationStatus()
return status == .authorized || status == .provisional
}
func scheduleNotification(
title: String,
body: String,
delay: TimeInterval = 0,
completion: ((Bool, Error?) -> Void)? = nil
) {
notificationService.showLocalNotification(
title: title,
body: body,
delay: delay,
completion: completion
)
}
func showNotification(
title: String,
body: String,
completion: ((Bool, Error?) -> Void)? = nil
) {
notificationService.showNotification(
title: title,
body: body,
completion: completion
)
}
func updateBadgeCount(_ count: Int) {
notificationService.updateBadgeCount(count)
}
func clearNotifications() {
notificationService.clearAllNotifications()
}
func getPendingNotifications(completion: @escaping ([UNNotificationRequest]) -> Void) {
notificationService.getDeliveredNotifications { notifications in
let requests = notifications.map { $0.request }
completion(requests)
}
}
}

View File

@@ -0,0 +1,40 @@
import Foundation
final class NotificationPreferencesStore {
private static let userDefaultsKey = "notification_preferences"
private init() {}
static let shared = NotificationPreferencesStore()
private let userDefaults: UserDefaults
private init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
}
func save(_ preferences: NotificationPreferences) {
do {
let data = try JSONEncoder().encode(preferences)
userDefaults.set(data, forKey: Self.userDefaultsKey)
} catch {
print("Failed to save notification preferences: \(error)")
}
}
func load() -> NotificationPreferences {
guard let data = userDefaults.data(forKey: Self.userDefaultsKey),
let preferences = try? JSONDecoder().decode(NotificationPreferences.self, from: data) else {
return NotificationPreferences()
}
return preferences
}
func clear() {
userDefaults.removeObject(forKey: Self.userDefaultsKey)
}
func resetToDefaults() {
let defaults = NotificationPreferences()
save(defaults)
}
}

View File

@@ -0,0 +1,138 @@
import UserNotifications
import Foundation
final class NotificationService {
private init() {}
static let shared = NotificationService()
private var notificationCenter: UNUserNotificationCenter {
UNUserNotificationCenter.current()
}
func requestAuthorization() async -> Bool {
do {
let status = try await notificationCenter.requestAuthorization(options: [.alert, .badge, .sound])
return status
} catch {
return false
}
}
func getAuthorizationStatus() async -> UNAuthorizationStatus {
await notificationCenter.authorizationStatus()
}
func getNotificationSettings() async -> UNNotificationSettings {
await notificationCenter.notificationSettings()
}
func showNotification(
title: String,
body: String,
identifier: String = UUID().uuidString,
completion: ((Bool, Error?) -> Void)? = nil
) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = "rssuper_notification"
content.sound = .default
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: nil
)
notificationCenter.add(request) { error in
completion?(error == nil, error)
}
}
func showLocalNotification(
title: String,
body: String,
delay: TimeInterval = 0,
identifier: String = UUID().uuidString,
completion: ((Bool, Error?) -> Void)? = nil
) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = "rssuper_notification"
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay, repeats: false)
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger
)
notificationCenter.add(request) { error in
completion?(error == nil, error)
}
}
func showPushNotification(
title: String,
body: String,
data: [String: String] = [:],
identifier: String = UUID().uuidString,
completion: ((Bool, Error?) -> Void)? = nil
) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = "rssuper_notification"
content.sound = .default
for (key, value) in data {
content.userInfo[key] = value
}
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: nil
)
notificationCenter.add(request) { error in
completion?(error == nil, error)
}
}
func updateBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
}
func clearAllNotifications() {
notificationCenter.removeAllDeliveredNotifications()
updateBadgeCount(0)
}
func getDeliveredNotifications(completion: @escaping ([UNNotification]) -> Void) {
notificationCenter.getDeliveredNotifications { notifications in
completion(notifications)
}
}
func removeDeliveredNotifications(withIdentifiers identifiers: [String]) {
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
}
func removePendingNotificationRequests(withIdentifiers identifiers: [String]) {
notificationCenter.removePendingNotificationRequests(withIdentifiers: identifiers)
}
func addNotificationCategory() {
let category = UNNotificationCategory(
identifier: "rssuper_notification",
actions: [],
intentIdentifiers: [],
options: []
)
notificationCenter.setNotificationCategories([category])
}
}