1→import Foundation 2→import UserNotifications 3→ 4→@MainActor 5→class NotificationService: ObservableObject { 6→ static let shared = NotificationService() 7→ 8→ @Published var isAuthorized = false 9→ @Published var notificationsEnabled = true 10→ 11→ // Thresholds for notifications 12→ @Published var dollarChangeThreshold: Double = 0.5 // 0.5% change 13→ @Published var notifyOnAnyChange: Bool = false 14→ 15→ // Track previous values 16→ private var previousDollarRate: Double? 17→ private var previousEuroRate: Double? 18→ 19→ private init() { 20→ loadSettings() 21→ // Delay authorization check to avoid crash when not running as app bundle 22→ DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in 23→ self?.checkAuthorization() 24→ } 25→ } 26→ 27→ private var isAppBundle: Bool { 28→ Bundle.main.bundleIdentifier != nil 29→ } 30→ 31→ // MARK: - Authorization 32→ 33→ func requestAuthorization() async -> Bool { 34→ guard isAppBundle else { 35→ print("NotificationService: Not running as app bundle, notifications disabled") 36→ return false 37→ } 38→ 39→ do { 40→ let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) 41→ await MainActor.run { 42→ isAuthorized = granted 43→ } 44→ return granted 45→ } catch { 46→ // Silently handle - notifications not allowed is expected on first run 47→ return false 48→ } 49→ } 50→ 51→ func checkAuthorization() { 52→ guard isAppBundle else { 53→ isAuthorized = false 54→ return 55→ } 56→ 57→ UNUserNotificationCenter.current().getNotificationSettings { settings in 58→ Task { @MainActor in 59→ self.isAuthorized = settings.authorizationStatus == .authorized 60→ } 61→ } 62→ } 63→ 64→ // MARK: - Dollar Change Detection 65→ 66→ func checkDollarChange(newRate: Double) { 67→ guard notificationsEnabled, isAuthorized else { return } 68→ 69→ defer { previousDollarRate = newRate } 70→ 71→ guard let previousRate = previousDollarRate else { return } 72→ 73→ let change = newRate - previousRate 74→ let changePercent = (change / previousRate) * 100 75→ 76→ // Check if change exceeds threshold 77→ if notifyOnAnyChange || abs(changePercent) >= dollarChangeThreshold { 78→ sendDollarChangeNotification( 79→ previousRate: previousRate, 80→ newRate: newRate, 81→ changePercent: changePercent 82→ ) 83→ } 84→ } 85→ 86→ func checkEuroChange(newRate: Double) { 87→ guard notificationsEnabled, isAuthorized else { return } 88→ 89→ defer { previousEuroRate = newRate } 90→ 91→ guard let previousRate = previousEuroRate else { return } 92→ 93→ let change = newRate - previousRate 94→ let changePercent = (change / previousRate) * 100 95→ 96→ if notifyOnAnyChange || abs(changePercent) >= dollarChangeThreshold { 97→ sendEuroChangeNotification( 98→ previousRate: previousRate, 99→ newRate: newRate, 100→ changePercent: changePercent 101→ ) 102→ } 103→ } 104→ 105→ // MARK: - Send Notifications 106→ 107→ private func sendDollarChangeNotification(previousRate: Double, newRate: Double, changePercent: Double) { 108→ let content = UNMutableNotificationContent() 109→ 110→ let direction = changePercent >= 0 ? "subiu" : "caiu" 111→ let arrow = changePercent >= 0 ? "📈" : "📉" 112→ 113→ content.title = "\(arrow) Dólar \(direction)!" 114→ content.subtitle = String(format: "R$ %.4f → R$ %.4f", previousRate, newRate) 115→ content.body = String(format: "Variação de %.2f%% na cotação PTAX", abs(changePercent)) 116→ content.sound = .default 117→ content.categoryIdentifier = "DOLLAR_CHANGE" 118→ 119→ // Add custom data 120→ content.userInfo = [ 121→ "type": "dollar", 122→ "previousRate": previousRate, 123→ "newRate": newRate, 124→ "changePercent": changePercent 125→ ] 126→ 127→ let request = UNNotificationRequest( 128→ identifier: "dollar-\(Date().timeIntervalSince1970)", 129→ content: content, 130→ trigger: nil // Deliver immediately 131→ ) 132→ 133→ UNUserNotificationCenter.current().add(request) { error in 134→ if let error = error { 135→ print("Failed to send notification: \(error)") 136→ } 137→ } 138→ } 139→ 140→ private func sendEuroChangeNotification(previousRate: Double, newRate: Double, changePercent: Double) { 141→ let content = UNMutableNotificationContent() 142→ 143→ let direction = changePercent >= 0 ? "subiu" : "caiu" 144→ let arrow = changePercent >= 0 ? "📈" : "📉" 145→ 146→ content.title = "\(arrow) Euro \(direction)!" 147→ content.subtitle = String(format: "R$ %.4f → R$ %.4f", previousRate, newRate) 148→ content.body = String(format: "Variação de %.2f%% na cotação PTAX", abs(changePercent)) 149→ content.sound = .default 150→ content.categoryIdentifier = "EURO_CHANGE" 151→ 152→ let request = UNNotificationRequest( 153→ identifier: "euro-\(Date().timeIntervalSince1970)", 154→ content: content, 155→ trigger: nil 156→ ) 157→ 158→ UNUserNotificationCenter.current().add(request) 159→ } 160→ 161→ // MARK: - Rate Alert 162→ 163→ func sendRateAlert(currency: String, rate: Double, message: String) { 164→ guard notificationsEnabled, isAuthorized else { return } 165→ 166→ let content = UNMutableNotificationContent() 167→ content.title = "🔔 Alerta de Cotação" 168→ content.subtitle = "\(currency): R$ \(String(format: "%.4f", rate))" 169→ content.body = message 170→ content.sound = .default 171→ 172→ let request = UNNotificationRequest( 173→ identifier: "alert-\(Date().timeIntervalSince1970)", 174→ content: content, 175→ trigger: nil 176→ ) 177→ 178→ UNUserNotificationCenter.current().add(request) 179→ } 180→ 181→ // MARK: - SELIC/IPCA Notifications 182→ 183→ func sendRateChangeNotification(type: String, oldValue: Double, newValue: Double) { 184→ guard notificationsEnabled, isAuthorized else { return } 185→ 186→ let content = UNMutableNotificationContent() 187→ 188→ let direction = newValue > oldValue ? "aumentou" : "diminuiu" 189→ 190→ content.title = "📊 \(type) \(direction)!" 191→ content.subtitle = String(format: "%.2f%% → %.2f%%", oldValue, newValue) 192→ content.body = "Nova taxa definida pelo Banco Central" 193→ content.sound = .default 194→ 195→ let request = UNNotificationRequest( 196→ identifier: "\(type.lowercased())-\(Date().timeIntervalSince1970)", 197→ content: content, 198→ trigger: nil 199→ ) 200→ 201→ UNUserNotificationCenter.current().add(request) 202→ } 203→ 204→ // MARK: - Settings 205→ 206→ private func loadSettings() { 207→ let defaults = UserDefaults.standard 208→ notificationsEnabled = defaults.bool(forKey: "notificationsEnabled") 209→ dollarChangeThreshold = defaults.double(forKey: "dollarChangeThreshold") 210→ notifyOnAnyChange = defaults.bool(forKey: "notifyOnAnyChange") 211→ 212→ // Set defaults if not configured 213→ if dollarChangeThreshold == 0 { 214→ dollarChangeThreshold = 0.5 215→ } 216→ if !defaults.bool(forKey: "notificationsConfigured") { 217→ notificationsEnabled = true 218→ defaults.set(true, forKey: "notificationsConfigured") 219→ } 220→ } 221→ 222→ func saveSettings() { 223→ let defaults = UserDefaults.standard 224→ defaults.set(notificationsEnabled, forKey: "notificationsEnabled") 225→ defaults.set(dollarChangeThreshold, forKey: "dollarChangeThreshold") 226→ defaults.set(notifyOnAnyChange, forKey: "notifyOnAnyChange") 227→ } 228→ 229→ // MARK: - Test Notification 230→ 231→ func sendTestNotification() { 232→ let content = UNMutableNotificationContent() 233→ content.title = "🧪 Teste de Notificação" 234→ content.subtitle = "BCB Dashboard" 235→ content.body = "As notificações estão funcionando corretamente!" 236→ content.sound = .default 237→ 238→ let request = UNNotificationRequest( 239→ identifier: "test-\(Date().timeIntervalSince1970)", 240→ content: content, 241→ trigger: nil 242→ ) 243→ 244→ UNUserNotificationCenter.current().add(request) 245→ } 246→ 247→ // MARK: - Clear Notifications 248→ 249→ func clearAllNotifications() { 250→ UNUserNotificationCenter.current().removeAllDeliveredNotifications() 251→ UNUserNotificationCenter.current().removeAllPendingNotificationRequests() 252→ } 253→} 254→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.