TF-1453 Working MDM functionality

This commit is contained in:
Zbigniew Cisiński 2024-03-03 20:03:32 +01:00
parent c7780859a8
commit 3642d80870
29 changed files with 318 additions and 37 deletions

View File

@ -248,6 +248,9 @@ public final class InteractorFactory {
}
public func mdmInteractor() -> MDMInteracting {
MDMInteractor(mainRepository: MainRepositoryImpl.shared)
MDMInteractor(
mainRepository: MainRepositoryImpl.shared,
pairingInteractor: pairingWebExtensionInteractor()
)
}
}

View File

@ -23,6 +23,10 @@ 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()
}
@ -50,6 +54,22 @@ extension MDMInteractor: MDMInteracting {
mainRepository.mdmIsBrowserExtensionBlocked
}
var isLockoutAttemptsChangeBlocked: Bool {
mainRepository.mdmLockoutAttepts != nil
}
var isLockoutBlockTimeChangeBlocked: Bool {
mainRepository.mdmLockoutBlockTime != nil
}
var isPasscodeRequried: Bool {
mainRepository.mdmIsPasscodeRequried
}
var shouldSetPasscode: Bool {
isPasscodeRequried && !mainRepository.isPINSet
}
func apply() {
if isBackupBlocked && mainRepository.isCloudBackupConnected {
mainRepository.clearBackup()
@ -62,5 +82,13 @@ extension MDMInteractor: MDMInteracting {
if isBrowserExtensionBlocked && pairingInteractor.hasActiveBrowserExtension {
pairingInteractor.disableExtension(completion: { _ in })
}
if let lockoutAttepts = mainRepository.mdmLockoutAttepts {
mainRepository.setAppLockAttempts(lockoutAttepts)
}
if let blockTime = mainRepository.mdmLockoutBlockTime {
mainRepository.setAppLockBlockTime(blockTime)
}
}
}

View File

@ -27,6 +27,7 @@ public enum PairingWebExtensionError: Error {
case noPublicKey
case serverError
case noInternet
case blocked
}
public enum UnparingWebExtensionError: Error {
@ -82,6 +83,11 @@ extension PairingWebExtensionInteractor: PairingWebExtensionInteracting {
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))

View File

@ -23,4 +23,7 @@ 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 }
}

View File

@ -19,10 +19,6 @@
import Foundation
//option that passcode is required
//preset lockout settings
//toggle/hide browser extension
final class MDMRepositoryImpl {
private let mdmKey = "com.apple.configuration.managed"
private let userDefaults = UserDefaults.standard
@ -33,11 +29,15 @@ final class MDMRepositoryImpl {
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()
@ -69,4 +69,36 @@ extension MDMRepositoryImpl: MDMRepository {
}
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
}
}

View File

@ -476,4 +476,7 @@ protocol MainRepository: AnyObject {
var mdmIsBackupBlocked: Bool { get }
var mdmIsBiometryBlocked: Bool { get }
var mdmIsBrowserExtensionBlocked: Bool { get }
var mdmLockoutAttepts: AppLockAttempts? { get }
var mdmLockoutBlockTime: AppLockBlockTime? { get }
var mdmIsPasscodeRequried: Bool { get }
}

View File

@ -31,4 +31,16 @@ extension MainRepositoryImpl {
var mdmIsBrowserExtensionBlocked: Bool {
mdmRepository.isBrowserExtensionBlocked
}
var mdmLockoutAttepts: AppLockAttempts? {
mdmRepository.lockoutAttepts
}
var mdmLockoutBlockTime: AppLockBlockTime? {
mdmRepository.lockoutBlockTime
}
var mdmIsPasscodeRequried: Bool {
mdmRepository.isPasscodeRequried
}
}

View File

