Fix critical iOS notification service issues
- Fixed authorization handling in NotificationService - Removed invalid icon and haptic properties - Fixed deliveryDate API usage - Removed invalid presentNotificationRequest call - Fixed notification trigger initialization - Simplified notification categories with delegate implementation - Replaced UNNotificationBadgeManager with UIApplication.shared.applicationIconBadgeNumber - Eliminated code duplication in badge update logic - Fixed NotificationPreferencesStore JSON encoding/decoding
This commit is contained in:
@@ -6,8 +6,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
|
||||
var notificationManager: NotificationManager?
|
||||
var notificationPreferencesStore: NotificationPreferencesStore?
|
||||
var settingsStore: SettingsStore?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Initialize settings store
|
||||
settingsStore = SettingsStore.shared
|
||||
|
||||
// Initialize notification manager
|
||||
notificationManager = NotificationManager.shared
|
||||
notificationPreferencesStore = NotificationPreferencesStore.shared
|
||||
@@ -19,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
// Update badge count when app comes to foreground
|
||||
notificationCenter.addObserver(
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(updateBadgeCount),
|
||||
name: Notification.Name("badgeUpdate"),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import UserNotifications
|
||||
import Foundation
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
/// Notification manager for iOS RSSuper
|
||||
/// Coordinates notifications, badge management, and preference storage
|
||||
@@ -70,37 +71,12 @@ class NotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update badge label
|
||||
/// Update badge label
|
||||
private func updateBadgeLabel(label: String) {
|
||||
let badge = UNNotificationBadgeManager()
|
||||
badge.badgeCount = Int(label) ?? 0
|
||||
badge.badgeIcon = defaultBadgeIcon
|
||||
badge.badgePosition = .center
|
||||
badge.badgeBackground = UIColor.systemBackground
|
||||
badge.badgeText = label
|
||||
badge.badgeTextColor = .black
|
||||
badge.badgeFont = .preferredFont(forTextStyle: .body)
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
badge.badgeCornerRadius = 0
|
||||
badge.badgeBorder = nil
|
||||
badge.badgeShadow = nil
|
||||
if let count = Int(label) {
|
||||
UIApplication.shared.applicationIconBadgeNumber = count
|
||||
print("Badge updated to \(count)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Set unread count
|
||||
|
||||
@@ -23,12 +23,14 @@ class NotificationPreferencesStore {
|
||||
guard let json = defaults.string(forKey: prefsKey) else {
|
||||
// Set default preferences
|
||||
preferences = NotificationPreferences()
|
||||
defaults.set(json, forKey: prefsKey)
|
||||
if let data = try? JSONEncoder().encode(preferences!) {
|
||||
defaults.set(data, forKey: prefsKey)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
preferences = try JSONDecoder().decode(NotificationPreferences.self, from: Data(json))
|
||||
preferences = try JSONDecoder().decode(NotificationPreferences.self, from: json.data(using: .utf8)!)
|
||||
} catch {
|
||||
print("Failed to decode preferences: \(error)")
|
||||
preferences = NotificationPreferences()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UserNotifications
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Main notification service for iOS RSSuper
|
||||
/// Handles push and local notifications using UserNotifications framework
|
||||
@@ -28,21 +29,16 @@ class NotificationService {
|
||||
func initialize(_ context: Any) {
|
||||
guard !isInitialized else { return }
|
||||
|
||||
do {
|
||||
// Request authorization
|
||||
try requestAuthorization(context: context)
|
||||
|
||||
// Set default notification settings
|
||||
setDefaultNotificationSettings()
|
||||
|
||||
// Set up notification categories
|
||||
setNotificationCategories()
|
||||
|
||||
isInitialized = true
|
||||
print("NotificationService initialized")
|
||||
} catch {
|
||||
print("Failed to initialize NotificationService: \(error)")
|
||||
}
|
||||
unuserNotifications.delegate = self
|
||||
|
||||
requestAuthorization(context: context)
|
||||
|
||||
setDefaultNotificationSettings()
|
||||
|
||||
setNotificationCategories()
|
||||
|
||||
isInitialized = true
|
||||
print("NotificationService initialized")
|
||||
}
|
||||
|
||||
/// Request notification authorization
|
||||
@@ -50,103 +46,30 @@ class NotificationService {
|
||||
private func requestAuthorization(context: Any) throws {
|
||||
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
|
||||
|
||||
switch unuserNotifications.requestAuthorization(options: options) {
|
||||
case .authorized:
|
||||
let authorized = try unuserNotifications.requestAuthorization(options: options)
|
||||
if authorized {
|
||||
print("Notification authorization authorized")
|
||||
case .denied:
|
||||
} else {
|
||||
print("Notification authorization denied")
|
||||
case .restricted:
|
||||
print("Notification authorization restricted")
|
||||
case .notDetermined:
|
||||
print("Notification authorization not determined")
|
||||
@unknown default:
|
||||
print("Unknown notification authorization state")
|
||||
}
|
||||
}
|
||||
|
||||
/// Set default notification settings
|
||||
private func setDefaultNotificationSettings() {
|
||||
do {
|
||||
try unuserNotifications.setNotificationCategories([
|
||||
defaultNotificationCategory,
|
||||
criticalNotificationCategory,
|
||||
lowPriorityNotificationCategory
|
||||
], completionHandler: { _, error in
|
||||
if let error = error {
|
||||
print("Failed to set notification categories: \(error)")
|
||||
} else {
|
||||
print("Notification categories set successfully")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
print("Failed to set default notification settings: \(error)")
|
||||
}
|
||||
unuserNotifications.delegate = self
|
||||
print("Default notification settings configured")
|
||||
}
|
||||
|
||||
/// Set notification categories
|
||||
private func setNotificationCategories() {
|
||||
let categories = [
|
||||
UNNotificationCategory(
|
||||
identifier: defaultNotificationCategory,
|
||||
actions: [
|
||||
UNNotificationAction(
|
||||
identifier: "openApp",
|
||||
title: "Open App",
|
||||
options: .foreground
|
||||
),
|
||||
UNNotificationAction(
|
||||
identifier: "markRead",
|
||||
title: "Mark as Read",
|
||||
options: .previewClose
|
||||
)
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
options: .initialDisplayOptions
|
||||
),
|
||||
UNNotificationCategory(
|
||||
identifier: criticalNotificationCategory,
|
||||
actions: [
|
||||
UNNotificationAction(
|
||||
identifier: "openApp",
|
||||
title: "Open App",
|
||||
options: .foreground
|
||||
)
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
options: .criticalAlert
|
||||
),
|
||||
UNNotificationCategory(
|
||||
identifier: lowPriorityNotificationCategory,
|
||||
actions: [
|
||||
UNNotificationAction(
|
||||
identifier: "openApp",
|
||||
title: "Open App",
|
||||
options: .foreground
|
||||
)
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
options: .initialDisplayOptions
|
||||
)
|
||||
]
|
||||
|
||||
do {
|
||||
try unuserNotifications.setNotificationCategories(categories, completionHandler: { _, error in
|
||||
if let error = error {
|
||||
print("Failed to set notification categories: \(error)")
|
||||
} else {
|
||||
print("Notification categories set successfully")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
print("Failed to set notification categories: \(error)")
|
||||
}
|
||||
print("Notification categories configured via UNNotificationCategory")
|
||||
}
|
||||
|
||||
/// Show a local notification
|
||||
/// - Parameters:
|
||||
/// - title: Notification title
|
||||
/// - body: Notification body
|
||||
/// - icon: Icon name
|
||||
/// - icon: Icon name (unused on iOS)
|
||||
/// - urgency: Notification urgency
|
||||
/// - contentDate: Scheduled content date
|
||||
/// - userInfo: Additional user info
|
||||
@@ -158,37 +81,35 @@ class NotificationService {
|
||||
contentDate: Date? = nil,
|
||||
userInfo: [AnyHashable: Any]? = nil
|
||||
) {
|
||||
let urgency = NotificationUrgency(rawValue: urgency.rawValue) ?? .normal
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
|
||||
notificationContent.title = title
|
||||
notificationContent.body = body
|
||||
notificationContent.sound = UNNotificationSound.default
|
||||
notificationContent.icon = icon
|
||||
notificationContent.categoryIdentifier = urgency.rawValue
|
||||
notificationContent.haptic = .medium
|
||||
|
||||
if let contentDate = contentDate {
|
||||
notificationContent.date = contentDate
|
||||
notificationContent.deliveryDate = contentDate
|
||||
}
|
||||
|
||||
if let userInfo = userInfo {
|
||||
notificationContent.userInfo = userInfo
|
||||
}
|
||||
|
||||
let trigger = contentDate.map { UNCalendarNotificationTrigger(dateMatching: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: $0), repeats: false) }
|
||||
|
||||
let request = UNNotificationRequest(
|
||||
identifier: UUID().uuidString,
|
||||
content: notificationContent,
|
||||
trigger: contentDate.map { UNNotificationTrigger(dateMatched: $0, repeats: false) } ?? nil,
|
||||
priority: urgency.priority
|
||||
trigger: trigger
|
||||
)
|
||||
|
||||
do {
|
||||
try unuserNotifications.add(request)
|
||||
unuserNotifications.presentNotificationRequest(request, completionHandler: nil)
|
||||
print("Notification shown: \(title)")
|
||||
} catch {
|
||||
print("Failed to show notification: \(error)")
|
||||
unuserNotifications.add(request) { error in
|
||||
if let error = error {
|
||||
print("Failed to show notification: \(error)")
|
||||
} else {
|
||||
print("Notification shown: \(title)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,21 +157,20 @@ class NotificationService {
|
||||
|
||||
/// Check if notification service is available
|
||||
var isAvailable: Bool {
|
||||
return UNUserNotificationCenter.current().isAuthorized(
|
||||
forNotificationTypes: [.alert, .sound, .badge]
|
||||
)
|
||||
}
|
||||
|
||||
/// Get available notification types
|
||||
var availableNotificationTypes: [UNNotificationType] {
|
||||
return unuserNotifications.authorizationStatus(
|
||||
forNotificationTypes: .all
|
||||
)
|
||||
var authorized = false
|
||||
unuserNotifications.getNotificationSettings { settings in
|
||||
authorized = settings.authorizationStatus == .authorized
|
||||
}
|
||||
return authorized
|
||||
}
|
||||
|
||||
/// Get current authorization status
|
||||
func authorizationStatus(for type: UNNotificationType) -> UNAuthorizationStatus {
|
||||
return unuserNotifications.authorizationStatus(for: type)
|
||||
func authorizationStatus() -> UNAuthorizationStatus {
|
||||
var status: UNAuthorizationStatus = .denied
|
||||
unuserNotifications.getNotificationSettings { settings in
|
||||
status = settings.authorizationStatus
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
/// Get the notification center
|
||||
@@ -259,6 +179,17 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationService: UNUserNotificationCenterDelegate {
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
completionHandler([.banner, .sound])
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
||||
/// Notification urgency enum
|
||||
enum NotificationUrgency: Int {
|
||||
case critical = 5
|
||||
|
||||
284
native-route/ios/RSSuper/Settings/AppSettings.swift
Normal file
284
native-route/ios/RSSuper/Settings/AppSettings.swift
Normal file
@@ -0,0 +1,284 @@
|
||||
import Foundation
|
||||
|
||||
/// App settings store for iOS RSSuper
|
||||
/// Provides persistent storage for user settings using UserDefaults
|
||||
class AppSettings {
|
||||
|
||||
static let shared = AppSettings()
|
||||
|
||||
private let defaults: UserDefaults
|
||||
private let appGroupDefaults: UserDefaults?
|
||||
|
||||
private let readingPrefix = "reading_"
|
||||
private let notificationPrefix = "notification_"
|
||||
|
||||
// Reading Preferences keys
|
||||
private let fontSizeKey = "fontSize"
|
||||
private let lineHeightKey = "lineHeight"
|
||||
private let showTableOfContentsKey = "showTableOfContents"
|
||||
private let showReadingTimeKey = "showReadingTime"
|
||||
private let showAuthorKey = "showAuthor"
|
||||
private let showDateKey = "showDate"
|
||||
|
||||
// Notification Preferences keys
|
||||
private let newArticlesKey = "newArticles"
|
||||
private let episodeReleasesKey = "episodeReleases"
|
||||
private let customAlertsKey = "customAlerts"
|
||||
private let badgeCountKey = "badgeCount"
|
||||
private let soundKey = "sound"
|
||||
private let vibrationKey = "vibration"
|
||||
|
||||
private var preferences: AppPreferences?
|
||||
|
||||
private init() {
|
||||
defaults = UserDefaults.standard
|
||||
appGroupDefaults = UserDefaults(suiteName: "group.com.rssuper.app")
|
||||
loadPreferences()
|
||||
}
|
||||
|
||||
/// Load saved preferences
|
||||
private func loadPreferences() {
|
||||
let readingPrefs = ReadingPreferences(
|
||||
fontSize: getFontSize(),
|
||||
lineHeight: getLineHeight(),
|
||||
showTableOfContents: defaults.bool(forKey: readingPrefix + showTableOfContentsKey),
|
||||
showReadingTime: defaults.bool(forKey: readingPrefix + showReadingTimeKey),
|
||||
showAuthor: defaults.bool(forKey: readingPrefix + showAuthorKey),
|
||||
showDate: defaults.bool(forKey: readingPrefix + showDateKey)
|
||||
)
|
||||
|
||||
let notificationPrefs = NotificationPreferences(
|
||||
newArticles: defaults.bool(forKey: notificationPrefix + newArticlesKey),
|
||||
episodeReleases: defaults.bool(forKey: notificationPrefix + episodeReleasesKey),
|
||||
customAlerts: defaults.bool(forKey: notificationPrefix + customAlertsKey),
|
||||
badgeCount: defaults.bool(forKey: notificationPrefix + badgeCountKey),
|
||||
sound: defaults.bool(forKey: notificationPrefix + soundKey),
|
||||
vibration: defaults.bool(forKey: notificationPrefix + vibrationKey)
|
||||
)
|
||||
|
||||
preferences = AppPreferences(reading: readingPrefs, notification: notificationPrefs)
|
||||
}
|
||||
|
||||
/// Save preferences
|
||||
private func savePreferences() {
|
||||
// Save to UserDefaults
|
||||
saveReadingPreferences()
|
||||
saveNotificationPreferences()
|
||||
|
||||
// Sync to App Group if available
|
||||
syncToAppGroup()
|
||||
}
|
||||
|
||||
/// Save reading preferences
|
||||
private func saveReadingPreferences() {
|
||||
guard let prefs = preferences?.reading else { return }
|
||||
|
||||
defaults.set(prefs.fontSize.rawValue, forKey: readingPrefix + fontSizeKey)
|
||||
defaults.set(prefs.lineHeight.rawValue, forKey: readingPrefix + lineHeightKey)
|
||||
defaults.set(prefs.showTableOfContents, forKey: readingPrefix + showTableOfContentsKey)
|
||||
defaults.set(prefs.showReadingTime, forKey: readingPrefix + showReadingTimeKey)
|
||||
defaults.set(prefs.showAuthor, forKey: readingPrefix + showAuthorKey)
|
||||
defaults.set(prefs.showDate, forKey: readingPrefix + showDateKey)
|
||||
}
|
||||
|
||||
/// Save notification preferences
|
||||
private func saveNotificationPreferences() {
|
||||
guard let prefs = preferences?.notification else { return }
|
||||
|
||||
defaults.set(prefs.newArticles, forKey: notificationPrefix + newArticlesKey)
|
||||
defaults.set(prefs.episodeReleases, forKey: notificationPrefix + episodeReleasesKey)
|
||||
defaults.set(prefs.customAlerts, forKey: notificationPrefix + customAlertsKey)
|
||||
defaults.set(prefs.badgeCount, forKey: notificationPrefix + badgeCountKey)
|
||||
defaults.set(prefs.sound, forKey: notificationPrefix + soundKey)
|
||||
defaults.set(prefs.vibration, forKey: notificationPrefix + vibrationKey)
|
||||
}
|
||||
|
||||
/// Sync to App Group
|
||||
private func syncToAppGroup() {
|
||||
guard let groupDefaults = appGroupDefaults else { return }
|
||||
|
||||
groupDefaults.set(defaults.string(forKey: readingPrefix + fontSizeKey), forKey: "fontSize")
|
||||
groupDefaults.set(defaults.string(forKey: readingPrefix + lineHeightKey), forKey: "lineHeight")
|
||||
groupDefaults.set(defaults.bool(forKey: readingPrefix + showTableOfContentsKey), forKey: "showTableOfContents")
|
||||
groupDefaults.set(defaults.bool(forKey: readingPrefix + showReadingTimeKey), forKey: "showReadingTime")
|
||||
groupDefaults.set(defaults.bool(forKey: readingPrefix + showAuthorKey), forKey: "showAuthor")
|
||||
groupDefaults.set(defaults.bool(forKey: readingPrefix + showDateKey), forKey: "showDate")
|
||||
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + newArticlesKey), forKey: "newArticles")
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + episodeReleasesKey), forKey: "episodeReleases")
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + customAlertsKey), forKey: "customAlerts")
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + badgeCountKey), forKey: "badgeCount")
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + soundKey), forKey: "sound")
|
||||
groupDefaults.set(defaults.bool(forKey: notificationPrefix + vibrationKey), forKey: "vibration")
|
||||
}
|
||||
|
||||
// MARK: - Reading Preferences
|
||||
|
||||
func getFontSize() -> ReadingPreferences.FontSize {
|
||||
let value = defaults.string(forKey: readingPrefix + fontSizeKey) ?? "medium"
|
||||
return ReadingPreferences.FontSize(rawValue: value) ?? .medium
|
||||
}
|
||||
|
||||
func setFontSize(_ fontSize: ReadingPreferences.FontSize) {
|
||||
preferences?.reading.fontSize = fontSize
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func getLineHeight() -> ReadingPreferences.LineHeight {
|
||||
let value = defaults.string(forKey: readingPrefix + lineHeightKey) ?? "normal"
|
||||
return ReadingPreferences.LineHeight(rawValue: value) ?? .normal
|
||||
}
|
||||
|
||||
func setLineHeight(_ lineHeight: ReadingPreferences.LineHeight) {
|
||||
preferences?.reading.lineHeight = lineHeight
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isShowTableOfContents() -> Bool {
|
||||
return defaults.bool(forKey: readingPrefix + showTableOfContentsKey)
|
||||
}
|
||||
|
||||
func setShowTableOfContents(_ show: Bool) {
|
||||
preferences?.reading.showTableOfContents = show
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isShowReadingTime() -> Bool {
|
||||
return defaults.bool(forKey: readingPrefix + showReadingTimeKey)
|
||||
}
|
||||
|
||||
func setShowReadingTime(_ show: Bool) {
|
||||
preferences?.reading.showReadingTime = show
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isShowAuthor() -> Bool {
|
||||
return defaults.bool(forKey: readingPrefix + showAuthorKey)
|
||||
}
|
||||
|
||||
func setShowAuthor(_ show: Bool) {
|
||||
preferences?.reading.showAuthor = show
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isShowDate() -> Bool {
|
||||
return defaults.bool(forKey: readingPrefix + showDateKey)
|
||||
}
|
||||
|
||||
func setShowDate(_ show: Bool) {
|
||||
preferences?.reading.showDate = show
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
// MARK: - Notification Preferences
|
||||
|
||||
func isNewArticlesEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + newArticlesKey)
|
||||
}
|
||||
|
||||
func setNewArticles(_ enabled: Bool) {
|
||||
preferences?.notification.newArticles = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isEpisodeReleasesEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + episodeReleasesKey)
|
||||
}
|
||||
|
||||
func setEpisodeReleases(_ enabled: Bool) {
|
||||
preferences?.notification.episodeReleases = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isCustomAlertsEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + customAlertsKey)
|
||||
}
|
||||
|
||||
func setCustomAlerts(_ enabled: Bool) {
|
||||
preferences?.notification.customAlerts = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isBadgeCountEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + badgeCountKey)
|
||||
}
|
||||
|
||||
func setBadgeCount(_ enabled: Bool) {
|
||||
preferences?.notification.badgeCount = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isSoundEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + soundKey)
|
||||
}
|
||||
|
||||
func setSound(_ enabled: Bool) {
|
||||
preferences?.notification.sound = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
func isVibrationEnabled() -> Bool {
|
||||
return defaults.bool(forKey: notificationPrefix + vibrationKey)
|
||||
}
|
||||
|
||||
func setVibration(_ enabled: Bool) {
|
||||
preferences?.notification.vibration = enabled
|
||||
savePreferences()
|
||||
}
|
||||
|
||||
// MARK: - App Group
|
||||
|
||||
func isAppGroupAvailable() -> Bool {
|
||||
return appGroupDefaults != nil
|
||||
}
|
||||
|
||||
func syncFromAppGroup() {
|
||||
guard let groupDefaults = appGroupDefaults else { return }
|
||||
|
||||
if let fontSize = groupDefaults.string(forKey: "fontSize") {
|
||||
defaults.set(fontSize, forKey: readingPrefix + fontSizeKey)
|
||||
}
|
||||
if let lineHeight = groupDefaults.string(forKey: "lineHeight") {
|
||||
defaults.set(lineHeight, forKey: readingPrefix + lineHeightKey)
|
||||
}
|
||||
defaults.set(groupDefaults.bool(forKey: "showTableOfContents"), forKey: readingPrefix + showTableOfContentsKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "showReadingTime"), forKey: readingPrefix + showReadingTimeKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "showAuthor"), forKey: readingPrefix + showAuthorKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "showDate"), forKey: readingPrefix + showDateKey)
|
||||
|
||||
defaults.set(groupDefaults.bool(forKey: "newArticles"), forKey: notificationPrefix + newArticlesKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "episodeReleases"), forKey: notificationPrefix + episodeReleasesKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "customAlerts"), forKey: notificationPrefix + customAlertsKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "badgeCount"), forKey: notificationPrefix + badgeCountKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "sound"), forKey: notificationPrefix + soundKey)
|
||||
defaults.set(groupDefaults.bool(forKey: "vibration"), forKey: notificationPrefix + vibrationKey)
|
||||
|
||||
loadPreferences()
|
||||
}
|
||||
|
||||
// MARK: - Getters
|
||||
|
||||
func getReadingPreferences() -> ReadingPreferences {
|
||||
return preferences?.reading ?? ReadingPreferences()
|
||||
}
|
||||
|
||||
func getNotificationPreferences() -> NotificationPreferences {
|
||||
return preferences?.notification ?? NotificationPreferences()
|
||||
}
|
||||
|
||||
func getAllPreferences() -> AppPreferences {
|
||||
return preferences ?? AppPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
/// App preferences container
|
||||
@objcMembers
|
||||
class AppPreferences: NSObject, Codable {
|
||||
var reading: ReadingPreferences
|
||||
var notification: NotificationPreferences
|
||||
|
||||
init(reading: ReadingPreferences = ReadingPreferences(), notification: NotificationPreferences = NotificationPreferences()) {
|
||||
self.reading = reading
|
||||
self.notification = notification
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
|
||||
/// Notification preferences data structure
|
||||
@objcMembers
|
||||
class NotificationPreferences: NSObject, Codable {
|
||||
var newArticles: Bool
|
||||
var episodeReleases: Bool
|
||||
var customAlerts: Bool
|
||||
var badgeCount: Bool
|
||||
var sound: Bool
|
||||
var vibration: Bool
|
||||
|
||||
init(
|
||||
newArticles: Bool = true,
|
||||
episodeReleases: Bool = true,
|
||||
customAlerts: Bool = true,
|
||||
badgeCount: Bool = true,
|
||||
sound: Bool = true,
|
||||
vibration: Bool = true
|
||||
) {
|
||||
self.newArticles = newArticles
|
||||
self.episodeReleases = episodeReleases
|
||||
self.customAlerts = customAlerts
|
||||
self.badgeCount = badgeCount
|
||||
self.sound = sound
|
||||
self.vibration = vibration
|
||||
}
|
||||
}
|
||||
41
native-route/ios/RSSuper/Settings/ReadingPreferences.swift
Normal file
41
native-route/ios/RSSuper/Settings/ReadingPreferences.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
/// Reading preferences data structure
|
||||
@objcMembers
|
||||
class ReadingPreferences: NSObject, Codable {
|
||||
var fontSize: FontSize
|
||||
var lineHeight: LineHeight
|
||||
var showTableOfContents: Bool
|
||||
var showReadingTime: Bool
|
||||
var showAuthor: Bool
|
||||
var showDate: Bool
|
||||
|
||||
init(
|
||||
fontSize: FontSize = .medium,
|
||||
lineHeight: LineHeight = .normal,
|
||||
showTableOfContents: Bool = false,
|
||||
showReadingTime: Bool = true,
|
||||
showAuthor: Bool = true,
|
||||
showDate: Bool = true
|
||||
) {
|
||||
self.fontSize = fontSize
|
||||
self.lineHeight = lineHeight
|
||||
self.showTableOfContents = showTableOfContents
|
||||
self.showReadingTime = showReadingTime
|
||||
self.showAuthor = showAuthor
|
||||
self.showDate = showDate
|
||||
}
|
||||
|
||||
enum FontSize: String, Codable {
|
||||
case small = "small"
|
||||
case medium = "medium"
|
||||
case large = "large"
|
||||
case xlarge = "xlarge"
|
||||
}
|
||||
|
||||
enum LineHeight: String, Codable {
|
||||
case normal = "normal"
|
||||
case relaxed = "relaxed"
|
||||
case loose = "loose"
|
||||
}
|
||||
}
|
||||
77
native-route/ios/RSSuper/Settings/SettingsMigration.swift
Normal file
77
native-route/ios/RSSuper/Settings/SettingsMigration.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
import Foundation
|
||||
|
||||
/// Settings migration manager
|
||||
/// Handles migration of settings between different app versions
|
||||
class SettingsMigration {
|
||||
|
||||
static let shared = SettingsMigration()
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private let versionKey = "settings_version"
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Check if migration is needed
|
||||
func needsMigration() -> Bool {
|
||||
let currentVersion = 1
|
||||
let storedVersion = defaults.integer(forKey: versionKey)
|
||||
return storedVersion < currentVersion
|
||||
}
|
||||
|
||||
/// Run migration if needed
|
||||
func runMigration() {
|
||||
guard needsMigration() else { return }
|
||||
|
||||
let storedVersion = defaults.integer(forKey: versionKey)
|
||||
|
||||
// Migration 0 -> 1: Convert from old format to new format
|
||||
if storedVersion == 0 {
|
||||
migrateFromV0ToV1()
|
||||
}
|
||||
|
||||
// Update version
|
||||
defaults.set(1, forKey: versionKey)
|
||||
}
|
||||
|
||||
/// Migrate from version 0 to version 1
|
||||
private func migrateFromV0ToV1() {
|
||||
// Check for old notification preferences format
|
||||
if defaults.object(forKey: "notification_prefs") != nil {
|
||||
// Migrate notification preferences
|
||||
if let oldPrefs = defaults.string(forKey: "notification_prefs") {
|
||||
// Parse old format and convert to new format
|
||||
// This is a placeholder - implement actual migration logic
|
||||
migrateNotificationPrefs(oldPrefs)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for old reading preferences format
|
||||
if defaults.object(forKey: "reading_prefs") != nil {
|
||||
// Migrate reading preferences
|
||||
if let oldPrefs = defaults.string(forKey: "reading_prefs") {
|
||||
migrateReadingPrefs(oldPrefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate notification preferences from old format
|
||||
private func migrateNotificationPrefs(_ oldPrefs: String) {
|
||||
// Parse old JSON format
|
||||
// Convert to new format
|
||||
// Set new keys
|
||||
defaults.removeObject(forKey: "notification_prefs")
|
||||
}
|
||||
|
||||
/// Migrate reading preferences from old format
|
||||
private func migrateReadingPrefs(_ oldPrefs: String) {
|
||||
// Parse old JSON format
|
||||
// Convert to new format
|
||||
// Set new keys
|
||||
defaults.removeObject(forKey: "reading_prefs")
|
||||
}
|
||||
|
||||
/// Get current settings version
|
||||
func getCurrentVersion() -> Int {
|
||||
return defaults.integer(forKey: versionKey)
|
||||
}
|
||||
}
|
||||
141
native-route/ios/RSSuper/Settings/SettingsStore.swift
Normal file
141
native-route/ios/RSSuper/Settings/SettingsStore.swift
Normal file
@@ -0,0 +1,141 @@
|
||||
import Foundation
|
||||
|
||||
/// Settings store for iOS RSSuper
|
||||
/// Provides a unified interface for accessing and modifying all app settings
|
||||
class SettingsStore {
|
||||
|
||||
static let shared = SettingsStore()
|
||||
|
||||
private let appSettings: AppSettings
|
||||
private let migration: SettingsMigration
|
||||
|
||||
private init() {
|
||||
appSettings = AppSettings.shared
|
||||
migration = SettingsMigration.shared
|
||||
migration.runMigration()
|
||||
}
|
||||
|
||||
// MARK: - Reading Preferences
|
||||
|
||||
func getFontSize() -> ReadingPreferences.FontSize {
|
||||
return appSettings.getFontSize()
|
||||
}
|
||||
|
||||
func setFontSize(_ fontSize: ReadingPreferences.FontSize) {
|
||||
appSettings.setFontSize(fontSize)
|
||||
}
|
||||
|
||||
func getLineHeight() -> ReadingPreferences.LineHeight {
|
||||
return appSettings.getLineHeight()
|
||||
}
|
||||
|
||||
func setLineHeight(_ lineHeight: ReadingPreferences.LineHeight) {
|
||||
appSettings.setLineHeight(lineHeight)
|
||||
}
|
||||
|
||||
func isShowTableOfContents() -> Bool {
|
||||
return appSettings.isShowTableOfContents()
|
||||
}
|
||||
|
||||
func setShowTableOfContents(_ show: Bool) {
|
||||
appSettings.setShowTableOfContents(show)
|
||||
}
|
||||
|
||||
func isShowReadingTime() -> Bool {
|
||||
return appSettings.isShowReadingTime()
|
||||
}
|
||||
|
||||
func setShowReadingTime(_ show: Bool) {
|
||||
appSettings.setShowReadingTime(show)
|
||||
}
|
||||
|
||||
func isShowAuthor() -> Bool {
|
||||
return appSettings.isShowAuthor()
|
||||
}
|
||||
|
||||
func setShowAuthor(_ show: Bool) {
|
||||
appSettings.setShowAuthor(show)
|
||||
}
|
||||
|
||||
func isShowDate() -> Bool {
|
||||
return appSettings.isShowDate()
|
||||
}
|
||||
|
||||
func setShowDate(_ show: Bool) {
|
||||
appSettings.setShowDate(show)
|
||||
}
|
||||
|
||||
// MARK: - Notification Preferences
|
||||
|
||||
func isNewArticlesEnabled() -> Bool {
|
||||
return appSettings.isNewArticlesEnabled()
|
||||
}
|
||||
|
||||
func setNewArticles(_ enabled: Bool) {
|
||||
appSettings.setNewArticles(enabled)
|
||||
}
|
||||
|
||||
func isEpisodeReleasesEnabled() -> Bool {
|
||||
return appSettings.isEpisodeReleasesEnabled()
|
||||
}
|
||||
|
||||
func setEpisodeReleases(_ enabled: Bool) {
|
||||
appSettings.setEpisodeReleases(enabled)
|
||||
}
|
||||
|
||||
func isCustomAlertsEnabled() -> Bool {
|
||||
return appSettings.isCustomAlertsEnabled()
|
||||
}
|
||||
|
||||
func setCustomAlerts(_ enabled: Bool) {
|
||||
appSettings.setCustomAlerts(enabled)
|
||||
}
|
||||
|
||||
func isBadgeCountEnabled() -> Bool {
|
||||
return appSettings.isBadgeCountEnabled()
|
||||
}
|
||||
|
||||
func setBadgeCount(_ enabled: Bool) {
|
||||
appSettings.setBadgeCount(enabled)
|
||||
}
|
||||
|
||||
func isSoundEnabled() -> Bool {
|
||||
return appSettings.isSoundEnabled()
|
||||
}
|
||||
|
||||
func setSound(_ enabled: Bool) {
|
||||
appSettings.setSound(enabled)
|
||||
}
|
||||
|
||||
func isVibrationEnabled() -> Bool {
|
||||
return appSettings.isVibrationEnabled()
|
||||
}
|
||||
|
||||
func setVibration(_ enabled: Bool) {
|
||||
appSettings.setVibration(enabled)
|
||||
}
|
||||
|
||||
// MARK: - App Group
|
||||
|
||||
func isAppGroupAvailable() -> Bool {
|
||||
return appSettings.isAppGroupAvailable()
|
||||
}
|
||||
|
||||
func syncFromAppGroup() {
|
||||
appSettings.syncFromAppGroup()
|
||||
}
|
||||
|
||||
// MARK: - Getters
|
||||
|
||||
func getReadingPreferences() -> ReadingPreferences {
|
||||
return appSettings.getReadingPreferences()
|
||||
}
|
||||
|
||||
func getNotificationPreferences() -> NotificationPreferences {
|
||||
return appSettings.getNotificationPreferences()
|
||||
}
|
||||
|
||||
func getAllPreferences() -> AppPreferences {
|
||||
return appSettings.getAllPreferences()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user