mirror of
https://github.com/twofas/2fas-ios.git
synced 2024-11-25 03:40:26 +01:00
Merge tag 'vv5.3.5' into develop
v5.3.5
This commit is contained in:
commit
d223770737
@ -81,7 +81,9 @@ public final class Camera {
|
||||
|
||||
extension Camera: CameraControllerDelegate {
|
||||
func cameraDidInitialize() {}
|
||||
func cameraFailedToInitilize(with error: CameraController.CameraError) {}
|
||||
func cameraFailedToInitilize(with error: CameraController.CameraError) {
|
||||
Log("Camera - can't start: \(error)", module: .camera)
|
||||
}
|
||||
func cameraStartedPreview() {
|
||||
delegate?.didStartScanning()
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ import Foundation
|
||||
public extension Notification.Name {
|
||||
static let servicesWereUpdated = Notification.Name("servicesWereUpdatedNotification")
|
||||
static let sectionsWereUpdated = Notification.Name("sectionsWereUpdatedNotification")
|
||||
static let syncCompletedSuccessfuly = Notification.Name("syncCompletedSuccessfuly")
|
||||
static let clearSyncCompletedSuccessfuly = Notification.Name("clearSyncCompletedSuccessfuly")
|
||||
static let localNotificationsHandled = Notification.Name("localNotificationsHandled")
|
||||
}
|
||||
|
||||
public extension Notification {
|
||||
|
@ -25,6 +25,11 @@ public struct ListNewsEntry: Hashable {
|
||||
case news
|
||||
case features
|
||||
case youtube
|
||||
case tips
|
||||
}
|
||||
|
||||
public enum InternalLink {
|
||||
case backup
|
||||
}
|
||||
|
||||
public let newsID: String
|
||||
@ -34,6 +39,8 @@ public struct ListNewsEntry: Hashable {
|
||||
public let publishedAt: Date
|
||||
public let createdAt: Date?
|
||||
public let wasRead: Bool
|
||||
public let internalLink: InternalLink?
|
||||
public let localNotificationType: String?
|
||||
|
||||
public init(
|
||||
newsID: String,
|
||||
@ -42,7 +49,9 @@ public struct ListNewsEntry: Hashable {
|
||||
message: String?,
|
||||
publishedAt: Date,
|
||||
createdAt: Date?,
|
||||
wasRead: Bool
|
||||
wasRead: Bool,
|
||||
internalLink: InternalLink?,
|
||||
localNotificationType: String? = nil
|
||||
) {
|
||||
self.newsID = newsID
|
||||
self.icon = icon
|
||||
@ -51,5 +60,7 @@ public struct ListNewsEntry: Hashable {
|
||||
self.publishedAt = publishedAt
|
||||
self.createdAt = createdAt
|
||||
self.wasRead = wasRead
|
||||
self.internalLink = internalLink
|
||||
self.localNotificationType = localNotificationType
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -32,6 +32,7 @@ public enum AppEvent {
|
||||
case missingIssuer(String)
|
||||
case supportedCodeAdded(String)
|
||||
case articleRead(String)
|
||||
case localNotificationRead(String)
|
||||
case codeDetailsTypeAdded(String)
|
||||
case codeDetailsAlgorithmChosen(String)
|
||||
case codeDetailsRefreshTimeChosen(String)
|
||||
@ -111,6 +112,7 @@ private extension AppEvent {
|
||||
case .orderIconAsCompany: return "request_icon_as_company_click"
|
||||
case .orderIconDiscord: return "request_icon_discord_click"
|
||||
case .orderIconShare: return "request_icon_share_click"
|
||||
case .localNotificationRead: return "local_notification_read"
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +128,7 @@ private extension AppEvent {
|
||||
case .codeDetailsRefreshTimeChosen(let string): return [AppEventController.KeyValue: string]
|
||||
case .codeDetailsNumberOfDigitsChosen(let string): return [AppEventController.KeyValue: string]
|
||||
case .codeDetailsInitialCounterChosen(let string): return [AppEventController.KeyValue: string]
|
||||
case .localNotificationRead(let string): return [AppEventController.KeyValue: string]
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,10 @@ public protocol CloudBackupStateInteracting: AnyObject {
|
||||
func clearBackup()
|
||||
|
||||
func synchronizeBackup()
|
||||
|
||||
var successSyncDate: Date? { get }
|
||||
func saveSuccessSyncDate()
|
||||
func clearSaveSuccessSync()
|
||||
}
|
||||
|
||||
/// Use one instance per use case
|
||||
@ -80,6 +84,10 @@ extension CloudBackupStateInteractor: CloudBackupStateInteracting {
|
||||
var isBackupEnabled: Bool { isEnabled }
|
||||
var isBackupAvailable: Bool { isAvailable }
|
||||
|
||||
var successSyncDate: Date? {
|
||||
mainRepository.successSyncDate
|
||||
}
|
||||
|
||||
func startMonitoring() {
|
||||
Log("CloudBackupStateInteractor - start monitoring, listenerID: \(listenerID)", module: .interactor)
|
||||
saveStates()
|
||||
@ -138,6 +146,16 @@ extension CloudBackupStateInteractor: CloudBackupStateInteracting {
|
||||
Log("CloudBackupStateInteractor - synchronizeBackup", module: .interactor)
|
||||
mainRepository.synchronizeBackup()
|
||||
}
|
||||
|
||||
func saveSuccessSyncDate() {
|
||||
Log("CloudBackupStateInteractor - saveSuccessSync", module: .interactor)
|
||||
mainRepository.saveSuccessSyncDate(Date())
|
||||
}
|
||||
|
||||
func clearSaveSuccessSync() {
|
||||
Log("CloudBackupStateInteractor - clearSavesuccessSync", module: .interactor)
|
||||
mainRepository.saveSuccessSyncDate(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension CloudBackupStateInteractor {
|
||||
|
@ -246,4 +246,28 @@ public final class InteractorFactory {
|
||||
public func appStateInteractor() -> AppStateInteracting {
|
||||
AppStateInteractor(mainRepository: MainRepositoryImpl.shared)
|
||||
}
|
||||
|
||||
public func mdmInteractor() -> MDMInteracting {
|
||||
MDMInteractor(
|
||||
mainRepository: MainRepositoryImpl.shared,
|
||||
pairingInteractor: pairingWebExtensionInteractor(),
|
||||
cloudBackupStateInteractor: cloudBackupStateInteractor(listenerID: "MDMInteractor")
|
||||
)
|
||||
}
|
||||
|
||||
public func localNotificationStateInteractor() -> LocalNotificationStateInteracting {
|
||||
LocalNotificationStateInteractor(
|
||||
mainRepository: MainRepositoryImpl.shared,
|
||||
serviceListingInteractor: serviceListingInteractor(),
|
||||
cloudBackup: cloudBackupStateInteractor(listenerID: "localNotificationStateInteractor"),
|
||||
pairingDeviceInteractor: pairingWebExtensionInteractor(),
|
||||
mdmInteractor: mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
public func localNotificationFetchInteractor() -> LocalNotificationFetchInteracting {
|
||||
LocalNotificationFetchInteractor(
|
||||
mainRepository: MainRepositoryImpl.shared
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ extension ListNewsNetworkInteractor: ListNewsNetworkInteracting {
|
||||
|
||||
private extension ListNewsNetworkInteractor {
|
||||
func parsedList(_ list: [ListNews.NewsEntry]) -> [ListNewsEntry] {
|
||||
list.compactMap { entry in
|
||||
list.compactMap { entry -> ListNewsEntry? in
|
||||
guard let icon = ListNewsEntry.Icon(rawValue: entry.icon),
|
||||
let publishedAt = dateFormatter.date(from: entry.publishedAt)
|
||||
else { return nil }
|
||||
@ -92,7 +92,8 @@ private extension ListNewsNetworkInteractor {
|
||||
guard let createdAt = entry.createdAt else { return nil }
|
||||
return dateFormatter.date(from: createdAt)
|
||||
}(),
|
||||
wasRead: false
|
||||
wasRead: false,
|
||||
internalLink: nil
|
||||
)
|
||||
}
|
||||
.sorted(by: { $0.publishedAt > $1.publishedAt })
|
||||
|
135
TwoFAS/Data/Interactors/LocalNotificationFetchInteractor.swift
Normal file
135
TwoFAS/Data/Interactors/LocalNotificationFetchInteractor.swift
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Common
|
||||
|
||||
public struct LocalNotification: Hashable {
|
||||
public enum NotificationKind: String, Decodable, Hashable {
|
||||
case tipsNTricks
|
||||
case backup
|
||||
case browserExtension
|
||||
case donation
|
||||
|
||||
static func kindFromCycle(_ cycle: Int) -> NotificationKind {
|
||||
switch cycle {
|
||||
case -1: .tipsNTricks
|
||||
case 0: .backup
|
||||
case 1: .browserExtension
|
||||
default: .donation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let kind: NotificationKind
|
||||
public let publishedAt: Date
|
||||
public let wasRead: Bool
|
||||
}
|
||||
|
||||
public protocol LocalNotificationFetchInteracting: AnyObject {
|
||||
func getNotification(completion: @escaping (LocalNotification?) -> Void)
|
||||
func markNotificationAsRead()
|
||||
}
|
||||
|
||||
final class LocalNotificationFetchInteractor {
|
||||
private let mainRepository: MainRepository
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
private var fetched = false
|
||||
private var notificationCallback: ((LocalNotification?) -> Void)?
|
||||
|
||||
init(mainRepository: MainRepository) {
|
||||
self.mainRepository = mainRepository
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(notificationsHandled),
|
||||
name: .localNotificationsHandled,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalNotificationFetchInteractor: LocalNotificationFetchInteracting {
|
||||
func getNotification(completion: @escaping (LocalNotification?) -> Void) {
|
||||
Log("Local Notification Fetch - fetching", module: .interactor)
|
||||
if mainRepository.localNotificationsHandled {
|
||||
Log("Local Notification Fetch - handled, ready", module: .interactor)
|
||||
fetched = true
|
||||
completion(currentNotification())
|
||||
} else {
|
||||
Log("Local Notification Fetch - awaiting", module: .interactor)
|
||||
notificationCallback = completion
|
||||
}
|
||||
}
|
||||
|
||||
func markNotificationAsRead() {
|
||||
Log("Local Notification Fetch - mark as read", module: .interactor)
|
||||
setWasRead(true)
|
||||
}
|
||||
}
|
||||
|
||||
private extension LocalNotificationFetchInteractor {
|
||||
@objc
|
||||
private func notificationsHandled() {
|
||||
guard !fetched else { return }
|
||||
fetched = true
|
||||
Log("Local Notification Fetch - handled after awaiting", module: .interactor)
|
||||
notificationCallback?(currentNotification())
|
||||
}
|
||||
|
||||
var wasRead: Bool {
|
||||
mainRepository.localNotificationWasRead
|
||||
}
|
||||
|
||||
func setWasRead(_ wasRead: Bool) {
|
||||
mainRepository.saveLocalNotificationWasRead(wasRead)
|
||||
}
|
||||
|
||||
var publishedID: String? {
|
||||
mainRepository.localNotificationPublicationID
|
||||
}
|
||||
|
||||
var cycle: Int {
|
||||
mainRepository.localNotificationCycle
|
||||
}
|
||||
|
||||
func currentNotification() -> LocalNotification? {
|
||||
guard let publishedID, let notificationPublishedDate else {
|
||||
Log("Local Notification Fetch - no notification", module: .interactor)
|
||||
return nil
|
||||
}
|
||||
|
||||
let kind = LocalNotification.NotificationKind.kindFromCycle(cycle)
|
||||
Log("Local Notification Fetch - we have notification of kind: \(kind)", module: .interactor)
|
||||
return LocalNotification(
|
||||
id: publishedID,
|
||||
kind: kind,
|
||||
publishedAt: notificationPublishedDate,
|
||||
wasRead: wasRead
|
||||
)
|
||||
}
|
||||
|
||||
var isPublished: Bool {
|
||||
mainRepository.localNotificationPublicationID != nil
|
||||
}
|
||||
|
||||
var notificationPublishedDate: Date? {
|
||||
mainRepository.localNotificationPublicationDate
|
||||
}
|
||||
}
|
236
TwoFAS/Data/Interactors/LocalNotificationStateInteractor.swift
Normal file
236
TwoFAS/Data/Interactors/LocalNotificationStateInteractor.swift
Normal file
@ -0,0 +1,236 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Common
|
||||
|
||||
public protocol LocalNotificationStateInteracting: AnyObject {
|
||||
func activate()
|
||||
}
|
||||
|
||||
final class LocalNotificationStateInteractor {
|
||||
private let mainRepository: MainRepository
|
||||
private let serviceListingInteractor: ServiceListingInteracting
|
||||
private let cloudBackup: CloudBackupStateInteracting
|
||||
private let pairingDeviceInteractor: PairingWebExtensionInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
|
||||
private let cycleDays: Int = 30
|
||||
private let firstNotificationDays: Int = 2
|
||||
|
||||
private var backupStateKnown = false
|
||||
|
||||
private var awaitsBackupStateChange: Callback?
|
||||
|
||||
init(
|
||||
mainRepository: MainRepository,
|
||||
serviceListingInteractor: ServiceListingInteracting,
|
||||
cloudBackup: CloudBackupStateInteracting,
|
||||
pairingDeviceInteractor: PairingWebExtensionInteracting,
|
||||
mdmInteractor: MDMInteracting
|
||||
) {
|
||||
self.mainRepository = mainRepository
|
||||
self.serviceListingInteractor = serviceListingInteractor
|
||||
self.cloudBackup = cloudBackup
|
||||
self.pairingDeviceInteractor = pairingDeviceInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
|
||||
cloudBackup.stateChanged = { [weak self] in
|
||||
guard self?.backupStateKnown == false else { return }
|
||||
self?.backupStateKnown = true
|
||||
self?.awaitsBackupStateChange?()
|
||||
self?.awaitsBackupStateChange = nil
|
||||
}
|
||||
cloudBackup.startMonitoring()
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalNotificationStateInteractor: LocalNotificationStateInteracting {
|
||||
func activate() {
|
||||
if runCount == 0 { // First run
|
||||
saveCycle(-2)
|
||||
}
|
||||
increaseRunCount()
|
||||
|
||||
if runCount >= 2 && cycle == -2 {
|
||||
startNotification(-1)
|
||||
markLocalNotificationsAsHandled()
|
||||
return
|
||||
}
|
||||
|
||||
guard isTimeForNext else {
|
||||
markLocalNotificationsAsHandled()
|
||||
return
|
||||
}
|
||||
|
||||
clearNotification()
|
||||
|
||||
let next = nextCycle()
|
||||
|
||||
Log("Local Notification State - next cycle: \(next)", module: .interactor)
|
||||
|
||||
switch next {
|
||||
case 0: canDisplayBackup { [weak self] value in
|
||||
if value {
|
||||
self?.startNotification(next)
|
||||
} else {
|
||||
self?.setInactiveNotification(next)
|
||||
}
|
||||
self?.markLocalNotificationsAsHandled()
|
||||
}
|
||||
case 1: if canDisplayBrowserExtension() {
|
||||
startNotification(next)
|
||||
} else {
|
||||
setInactiveNotification(next)
|
||||
}
|
||||
markLocalNotificationsAsHandled()
|
||||
case 2: if canDisplayDonation() {
|
||||
startNotification(3)
|
||||
} else {
|
||||
setInactiveNotification(next)
|
||||
}
|
||||
markLocalNotificationsAsHandled()
|
||||
default:
|
||||
markLocalNotificationsAsHandled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension LocalNotificationStateInteractor {
|
||||
func markLocalNotificationsAsHandled() {
|
||||
Log("Local Notification State - notification handled", module: .interactor)
|
||||
mainRepository.markLocalNotificationsAsHandled()
|
||||
notificationCenter.post(name: .localNotificationsHandled, object: nil)
|
||||
}
|
||||
|
||||
var isTimeForNext: Bool {
|
||||
guard let publicationDate = mainRepository.localNotificationPublicationDate else {
|
||||
return true
|
||||
}
|
||||
let days = publicationDate.days(from: .now)
|
||||
return days >= cycleDays
|
||||
}
|
||||
|
||||
func saveNotificationPublicationDate() {
|
||||
mainRepository.saveLocalNotificationPublicationDate(.now)
|
||||
}
|
||||
|
||||
func clearNotificationPublicationDate() {
|
||||
mainRepository.saveLocalNotificationPublicationDate(nil)
|
||||
}
|
||||
|
||||
func saveCycle(_ cycle: Int) {
|
||||
Log("Local Notification State - setting cycle: \(cycle)", module: .interactor)
|
||||
mainRepository.saveLocalNotificationCycle(cycle)
|
||||
}
|
||||
|
||||
func increaseRunCount() {
|
||||
Log("Local Notification State - increaseRunCount", module: .interactor)
|
||||
mainRepository.saveRunCount(runCount + 1)
|
||||
}
|
||||
|
||||
var runCount: Int {
|
||||
mainRepository.runCount
|
||||
}
|
||||
|
||||
var cycle: Int {
|
||||
mainRepository.localNotificationCycle
|
||||
}
|
||||
|
||||
func startNotification(_ cycle: Int) {
|
||||
Log("Local Notification State - start notification for cycle: \(cycle)", module: .interactor)
|
||||
saveNotificationPublicationDate()
|
||||
saveCycle(cycle)
|
||||
setIsPublished(true)
|
||||
}
|
||||
|
||||
func setInactiveNotification(_ cycle: Int) {
|
||||
Log("Local Notification State - set inactive notification for cycle: \(cycle)", module: .interactor)
|
||||
saveNotificationPublicationDate()
|
||||
saveCycle(cycle)
|
||||
setIsPublished(false)
|
||||
}
|
||||
|
||||
func increaseCycle() {
|
||||
saveCycle(nextCycle())
|
||||
}
|
||||
|
||||
func clearNotification() {
|
||||
Log("Local Notification State - clear notifications", module: .interactor)
|
||||
setIsPublished(false)
|
||||
clearNotificationPublicationDate()
|
||||
setWasRead(false)
|
||||
}
|
||||
|
||||
func setWasRead(_ wasRead: Bool) {
|
||||
mainRepository.saveLocalNotificationWasRead(wasRead)
|
||||
}
|
||||
|
||||
var hasServices: Bool {
|
||||
serviceListingInteractor.hasServices
|
||||
}
|
||||
|
||||
var isBrowserExtensionActive: Bool {
|
||||
pairingDeviceInteractor.hasActiveBrowserExtension
|
||||
}
|
||||
|
||||
func setIsPublished(_ isPublished: Bool) {
|
||||
if isPublished {
|
||||
mainRepository.saveLocalNotificationPublicationID(UUID().uuidString)
|
||||
} else {
|
||||
mainRepository.saveLocalNotificationPublicationID(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func nextCycle() -> Int {
|
||||
switch cycle {
|
||||
case -2: -1
|
||||
case -1: 0
|
||||
case 0: 1
|
||||
case 1: 2
|
||||
case 2: 0
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
|
||||
func canDisplayBackup(completion: @escaping (Bool) -> Void) {
|
||||
if backupStateKnown {
|
||||
completion(isBackupPossible())
|
||||
return
|
||||
}
|
||||
awaitsBackupStateChange = { [weak self] in
|
||||
guard let self else { return }
|
||||
completion(isBackupPossible())
|
||||
}
|
||||
}
|
||||
|
||||
func isBackupPossible() -> Bool {
|
||||
!cloudBackup.isBackupEnabled && hasServices && !mdmInteractor.isBackupBlocked
|
||||
}
|
||||
|
||||
func canDisplayBrowserExtension() -> Bool {
|
||||
!isBrowserExtensionActive && hasServices && !mdmInteractor.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
func canDisplayDonation() -> Bool {
|
||||
hasServices
|
||||
}
|
||||
}
|
136
TwoFAS/Data/Interactors/MDMInteractor.swift
Normal file
136
TwoFAS/Data/Interactors/MDMInteractor.swift
Normal file
@ -0,0 +1,136 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Common
|
||||
|
||||
public protocol MDMInteracting: AnyObject {
|
||||
var isBackupBlocked: Bool { get }
|
||||
var isBiometryBlocked: Bool { get }
|
||||
var isBrowserExtensionBlocked: Bool { get }
|
||||
var isLockoutAttemptsChangeBlocked: Bool { get }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { get }
|
||||
var isPasscodeRequried: Bool { get }
|
||||
var shouldSetPasscode: Bool { get }
|
||||
|
||||
func apply()
|
||||
}
|
||||
|
||||
final class MDMInteractor {
|
||||
private let mainRepository: MainRepository
|
||||
private let pairingInteractor: PairingWebExtensionInteracting
|
||||
private let cloudBackupStateInteractor: CloudBackupStateInteracting
|
||||
|
||||
private var syncDetermined = false
|
||||
private var syncDisabled = false
|
||||
|
||||
init(
|
||||
mainRepository: MainRepository,
|
||||
pairingInteractor: PairingWebExtensionInteracting,
|
||||
cloudBackupStateInteractor: CloudBackupStateInteracting
|
||||
) {
|
||||
self.mainRepository = mainRepository
|
||||
self.pairingInteractor = pairingInteractor
|
||||
self.cloudBackupStateInteractor = cloudBackupStateInteractor
|
||||
|
||||
cloudBackupStateInteractor.stateChanged = { [weak self] in self?.syncStateDetermined() }
|
||||
cloudBackupStateInteractor.startMonitoring()
|
||||
if cloudBackupStateInteractor.isBackupEnabled {
|
||||
syncStateDetermined()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MDMInteractor: MDMInteracting {
|
||||
var isBackupBlocked: Bool {
|
||||
mainRepository.mdmIsBackupBlocked
|
||||
}
|
||||
|
||||
var isBiometryBlocked: Bool {
|
||||
mainRepository.mdmIsBiometryBlocked
|
||||
}
|
||||
|
||||
var isBrowserExtensionBlocked: Bool {
|
||||
mainRepository.mdmIsBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var isLockoutAttemptsChangeBlocked: Bool {
|
||||
mainRepository.mdmLockoutAttempts != nil
|
||||
}
|
||||
|
||||
var isLockoutBlockTimeChangeBlocked: Bool {
|
||||
mainRepository.mdmLockoutBlockTime != nil
|
||||
}
|
||||
|
||||
var isPasscodeRequried: Bool {
|
||||
mainRepository.mdmIsPasscodeRequried
|
||||
}
|
||||
|
||||
var shouldSetPasscode: Bool {
|
||||
isPasscodeRequried && !mainRepository.isPINSet
|
||||
}
|
||||
|
||||
func apply() {
|
||||
if syncDetermined {
|
||||
disableSyncIfNecessary()
|
||||
}
|
||||
|
||||
if isBiometryBlocked && mainRepository.isBiometryEnabled {
|
||||
Log("MDMInteractor - disabling Biometry", module: .interactor)
|
||||
mainRepository.disableBiometry()
|
||||
}
|
||||
|
||||
if isBrowserExtensionBlocked && pairingInteractor.hasActiveBrowserExtension {
|
||||
Log("MDMInteractor - disabling Browser Extension", module: .interactor)
|
||||
pairingInteractor.disableExtension(completion: { _ in })
|
||||
}
|
||||
|
||||
if let lockoutAttempts = mainRepository.mdmLockoutAttempts {
|
||||
Log("MDMInteractor - setting Lockout Attemtps", module: .interactor)
|
||||
mainRepository.setAppLockAttempts(lockoutAttempts)
|
||||
}
|
||||
|
||||
if let blockTime = mainRepository.mdmLockoutBlockTime {
|
||||
Log("MDMInteractor - setting Lockout Block Time", module: .interactor)
|
||||
mainRepository.setAppLockBlockTime(blockTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension MDMInteractor {
|
||||
func syncStateDetermined() {
|
||||
guard !syncDetermined else { return }
|
||||
Log("MDMInteractor - syncStateDetermined", module: .interactor)
|
||||
syncDetermined = true
|
||||
cloudBackupStateInteractor.stopMonitoring()
|
||||
disableSyncIfNecessary()
|
||||
}
|
||||
|
||||
func disableSyncIfNecessary() {
|
||||
Log(
|
||||
"MDMInteractor - disableSyncIfNecessary: Backup enabled: \(cloudBackupStateInteractor.isBackupEnabled)",
|
||||
module: .interactor
|
||||
)
|
||||
if isBackupBlocked && cloudBackupStateInteractor.isBackupEnabled && !syncDisabled {
|
||||
Log("MDMInteractor - disableSyncIfNecessary - Clearing", module: .interactor)
|
||||
syncDisabled = true
|
||||
mainRepository.clearBackup()
|
||||
}
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@ extension NewsInteractor: NewsInteracting {
|
||||
network.fetchNews { [weak self] result in
|
||||
switch result {
|
||||
case .success(let newList):
|
||||
Log("NewsInteractor: News list fetched, items count: \(newList.count)", module: .moduleInteractor)
|
||||
self?.mainRepository.saveLastNewsFetch(Date())
|
||||
self?.handleFetchedList(newList)
|
||||
case .failure:
|
||||
|
@ -27,6 +27,7 @@ public enum PairingWebExtensionError: Error {
|
||||
case noPublicKey
|
||||
case serverError
|
||||
case noInternet
|
||||
case blocked
|
||||
}
|
||||
|
||||
public enum UnparingWebExtensionError: Error {
|
||||
@ -43,6 +44,7 @@ public enum FetchingListError: Error {
|
||||
}
|
||||
|
||||
public protocol PairingWebExtensionInteracting: AnyObject {
|
||||
var hasActiveBrowserExtension: Bool { get }
|
||||
func extensionData(for extensionID: ExtensionID) -> PairedWebExtension?
|
||||
func pair(with extensionID: ExtensionID, completion: @escaping (Result<Void, PairingWebExtensionError>) -> Void)
|
||||
func listAll() -> [PairedWebExtension]
|
||||
@ -51,6 +53,7 @@ public protocol PairingWebExtensionInteracting: AnyObject {
|
||||
completion: @escaping (Result<Void, UnparingWebExtensionError>) -> Void
|
||||
)
|
||||
func fetchList(completion: @escaping (Result<[PairedWebExtension], FetchingListError>) -> Void)
|
||||
func disableExtension(completion: @escaping (Result<Void, UnparingWebExtensionError>) -> Void)
|
||||
}
|
||||
|
||||
final class PairingWebExtensionInteractor {
|
||||
@ -72,12 +75,19 @@ final class PairingWebExtensionInteractor {
|
||||
}
|
||||
|
||||
extension PairingWebExtensionInteractor: PairingWebExtensionInteracting {
|
||||
var hasActiveBrowserExtension: Bool { !listAll().isEmpty }
|
||||
|
||||
func extensionData(for extensionID: ExtensionID) -> PairedWebExtension? {
|
||||
listAll().first { $0.extensionID == extensionID }
|
||||
}
|
||||
|
||||
func pair(with extensionID: ExtensionID, completion: @escaping (Result<Void, PairingWebExtensionError>) -> Void) {
|
||||
Log("PairingWebExtensionInteractor - pair. extensionID: \(extensionID)", module: .interactor)
|
||||
guard !mainRepository.mdmIsBrowserExtensionBlocked else {
|
||||
Log("PairingWebExtensionInteractor - pair. Error: blocked!", module: .interactor)
|
||||
completion(.failure(.blocked))
|
||||
return
|
||||
}
|
||||
guard !mainRepository.listAllPairedExtensions().map({ $0.extensionID }).contains(extensionID) else {
|
||||
Log("PairingWebExtensionInteractor - failure. Already paired", module: .interactor)
|
||||
completion(.failure(.alreadyPaired))
|
||||
@ -193,6 +203,51 @@ extension PairingWebExtensionInteractor: PairingWebExtensionInteracting {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disableExtension(completion: @escaping (Result<Void, UnparingWebExtensionError>) -> Void) {
|
||||
Log("PairingWebExtensionInteractor - disableExtension", module: .interactor)
|
||||
guard let deviceID = mainRepository.deviceID else {
|
||||
Log("PairingWebExtensionInteractor - disableExtension. Failure: not registered", module: .interactor)
|
||||
completion(.failure(.notRegistered))
|
||||
return
|
||||
}
|
||||
|
||||
let extensionIDs = mainRepository.listAllPairedExtensions().map({ $0.extensionID })
|
||||
|
||||
guard !extensionIDs.isEmpty else {
|
||||
Log("PairingWebExtensionInteractor - disableExtension. Failure: not paired", module: .interactor)
|
||||
completion(.failure(.notPaired))
|
||||
return
|
||||
}
|
||||
|
||||
var count = extensionIDs.count
|
||||
var errored = false
|
||||
|
||||
for extensionID in extensionIDs {
|
||||
mainRepository.deletePairing(for: deviceID, extensionID: extensionID) { [weak self] result in
|
||||
switch result {
|
||||
case .success:
|
||||
Log("PairingWebExtensionInteractor - disableExtension. Success", module: .interactor)
|
||||
self?.mainRepository.deletePairedExtension(with: extensionID)
|
||||
self?.mainRepository.removeAuthRequest(for: extensionID)
|
||||
|
||||
count -= 1
|
||||
|
||||
if count == 0 && !errored {
|
||||
completion(.success(Void()))
|
||||
}
|
||||
case .failure(let error):
|
||||
guard !errored else { return }
|
||||
Log("PairingWebExtensionInteractor - disableExtension. Error: \(error)", module: .interactor)
|
||||
errored = true
|
||||
switch error {
|
||||
case .noInternet: completion(.failure(.noInternet))
|
||||
case .connection: completion(.failure(.serverError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PairingWebExtensionInteractor {
|
||||
|
@ -81,7 +81,9 @@ extension RootInteractor: RootInteracting {
|
||||
|
||||
func markIntroAsShown() {
|
||||
mainRepository.setIntroductionAsShown()
|
||||
mainRepository.enableCloudBackup()
|
||||
if !mainRepository.mdmIsBackupBlocked {
|
||||
mainRepository.enableCloudBackup()
|
||||
}
|
||||
}
|
||||
|
||||
func lockApplicationIfNeeded(presentLoginImmediately: @escaping () -> Void) {
|
||||
|
29
TwoFAS/Data/MainRepository/MDMRepository/MDMRepository.swift
Normal file
29
TwoFAS/Data/MainRepository/MDMRepository/MDMRepository.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol MDMRepository: AnyObject {
|
||||
var isBackupBlocked: Bool { get }
|
||||
var isBiometryBlocked: Bool { get }
|
||||
var isBrowserExtensionBlocked: Bool { get }
|
||||
var lockoutAttepts: AppLockAttempts? { get }
|
||||
var lockoutBlockTime: AppLockBlockTime? { get }
|
||||
var isPasscodeRequried: Bool { get }
|
||||
}
|
104
TwoFAS/Data/MainRepository/MDMRepository/MDMRepositoryImpl.swift
Normal file
104
TwoFAS/Data/MainRepository/MDMRepository/MDMRepositoryImpl.swift
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class MDMRepositoryImpl {
|
||||
private let mdmKey = "com.apple.configuration.managed"
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
private var settings: [String: Any] = [:]
|
||||
|
||||
private enum SettingsKeys: String {
|
||||
case isBackupBlocked = "blockBackup"
|
||||
case isBiometryBlocked = "blockBiometry"
|
||||
case isBrowserExtensionBlocked = "blockBrowserExtension"
|
||||
case lockoutAttempts = "lockoutAttempts"
|
||||
case lockoutBlockTime = "lockoutBlockTime"
|
||||
case isPasscodeRequried = "requirePasscode"
|
||||
}
|
||||
|
||||
private let isBackupBlockedDefaultValue = false
|
||||
private let isBiometryBlockedDefaultValue = false
|
||||
private let isBrowserExtensionBlockedDefaultValue = false
|
||||
private let isPasscodeRequriedDefaultValue = false
|
||||
|
||||
init() {
|
||||
reload()
|
||||
}
|
||||
|
||||
private func reload() {
|
||||
settings = userDefaults.dictionary(forKey: mdmKey) ?? [:]
|
||||
}
|
||||
}
|
||||
|
||||
extension MDMRepositoryImpl: MDMRepository {
|
||||
var isBackupBlocked: Bool {
|
||||
guard let value = settings[SettingsKeys.isBackupBlocked.rawValue] as? Bool else {
|
||||
return isBackupBlockedDefaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var isBiometryBlocked: Bool {
|
||||
guard let value = settings[SettingsKeys.isBiometryBlocked.rawValue] as? Bool else {
|
||||
return isBiometryBlockedDefaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var isBrowserExtensionBlocked: Bool {
|
||||
guard let value = settings[SettingsKeys.isBrowserExtensionBlocked.rawValue] as? Bool else {
|
||||
return isBrowserExtensionBlockedDefaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var lockoutAttepts: AppLockAttempts? {
|
||||
guard let value = settings[SettingsKeys.lockoutAttempts.rawValue] as? Int else {
|
||||
return nil
|
||||
}
|
||||
switch value {
|
||||
case 0: return .noLimit
|
||||
case 3: return .try3
|
||||
case 5: return .try5
|
||||
case 10: return .try10
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var lockoutBlockTime: AppLockBlockTime? {
|
||||
guard let value = settings[SettingsKeys.lockoutBlockTime.rawValue] as? Int else {
|
||||
return nil
|
||||
}
|
||||
switch value {
|
||||
case 3: return .min3
|
||||
case 5: return .min5
|
||||
case 10: return .min10
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var isPasscodeRequried: Bool {
|
||||
guard let value = settings[SettingsKeys.isPasscodeRequried.rawValue] as? Bool else {
|
||||
return isPasscodeRequriedDefaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
@ -122,6 +122,7 @@ protocol MainRepository: AnyObject {
|
||||
// MARK: - Cloud
|
||||
var secretSyncError: ((String) -> Void)? { get set }
|
||||
var isCloudBackupConnected: Bool { get }
|
||||
var successSyncDate: Date? { get }
|
||||
var cloudCurrentState: CloudState { get }
|
||||
func registerForCloudStateChanges(_ listener: @escaping CloudStateListener, id: CloudStateListenerID)
|
||||
func unregisterForCloudStageChanges(with id: CloudStateListenerID)
|
||||
@ -133,6 +134,7 @@ protocol MainRepository: AnyObject {
|
||||
userInfo: [AnyHashable: Any],
|
||||
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
|
||||
)
|
||||
func saveSuccessSyncDate(_ date: Date?)
|
||||
|
||||
// MARK: - Import
|
||||
var fileURL: URL? { get set }
|
||||
@ -471,4 +473,31 @@ protocol MainRepository: AnyObject {
|
||||
|
||||
// MARK: - Time Verification
|
||||
func timeVerificationStart()
|
||||
|
||||
// MARK: - MDM options
|
||||
var mdmIsBackupBlocked: Bool { get }
|
||||
var mdmIsBiometryBlocked: Bool { get }
|
||||
var mdmIsBrowserExtensionBlocked: Bool { get }
|
||||
var mdmLockoutAttempts: AppLockAttempts? { get }
|
||||
var mdmLockoutBlockTime: AppLockBlockTime? { get }
|
||||
var mdmIsPasscodeRequried: Bool { get }
|
||||
|
||||
// MARK: - Local Notifications
|
||||
var localNotificationPublicationDate: Date? { get }
|
||||
func saveLocalNotificationPublicationDate(_ date: Date?)
|
||||
|
||||
var localNotificationPublicationID: String? { get }
|
||||
func saveLocalNotificationPublicationID(_ ID: String?)
|
||||
|
||||
var localNotificationWasRead: Bool { get }
|
||||
func saveLocalNotificationWasRead(_ wasRead: Bool)
|
||||
|
||||
var localNotificationCycle: Int { get }
|
||||
func saveLocalNotificationCycle(_ cycle: Int)
|
||||
|
||||
var runCount: Int { get }
|
||||
func saveRunCount(_ count: Int)
|
||||
|
||||
var localNotificationsHandled: Bool { get }
|
||||
func markLocalNotificationsAsHandled()
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ public enum CloudState: Equatable {
|
||||
}
|
||||
|
||||
extension MainRepositoryImpl {
|
||||
var successSyncDate: Date? {
|
||||
userDefaultsRepository.successSyncDate
|
||||
}
|
||||
|
||||
var secretSyncError: ((String) -> Void)? {
|
||||
get {
|
||||
cloudHandler.secretSyncError
|
||||
@ -89,6 +93,10 @@ extension MainRepositoryImpl {
|
||||
) {
|
||||
SyncInstance.didReceiveRemoteNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func saveSuccessSyncDate(_ date: Date?) {
|
||||
userDefaultsRepository.saveSuccessSyncDate(date)
|
||||
}
|
||||
}
|
||||
|
||||
private extension MainRepositoryImpl {
|
||||
|
@ -0,0 +1,70 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MainRepositoryImpl {
|
||||
var localNotificationPublicationDate: Date? {
|
||||
userDefaultsRepository.localNotificationPublicationDate
|
||||
}
|
||||
|
||||
func saveLocalNotificationPublicationDate(_ date: Date?) {
|
||||
userDefaultsRepository.saveLocalNotificationPublicationDate(date)
|
||||
}
|
||||
|
||||
var localNotificationPublicationID: String? {
|
||||
userDefaultsRepository.localNotificationPublicationID
|
||||
}
|
||||
|
||||
func saveLocalNotificationPublicationID(_ ID: String?) {
|
||||
userDefaultsRepository.saveLocalNotificationPublicationID(ID)
|
||||
}
|
||||
|
||||
var localNotificationWasRead: Bool {
|
||||
userDefaultsRepository.localNotificationWasRead
|
||||
}
|
||||
|
||||
func saveLocalNotificationWasRead(_ wasRead: Bool) {
|
||||
userDefaultsRepository.saveLocalNotificationWasRead(wasRead)
|
||||
}
|
||||
|
||||
var localNotificationCycle: Int {
|
||||
userDefaultsRepository.localNotificationCycle
|
||||
}
|
||||
|
||||
func saveLocalNotificationCycle(_ cycle: Int) {
|
||||
userDefaultsRepository.saveLocalNotificationCycle(cycle)
|
||||
}
|
||||
|
||||
var runCount: Int {
|
||||
userDefaultsRepository.runCount
|
||||
}
|
||||
|
||||
func saveRunCount(_ count: Int) {
|
||||
userDefaultsRepository.saveRunCount(count)
|
||||
}
|
||||
|
||||
var localNotificationsHandled: Bool {
|
||||
_areLocalNotificationsHandled
|
||||
}
|
||||
|
||||
func markLocalNotificationsAsHandled() {
|
||||
_areLocalNotificationsHandled = true
|
||||
}
|
||||
}
|
46
TwoFAS/Data/MainRepository/MainRepositoryImpl+MDM.swift
Normal file
46
TwoFAS/Data/MainRepository/MainRepositoryImpl+MDM.swift
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MainRepositoryImpl {
|
||||
var mdmIsBackupBlocked: Bool {
|
||||
mdmRepository.isBackupBlocked
|
||||
}
|
||||
|
||||
var mdmIsBiometryBlocked: Bool {
|
||||
mdmRepository.isBiometryBlocked
|
||||
}
|
||||
|
||||
var mdmIsBrowserExtensionBlocked: Bool {
|
||||
mdmRepository.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var mdmLockoutAttempts: AppLockAttempts? {
|
||||
mdmRepository.lockoutAttepts
|
||||
}
|
||||
|
||||
var mdmLockoutBlockTime: AppLockBlockTime? {
|
||||
mdmRepository.lockoutBlockTime
|
||||
}
|
||||
|
||||
var mdmIsPasscodeRequried: Bool {
|
||||
mdmRepository.isPasscodeRequried
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ final class MainRepositoryImpl: MainRepository {
|
||||
let serviceDefinitionDatabase: ServiceDefinitionDatabase = ServiceDefinitionDatabaseImpl()
|
||||
let iconDescriptionDatabase: IconDescriptionDatabase = IconDescriptionDatabaseImpl()
|
||||
let initialPermissionStateDataController = PermissionsStateDataController()
|
||||
let mdmRepository: MDMRepository = MDMRepositoryImpl()
|
||||
|
||||
let serviceNameTranslation: String
|
||||
let notificationCenter = NotificationCenter.default
|
||||
@ -86,6 +87,7 @@ final class MainRepositoryImpl: MainRepository {
|
||||
var storageError: ((String) -> Void)?
|
||||
|
||||
var _isLockScreenActive = false
|
||||
var _areLocalNotificationsHandled = false
|
||||
|
||||
// Cached values for higher pefrormance
|
||||
var cachedSortType: SortType?
|
||||
|
@ -97,4 +97,24 @@ protocol UserDefaultsRepository: AnyObject {
|
||||
var exchangeToken: String? { get }
|
||||
func setExchangeToken(_ key: String)
|
||||
func clearExchangeToken()
|
||||
|
||||
var successSyncDate: Date? { get }
|
||||
func saveSuccessSyncDate(_ date: Date?)
|
||||
|
||||
// MARK: - Local Notifications
|
||||
|
||||
var localNotificationPublicationDate: Date? { get }
|
||||
func saveLocalNotificationPublicationDate(_ date: Date?)
|
||||
|
||||
var localNotificationPublicationID: String? { get }
|
||||
func saveLocalNotificationPublicationID(_ ID: String?)
|
||||
|
||||
var localNotificationWasRead: Bool { get }
|
||||
func saveLocalNotificationWasRead(_ wasRead: Bool)
|
||||
|
||||
var localNotificationCycle: Int { get }
|
||||
func saveLocalNotificationCycle(_ cycle: Int)
|
||||
|
||||
var runCount: Int { get }
|
||||
func saveRunCount(_ count: Int)
|
||||
}
|
||||
|
@ -51,6 +51,12 @@ final class UserDefaultsRepositoryImpl: UserDefaultsRepository {
|
||||
case mainMenuPortraitCollapsed
|
||||
case mainMenuLandscapeCollapsed
|
||||
case dateOfFirstRun
|
||||
case syncSuccessDate
|
||||
case localNotificationPublicationDate
|
||||
case localNotificationPublicationID
|
||||
case localNotificationWasRead
|
||||
case localNotificationCycle
|
||||
case runCount
|
||||
}
|
||||
private let userDefaults = UserDefaults()
|
||||
private let sharedDefaults = UserDefaults(suiteName: Config.suiteName)!
|
||||
@ -279,6 +285,16 @@ final class UserDefaultsRepositoryImpl: UserDefaultsRepository {
|
||||
userDefaults.bool(forKey: Keys.introductionWasShown.rawValue)
|
||||
}
|
||||
|
||||
// MARK: - Sync success
|
||||
|
||||
var successSyncDate: Date? {
|
||||
userDefaults.object(forKey: Keys.syncSuccessDate.rawValue) as? Date
|
||||
}
|
||||
func saveSuccessSyncDate(_ date: Date?) {
|
||||
userDefaults.set(date, forKey: Keys.syncSuccessDate.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
// MARK: - View Path
|
||||
|
||||
func clearViewPath() {
|
||||
@ -332,6 +348,53 @@ final class UserDefaultsRepositoryImpl: UserDefaultsRepository {
|
||||
sharedDefaults.synchronize()
|
||||
}
|
||||
|
||||
// MARK: - Local Notifications
|
||||
|
||||
var localNotificationPublicationDate: Date? {
|
||||
userDefaults.object(forKey: Keys.localNotificationPublicationDate.rawValue) as? Date
|
||||
}
|
||||
|
||||
func saveLocalNotificationPublicationDate(_ date: Date?) {
|
||||
userDefaults.set(date, forKey: Keys.localNotificationPublicationDate.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
var localNotificationPublicationID: String? {
|
||||
userDefaults.string(forKey: Keys.localNotificationPublicationID.rawValue)
|
||||
}
|
||||
|
||||
func saveLocalNotificationPublicationID(_ ID: String?) {
|
||||
userDefaults.set(ID, forKey: Keys.localNotificationPublicationID.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
var localNotificationWasRead: Bool {
|
||||
userDefaults.bool(forKey: Keys.localNotificationWasRead.rawValue)
|
||||
}
|
||||
|
||||
func saveLocalNotificationWasRead(_ wasRead: Bool) {
|
||||
userDefaults.set(wasRead, forKey: Keys.localNotificationWasRead.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
var localNotificationCycle: Int {
|
||||
userDefaults.integer(forKey: Keys.localNotificationCycle.rawValue)
|
||||
}
|
||||
|
||||
func saveLocalNotificationCycle(_ cycle: Int) {
|
||||
userDefaults.set(cycle, forKey: Keys.localNotificationCycle.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
var runCount: Int {
|
||||
userDefaults.integer(forKey: Keys.runCount.rawValue)
|
||||
}
|
||||
|
||||
func saveRunCount(_ count: Int) {
|
||||
userDefaults.set(count, forKey: Keys.runCount.rawValue)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
// MARK: - Clear all
|
||||
|
||||
func clearAll() {
|
||||
|
75
TwoFAS/MDM/Description.md
Normal file
75
TwoFAS/MDM/Description.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Managed App Configuration
|
||||
|
||||
This is an early version of MAC support. We've implemented basic parameters requested via GitHub issue.
|
||||
|
||||
# Parameters
|
||||
|
||||
Provided parameters are optional and should work for new deployments. Those scenarios were also tested on existing deployments, but please note that there could be bugs.
|
||||
If you need more parameters and you want to support the project please don't hesitate to contact us.
|
||||
## Block Backup
|
||||
### Key
|
||||
`blockBackup`
|
||||
### Value type
|
||||
`bool`
|
||||
### Value
|
||||
`true` to blocked backup, `null` or `false` otherwise.
|
||||
### Description
|
||||
Blocks Sync, Copy Secret and file Export functionality.
|
||||
### Existing deployment
|
||||
iCloud sync, if enabled, is wipe out and disabled.
|
||||
|
||||
## Block Biometry
|
||||
### Key
|
||||
`blockBiometry`
|
||||
### Value type
|
||||
`bool`
|
||||
### Value
|
||||
`true` to block biometry, `null` or `false` otherwise.
|
||||
### Description
|
||||
Blocks usage of biometry to authorize user into the app.
|
||||
### Existing deployment
|
||||
Disables biometry on next run.
|
||||
## Block Browser Extension
|
||||
### Key
|
||||
`blockBrowserExtension`
|
||||
### Value type
|
||||
`bool`
|
||||
### Value
|
||||
`true` to block Browser Extension, `null` or `false` otherwise.
|
||||
### Description
|
||||
Blocks usage of Browser Extension.
|
||||
### Existing deployment
|
||||
Unpairs the device.
|
||||
## Lockout - Attempts
|
||||
### Key
|
||||
`lockoutAttempts`
|
||||
### Value type
|
||||
`int`
|
||||
### Value
|
||||
`0`, `3`, `5` or `10`. `null` for app's default value (3).
|
||||
### Description
|
||||
Locks app after 3, 5 or 10 wrong authorisation attempts. `0` - no limit.
|
||||
### Existing deployment
|
||||
Sets the value.
|
||||
## Lockout - Block Time
|
||||
### Key
|
||||
`lockoutBlockTime`
|
||||
### Value type
|
||||
`int`
|
||||
### Value
|
||||
`3`, `5` or `10`. `null` for app's default value (10).
|
||||
### Description
|
||||
Block the app for 3, 5, 10 minutes.
|
||||
### Existing deployment
|
||||
Sets the value.
|
||||
## Require Passcode
|
||||
### Key
|
||||
`requirePasscode`
|
||||
### Value type
|
||||
`bool`
|
||||
### Value
|
||||
`true` to require passcode for user authorisation, `null` or `false` otherwise.
|
||||
### Description
|
||||
Requries user to setup a passcode. Blocks disabling it. User can change existing passcode.
|
||||
### Existing deployment
|
||||
If passcode is not set than after the next run user will be required to setup one.
|
92
TwoFAS/MDM/specfile.xml
Normal file
92
TwoFAS/MDM/specfile.xml
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0"?>
|
||||
<managedAppConfiguration>
|
||||
<version>1.0.0</version>
|
||||
<bundleId>com.twofas.org</bundleId>
|
||||
<dict>
|
||||
<boolean keyName="blockBackup">
|
||||
<constraints nullable="true">
|
||||
</constraints>
|
||||
</boolean>
|
||||
<boolean keyName="blockBiometry">
|
||||
<constraints nullable="true">
|
||||
</constraints>
|
||||
</boolean>
|
||||
<boolean keyName="blockBrowserExtension">
|
||||
<constraints nullable="true">
|
||||
</constraints>
|
||||
</boolean>
|
||||
<integer keyName="lockoutAttempts">
|
||||
<constraints nullable="true">
|
||||
<values>
|
||||
<value>0</value>
|
||||
<value>3</value>
|
||||
<value>5</value>
|
||||
<value>10</value>
|
||||
</values>
|
||||
</constraints>
|
||||
</integer>
|
||||
<integer keyName="lockoutBlockTime">
|
||||
<constraints nullable="true">
|
||||
<values>
|
||||
<value>3</value>
|
||||
<value>5</value>
|
||||
<value>10</value>
|
||||
</values>
|
||||
</constraints>
|
||||
</integer>
|
||||
<boolean keyName="requirePasscode">
|
||||
<constraints nullable="true">
|
||||
</constraints>
|
||||
</boolean>
|
||||
</dict>
|
||||
<presentation defaultLocale="en-US">
|
||||
<field keyName="blockBackup" type="checkbox">
|
||||
<label>
|
||||
<language value="en-US">Block backup</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Blocks iCloud sync and Export functionality</language>
|
||||
</description>
|
||||
</field>
|
||||
<field keyName="blockBiometry" type="checkbox">
|
||||
<label>
|
||||
<language value="en-US">Block biometry</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Blocks usage of biometry to authenticate</language>
|
||||
</description>
|
||||
</field>
|
||||
<field keyName="blockBrowserExtension" type="checkbox">
|
||||
<label>
|
||||
<language value="en-US">Block Browser Extension</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Blocks usage of Browser Extension</language>
|
||||
</description>
|
||||
</field>
|
||||
<field keyName="lockoutAttempts" type="input">
|
||||
<label>
|
||||
<language value="en-US">Lockout Attempts</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Block app after X login attempts. Valid values: 0 - No limit, 3, 5 and 10 attempts</language>
|
||||
</description>
|
||||
</field>
|
||||
<field keyName="lockoutBlockTime" type="input">
|
||||
<label>
|
||||
<language value="en-US">Lockout Block Time</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Block authentication for X minutes. Valid values are: 3, 5, and 10 (minutes).</language>
|
||||
</description>
|
||||
</field>
|
||||
<field keyName="requirePasscode" type="checkbox">
|
||||
<label>
|
||||
<language value="en-US">Require Passcode</language>
|
||||
</label>
|
||||
<description>
|
||||
<language value="en-US">Require Passcode</language>
|
||||
</description>
|
||||
</field>
|
||||
</presentation>
|
||||
</managedAppConfiguration>
|
@ -85,7 +85,8 @@ private extension StorageRepositoryImpl {
|
||||
message: entity.message,
|
||||
publishedAt: entity.publishedAt,
|
||||
createdAt: entity.createdAt,
|
||||
wasRead: entity.wasRead
|
||||
wasRead: entity.wasRead,
|
||||
internalLink: nil
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -85,6 +85,8 @@ final class CloudHandler: CloudHandlerType {
|
||||
private let itemHandlerMigrationProxy: ItemHandlerMigrationProxy
|
||||
private let cloudKit: CloudKit
|
||||
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
|
||||
private var isClearing = false
|
||||
|
||||
private var listeners: [String: CloudHandlerStateListener] = [:]
|
||||
@ -313,6 +315,7 @@ final class CloudHandler: CloudHandlerType {
|
||||
private func setDisabled() {
|
||||
Log("Cloud Handler - Set Disabled", module: .cloudSync)
|
||||
ConstStorage.cloudEnabled = false
|
||||
notificationCenter.post(name: .clearSyncCompletedSuccessfuly, object: nil)
|
||||
}
|
||||
|
||||
private var isEnabled: Bool { ConstStorage.cloudEnabled }
|
||||
@ -344,6 +347,8 @@ final class CloudHandler: CloudHandlerType {
|
||||
|
||||
if isClearing {
|
||||
clearBackup()
|
||||
} else {
|
||||
notificationCenter.post(name: .syncCompletedSuccessfuly, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
import Foundation
|
||||
import CloudKit
|
||||
import Common
|
||||
import UIKit
|
||||
|
||||
final class CloudKit {
|
||||
typealias DeletedEntries = ([(name: String, type: String)]) -> Void
|
||||
@ -37,6 +38,7 @@ final class CloudKit {
|
||||
var useriCloudProblem: Callback?
|
||||
var userLoggedOut: Callback?
|
||||
var resetStack: Callback?
|
||||
var abortSync: Callback?
|
||||
|
||||
var fetchFinishedSuccessfuly: Callback?
|
||||
var changesSavedSuccessfuly: Callback?
|
||||
@ -472,6 +474,15 @@ final class CloudKit {
|
||||
zoneUpdated = false
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if UIApplication.shared.applicationState == .background {
|
||||
self.abortSync?()
|
||||
self.syncTokenHandler.prepare()
|
||||
self.clearRecordChanges()
|
||||
self.operation?.cancel()
|
||||
self.operation = nil
|
||||
return
|
||||
}
|
||||
|
||||
if !self.deletedRecords.isEmpty {
|
||||
Log("CloudKit - deletedRecords not empty", module: .cloudSync)
|
||||
self.deletedEntries?(self.deletedRecords.map { (name: $0.record.recordName, type: $0.type) })
|
||||
|
@ -62,6 +62,7 @@ final class SyncHandler {
|
||||
cloudKit.updatedEntries = { [weak self] entries in self?.updateEntries(entries) }
|
||||
cloudKit.fetchFinishedSuccessfuly = { [weak self] in self?.fetchFinishedSuccessfuly() }
|
||||
cloudKit.changesSavedSuccessfuly = { [weak self] in self?.changesSavedSuccessfuly() }
|
||||
cloudKit.abortSync = { [weak self] in self?.abortSync() }
|
||||
|
||||
cloudKit.resetStack = { [weak self] in
|
||||
Log("SyncHandler - resetStack", module: .cloudSync)
|
||||
@ -386,5 +387,11 @@ final class SyncHandler {
|
||||
private func dateOffsetet(for logEntity: LogEntity) -> Date {
|
||||
logEntity.date.addingTimeInterval(TimeInterval(timeOffset))
|
||||
}
|
||||
|
||||
private func abortSync() {
|
||||
isSyncing = false
|
||||
applyingChanges = false
|
||||
}
|
||||
|
||||
// swiftlint:enable line_length
|
||||
}
|
||||
|
@ -142,6 +142,10 @@
|
||||
C2286034266972B5005E88E3 /* TokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2286033266972B5005E88E3 /* TokenCell.swift */; };
|
||||
C22C1399267682B4001AA5F1 /* CategoryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22C1398267682B4001AA5F1 /* CategoryData.swift */; };
|
||||
C22C139B267684DC001AA5F1 /* CategoryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22C139A267684DC001AA5F1 /* CategoryHandler.swift */; };
|
||||
C22CD3922B913E94005FE348 /* MDMRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CD3912B913E94005FE348 /* MDMRepository.swift */; };
|
||||
C22CD3942B913EB4005FE348 /* MDMRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CD3932B913EB4005FE348 /* MDMRepositoryImpl.swift */; };
|
||||
C22CD3962B91419B005FE348 /* MainRepositoryImpl+MDM.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CD3952B91419B005FE348 /* MainRepositoryImpl+MDM.swift */; };
|
||||
C22CD3982B9142CA005FE348 /* MDMInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CD3972B9142CA005FE348 /* MDMInteractor.swift */; };
|
||||
C22CF3DF27413F0F004F6A03 /* LogStorage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C22CF3DD27413F0F004F6A03 /* LogStorage.xcdatamodeld */; };
|
||||
C22CF3E327414073004F6A03 /* LogEntryEntity+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CF3E127414073004F6A03 /* LogEntryEntity+CoreDataClass.swift */; };
|
||||
C22CF3E427414073004F6A03 /* LogEntryEntity+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22CF3E227414073004F6A03 /* LogEntryEntity+CoreDataProperties.swift */; };
|
||||
@ -404,6 +408,7 @@
|
||||
C274C7742ADD3CB000B8AAC1 /* CounterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24DFB2027D1770D00F3EACC /* CounterState.swift */; };
|
||||
C274C7762ADD3DF900B8AAC1 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = C274C7752ADD3DF900B8AAC1 /* FirebaseMessaging */; };
|
||||
C274C77C2ADD3E3500B8AAC1 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = C274C77B2ADD3E3500B8AAC1 /* FirebaseCrashlytics */; };
|
||||
C276D1712B9A672C008C9CD4 /* LocalNotificationStateInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C276D1702B9A672C008C9CD4 /* LocalNotificationStateInteractor.swift */; };
|
||||
C277C32C245C3FD6009214F3 /* MainContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C277C32B245C3FD6009214F3 /* MainContainerViewController.swift */; };
|
||||
C278121C27F9F3E600F31453 /* ExportPublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C278121B27F9F3E600F31453 /* ExportPublicKey.swift */; };
|
||||
C278121F27F9F4C000F31453 /* SwCrypt in Frameworks */ = {isa = PBXBuildFile; productRef = C278121E27F9F4C000F31453 /* SwCrypt */; };
|
||||
@ -667,6 +672,7 @@
|
||||
C2BBD1D42B8113A9009A91FB /* TwoFASWidgetInline.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BBD1D22B81127C009A91FB /* TwoFASWidgetInline.swift */; };
|
||||
C2BBD1D72B812117009A91FB /* TwoFASWidgetCircular.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BBD1D52B8120F4009A91FB /* TwoFASWidgetCircular.swift */; };
|
||||
C2BBD2382B8130B0009A91FB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C2BBD2332B81308C009A91FB /* Localizable.strings */; };
|
||||
C2BC3FC92B94E70F004C4BA0 /* NewPINNavigationFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */; };
|
||||
C2BD82B2236619E800FBD69A /* BackupAreaWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD82B1236619E800FBD69A /* BackupAreaWarningView.swift */; };
|
||||
C2BD85DA2640290E0087D087 /* IntroductionFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD85D92640290E0087D087 /* IntroductionFlowController.swift */; };
|
||||
C2BD85DC26402A910087D087 /* IntroductionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD85DB26402A910087D087 /* IntroductionContainerView.swift */; };
|
||||
@ -1019,6 +1025,8 @@
|
||||
C2EA566D285696FE00026BFE /* AskForAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA566C285696FE00026BFE /* AskForAuthViewController.swift */; };
|
||||
C2EA566F28569A5E00026BFE /* AskForAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA566E28569A5E00026BFE /* AskForAuthPresenter.swift */; };
|
||||
C2EA567128569ADD00026BFE /* AskForAuthFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA567028569ADD00026BFE /* AskForAuthFlowController.swift */; };
|
||||
C2EA6E5C2BA10AFE0034A964 /* LocalNotificationFetchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA6E5B2BA10AFE0034A964 /* LocalNotificationFetchInteractor.swift */; };
|
||||
C2EA6E5E2BA110FA0034A964 /* MainRepositoryImpl+LocalNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA6E5D2BA110FA0034A964 /* MainRepositoryImpl+LocalNotifications.swift */; };
|
||||
C2EBDFFC2AC44750008FD744 /* GuideMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EBDFFB2AC44750008FD744 /* GuideMenuViewController.swift */; };
|
||||
C2EBDFFE2AC4476D008FD744 /* GuideMenuPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EBDFFD2AC4476D008FD744 /* GuideMenuPresenter.swift */; };
|
||||
C2EBE0022AC44787008FD744 /* GuideMenuFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EBE0012AC44787008FD744 /* GuideMenuFlowController.swift */; };
|
||||
@ -1674,6 +1682,10 @@
|
||||
C22A8669287F60BD009A43FE /* MainRepositoryImpl+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainRepositoryImpl+Notifications.swift"; sourceTree = "<group>"; };
|
||||
C22C1398267682B4001AA5F1 /* CategoryData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryData.swift; sourceTree = "<group>"; };
|
||||
C22C139A267684DC001AA5F1 /* CategoryHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryHandler.swift; sourceTree = "<group>"; };
|
||||
C22CD3912B913E94005FE348 /* MDMRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMRepository.swift; sourceTree = "<group>"; };
|
||||
C22CD3932B913EB4005FE348 /* MDMRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMRepositoryImpl.swift; sourceTree = "<group>"; };
|
||||
C22CD3952B91419B005FE348 /* MainRepositoryImpl+MDM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainRepositoryImpl+MDM.swift"; sourceTree = "<group>"; };
|
||||
C22CD3972B9142CA005FE348 /* MDMInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDMInteractor.swift; sourceTree = "<group>"; };
|
||||
C22CD79A2630D43F009C0258 /* FileInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileInteractor.swift; sourceTree = "<group>"; };
|
||||
C22CF3DE27413F0F004F6A03 /* LogStorage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LogStorage.xcdatamodel; sourceTree = "<group>"; };
|
||||
C22CF3E127414073004F6A03 /* LogEntryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogEntryEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
@ -1969,6 +1981,7 @@
|
||||
C274BDC1276941FB00EE28BC /* AppSecurityPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecurityPresenter.swift; sourceTree = "<group>"; };
|
||||
C274BDC32769425900EE28BC /* AppSecurityPresenter+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppSecurityPresenter+Menu.swift"; sourceTree = "<group>"; };
|
||||
C274BDC52769428700EE28BC /* AppSecurityModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecurityModels.swift; sourceTree = "<group>"; };
|
||||
C276D1702B9A672C008C9CD4 /* LocalNotificationStateInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationStateInteractor.swift; sourceTree = "<group>"; };
|
||||
C277C32B245C3FD6009214F3 /* MainContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContainerViewController.swift; sourceTree = "<group>"; };
|
||||
C278121B27F9F3E600F31453 /* ExportPublicKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportPublicKey.swift; sourceTree = "<group>"; };
|
||||
C278122027F9FA9900F31453 /* RSAEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSAEncryption.swift; sourceTree = "<group>"; };
|
||||
@ -2283,6 +2296,7 @@
|
||||
C2BBD24C2B813162009A91FB /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = TwoFAS/Other/uk.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BBD24D2B813162009A91FB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = TwoFAS/Other/de.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BBD24E2B813162009A91FB /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = TwoFAS/Other/pl.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
|
||||
C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPINNavigationFlowController.swift; sourceTree = "<group>"; };
|
||||
C2BD82B1236619E800FBD69A /* BackupAreaWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAreaWarningView.swift; sourceTree = "<group>"; };
|
||||
C2BD85D92640290E0087D087 /* IntroductionFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionFlowController.swift; sourceTree = "<group>"; };
|
||||
C2BD85DB26402A910087D087 /* IntroductionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionContainerView.swift; sourceTree = "<group>"; };
|
||||
@ -2529,6 +2543,8 @@
|
||||
C2EA566C285696FE00026BFE /* AskForAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AskForAuthViewController.swift; sourceTree = "<group>"; };
|
||||
C2EA566E28569A5E00026BFE /* AskForAuthPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AskForAuthPresenter.swift; sourceTree = "<group>"; };
|
||||
C2EA567028569ADD00026BFE /* AskForAuthFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AskForAuthFlowController.swift; sourceTree = "<group>"; };
|
||||
C2EA6E5B2BA10AFE0034A964 /* LocalNotificationFetchInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationFetchInteractor.swift; sourceTree = "<group>"; };
|
||||
C2EA6E5D2BA110FA0034A964 /* MainRepositoryImpl+LocalNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainRepositoryImpl+LocalNotifications.swift"; sourceTree = "<group>"; };
|
||||
C2EBDFFB2AC44750008FD744 /* GuideMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideMenuViewController.swift; sourceTree = "<group>"; };
|
||||
C2EBDFFD2AC4476D008FD744 /* GuideMenuPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideMenuPresenter.swift; sourceTree = "<group>"; };
|
||||
C2EBE0012AC44787008FD744 /* GuideMenuFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideMenuFlowController.swift; sourceTree = "<group>"; };
|
||||
@ -3316,6 +3332,9 @@
|
||||
C212ACDD2AF6F929001C8665 /* RootInteractor.swift */,
|
||||
C2CACFBF2AFFB7F7001E0F8E /* LoginInteractor.swift */,
|
||||
C2CACFC12B015725001E0F8E /* AppStateInteractor.swift */,
|
||||
C22CD3972B9142CA005FE348 /* MDMInteractor.swift */,
|
||||
C276D1702B9A672C008C9CD4 /* LocalNotificationStateInteractor.swift */,
|
||||
C2EA6E5B2BA10AFE0034A964 /* LocalNotificationFetchInteractor.swift */,
|
||||
);
|
||||
path = Interactors;
|
||||
sourceTree = "<group>";
|
||||
@ -3738,6 +3757,15 @@
|
||||
path = Architecture;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C22CD3902B913E85005FE348 /* MDMRepository */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C22CD3912B913E94005FE348 /* MDMRepository.swift */,
|
||||
C22CD3932B913EB4005FE348 /* MDMRepositoryImpl.swift */,
|
||||
);
|
||||
path = MDMRepository;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C22CF3E027414028004F6A03 /* Log */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -5629,6 +5657,7 @@
|
||||
C2A7067C276D1A5F00885D79 /* Flow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */,
|
||||
C2A7067D276D1A7500885D79 /* NewPINFlowController.swift */,
|
||||
C2997AA82454D767006D5943 /* SelectPINLengthController.swift */,
|
||||
);
|
||||
@ -6710,6 +6739,7 @@
|
||||
C2D44E6B275C14840043F5D6 /* MainRepository */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C22CD3902B913E85005FE348 /* MDMRepository */,
|
||||
C2A026B42AF82FDB00DB2E52 /* DataExternalTranslations.swift */,
|
||||
C240485627652E170076376E /* DataTypes */,
|
||||
C2D44E69275C14790043F5D6 /* MainRepository.swift */,
|
||||
@ -6746,7 +6776,9 @@
|
||||
C2DF245F29859F9700762A26 /* MainRepositoryImpl+ViewPath.swift */,
|
||||
C2A026B22AF7C42500DB2E52 /* MainRepositoryImpl+TimeVerification.swift */,
|
||||
C2CACFC32B015847001E0F8E /* MainRepositoryImpl+LockScreen.swift */,
|
||||
C2EA6E5D2BA110FA0034A964 /* MainRepositoryImpl+LocalNotifications.swift */,
|
||||
C249AF62278A40FE005F9D80 /* UserDefaults */,
|
||||
C22CD3952B91419B005FE348 /* MainRepositoryImpl+MDM.swift */,
|
||||
);
|
||||
path = MainRepository;
|
||||
sourceTree = "<group>";
|
||||
@ -9004,6 +9036,7 @@
|
||||
C2B1208F29D76BD10020281E /* AppearancePresenter+Menu.swift in Sources */,
|
||||
C2A7945127F9016D00E5C641 /* BrowserExtensionEditNameViewController.swift in Sources */,
|
||||
C242B4D12A9BD950005CC1BC /* AddingServiceAdvancedSectionDividerView.swift in Sources */,
|
||||
C2BC3FC92B94E70F004C4BA0 /* NewPINNavigationFlowController.swift in Sources */,
|
||||
C21B3A1427AF0BC9005C603B /* ColorPickerFlowController.swift in Sources */,
|
||||
C2B39E5929F5BD7800EC31F6 /* TokensCategory.swift in Sources */,
|
||||
C26BCE01281010A000CA6A9A /* SelectServiceModuleInteractor.swift in Sources */,
|
||||
@ -9338,6 +9371,7 @@
|
||||
C2E7C4052ADB2BB400478D89 /* ImportFromFileInteractor+Parsers.swift in Sources */,
|
||||
C2E7C3E02ADB2BB400478D89 /* ImportFromFileInteractor.swift in Sources */,
|
||||
C2E7C3C82ADB2B9C00478D89 /* MainRepositoryImpl+DeviceName.swift in Sources */,
|
||||
C22CD3982B9142CA005FE348 /* MDMInteractor.swift in Sources */,
|
||||
C2E7C3E22ADB2BB400478D89 /* InteractorFactory.swift in Sources */,
|
||||
C2E7C3E92ADB2BB400478D89 /* AdvancedAlertInteractor.swift in Sources */,
|
||||
C274C7722ADD3CA500B8AAC1 /* Generator.swift in Sources */,
|
||||
@ -9354,6 +9388,7 @@
|
||||
C2E7C4042ADB2BB400478D89 /* WebExtensionEncryptionInteractor.swift in Sources */,
|
||||
C2AE5F512ADC199F00AED670 /* AppEventController.swift in Sources */,
|
||||
C2E7C3CE2ADB2B9C00478D89 /* MainRepositoryImpl+Code.swift in Sources */,
|
||||
C2EA6E5C2BA10AFE0034A964 /* LocalNotificationFetchInteractor.swift in Sources */,
|
||||
C2E7C3DC2ADB2BAC00478D89 /* UserDefaultsRepository.swift in Sources */,
|
||||
C2E7C4022ADB2BB400478D89 /* ServiceModifyInteractor.swift in Sources */,
|
||||
C2AE5F552ADC1A4A00AED670 /* SecurityProtocol.swift in Sources */,
|
||||
@ -9369,6 +9404,7 @@
|
||||
C2AE5F802ADC8B0D00AED670 /* Code+Support.swift in Sources */,
|
||||
C2E7C3D62ADB2B9C00478D89 /* MainRepositoryImpl+PushNotifications.swift in Sources */,
|
||||
C2E7C40B2ADB2BE500478D89 /* SocialChannel.swift in Sources */,
|
||||
C22CD3962B91419B005FE348 /* MainRepositoryImpl+MDM.swift in Sources */,
|
||||
C2E7C3C32ADB2B9C00478D89 /* MainRepositoryImpl+News.swift in Sources */,
|
||||
C2E7C3BC2ADB2B9C00478D89 /* MainRepositoryImpl+Guides.swift in Sources */,
|
||||
C2E7C3C62ADB2B9C00478D89 /* MainRepositoryImpl+Cloud.swift in Sources */,
|
||||
@ -9380,11 +9416,13 @@
|
||||
C2E7C3CF2ADB2B9C00478D89 /* MainRepositoryImpl+ServiceDefinition.swift in Sources */,
|
||||
C2E7C3D92ADB2B9C00478D89 /* MainRepositoryImpl+General.swift in Sources */,
|
||||
C2E7C3DA2ADB2BA600478D89 /* MainRepositoryImpl+ViewPath.swift in Sources */,
|
||||
C2EA6E5E2BA110FA0034A964 /* MainRepositoryImpl+LocalNotifications.swift in Sources */,
|
||||
C2E7C3C72ADB2B9C00478D89 /* MainRepositoryImpl+Camera.swift in Sources */,
|
||||
C2E7C4062ADB2BB400478D89 /* PairingWebExtensionInteractor.swift in Sources */,
|
||||
C2E7C4012ADB2BB400478D89 /* TrashingServiceInteractor.swift in Sources */,
|
||||
C2E7C3D82ADB2B9C00478D89 /* MainRepositoryImpl+Storage.swift in Sources */,
|
||||
C2AE5F7E2ADC8B0D00AED670 /* Extensions.swift in Sources */,
|
||||
C22CD3922B913E94005FE348 /* MDMRepository.swift in Sources */,
|
||||
C2E7C3F72ADB2BB400478D89 /* IconInteractor.swift in Sources */,
|
||||
C2E7C4122ADB2BE500478D89 /* ExchangeData2.swift in Sources */,
|
||||
C2AE5F812ADC8B0D00AED670 /* Code+TwoFASWebExtension.swift in Sources */,
|
||||
@ -9419,6 +9457,7 @@
|
||||
C2AE5F522ADC19E600AED670 /* Notifications.swift in Sources */,
|
||||
C2E7C3C22ADB2B9C00478D89 /* MainRepositoryImpl+AdvancedAlertState.swift in Sources */,
|
||||
C2E7C4152ADB2BE500478D89 /* ExchangeData.swift in Sources */,
|
||||
C22CD3942B913EB4005FE348 /* MDMRepositoryImpl.swift in Sources */,
|
||||
C2E7C3F32ADB2BB400478D89 /* ViewPathInteractor.swift in Sources */,
|
||||
C2E7C3FB2ADB2BB400478D89 /* ServiceListingInteractor.swift in Sources */,
|
||||
C2A026B32AF7C42500DB2E52 /* MainRepositoryImpl+TimeVerification.swift in Sources */,
|
||||
@ -9436,6 +9475,7 @@
|
||||
C2CACFC22B015725001E0F8E /* AppStateInteractor.swift in Sources */,
|
||||
C2AE5F692ADC5D9D00AED670 /* Security.swift in Sources */,
|
||||
C2E7C3DD2ADB2BB400478D89 /* RegisterDeviceInteractor.swift in Sources */,
|
||||
C276D1712B9A672C008C9CD4 /* LocalNotificationStateInteractor.swift in Sources */,
|
||||
C2E7C40F2ADB2BE500478D89 /* PINType.swift in Sources */,
|
||||
C2E7C3CB2ADB2B9C00478D89 /* MainRepositoryImpl+Appearance.swift in Sources */,
|
||||
C2E7C3F62ADB2BB400478D89 /* WebExtensionAuthInteractor.swift in Sources */,
|
||||
@ -9882,7 +9922,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -9925,7 +9965,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -9967,7 +10007,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10011,7 +10051,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10053,7 +10093,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10101,7 +10141,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10273,7 +10313,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = mh_execute;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
OTHER_LDFLAGS = (
|
||||
@ -10318,7 +10358,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = mh_execute;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(OTHER_LDFLAGS)",
|
||||
@ -10365,7 +10405,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
|
||||
@ -10412,7 +10452,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
|
||||
@ -10455,7 +10495,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10499,7 +10539,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10544,7 +10584,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
@ -10589,7 +10629,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_FAST_MATH = YES;
|
||||
@ -10626,7 +10666,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.twofas.org.TwoFASAuth;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -10660,7 +10700,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.twofas.org.TwoFASAuth;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -10699,7 +10739,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10744,7 +10784,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
@ -10842,7 +10882,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
@ -10878,7 +10918,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.twofas.org.TwoFASWidget;
|
||||
@ -10911,7 +10951,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
@ -10945,7 +10985,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGED_BINARY_TYPE = manual;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.twofas.org.TwoFASServiceIntent;
|
||||
@ -11042,7 +11082,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
@ -11089,7 +11129,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_FAST_MATH = YES;
|
||||
@ -11134,7 +11174,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
@ -11181,7 +11221,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
MTL_FAST_MATH = YES;
|
||||
@ -11226,7 +11266,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
@ -11274,7 +11314,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
@ -11320,7 +11360,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
@ -11368,7 +11408,7 @@
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 5.3.0;
|
||||
MARKETING_VERSION = 5.3.5;
|
||||
MERGEABLE_LIBRARY = NO;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
|
@ -32,8 +32,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "f91c8167141d0279726c6f6d9d4a47c026785cbc",
|
||||
"version" : "10.21.0"
|
||||
"revision" : "fe09d61a539e11fdbe24f269bba10144b6145fe2",
|
||||
"version" : "10.22.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -41,8 +41,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "cb8617fab75d181270a1d8f763f26b15c73e2e1e",
|
||||
"version" : "10.21.0"
|
||||
"revision" : "bf3bb24f6b60a7acedaef504e9ce97154203217a",
|
||||
"version" : "10.22.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -50,8 +50,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "a732a4b47f59e4f725a2ea10f0c77e93a7131117",
|
||||
"version" : "9.3.0"
|
||||
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
|
||||
"version" : "9.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -59,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "bc27fad73504f3d4af235de451f02ee22586ebd3",
|
||||
"version" : "7.12.1"
|
||||
"revision" : "830ffa9276e10267881f2697283c2fcd867603fd",
|
||||
"version" : "7.13.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -113,8 +113,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "9d108e9112aa1d65ce508facf804674546116d9c",
|
||||
"version" : "1.22.3"
|
||||
"revision" : "43aaef65e0c665daadf848761d560e446d350d3d",
|
||||
"version" : "1.22.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -122,8 +122,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/nanopb.git",
|
||||
"state" : {
|
||||
"revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692",
|
||||
"version" : "2.30909.0"
|
||||
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
|
||||
"version" : "2.30910.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -140,8 +140,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/promises.git",
|
||||
"state" : {
|
||||
"revision" : "e70e889c0196c76d22759eb50d6a0270ca9f1d9e",
|
||||
"version" : "2.3.1"
|
||||
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
|
||||
"version" : "2.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -50,6 +50,12 @@ extension UIViewController {
|
||||
definesPresentationContext = true
|
||||
}
|
||||
|
||||
func configureAsFullscreenModal() {
|
||||
modalPresentationStyle = .fullScreen
|
||||
isModalInPresentation = true
|
||||
definesPresentationContext = true
|
||||
}
|
||||
|
||||
func setCustomLeftBackButton() {
|
||||
navigationItem.leftBarButtonItem = createCustomLeftBackButton()
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ final class PasswordTextField: LimitedTextField {
|
||||
var isActive: Callback?
|
||||
var textDidChange: TextDidChange?
|
||||
var didResign: Callback?
|
||||
var verifyPassword = true
|
||||
|
||||
override func textField(
|
||||
_ textField: UITextField,
|
||||
@ -43,13 +44,13 @@ final class PasswordTextField: LimitedTextField {
|
||||
|
||||
let matches = string.matches(ExportFileRules.regExp) || string.isBackspace
|
||||
|
||||
if !matches {
|
||||
if !matches && verifyPassword {
|
||||
notAllowedCharacter?()
|
||||
} else {
|
||||
textDidChange?(newString as String)
|
||||
}
|
||||
|
||||
return matches
|
||||
return matches || !verifyPassword
|
||||
}
|
||||
|
||||
override func resignFirstResponder() -> Bool {
|
||||
|
@ -51,6 +51,16 @@ final class RevealPasswordInput: UIView {
|
||||
input.text ?? ""
|
||||
}
|
||||
|
||||
var verifyPassword: Bool {
|
||||
get {
|
||||
input.verifyPassword
|
||||
}
|
||||
|
||||
set {
|
||||
input.verifyPassword = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private let lineHeight: CGFloat = 25
|
||||
|
||||
private let titleLabel = TitleLabel()
|
||||
|
@ -32,7 +32,8 @@ final class ModuleInteractorFactory {
|
||||
registerDeviceInteractor: InteractorFactory.shared.registerDeviceInteractor(),
|
||||
appStateInteractor: InteractorFactory.shared.appStateInteractor(),
|
||||
notificationInteractor: InteractorFactory.shared.notificationInteractor(),
|
||||
widgetsInteractor: InteractorFactory.shared.widgetsInteractor()
|
||||
widgetsInteractor: InteractorFactory.shared.widgetsInteractor(),
|
||||
localNotificationStateInteractor: InteractorFactory.shared.localNotificationStateInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -46,14 +47,16 @@ final class ModuleInteractorFactory {
|
||||
pushNotifications: InteractorFactory.shared.pushNotificationRegistrationInteractor(),
|
||||
protectionInteractor: InteractorFactory.shared.protectionInteractor(),
|
||||
networkStatusInteractor: InteractorFactory.shared.networkStatusInteractor(),
|
||||
pairingDeviceInteractor: InteractorFactory.shared.pairingWebExtensionInteractor()
|
||||
pairingDeviceInteractor: InteractorFactory.shared.pairingWebExtensionInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
func backupMenuModuleInteractor() -> BackupMenuModuleInteracting {
|
||||
BackupMenuModuleInteractor(
|
||||
serviceListingInteractor: InteractorFactory.shared.serviceListingInteractor(),
|
||||
cloudBackup: InteractorFactory.shared.cloudBackupStateInteractor(listenerID: "BackupMenuModuleInteractor")
|
||||
cloudBackup: InteractorFactory.shared.cloudBackupStateInteractor(listenerID: "BackupMenuModuleInteractor"),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -122,13 +125,15 @@ final class ModuleInteractorFactory {
|
||||
func appSecurityModuleInteractor() -> AppSecurityModuleInteracting {
|
||||
AppSecurityModuleInteractor(
|
||||
protectionInteractor: InteractorFactory.shared.protectionInteractor(),
|
||||
appLockStateInteractor: InteractorFactory.shared.appLockStateInteractor()
|
||||
appLockStateInteractor: InteractorFactory.shared.appLockStateInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
func appLockModuleInteractor() -> AppLockModuleInteracting {
|
||||
AppLockModuleInteractor(
|
||||
appLockInteractor: InteractorFactory.shared.appLockStateInteractor()
|
||||
appLockInteractor: InteractorFactory.shared.appLockStateInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -140,8 +145,8 @@ final class ModuleInteractorFactory {
|
||||
)
|
||||
}
|
||||
|
||||
func newPINModuleInteractor() -> NewPINModuleInteracting {
|
||||
NewPINModuleInteractor()
|
||||
func newPINModuleInteractor(lockNavigation: Bool) -> NewPINModuleInteracting {
|
||||
NewPINModuleInteractor(lockNavigation: lockNavigation)
|
||||
}
|
||||
|
||||
func trashModuleInteractor() -> TrashModuleInteracting {
|
||||
@ -161,7 +166,8 @@ final class ModuleInteractorFactory {
|
||||
func cameraScannerModuleInteractor() -> CameraScannerModuleInteracting {
|
||||
CameraScannerModuleInteractor(
|
||||
newCodeInteractor: InteractorFactory.shared.newCodeInteractor(),
|
||||
pushNotificationPermission: InteractorFactory.shared.pushNotificationRegistrationInteractor()
|
||||
pushNotificationPermission: InteractorFactory.shared.pushNotificationRegistrationInteractor(),
|
||||
cameraPermissionInteractor: InteractorFactory.shared.cameraPermissionInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -198,7 +204,8 @@ final class ModuleInteractorFactory {
|
||||
sectionInteractor: InteractorFactory.shared.sectionInteractor(),
|
||||
notificationsInteractor: InteractorFactory.shared.notificationInteractor(),
|
||||
serviceDefinitionInteractor: InteractorFactory.shared.serviceDefinitionInteractor(),
|
||||
advancedAlertInteractor: InteractorFactory.shared.advancedAlertInteractor()
|
||||
advancedAlertInteractor: InteractorFactory.shared.advancedAlertInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -276,7 +283,10 @@ final class ModuleInteractorFactory {
|
||||
}
|
||||
|
||||
func newsModuleInteractor() -> NewsModuleInteracting {
|
||||
NewsModuleInteractor(newsInteractor: InteractorFactory.shared.newsInteractor())
|
||||
NewsModuleInteractor(
|
||||
newsInteractor: InteractorFactory.shared.newsInteractor(),
|
||||
localNotificationFetchInteractor: InteractorFactory.shared.localNotificationFetchInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
func composeServiceCategorySelectionModuleInteractor(
|
||||
@ -318,7 +328,8 @@ final class ModuleInteractorFactory {
|
||||
widgetsInteractor: InteractorFactory.shared.widgetsInteractor(),
|
||||
newCodeInteractor: InteractorFactory.shared.newCodeInteractor(),
|
||||
newsInteractor: InteractorFactory.shared.newsInteractor(),
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor()
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor(),
|
||||
localNotificationFetchInteractor: InteractorFactory.shared.localNotificationFetchInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
@ -331,7 +342,9 @@ final class ModuleInteractorFactory {
|
||||
newVersionInteractor: InteractorFactory.shared.newVersionInteractor(),
|
||||
networkStatusInteractor: InteractorFactory.shared.networkStatusInteractor(),
|
||||
appInfoInteractor: InteractorFactory.shared.appInfoInteractor(),
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor()
|
||||
rootInteractor: InteractorFactory.shared.rootInteractor(),
|
||||
mdmInteractor: InteractorFactory.shared.mdmInteractor(),
|
||||
protectionInteractor: InteractorFactory.shared.protectionInteractor()
|
||||
)
|
||||
}
|
||||
|
||||
|
12
TwoFAS/TwoFAS/Other/Assets.xcassets/NotificationsIcons/NotificationTips.imageset/Contents.json
vendored
Normal file
12
TwoFAS/TwoFAS/Other/Assets.xcassets/NotificationsIcons/NotificationTips.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "NotificationTips.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -24,7 +24,7 @@ extension SocialChannel {
|
||||
var url: URL {
|
||||
switch self {
|
||||
case .discord:
|
||||
return URL(string: "https://discord.gg/q4cP6qh2g5")!
|
||||
return URL(string: "https://2fas.com/discord")!
|
||||
case .youtube:
|
||||
return URL(string: "https://www.youtube.com/@2fas")!
|
||||
case .twitter:
|
||||
|
@ -112,6 +112,7 @@ internal enum Asset {
|
||||
internal static let navibarNewsIconBadge = ImageAsset(name: "NavibarNewsIconBadge")
|
||||
internal static let notificationFeatures = ImageAsset(name: "NotificationFeatures")
|
||||
internal static let notificationNews = ImageAsset(name: "NotificationNews")
|
||||
internal static let notificationTips = ImageAsset(name: "NotificationTips")
|
||||
internal static let notificationUpdates = ImageAsset(name: "NotificationUpdates")
|
||||
internal static let notificationYoutube = ImageAsset(name: "NotificationYoutube")
|
||||
internal static let openGallery = ImageAsset(name: "OpenGallery")
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -44,7 +44,7 @@ protocol RootModuleInteracting: AnyObject {
|
||||
)
|
||||
|
||||
func lockScreenActive()
|
||||
func lockScreenInactive()
|
||||
func lockScreenInactive()
|
||||
}
|
||||
|
||||
final class RootModuleInteractor {
|
||||
@ -57,6 +57,7 @@ final class RootModuleInteractor {
|
||||
private let appStateInteractor: AppStateInteracting
|
||||
private let notificationInteractor: NotificationInteracting
|
||||
private let widgetsInteractor: WidgetsInteracting
|
||||
private let localNotificationStateInteractor: LocalNotificationStateInteracting
|
||||
|
||||
init(
|
||||
rootInteractor: RootInteracting,
|
||||
@ -65,7 +66,8 @@ final class RootModuleInteractor {
|
||||
registerDeviceInteractor: RegisterDeviceInteracting,
|
||||
appStateInteractor: AppStateInteracting,
|
||||
notificationInteractor: NotificationInteracting,
|
||||
widgetsInteractor: WidgetsInteracting
|
||||
widgetsInteractor: WidgetsInteracting,
|
||||
localNotificationStateInteractor: LocalNotificationStateInteracting
|
||||
) {
|
||||
self.rootInteractor = rootInteractor
|
||||
self.linkInteractor = linkInteractor
|
||||
@ -74,6 +76,7 @@ final class RootModuleInteractor {
|
||||
self.appStateInteractor = appStateInteractor
|
||||
self.notificationInteractor = notificationInteractor
|
||||
self.widgetsInteractor = widgetsInteractor
|
||||
self.localNotificationStateInteractor = localNotificationStateInteractor
|
||||
|
||||
rootInteractor.storageError = { [weak self] error in
|
||||
self?.storageError?(error)
|
||||
@ -94,7 +97,7 @@ extension RootModuleInteractor: RootModuleInteracting {
|
||||
rootInteractor.initializeApp()
|
||||
registerDeviceInteractor.initialize()
|
||||
}
|
||||
|
||||
|
||||
func lockApplicationIfNeeded(presentLoginImmediately: @escaping () -> Void) {
|
||||
rootInteractor.lockApplicationIfNeeded(
|
||||
presentLoginImmediately: presentLoginImmediately
|
||||
@ -120,6 +123,7 @@ extension RootModuleInteractor: RootModuleInteracting {
|
||||
didCopyToken()
|
||||
}
|
||||
rootInteractor.applicationDidBecomeActive()
|
||||
localNotificationStateInteractor.activate()
|
||||
}
|
||||
|
||||
func shouldHandleURL(url: URL) -> Bool {
|
||||
|
@ -44,6 +44,7 @@ protocol CameraScannerFlowControlling: AnyObject {
|
||||
func toPushPermissions(extensionID: ExtensionID)
|
||||
func toRename(currentName: String, secret: String)
|
||||
func toServiceWasCreated(serviceData: ServiceData)
|
||||
func toCameraNotAvailable()
|
||||
}
|
||||
|
||||
final class CameraScannerFlowController: FlowController {
|
||||
@ -189,6 +190,11 @@ extension CameraScannerFlowController: CameraScannerFlowControlling {
|
||||
viewController.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func toCameraNotAvailable() {
|
||||
let ac = AlertController.cameraNotAvailable
|
||||
viewController.present(ac, animated: true)
|
||||
}
|
||||
|
||||
func toPushPermissions(extensionID: ExtensionID) {
|
||||
guard let navi = viewController.navigationController else { return }
|
||||
navi.setNavigationBarHidden(true, animated: false)
|
||||
|
@ -26,6 +26,10 @@ protocol CameraScannerModuleInteracting: AnyObject {
|
||||
var shouldRename: ((String, String) -> Void)? { get set }
|
||||
var wasUserAskedAboutPush: Bool { get }
|
||||
|
||||
func isCameraAvailable() -> Bool
|
||||
func isCameraAllowed() -> Bool
|
||||
func registerCamera(callback: @escaping (Bool) -> Void)
|
||||
|
||||
func addCode(_ code: Code, force: Bool)
|
||||
func codeExists(_ code: Code) -> Bool
|
||||
func filterImportableCodes(_ codes: [Code]) -> [Code]
|
||||
@ -38,19 +42,39 @@ protocol CameraScannerModuleInteracting: AnyObject {
|
||||
final class CameraScannerModuleInteractor {
|
||||
private let newCodeInteractor: NewCodeInteracting
|
||||
private let pushNotificationPermission: PushNotificationRegistrationInteracting
|
||||
private let cameraPermissionInteractor: CameraPermissionInteracting
|
||||
|
||||
var serviceWasCreated: ((ServiceData) -> Void)?
|
||||
var shouldRename: ((String, String) -> Void)?
|
||||
|
||||
init(newCodeInteractor: NewCodeInteracting, pushNotificationPermission: PushNotificationRegistrationInteracting) {
|
||||
init(
|
||||
newCodeInteractor: NewCodeInteracting,
|
||||
pushNotificationPermission: PushNotificationRegistrationInteracting,
|
||||
cameraPermissionInteractor: CameraPermissionInteracting
|
||||
) {
|
||||
self.newCodeInteractor = newCodeInteractor
|
||||
self.pushNotificationPermission = pushNotificationPermission
|
||||
self.cameraPermissionInteractor = cameraPermissionInteractor
|
||||
newCodeInteractor.serviceWasCreated = { [weak self] in self?.serviceWasCreated?($0) }
|
||||
newCodeInteractor.shouldRename = { [weak self] in self?.shouldRename?($0, $1) }
|
||||
}
|
||||
}
|
||||
|
||||
extension CameraScannerModuleInteractor: CameraScannerModuleInteracting {
|
||||
func isCameraAvailable() -> Bool {
|
||||
cameraPermissionInteractor.isCameraAvailable
|
||||
}
|
||||
|
||||
func isCameraAllowed() -> Bool {
|
||||
cameraPermissionInteractor.isCameraAllowed
|
||||
}
|
||||
|
||||
func registerCamera(callback: @escaping (Bool) -> Void) {
|
||||
cameraPermissionInteractor.register { status in
|
||||
callback(status == .granted)
|
||||
}
|
||||
}
|
||||
|
||||
var wasUserAskedAboutPush: Bool {
|
||||
pushNotificationPermission.wasUserAsked
|
||||
}
|
||||
|
@ -39,6 +39,20 @@ final class CameraScannerPresenter {
|
||||
}
|
||||
|
||||
extension CameraScannerPresenter {
|
||||
func viewDidAppear() {
|
||||
if interactor.isCameraAvailable() {
|
||||
if !interactor.isCameraAllowed() {
|
||||
interactor.registerCamera { [weak self] isGranted in
|
||||
if !isGranted {
|
||||
self?.handleCameraNotAvailable()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleCameraNotAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
func handleOpenGallery() {
|
||||
lockScanning = true
|
||||
flowController.toGallery()
|
||||
@ -137,6 +151,12 @@ extension CameraScannerPresenter {
|
||||
}
|
||||
}
|
||||
|
||||
func handleCameraNotAvailable() {
|
||||
view?.feedback()
|
||||
|
||||
flowController.toCameraNotAvailable()
|
||||
}
|
||||
|
||||
func handleCameraError(_ str: String) {
|
||||
view?.enableOverlay()
|
||||
view?.feedback()
|
||||
|
@ -66,6 +66,11 @@ final class CameraScannerViewController: UIViewController {
|
||||
navigationController?.setNavigationBarHidden(true, animated: false)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
presenter.viewDidAppear()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
|
@ -83,6 +83,10 @@ protocol ComposeServiceModuleInteracting: AnyObject {
|
||||
var actionType: ComposeServiceModuleInteractorActionType { get }
|
||||
var privateKeyError: ComposeServiceModuleInteractorPrivateKeyError? { get }
|
||||
|
||||
var isBrowserExtensionAllowed: Bool { get }
|
||||
|
||||
var isSecretCopyingBlocked: Bool { get }
|
||||
|
||||
var isDataCorrectNotifier: ((Bool) -> Void)? { get set }
|
||||
var serviceWasCreated: ((ServiceData) -> Void)? { get set }
|
||||
|
||||
@ -138,6 +142,10 @@ final class ComposeServiceModuleInteractor {
|
||||
private(set) var digits: Digits = .defaultValue
|
||||
private(set) var counter: Int?
|
||||
|
||||
var isSecretCopyingBlocked: Bool {
|
||||
mdmInteractor.isBackupBlocked
|
||||
}
|
||||
|
||||
// MARK: Section
|
||||
private var sectionState: ComposeServiceModuleInteractorSectionState = .none
|
||||
|
||||
@ -158,6 +166,7 @@ final class ComposeServiceModuleInteractor {
|
||||
private let notificationsInteractor: NotificationInteracting
|
||||
private let serviceDefinitionInteractor: ServiceDefinitionInteracting
|
||||
private let advancedAlertInteractor: AdvancedAlertInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
|
||||
private(set) var serviceData: ServiceData?
|
||||
|
||||
@ -170,7 +179,8 @@ final class ComposeServiceModuleInteractor {
|
||||
sectionInteractor: SectionInteracting,
|
||||
notificationsInteractor: NotificationInteracting,
|
||||
serviceDefinitionInteractor: ServiceDefinitionInteracting,
|
||||
advancedAlertInteractor: AdvancedAlertInteracting
|
||||
advancedAlertInteractor: AdvancedAlertInteracting,
|
||||
mdmInteractor: MDMInteracting
|
||||
) {
|
||||
self.modifyInteractor = modifyInteractor
|
||||
self.trashingServiceInteractor = trashingServiceInteractor
|
||||
@ -181,6 +191,7 @@ final class ComposeServiceModuleInteractor {
|
||||
self.notificationsInteractor = notificationsInteractor
|
||||
self.serviceDefinitionInteractor = serviceDefinitionInteractor
|
||||
self.advancedAlertInteractor = advancedAlertInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
|
||||
updateValues()
|
||||
}
|
||||
@ -211,6 +222,10 @@ extension ComposeServiceModuleInteractor: ComposeServiceModuleInteracting {
|
||||
serviceDefinitionInteractor.name(for: iconTypeID) ?? ""
|
||||
}
|
||||
|
||||
var isBrowserExtensionAllowed: Bool {
|
||||
!mdmInteractor.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
func setServiceName(_ newServiceName: String?) {
|
||||
serviceName = newServiceName
|
||||
validate()
|
||||
|
@ -53,6 +53,7 @@ struct ComposeServiceSectionCell: Hashable {
|
||||
enum PrivateKeyKind: Hashable {
|
||||
case empty
|
||||
case hidden
|
||||
case hiddenNonCopyable
|
||||
}
|
||||
|
||||
enum PrivateKeyError: Hashable {
|
||||
|
@ -34,6 +34,9 @@ extension ComposeServicePresenter {
|
||||
}()
|
||||
let privateKeyKind: ComposeServiceSectionCell.PrivateKeyConfig.PrivateKeyKind = {
|
||||
if interactor.actionType == .edit {
|
||||
if interactor.isSecretCopyingBlocked {
|
||||
return .hiddenNonCopyable
|
||||
}
|
||||
return .hidden
|
||||
}
|
||||
return .empty
|
||||
@ -118,7 +121,9 @@ extension ComposeServicePresenter {
|
||||
]
|
||||
|
||||
if interactor.actionType == .edit {
|
||||
array.append(webExtension)
|
||||
if interactor.isBrowserExtensionAllowed {
|
||||
array.append(webExtension)
|
||||
}
|
||||
array.append(remove)
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,8 @@ final class ComposeServiceFormReveal: UIView {
|
||||
return button
|
||||
}()
|
||||
|
||||
private var stack: ComposeServiceFormRow?
|
||||
|
||||
var buttonPressed: Callback?
|
||||
|
||||
enum State {
|
||||
@ -64,10 +66,16 @@ final class ComposeServiceFormReveal: UIView {
|
||||
|
||||
addSubview(stack)
|
||||
stack.pinToParent()
|
||||
self.stack = stack
|
||||
|
||||
changeState(newState: .masked)
|
||||
}
|
||||
|
||||
func removeActionButton() {
|
||||
stack?.removeArrangedSubviews()
|
||||
stack?.addArrangedSubview(privateKey)
|
||||
}
|
||||
|
||||
func changeState(newState: State) {
|
||||
switch newState {
|
||||
case .masked:
|
||||
|
@ -133,6 +133,10 @@ final class ComposeServicePrivateKeyCell: UITableViewCell, ComposeServiceInputCe
|
||||
case .hidden:
|
||||
privateKeyInput.isHidden = true
|
||||
privateKeyReveal.isHidden = false
|
||||
case .hiddenNonCopyable:
|
||||
privateKeyInput.isHidden = true
|
||||
privateKeyReveal.isHidden = false
|
||||
privateKeyReveal.removeActionButton()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,4 +28,5 @@ enum ComposeServiceInputKind: String {
|
||||
enum ComposeServicePrivateKeyKind {
|
||||
case empty
|
||||
case hidden
|
||||
case hiddenNonCopyable
|
||||
}
|
||||
|
@ -55,8 +55,9 @@ extension ComposeServiceViewController {
|
||||
) as? ComposeServicePrivateKeyCell else { return UITableViewCell() }
|
||||
let kind: ComposeServicePrivateKeyKind = {
|
||||
switch config.privateKeyKind {
|
||||
case .empty: return .empty
|
||||
case .hidden: return .hidden
|
||||
case .empty: .empty
|
||||
case .hidden: .hidden
|
||||
case .hiddenNonCopyable: .hiddenNonCopyable
|
||||
}
|
||||
}()
|
||||
cell.configure(privateKeyKind: kind)
|
||||
|
@ -30,6 +30,7 @@ protocol MainFlowControlling: AnyObject {
|
||||
func toSecretSyncError(_ serviceName: String)
|
||||
func toOpenFileImport(url: URL)
|
||||
func toSetupSplit()
|
||||
func toSetPIN()
|
||||
|
||||
// MARK: - App update
|
||||
func toShowNewVersionAlert(for appStoreURL: URL, skip: @escaping Callback)
|
||||
@ -137,6 +138,12 @@ extension MainFlowController: MainFlowControlling {
|
||||
|
||||
viewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - MDM requriments
|
||||
|
||||
func toSetPIN() {
|
||||
NewPINNavigationFlowController.present(on: viewController, parent: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension MainFlowController: ImporterOpenFileHeadlessFlowControllerParent {
|
||||
@ -200,4 +207,17 @@ extension MainFlowController: MainSplitFlowControllerParent {
|
||||
func navigationSwitchedToSettingsExternalImport() {
|
||||
viewController.presenter.handleSwitchToExternalImport()
|
||||
}
|
||||
|
||||
func navigationSwitchedToSettingsBackup() {
|
||||
viewController.presenter.handleSwitchToBackup()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainFlowController: NewPINNavigationFlowControllerParent {
|
||||
func pinGathered(with PIN: String, pinType: PINType) {
|
||||
viewController.presenter.handleSavePIN(PIN, pinType: pinType)
|
||||
viewController.dismiss(animated: true) { [weak viewController] in
|
||||
viewController?.presenter.handleViewIsVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +23,18 @@ import Data
|
||||
protocol MainModuleInteracting: AnyObject {
|
||||
var secretSyncError: ((String) -> Void)? { get set }
|
||||
var isAppLocked: Bool { get }
|
||||
var isBrowserExtensionAllowed: Bool { get }
|
||||
var shouldSetPasscode: Bool { get }
|
||||
|
||||
func applyMDMRules()
|
||||
func initialize()
|
||||
func checkForImport() -> URL?
|
||||
func clearImportedFileURL()
|
||||
|
||||
func savePIN(_ PIN: String, ofType pinType: PINType)
|
||||
func saveSuccessSync()
|
||||
func clearSavesuccessSync()
|
||||
|
||||
// MARK: - New app version
|
||||
func checkForNewAppVersion(completion: @escaping (URL?) -> Void)
|
||||
func skipAppVersion()
|
||||
@ -40,6 +47,14 @@ final class MainModuleInteractor {
|
||||
rootInteractor.isAuthenticationRequired
|
||||
}
|
||||
|
||||
var isBrowserExtensionAllowed: Bool {
|
||||
!mdmInteractor.isBrowserExtensionBlocked
|
||||
}
|
||||
|
||||
var shouldSetPasscode: Bool {
|
||||
mdmInteractor.shouldSetPasscode
|
||||
}
|
||||
|
||||
private let logUploadingInteractor: LogUploadingInteracting
|
||||
private let cloudBackupStateInteractor: CloudBackupStateInteracting
|
||||
private let fileInteractor: FileInteracting
|
||||
@ -47,6 +62,8 @@ final class MainModuleInteractor {
|
||||
private let networkStatusInteractor: NetworkStatusInteracting
|
||||
private let appInfoInteractor: AppInfoInteracting
|
||||
private let rootInteractor: RootInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
private let protectionInteractor: ProtectionInteracting
|
||||
|
||||
init(
|
||||
logUploadingInteractor: LogUploadingInteracting,
|
||||
@ -56,7 +73,9 @@ final class MainModuleInteractor {
|
||||
newVersionInteractor: NewVersionInteracting,
|
||||
networkStatusInteractor: NetworkStatusInteracting,
|
||||
appInfoInteractor: AppInfoInteracting,
|
||||
rootInteractor: RootInteracting
|
||||
rootInteractor: RootInteracting,
|
||||
mdmInteractor: MDMInteracting,
|
||||
protectionInteractor: ProtectionInteracting
|
||||
) {
|
||||
self.logUploadingInteractor = logUploadingInteractor
|
||||
self.cloudBackupStateInteractor = cloudBackupStateInteractor
|
||||
@ -65,6 +84,8 @@ final class MainModuleInteractor {
|
||||
self.networkStatusInteractor = networkStatusInteractor
|
||||
self.appInfoInteractor = appInfoInteractor
|
||||
self.rootInteractor = rootInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
self.protectionInteractor = protectionInteractor
|
||||
|
||||
cloudBackupStateInteractor.secretSyncError = { [weak self] in self?.secretSyncError?($0) }
|
||||
}
|
||||
@ -85,6 +106,22 @@ extension MainModuleInteractor: MainModuleInteracting {
|
||||
fileInteractor.markAsHandled()
|
||||
}
|
||||
|
||||
func applyMDMRules() {
|
||||
mdmInteractor.apply()
|
||||
}
|
||||
|
||||
func savePIN(_ PIN: String, ofType pinType: PINType) {
|
||||
protectionInteractor.savePIN(PIN, typeOfPIN: pinType)
|
||||
}
|
||||
|
||||
func saveSuccessSync() {
|
||||
cloudBackupStateInteractor.saveSuccessSyncDate()
|
||||
}
|
||||
|
||||
func clearSavesuccessSync() {
|
||||
cloudBackupStateInteractor.clearSaveSuccessSync()
|
||||
}
|
||||
|
||||
// MARK: - New app version
|
||||
|
||||
func checkForNewAppVersion(completion: @escaping (URL?) -> Void) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Data
|
||||
|
||||
final class MainPresenter {
|
||||
weak var view: MainViewControlling?
|
||||
@ -42,7 +43,7 @@ final class MainPresenter {
|
||||
func viewWillAppear() {
|
||||
viewIsVisible()
|
||||
}
|
||||
|
||||
|
||||
func handleSwitchToSetupPIN() {
|
||||
view?.navigateToViewPath(.settings(option: .security))
|
||||
}
|
||||
@ -55,6 +56,10 @@ final class MainPresenter {
|
||||
view?.navigateToViewPath(.settings(option: .externalImport))
|
||||
}
|
||||
|
||||
func handleSwitchToBackup() {
|
||||
view?.navigateToViewPath(.settings(option: .backup))
|
||||
}
|
||||
|
||||
func handleSwitchedToSettings() {
|
||||
view?.settingsTabActive()
|
||||
}
|
||||
@ -65,11 +70,11 @@ final class MainPresenter {
|
||||
|
||||
func handleRefreshAuthList() {
|
||||
guard !interactor.isAppLocked else { return }
|
||||
flowController.toAuthRequestFetch()
|
||||
handleAuthRequest()
|
||||
}
|
||||
|
||||
func handleAuthorize(for tokenRequestID: String) {
|
||||
guard !interactor.isAppLocked else { return }
|
||||
guard !interactor.isAppLocked && interactor.isBrowserExtensionAllowed else { return }
|
||||
flowController.toAuthorize(for: tokenRequestID)
|
||||
}
|
||||
|
||||
@ -82,13 +87,28 @@ final class MainPresenter {
|
||||
func handleViewIsVisible() {
|
||||
viewIsVisible()
|
||||
}
|
||||
|
||||
func handleSyncCompletedSuccessfuly() {
|
||||
interactor.saveSuccessSync()
|
||||
}
|
||||
|
||||
func handleClearSyncCompletedSuccessfuly() {
|
||||
interactor.clearSavesuccessSync()
|
||||
}
|
||||
|
||||
func handleSavePIN(_ PIN: String, pinType: PINType) {
|
||||
interactor.savePIN(PIN, ofType: pinType)
|
||||
}
|
||||
}
|
||||
|
||||
private extension MainPresenter {
|
||||
func viewIsVisible() {
|
||||
guard !interactor.isAppLocked && !handlingViewIsVisible else { return }
|
||||
handlingViewIsVisible = true
|
||||
if let url = interactor.checkForImport() {
|
||||
interactor.applyMDMRules()
|
||||
if interactor.shouldSetPasscode {
|
||||
flowController.toSetPIN()
|
||||
} else if let url = interactor.checkForImport() {
|
||||
flowController.toOpenFileImport(url: url)
|
||||
interactor.clearImportedFileURL()
|
||||
handlingViewIsVisible = false
|
||||
@ -113,6 +133,8 @@ private extension MainPresenter {
|
||||
}
|
||||
|
||||
func handleAuthRequest() {
|
||||
flowController.toAuthRequestFetch()
|
||||
if interactor.isBrowserExtensionAllowed {
|
||||
flowController.toAuthRequestFetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ final class MainViewController: UIViewController {
|
||||
var presenter: MainPresenter!
|
||||
|
||||
private let settingsEventController = SettingsEventController()
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
|
||||
var splitView: MainSplitViewController?
|
||||
|
||||
@ -48,66 +49,78 @@ final class MainViewController: UIViewController {
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
notificationCenter.removeObserver(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController {
|
||||
private func setupEvents() {
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(refreshAuthList),
|
||||
name: .pushNotificationRefreshAuthList,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(refreshAuthList),
|
||||
name: UIApplication.didBecomeActiveNotification,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(clearAuthList),
|
||||
name: UIApplication.willResignActiveNotification,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(authorizeFromApp),
|
||||
name: .pushNotificationAuthorizeFromApp,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(switchToSetupPIN),
|
||||
name: .switchToSetupPIN,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(switchToBrowserExtension),
|
||||
name: .switchToBrowserExtension,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(fileAwaitsOpening),
|
||||
name: .fileAwaitsOpening,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(tokensVisible),
|
||||
name: .tokensScreenIsVisible,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(tokensVisible),
|
||||
name: .userLoggedIn,
|
||||
object: nil
|
||||
)
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(syncCompletedSuccessfuly),
|
||||
name: .syncCompletedSuccessfuly,
|
||||
object: nil
|
||||
)
|
||||
notificationCenter.addObserver(
|
||||
self,
|
||||
selector: #selector(clearSyncCompletedSuccessfuly),
|
||||
name: .clearSyncCompletedSuccessfuly,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc
|
||||
@ -151,6 +164,16 @@ extension MainViewController {
|
||||
private func tokensVisible() {
|
||||
presenter.handleViewIsVisible()
|
||||
}
|
||||
|
||||
@objc
|
||||
private func syncCompletedSuccessfuly() {
|
||||
presenter.handleSyncCompletedSuccessfuly()
|
||||
}
|
||||
|
||||
@objc
|
||||
private func clearSyncCompletedSuccessfuly() {
|
||||
presenter.handleClearSyncCompletedSuccessfuly()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainViewController: MainViewControlling {
|
||||
|
@ -24,6 +24,7 @@ protocol MainSplitFlowControllerParent: AnyObject {
|
||||
func navigationSwitchedToTokens()
|
||||
func navigationSwitchedToSettings()
|
||||
func navigationSwitchedToSettingsExternalImport()
|
||||
func navigationSwitchedToSettingsBackup()
|
||||
}
|
||||
|
||||
protocol MainSplitFlowControlling: AnyObject {
|
||||
@ -175,6 +176,10 @@ extension MainSplitFlowController: TokensPlainFlowControllerParent {
|
||||
func tokensSwitchToSettingsExternalImport() {
|
||||
parent?.navigationSwitchedToSettingsExternalImport()
|
||||
}
|
||||
|
||||
func tokensSwitchToSettingsBackup() {
|
||||
parent?.navigationSwitchedToSettingsBackup()
|
||||
}
|
||||
}
|
||||
|
||||
extension MainSplitFlowController: SettingsFlowControllerParent {
|
||||
|
@ -22,6 +22,7 @@ import Common
|
||||
|
||||
protocol NewsNavigationFlowControllerParent: AnyObject {
|
||||
func newsClose()
|
||||
func newsToBackup()
|
||||
}
|
||||
|
||||
final class NewsNavigationFlowController: NavigationFlowController {
|
||||
@ -48,4 +49,8 @@ extension NewsNavigationFlowController: NewsPlainFlowControllerParent {
|
||||
func newsClose() {
|
||||
parent?.newsClose()
|
||||
}
|
||||
|
||||
func newsToBackup() {
|
||||
parent?.newsToBackup()
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,17 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Common
|
||||
|
||||
protocol NewsPlainFlowControllerParent: AnyObject {
|
||||
func newsClose()
|
||||
func newsToBackup()
|
||||
}
|
||||
|
||||
protocol NewsPlainFlowControlling: AnyObject {
|
||||
func openWeb(with url: URL)
|
||||
func toClose()
|
||||
func toInternalLink(_ internalLink: ListNewsEntry.InternalLink)
|
||||
}
|
||||
|
||||
final class NewsPlainFlowController: FlowController {
|
||||
@ -63,4 +66,11 @@ extension NewsPlainFlowController: NewsPlainFlowControlling {
|
||||
func toClose() {
|
||||
parent?.newsClose()
|
||||
}
|
||||
|
||||
func toInternalLink(_ internalLink: ListNewsEntry.InternalLink) {
|
||||
switch internalLink {
|
||||
case .backup:
|
||||
parent?.newsToBackup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import Data
|
||||
protocol NewsModuleInteracting: AnyObject {
|
||||
var hasUnreadNews: Bool { get }
|
||||
|
||||
func localList() -> [ListNewsEntry]
|
||||
func fetchList(completion: @escaping ([ListNewsEntry]) -> Void)
|
||||
func markAsRead(newsEntry: ListNewsEntry)
|
||||
func markAllAsRead()
|
||||
@ -32,9 +31,11 @@ protocol NewsModuleInteracting: AnyObject {
|
||||
|
||||
final class NewsModuleInteractor {
|
||||
private let newsInteractor: NewsInteracting
|
||||
|
||||
init(newsInteractor: NewsInteracting) {
|
||||
private let localNotificationFetchInteractor: LocalNotificationFetchInteracting
|
||||
|
||||
init(newsInteractor: NewsInteracting, localNotificationFetchInteractor: LocalNotificationFetchInteracting) {
|
||||
self.newsInteractor = newsInteractor
|
||||
self.localNotificationFetchInteractor = localNotificationFetchInteractor
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,22 +44,88 @@ extension NewsModuleInteractor: NewsModuleInteracting {
|
||||
newsInteractor.hasUnreadNews
|
||||
}
|
||||
|
||||
func localList() -> [ListNewsEntry] {
|
||||
newsInteractor.localList()
|
||||
}
|
||||
|
||||
func fetchList(completion: @escaping ([ListNewsEntry]) -> Void) {
|
||||
newsInteractor.fetchList(completion: { [weak self] in
|
||||
guard let self else { return }
|
||||
completion(self.newsInteractor.localList())
|
||||
})
|
||||
newsInteractor.fetchList { [weak self] in
|
||||
self?.localNotificationFetchInteractor.getNotification { [weak self] notification in
|
||||
guard let self else { return }
|
||||
let list = self.prepareList(news: self.newsInteractor.localList(), notification: notification)
|
||||
completion(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func markAsRead(newsEntry: ListNewsEntry) {
|
||||
newsInteractor.markAsRead(newsEntry: newsEntry)
|
||||
let localList = newsInteractor.localList()
|
||||
if localList.contains(newsEntry) {
|
||||
newsInteractor.markAsRead(newsEntry: newsEntry)
|
||||
} else {
|
||||
localNotificationFetchInteractor.markNotificationAsRead()
|
||||
}
|
||||
}
|
||||
|
||||
func markAllAsRead() {
|
||||
localNotificationFetchInteractor.markNotificationAsRead()
|
||||
newsInteractor.clearHasUnreadNews()
|
||||
}
|
||||
}
|
||||
|
||||
private extension NewsModuleInteractor {
|
||||
func prepareList(news: [ListNewsEntry], notification: LocalNotification?) -> [ListNewsEntry] {
|
||||
guard let notification else {
|
||||
return news.sorted
|
||||
}
|
||||
let notificationNewsEntry = notification.toNewsEntry()
|
||||
var completeList = news
|
||||
completeList.append(notificationNewsEntry)
|
||||
return completeList.sorted
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalNotification {
|
||||
func toNewsEntry() -> ListNewsEntry {
|
||||
let icon: ListNewsEntry.Icon
|
||||
let link: URL?
|
||||
let message: String
|
||||
let internalLink: ListNewsEntry.InternalLink?
|
||||
switch self.kind {
|
||||
case .tipsNTricks:
|
||||
icon = .tips
|
||||
link = URL(string: "https://2fas.com/2fasauth-tutorial")
|
||||
message = T.periodicNotificationTips
|
||||
internalLink = nil
|
||||
case .backup:
|
||||
icon = .updates
|
||||
link = nil
|
||||
message = T.periodicNotificationBackup
|
||||
internalLink = .backup
|
||||
case .browserExtension:
|
||||
icon = .news
|
||||
link = URL(string: "https://2fas.com/browser-extension/")
|
||||
message = T.periodicNotificationBrowserExtension
|
||||
internalLink = nil
|
||||
case .donation:
|
||||
icon = .features
|
||||
link = URL(string: "https://2fas.com/donate/")
|
||||
message = T.periodicNotificationDonate
|
||||
internalLink = nil
|
||||
}
|
||||
|
||||
return .init(
|
||||
newsID: self.id,
|
||||
icon: icon,
|
||||
link: link,
|
||||
message: message,
|
||||
publishedAt: self.publishedAt,
|
||||
createdAt: nil,
|
||||
wasRead: self.wasRead,
|
||||
internalLink: internalLink,
|
||||
localNotificationType: kind.rawValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ListNewsEntry {
|
||||
var sorted: Self {
|
||||
sorted(by: { $0.publishedAt <= $1.publishedAt })
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ extension ListNewsEntry.Icon {
|
||||
.withRenderingMode(.alwaysTemplate)
|
||||
case .youtube: return Asset.notificationYoutube.image
|
||||
.withRenderingMode(.alwaysTemplate)
|
||||
case .tips: return Asset.notificationTips.image
|
||||
.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,16 +47,27 @@ final class NewsPresenter {
|
||||
func viewWillAppear() {
|
||||
refreshView()
|
||||
}
|
||||
|
||||
|
||||
func handleRefreshView() {
|
||||
refreshView()
|
||||
}
|
||||
|
||||
func handleSelection(at row: Int) {
|
||||
guard let entry = interactor.localList()[safe: row], let link = entry.link else { return }
|
||||
interactor.markAsRead(newsEntry: entry)
|
||||
AppEventLog(.articleRead(entry.newsID))
|
||||
flowController.openWeb(with: link)
|
||||
interactor.fetchList { [weak self] list in
|
||||
guard let entry = list[safe: row] else { return }
|
||||
self?.interactor.markAsRead(newsEntry: entry)
|
||||
if let type = entry.localNotificationType {
|
||||
AppEventLog(.localNotificationRead(type))
|
||||
} else {
|
||||
AppEventLog(.articleRead(entry.newsID))
|
||||
}
|
||||
|
||||
if let internalLink = entry.internalLink {
|
||||
self?.flowController.toInternalLink(internalLink)
|
||||
} else if let link = entry.link {
|
||||
self?.flowController.openWeb(with: link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func close() {
|
||||
@ -79,25 +90,25 @@ private extension NewsPresenter {
|
||||
|
||||
func reload() {
|
||||
let now = Date()
|
||||
let cells = interactor
|
||||
.localList()
|
||||
.map { entry in
|
||||
interactor.fetchList { [weak self] news in
|
||||
let cells = news.map { entry in
|
||||
NewsCell(
|
||||
icon: entry.icon.image,
|
||||
title: entry.message ?? entry.link?.absoluteString ?? "",
|
||||
wasRead: entry.wasRead,
|
||||
publishedAgo: dateFormatter.localizedString(for: entry.publishedAt, relativeTo: now),
|
||||
publishedAgo: self?.dateFormatter.localizedString(for: entry.publishedAt, relativeTo: now) ?? "",
|
||||
hasURL: entry.link != nil,
|
||||
newsItem: entry
|
||||
)
|
||||
}
|
||||
|
||||
guard viewIsLoaded else { return }
|
||||
|
||||
if cells.isEmpty {
|
||||
view?.showEmptyScreen()
|
||||
} else {
|
||||
view?.reload(with: NewsSection(cells: cells))
|
||||
|
||||
guard self?.viewIsLoaded == true else { return }
|
||||
|
||||
if cells.isEmpty {
|
||||
self?.view?.showEmptyScreen()
|
||||
} else {
|
||||
self?.view?.reload(with: NewsSection(cells: cells))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ import Foundation
|
||||
import Data
|
||||
|
||||
protocol AppLockModuleInteracting: AnyObject {
|
||||
var isLockoutAttemptsChangeBlocked: Bool { get }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { get }
|
||||
|
||||
var selectedAttempts: AppLockAttempts { get }
|
||||
var selectedBlockTime: AppLockBlockTime { get }
|
||||
|
||||
@ -30,15 +33,19 @@ protocol AppLockModuleInteracting: AnyObject {
|
||||
|
||||
final class AppLockModuleInteractor {
|
||||
private let appLockInteractor: AppLockStateInteracting
|
||||
private let mdmInteractor: MDMInteracting
|
||||
|
||||
init(appLockInteractor: AppLockStateInteracting) {
|
||||
init(appLockInteractor: AppLockStateInteracting, mdmInteractor: MDMInteracting) {
|
||||
self.appLockInteractor = appLockInteractor
|
||||
self.mdmInteractor = mdmInteractor
|
||||
}
|
||||
}
|
||||
|
||||
extension AppLockModuleInteractor: AppLockModuleInteracting {
|
||||
var selectedAttempts: AppLockAttempts { appLockInteractor.appLockAttempts }
|
||||
var selectedBlockTime: AppLockBlockTime { appLockInteractor.appLockBlockTime }
|
||||
var isLockoutAttemptsChangeBlocked: Bool { mdmInteractor.isLockoutAttemptsChangeBlocked }
|
||||
var isLockoutBlockTimeChangeBlocked: Bool { mdmInteractor.isLockoutBlockTimeChangeBlocked }
|
||||
|
||||
func setAttempts(_ value: AppLockAttempts) {
|
||||
appLockInteractor.setAppLockAttempts(value)
|
||||
|
@ -34,4 +34,5 @@ struct AppLockMenuSection: TableViewSection {
|
||||
struct AppLockMenuCell: Hashable {
|
||||
let title: String
|
||||
let checkmark: Bool
|
||||
let disabled: Bool
|
||||
}
|
||||
|
@ -27,7 +27,11 @@ extension AppLockPresenter {
|
||||
title: T.Settings.tooManyAttemptsHeader,
|
||||
cells:
|
||||
AppLockAttempts.allCases.map {
|
||||
AppLockMenuCell(title: $0.localized, checkmark: selectedAttempt == $0)
|
||||
AppLockMenuCell(
|
||||
title: $0.localized,
|
||||
checkmark: selectedAttempt == $0,
|
||||
disabled: interactor.isLockoutAttemptsChangeBlocked
|
||||
)
|
||||
},
|
||||
footer: T.Settings.howManyAttemptsFooter
|
||||
)
|
||||
@ -37,7 +41,11 @@ extension AppLockPresenter {
|
||||
title: T.Settings.blockFor,
|
||||
cells:
|
||||
AppLockBlockTime.allCases.map {
|
||||
AppLockMenuCell(title: $0.localized, checkmark: selectedBlockTime == $0)
|
||||
AppLockMenuCell(
|
||||
title: $0.localized,
|
||||
checkmark: selectedBlockTime == $0,
|
||||
disabled: interactor.isLockoutBlockTimeChangeBlocked
|
||||
)
|
||||
}
|
||||
)
|
||||
var menu: [AppLockMenuSection] = [
|
||||
|
@ -37,10 +37,14 @@ final class AppLockPresenter {
|
||||
|
||||
func handleSelection(at indexPath: IndexPath) {
|
||||
if indexPath.section == 0 {
|
||||
guard let value = AppLockAttempts.allCases[safe: indexPath.row] else { return }
|
||||
guard let value = AppLockAttempts.allCases[
|
||||
safe: indexPath.row
|
||||
], !interactor.isLockoutAttemptsChangeBlocked else { return }
|
||||
interactor.setAttempts(value)
|
||||
} else {
|
||||
guard let value = AppLockBlockTime.allCases[safe: indexPath.row] else { return }
|
||||
guard let value = AppLockBlockTime.allCases[
|
||||
safe: indexPath.row
|
||||
], !interactor.isLockoutBlockTimeChangeBlocked else { return }
|
||||
interactor.setBlockTime(value)
|
||||
}
|
||||
reload()
|
||||
|
@ -132,6 +132,9 @@ extension AppLockViewController {
|
||||
cell.update(icon: nil, title: data.title, kind: accessory)
|
||||
cell.tintColor = Theme.Colors.Fill.theme
|
||||
cell.selectionStyle = .none
|
||||
if data.disabled {
|
||||
cell.disable()
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
@ -103,13 +103,14 @@ extension AppSecurityFlowController: AppSecurityFlowControlling {
|
||||
on: navi,
|
||||
parent: self,
|
||||
action: newAction,
|
||||
step: .second(PIN: PIN, pinType: typeOfPIN)
|
||||
step: .second(PIN: PIN, pinType: typeOfPIN),
|
||||
lockNavigation: false
|
||||
)
|
||||
}
|
||||
|
||||
func toCreatePIN(pinType: PINType) {
|
||||
let navi = navigationControllerForModal()
|
||||
NewPINFlowController.setRoot(in: navi, parent: self, pinType: pinType)
|
||||
NewPINFlowController.setRoot(in: navi, parent: self, pinType: pinType, lockNavigation: false)
|
||||
viewController.present(navi, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@ -164,7 +165,13 @@ extension AppSecurityFlowController: VerifyPINFlowControllerParent {
|
||||
dismiss()
|
||||
case .change(let currentPINType):
|
||||
guard let navi = viewController.presentedViewController as? UINavigationController else { return }
|
||||
NewPINFlowController.push(on: navi, parent: self, action: .change, step: .first(pinType: currentPINType))
|
||||
NewPINFlowController.push(
|
||||
on: navi,
|
||||
parent: self,
|
||||
action: .change,
|
||||
step: .first(pinType: currentPINType),
|
||||
lockNavigation: false
|
||||
)
|
||||
case .authorize:
|
||||
viewController.presenter.handleInitialAutorization()
|
||||
guard let vc = viewController.children.first(where: { $0 is VerifyPINViewController }) else { return }
|
||||
@ -190,7 +197,7 @@ extension AppSecurityFlowController: NewPINFlowControllerParent {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
func pingGathered(
|
||||
func pinGathered(
|
||||
with PIN: String,
|
||||
pinType: PINType,
|
||||
action: NewPINFlowController.Action,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user