@ -671,6 +671,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 */; };
@ -2291,6 +2292,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>"; };
@ -5647,6 +5649,7 @@
C2A7067C276D1A5F00885D79 /* Flow */ = {
isa = PBXGroup;
children = (
C2BC3FC82B94E70F004C4BA0 /* NewPINNavigationFlowController.swift */,
C2A7067D276D1A7500885D79 /* NewPINFlowController.swift */,
C2997AA82454D767006D5943 /* SelectPINLengthController.swift */,
);
@ -9024,6 +9027,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 */,

View File

@ -50,6 +50,12 @@ extension UIViewController {
definesPresentationContext = true
}
func configureAsFullscreenModal() {
modalPresentationStyle = .fullScreen
isModalInPresentation = true
definesPresentationContext = true
}
func setCustomLeftBackButton() {
navigationItem.leftBarButtonItem = createCustomLeftBackButton()
}

View File

@ -131,7 +131,8 @@ final class ModuleInteractorFactory {
func appLockModuleInteractor() -> AppLockModuleInteracting {
AppLockModuleInteractor(
appLockInteractor: InteractorFactory.shared.appLockStateInteractor()
appLockInteractor: InteractorFactory.shared.appLockStateInteractor(),
mdmInteractor: InteractorFactory.shared.mdmInteractor()
)
}
@ -143,8 +144,8 @@ final class ModuleInteractorFactory {
)
}
func newPINModuleInteractor() -> NewPINModuleInteracting {
NewPINModuleInteractor()
func newPINModuleInteractor(lockNavigation: Bool) -> NewPINModuleInteracting {
NewPINModuleInteractor(lockNavigation: lockNavigation)
}
func trashModuleInteractor() -> TrashModuleInteracting {
@ -334,7 +335,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()
)
}

View File

@ -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 {
@ -201,3 +208,12 @@ extension MainFlowController: MainSplitFlowControllerParent {
viewController.presenter.handleSwitchToExternalImport()
}
}
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()
}
}
}

View File

