<commit_before>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2020 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Foundation
import UserNotifications
import UIKit
import Combine

// NotificationService will display user notification in the future, even when the app is
// in the background. We use it for notifying users, but also as a mechanism to complete
// some actions that need to happen in the background (through the user). The reason is
// that the background processing api seemed very unreliable on iOS and we don't use it.
class NotificationService {

    private lazy var writeNotification = PassthroughSubject<String, Never>()

    private lazy var center = UNUserNotificationCenter.current()

    private var delegate: NotificationCenterDelegateHandler? = nil

    func registerNotifications() {
        let delegate = NotificationCenterDelegateHandler(
            writeNotification: self.writeNotification
        )
        self.center.delegate = delegate
    }

    func getPermissions() -> AnyPublisher<Granted, Error> {
        return Future<Granted, Error> { promise in
            self.center.getNotificationSettings { settings in
                guard settings.authorizationStatus == .authorized else {
                    return promise(.success(false))
                }

                return promise(.success(true))
            }
        }
        .eraseToAnyPublisher()
    }

    func askForPermissions(which: UNAuthorizationOptions = [.badge, .alert, .sound]) -> AnyPublisher<Ignored, Error> {
        return Future<Ignored, Error> { promise in
            self.center.requestAuthorization(options: which) { granted, error in
                if let error = error {
                    return promise(.failure(error))
                }

                self.center.getNotificationSettings { settings -> Void in
                    if settings.alertSetting == .enabled {
                        return promise(.success(true))
                    } else {
                        return promise(.failure("notifications not enabled"))
                    }
                }
            }
        }
        .eraseToAnyPublisher()
    }

    func scheduleNotification(id: String, when: Date) -> AnyPublisher<Ignored, Error> {
        let date = Calendar.current.dateComponents(
            [.year,.month,.day,.hour,.minute,.second,],
            from: when
        )
        let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: false)
        let request = UNNotificationRequest(
            identifier: id, content: mapNotificationToUser(id), trigger: trigger
        )

        return Future<Ignored, Error> { promise in
            self.center.getNotificationSettings { settings in
                guard settings.authorizationStatus == .authorized else {
                    return promise(.failure("unauthorized for notifications"))
                }

                self.center.add(request) { error in
                    if let error = error {
                        return promise(.failure(error))
                    } else {
                        return promise(.success(true))
                    }
                }
            }
        }
        .flatMap { _ in
            self.writeNotification.first { it in it == id }
            .map { _ in true }
        }
        .eraseToAnyPublisher()
    }

    func clearAllNotifications() {
        center.removeAllDeliveredNotifications()
        center.removeAllPendingNotificationRequests()
    }

}

class NotificationCenterDelegateHandler: NSObject, UNUserNotificationCenterDelegate {

    private let writeNotification: PassthroughSubject<String, Never>

    init(writeNotification: PassthroughSubject<String, Never>) {
        self.writeNotification = writeNotification
    }

    // Notification arrived (either in foreground or background)
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable : Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        // TODO: do we already use remote notifications ??
        Logger.e("Notif", "Got a remote notification callback and not sure what to do about it")
        completionHandler(.newData)
    }

    // Called when push notification received while in foreground
    func userNotificationCenter(<commit_msg>ios: handle push notifications (untested)<commit_after>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2020 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Foundation
import UserNotifications
import UIKit
import Combine

// NotificationService will display user notification in the future, even when the app is
// in the background. We use it for notifying users, but also as a mechanism to complete
// some actions that need to happen in the background (through the user). The reason is
// that the background processing api seemed very unreliable on iOS and we don't use it.
class NotificationService {

    private lazy var writeNotification = PassthroughSubject<String, Never>()

    private lazy var center = UNUserNotificationCenter.current()

    private var delegate: NotificationCenterDelegateHandler? = nil

    func registerNotifications() {
        let delegate = NotificationCenterDelegateHandler(
            writeNotification: self.writeNotification
        )
        self.center.delegate = delegate
    }

    func getPermissions() -> AnyPublisher<Granted, Error> {
        return Future<Granted, Error> { promise in
            self.center.getNotificationSettings { settings in
                guard settings.authorizationStatus == .authorized else {
                    return promise(.success(false))
                }

                return promise(.success(true))
            }
        }
        .eraseToAnyPublisher()
    }

    func askForPermissions(which: UNAuthorizationOptions = [.badge, .alert, .sound]) -> AnyPublisher<Ignored, Error> {
        return Future<Ignored, Error> { promise in
            self.center.requestAuthorization(options: which) { granted, error in
                if let error = error {
                    return promise(.failure(error))
                }

                self.center.getNotificationSettings { settings -> Void in
                    if settings.alertSetting == .enabled {
                        return promise(.success(true))
                    } else {
                        return promise(.failure("notifications not enabled"))
                    }
                }
            }
        }
        .eraseToAnyPublisher()
    }

    func scheduleNotification(id: String, when: Date) -> AnyPublisher<Ignored, Error> {
        let date = Calendar.current.dateComponents(
            [.year,.month,.day,.hour,.minute,.second,],
            from: when
        )
        let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: false)
        let request = UNNotificationRequest(
            identifier: id, content: mapNotificationToUser(id), trigger: trigger
        )

        return Future<Ignored, Error> { promise in
            self.center.getNotificationSettings { settings in
                guard settings.authorizationStatus == .authorized else {
                    return promise(.failure("unauthorized for notifications"))
                }

                self.center.add(request) { error in
                    if let error = error {
                        return promise(.failure(error))
                    } else {
                        return promise(.success(true))
                    }
                }
            }
        }
        .flatMap { _ in
            self.writeNotification.first { it in it == id }
            .map { _ in true }
        }
        .eraseToAnyPublisher()
    }

    func clearAllNotifications() {
        center.removeAllDeliveredNotifications()
        center.removeAllPendingNotificationRequests()
    }

}

class NotificationCenterDelegateHandler: NSObject, UNUserNotificationCenterDelegate {

    private let writeNotification: PassthroughSubject<String, Never>

    init(writeNotification: PassthroughSubject<String, Never>) {
        self.writeNotification = writeNotification
    }

    // Notification arrived (either in foreground or background)
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable : Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        // We currently only send push notifications when Lease/Account is about to expire
        // I haven't tested this, but let's try to invoke the usual flow and hope it works.
        // Normally (for foreground notif) this would execute timer expiration callback.
        // So in case of NOTIF_ACC_EXP, it will expire account and deactivate everything.
        Logger.w("Notif", "Received push notification, marking NOTIF_ACC_EXP")
        self.writeNotification.send(NOTIF_ACC_EXP)
        completionHandler(.newData)
    }

    // Called when push notification received while in foreground
    func userNotificationCenter(
{'fraction_non_alphanumeric': 0.07862484420440383, 'fraction_numerical': 0.0016618196925633569, 'mean_word_length': 2.1518821603927987, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1775989', 'n_tokens_mistral': 2383, 'n_tokens_neox': 2201, 'n_words': 723}