@ -24,11 +24,15 @@ 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)
// MARK: - New app version
func checkForNewAppVersion(completion: @escaping (URL?) -> Void)
func skipAppVersion()
@ -45,6 +49,10 @@ final class MainModuleInteractor {
!mdmInteractor.isBrowserExtensionBlocked
}
var shouldSetPasscode: Bool {
mdmInteractor.shouldSetPasscode
}
private let logUploadingInteractor: LogUploadingInteracting
private let cloudBackupStateInteractor: CloudBackupStateInteracting
private let fileInteractor: FileInteracting
@ -53,6 +61,7 @@ final class MainModuleInteractor {
private let appInfoInteractor: AppInfoInteracting
private let rootInteractor: RootInteracting
private let mdmInteractor: MDMInteracting
private let protectionInteractor: ProtectionInteracting
init(
logUploadingInteractor: LogUploadingInteracting,
@ -63,7 +72,8 @@ final class MainModuleInteractor {
networkStatusInteractor: NetworkStatusInteracting,
appInfoInteractor: AppInfoInteracting,
rootInteractor: RootInteracting,
mdmInteractor: MDMInteracting
mdmInteractor: MDMInteracting,
protectionInteractor: ProtectionInteracting
) {
self.logUploadingInteractor = logUploadingInteractor
self.cloudBackupStateInteractor = cloudBackupStateInteractor
@ -73,6 +83,7 @@ final class MainModuleInteractor {
self.appInfoInteractor = appInfoInteractor
self.rootInteractor = rootInteractor
self.mdmInteractor = mdmInteractor
self.protectionInteractor = protectionInteractor
cloudBackupStateInteractor.secretSyncError = { [weak self] in self?.secretSyncError?($0) }
}
@ -93,6 +104,14 @@ extension MainModuleInteractor: MainModuleInteracting {
fileInteractor.markAsHandled()
}
func applyMDMRules() {
mdmInteractor.apply()
}
func savePIN(_ PIN: String, ofType pinType: PINType) {
protectionInteractor.savePIN(PIN, typeOfPIN: pinType)
}
// MARK: - New app version
func checkForNewAppVersion(completion: @escaping (URL?) -> Void) {

View File

@ -18,6 +18,7 @@
//
import UIKit
import Data
final class MainPresenter {
weak var view: MainViewControlling?
@ -82,13 +83,20 @@ final class MainPresenter {
func handleViewIsVisible() {
viewIsVisible()
}
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

View File

@ -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)

View File

@ -34,4 +34,5 @@ struct AppLockMenuSection: TableViewSection {
struct AppLockMenuCell: Hashable {
let title: String
let checkmark: Bool
let disabled: Bool
}

View File

@ -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] = [

View File

@ -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()

View File

@ -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
}

View File

@ -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,

View File

@ -27,6 +27,7 @@ protocol AppSecurityModuleInteracting: AnyObject {
var isBiometryEnabled: Bool { get }
var isBiometryAllowed: Bool { get }
var currentPINType: PINType { get }
var isPasscodeRequried: Bool { get }
func toggleBiometry()
@ -62,6 +63,7 @@ extension AppSecurityModuleInteractor: AppSecurityModuleInteracting {
var isBiometryEnabled: Bool { protectionInteractor.isBiometryEnabled }
var isBiometryAllowed: Bool { !mdmInteractor.isBiometryBlocked }
var currentPINType: PINType { protectionInteractor.pinType ?? .digits4 }
var isPasscodeRequried: Bool { mdmInteractor.isPasscodeRequried }
func toggleBiometry() {
guard protectionInteractor.isBiometryAvailable else { return }

View File

@ -35,6 +35,7 @@ struct AppSecurityMenuCell: Hashable {
struct Toggle: Hashable {
let kind: ToggleKind
let isOn: Bool
let isBlocked: Bool
}
// swiftlint:disable discouraged_none_name

View File

@ -23,9 +23,10 @@ import Data
extension AppSecurityPresenter {
func buildMenu() -> [AppSecurityMenuSection] {
let isPINset = interactor.isPINSet
let PINcell = AppSecurityMenuCell(
title: T.Settings.pinCode,
accessory: .toggle(toggle: .init(kind: .PIN, isOn: isPINset))
accessory: .toggle(toggle: .init(kind: .PIN, isOn: isPINset, isBlocked: interactor.isPasscodeRequried))
)
guard isPINset else {
@ -57,7 +58,7 @@ extension AppSecurityPresenter {
let section = AppSecurityMenuSection(title: T.Settings.biometricAuthentication, cells: [
AppSecurityMenuCell(
title: biometryType.localized,
accessory: .toggle(toggle: .init(kind: .biometry, isOn: isBiometryEnabled))
accessory: .toggle(toggle: .init(kind: .biometry, isOn: isBiometryEnabled, isBlocked: false))
)
])
switch biometryType {

View File

@ -151,6 +151,12 @@ extension AppSecurityViewController {
} else {
cell.selectionStyle = .none
}
let accessory = data.accessory
if case let .toggle(toggle) = accessory, toggle.isBlocked {
cell.disable()
cell.disableToggle()
}
return cell
}
@ -194,7 +200,7 @@ extension AppSecurityViewController {
case .arrow: return .arrow
case .info(let text): return .infoArrow(text: text)
case .toggle(let toggle): return .toggle(isEnabled: toggle.isOn) { [weak tableView, weak self] calledCell, _ in
guard let indexPath = tableView?.indexPath(for: calledCell) else { return }
guard let indexPath = tableView?.indexPath(for: calledCell), !toggle.isBlocked else { return }
self?.presenter.handleToggle(for: indexPath)
}
}

View File

@ -145,7 +145,7 @@ extension SettingsMenuPresenter {
menu.append(contentsOf: [
backup,
security,
security
])
if interactor.isBrowserExtensionAllowed {

View File

@ -22,7 +22,7 @@ import Data
protocol NewPINFlowControllerParent: AnyObject {
func hideNewPIN()
func pingGathered(
func pinGathered(
with PIN: String,
pinType: PINType,
action: NewPINFlowController.Action,
@ -52,14 +52,15 @@ final class NewPINFlowController: FlowController {
static func setRoot(
in navigationController: UINavigationController,
parent: NewPINFlowControllerParent,
pinType: PINType
pinType: PINType,
lockNavigation: Bool
) {
let view = NewPINViewController()
let flowController = NewPINFlowController(viewController: view)
flowController.parent = parent
flowController.action = .create
flowController.step = .first(pinType: pinType)
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor()
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor(lockNavigation: lockNavigation)
interactor.selectedPINType = pinType
let presenter = NewPINPresenter(
@ -80,7 +81,8 @@ final class NewPINFlowController: FlowController {
on navigationController: UINavigationController,
parent: NewPINFlowControllerParent,
action: Action,
step: Step
step: Step,
lockNavigation: Bool
) {
let view = NewPINViewController()
let flowController = NewPINFlowController(viewController: view)
@ -101,7 +103,7 @@ final class NewPINFlowController: FlowController {
selectedPINType = pinType
}
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor()
let interactor = ModuleInteractorFactory.shared.newPINModuleInteractor(lockNavigation: lockNavigation)
interactor.selectedPIN = selectedPIN
interactor.selectedPINType = selectedPINType
@ -130,7 +132,7 @@ extension NewPINFlowController: NewPINFlowControlling {
func toPINGathered(with PIN: String, pinType: PINType) {
guard let action, let step else { return }
parent?.pingGathered(with: PIN, pinType: pinType, action: action, step: step)
parent?.pinGathered(with: PIN, pinType: pinType, action: action, step: step)
}
func toChangePINType() {

View File

@ -0,0 +1,76 @@
//
// 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 UIKit
import Data
protocol NewPINNavigationFlowControllerParent: AnyObject {
func pinGathered(
with PIN: String,
pinType: PINType
)
}
protocol NewPINNavigationFlowControlling: AnyObject {}
final class NewPINNavigationFlowController: NavigationFlowController {
private weak var parent: NewPINNavigationFlowControllerParent?
static func present(
on viewController: UIViewController,
parent: NewPINNavigationFlowControllerParent
) {
let flowController = NewPINNavigationFlowController()
flowController.parent = parent
let navi = CommonNavigationControllerFlow(flowController: flowController)
navi.configureAsFullscreenModal()
flowController.navigationController = navi
NewPINFlowController.setRoot(in: navi, parent: flowController, pinType: .digits6, lockNavigation: true)
viewController.present(navi, animated: true, completion: nil)
}
}
extension NewPINNavigationFlowController: NewPINNavigationFlowControlling {}
extension NewPINNavigationFlowController: NewPINFlowControllerParent {
func hideNewPIN() {}
func pinGathered(
with PIN: String,
pinType: PINType,
action: NewPINFlowController.Action,
step: NewPINFlowController.Step
) {
switch step {
case .first:
NewPINFlowController.push(
on: navigationController,
parent: self,
action: .create,
step: .second(PIN: PIN, pinType: pinType),
lockNavigation: true
)
case .second(let PIN, let pinType):
parent?.pinGathered(with: PIN, pinType: pinType)
}
}
}

View File

@ -24,6 +24,8 @@ protocol NewPINModuleInteracting: AnyObject {
var selectedPIN: String? { get set }
var selectedPINType: PINType? { get set }
var lockNavigation: Bool { get }
var pinType: PINType { get }
func validatePIN(_ PIN: String) -> Bool
@ -31,8 +33,15 @@ protocol NewPINModuleInteracting: AnyObject {
}
final class NewPINModuleInteractor {
let lockNavigation: Bool
var selectedPIN: String?
var selectedPINType: PINType?
init(lockNavigation: Bool, selectedPIN: String? = nil, selectedPINType: PINType? = nil) {
self.lockNavigation = lockNavigation
self.selectedPIN = selectedPIN
self.selectedPINType = selectedPINType
}
}
extension NewPINModuleInteractor: NewPINModuleInteracting {

View File

@ -37,6 +37,12 @@ final class NewPINPresenter: PINKeyboardPresenter {
codeLength = interactor.pinType.digits
}
func viewDidLoad() {
if !interactor.lockNavigation {
view?.showCancelButton()
}
}
override func viewWillAppear() {
super.viewWillAppear()

View File

@ -21,6 +21,7 @@ import UIKit
protocol NewPINViewControlling: AnyObject {
func setTitle(_ title: String)
func showCancelButton()
}
final class NewPINViewController: PINKeyboardViewController {
@ -28,13 +29,8 @@ final class NewPINViewController: PINKeyboardViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: T.Commons.cancel,
style: .plain,
target: self,
action: #selector(cancelAction)
)
navigationItem.backButtonDisplayMode = .minimal
presenter.viewDidLoad()
}
override func deleteButtonTap() {
@ -69,4 +65,13 @@ extension NewPINViewController: NewPINViewControlling {
func setTitle(_ title: String) {
self.title = title
}
func showCancelButton() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: T.Commons.cancel,
style: .plain,
target: self,
action: #selector(cancelAction)
)
}
}