diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 2931fc684a..5bd5e41ed1 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2399,6 +2399,8 @@ D05E96BA2A2A1113002CCD71 /* WatchTronService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05E96B82A2A1113002CCD71 /* WatchTronService.swift */; }; D05F132E2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F132D2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift */; }; D05F132F2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F132D2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift */; }; + D061A5322AA846FA009AAD57 /* SecuritySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */; }; + D061A5332AA846FA009AAD57 /* SecuritySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */; }; D06669022A31B559004B048D /* TronRecipientAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */; }; D06669032A31B559004B048D /* TronRecipientAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */; }; D07157DB2A2DD968006F141F /* SendTronModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07157DA2A2DD968006F141F /* SendTronModule.swift */; }; @@ -2561,9 +2563,7 @@ D3840589218317DF007D50AD /* MainSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564879AD72301AAB78F8F5 /* MainSettingsViewController.swift */; }; D384058B218317DF007D50AD /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C4FFB99D10A8F343E9C /* LanguageManager.swift */; }; D384058C218317DF007D50AD /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352044BCE494491257933 /* LocalStorage.swift */; }; - D384058E218317DF007D50AD /* SecuritySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5647AD931E3B90C974A6CB /* SecuritySettingsViewController.swift */; }; D384058F218317DF007D50AD /* SecuritySettingsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5641572B6E46E18B52A6A9 /* SecuritySettingsModule.swift */; }; - D3840590218317DF007D50AD /* SecuritySettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564082B28C17265B23D88A /* SecuritySettingsService.swift */; }; D3840592218317DF007D50AD /* SecuritySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A56446DB62F52AC4C3C2C30 /* SecuritySettingsViewModel.swift */; }; D3840593218317DF007D50AD /* BitcoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352D114BED753EEBA8B8D /* BitcoinAdapter.swift */; }; D384059A218317DF007D50AD /* KeyboardObservingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3535FC407BA20765EBCF4 /* KeyboardObservingViewController.swift */; }; @@ -2591,9 +2591,7 @@ D384067821831B3D007D50AD /* MainSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564879AD72301AAB78F8F5 /* MainSettingsViewController.swift */; }; D384067A21831B3D007D50AD /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C4FFB99D10A8F343E9C /* LanguageManager.swift */; }; D384067B21831B3D007D50AD /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352044BCE494491257933 /* LocalStorage.swift */; }; - D384067D21831B3D007D50AD /* SecuritySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5647AD931E3B90C974A6CB /* SecuritySettingsViewController.swift */; }; D384067E21831B3D007D50AD /* SecuritySettingsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5641572B6E46E18B52A6A9 /* SecuritySettingsModule.swift */; }; - D384067F21831B3D007D50AD /* SecuritySettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564082B28C17265B23D88A /* SecuritySettingsService.swift */; }; D384068121831B3D007D50AD /* SecuritySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A56446DB62F52AC4C3C2C30 /* SecuritySettingsViewModel.swift */; }; D384068221831B3D007D50AD /* BitcoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352D114BED753EEBA8B8D /* BitcoinAdapter.swift */; }; D384068921831B3D007D50AD /* KeyboardObservingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3535FC407BA20765EBCF4 /* KeyboardObservingViewController.swift */; }; @@ -3307,7 +3305,6 @@ 1A56400EBD84656A0447EA59 /* WalletConnectSignMessageRequestService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectSignMessageRequestService.swift; sourceTree = ""; }; 1A56404C1C16B85434117DB7 /* AppStatusModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStatusModule.swift; sourceTree = ""; }; 1A5640528EFD15137E218EA3 /* MainSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSettingsViewModel.swift; sourceTree = ""; }; - 1A564082B28C17265B23D88A /* SecuritySettingsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettingsService.swift; sourceTree = ""; }; 1A5640B4F6298D9F326C5EDE /* MarketOverviewTopCoinsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketOverviewTopCoinsViewModel.swift; sourceTree = ""; }; 1A5641572B6E46E18B52A6A9 /* SecuritySettingsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettingsModule.swift; sourceTree = ""; }; 1A5641679DC88BE355F0F3A0 /* Development.template.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Development.template.xcconfig; sourceTree = ""; }; @@ -3356,7 +3353,6 @@ 1A56477F6FC71270AD53A3AE /* MarketOverviewTopPlatformsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketOverviewTopPlatformsViewModel.swift; sourceTree = ""; }; 1A5647ACB8A65C250F62E07D /* AdapterState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterState.swift; sourceTree = ""; }; 1A5647AD7481B36F20D4DDF9 /* MainSettingsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSettingsModule.swift; sourceTree = ""; }; - 1A5647AD931E3B90C974A6CB /* SecuritySettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettingsViewController.swift; sourceTree = ""; }; 1A5647FA18CC69113ECB6581 /* MarketOverviewGlobalDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketOverviewGlobalDataSource.swift; sourceTree = ""; }; 1A564814721244F4D4D87557 /* ReachabilityViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityViewModel.swift; sourceTree = ""; }; 1A564827B8F8D94DC4D7CC0F /* AppStatusPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStatusPresenter.swift; sourceTree = ""; }; @@ -3894,6 +3890,7 @@ D05E96B52A29F9D3002CCD71 /* WatchTronAddressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchTronAddressViewModel.swift; sourceTree = ""; }; D05E96B82A2A1113002CCD71 /* WatchTronService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchTronService.swift; sourceTree = ""; }; D05F132D2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTronTokenBlockchainService.swift; sourceTree = ""; }; + D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuritySettingsView.swift; sourceTree = ""; }; D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronRecipientAddressViewModel.swift; sourceTree = ""; }; D07157DA2A2DD968006F141F /* SendTronModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTronModule.swift; sourceTree = ""; }; D07157DD2A2DDA09006F141F /* SendTronService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTronService.swift; sourceTree = ""; }; @@ -5877,9 +5874,8 @@ isa = PBXGroup; children = ( 1A5641572B6E46E18B52A6A9 /* SecuritySettingsModule.swift */, - 1A564082B28C17265B23D88A /* SecuritySettingsService.swift */, 1A56446DB62F52AC4C3C2C30 /* SecuritySettingsViewModel.swift */, - 1A5647AD931E3B90C974A6CB /* SecuritySettingsViewController.swift */, + D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */, ); path = Security; sourceTree = ""; @@ -7862,11 +7858,9 @@ D3840589218317DF007D50AD /* MainSettingsViewController.swift in Sources */, D384058B218317DF007D50AD /* LanguageManager.swift in Sources */, D384058C218317DF007D50AD /* LocalStorage.swift in Sources */, - D384058E218317DF007D50AD /* SecuritySettingsViewController.swift in Sources */, D023D26B2A24CD16004F65B0 /* BaseTronAdapter.swift in Sources */, D05E96BA2A2A1113002CCD71 /* WatchTronService.swift in Sources */, D384058F218317DF007D50AD /* SecuritySettingsModule.swift in Sources */, - D3840590218317DF007D50AD /* SecuritySettingsService.swift in Sources */, D3840592218317DF007D50AD /* SecuritySettingsViewModel.swift in Sources */, D3840593218317DF007D50AD /* BitcoinAdapter.swift in Sources */, D384059A218317DF007D50AD /* KeyboardObservingViewController.swift in Sources */, @@ -8574,6 +8568,7 @@ 11B358164F9FBBE78CBC806A /* NftActivityModule.swift in Sources */, 11B357ED24AA2E6ED1AE51FD /* NftActivityViewController.swift in Sources */, 11B35BDC20A4965D1793B97A /* NftActivityViewModel.swift in Sources */, + D061A5332AA846FA009AAD57 /* SecuritySettingsView.swift in Sources */, 11B358033DAB0FF23CF0E309 /* NftActivityService.swift in Sources */, 11B3584017622E1F2B3BA464 /* NftAssetButtonCell.swift in Sources */, 11B353B085BD167026DE4B5B /* CustomToken.swift in Sources */, @@ -9138,11 +9133,9 @@ D384067821831B3D007D50AD /* MainSettingsViewController.swift in Sources */, D384067A21831B3D007D50AD /* LanguageManager.swift in Sources */, D384067B21831B3D007D50AD /* LocalStorage.swift in Sources */, - D384067D21831B3D007D50AD /* SecuritySettingsViewController.swift in Sources */, D384067E21831B3D007D50AD /* SecuritySettingsModule.swift in Sources */, D023D26A2A24CD16004F65B0 /* BaseTronAdapter.swift in Sources */, D05E96B92A2A1113002CCD71 /* WatchTronService.swift in Sources */, - D384067F21831B3D007D50AD /* SecuritySettingsService.swift in Sources */, D384068121831B3D007D50AD /* SecuritySettingsViewModel.swift in Sources */, D384068221831B3D007D50AD /* BitcoinAdapter.swift in Sources */, D384068921831B3D007D50AD /* KeyboardObservingViewController.swift in Sources */, @@ -9851,6 +9844,7 @@ 11B35AB6026D794BAFEC094E /* NftCollectionAssetsService.swift in Sources */, 11B358AA46441AF0A7DCAA89 /* NftActivityModule.swift in Sources */, 11B35EBDDAB95E919600AE72 /* NftActivityViewController.swift in Sources */, + D061A5322AA846FA009AAD57 /* SecuritySettingsView.swift in Sources */, 11B35CCAC0A3C35C1B9BD918 /* NftActivityViewModel.swift in Sources */, 11B35AC60BE4DC210C3C2312 /* NftActivityService.swift in Sources */, 11B35DC513240DBF0C78ED92 /* NftAssetButtonCell.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift index dad25331b9..30494b97f4 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift @@ -249,7 +249,8 @@ class MainSettingsViewController: ThemeViewController { id: "security", height: .heightCell48, action: { [weak self] in - self?.navigationController?.pushViewController(SecuritySettingsModule.viewController(), animated: true) + let viewController = SecuritySettingsModule.view().toViewController(title: "settings_security.title".localized) + self?.navigationController?.pushViewController(viewController, animated: true) } ), StaticRow( diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsModule.swift index 0f67c6bddb..afa088dfc9 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsModule.swift @@ -1,11 +1,9 @@ -import UIKit +import SwiftUI struct SecuritySettingsModule { + static func view() -> some View { + let viewModel = SecuritySettingsViewModel(pinKit: App.shared.pinKit) - static func viewController() -> UIViewController { - let service = SecuritySettingsService(pinKit: App.shared.pinKit) - let viewModel = SecuritySettingsViewModel(service: service) - return SecuritySettingsViewController(viewModel: viewModel) + return SecuritySettingsView(viewModel: viewModel) } - } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsService.swift deleted file mode 100644 index f3872eb819..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsService.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Combine -import RxSwift -import RxRelay -import PinKit - -class SecuritySettingsService { - private let pinKit: PinKit.Kit - private var cancellables = Set() - - private let pinItemRelay = PublishRelay() - private(set) var pinItem: PinItem = PinItem(enabled: false, biometryEnabled: false, biometryType: nil) { - didSet { - pinItemRelay.accept(pinItem) - } - } - - init(pinKit: PinKit.Kit) { - self.pinKit = pinKit - - pinKit.isPinSetPublisher - .sink { [weak self] _ in self?.syncPinItem() } - .store(in: &cancellables) - - pinKit.biometryTypePublisher - .sink { [weak self] _ in self?.syncPinItem() } - .store(in: &cancellables) - - syncPinItem() - } - - private func syncPinItem() { - pinItem = PinItem(enabled: pinKit.isPinSet, biometryEnabled: pinKit.biometryEnabled, biometryType: pinKit.biometryType) - } - -} - -extension SecuritySettingsService { - - var pinItemObservable: Observable { - pinItemRelay.asObservable() - } - - func toggleBiometry(isOn: Bool) { - pinKit.biometryEnabled = isOn - } - - func disablePin() throws { - try pinKit.clear() - } - -} - -extension SecuritySettingsService { - - struct PinItem { - let enabled: Bool - let biometryEnabled: Bool - let biometryType: BiometryType? - } - -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift new file mode 100644 index 0000000000..770f1676f8 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift @@ -0,0 +1,109 @@ +import PinKit +import SwiftUI + +struct SecuritySettingsView: View { + @ObservedObject var viewModel: SecuritySettingsViewModel + @State var editPasscodePresented: Bool = false + + var body: some View { + ScrollableThemeView { + VStack(spacing: .margin32) { + ListSection { + ListRow { + Image("dialpad_alt_2_24") + Text("settings_security.passcode".localized).themeBody() + Spacer() + + if !viewModel.passcodeEnabled { + Image("warning_2_20") + .renderingMode(.template) + .foregroundColor(.themeLucian) + } + + Toggle(isOn: $viewModel.passcodeSwitchOn) {} + .labelsHidden() + } + .sheet(isPresented: $viewModel.setPasscodePresented, onDismiss: { viewModel.cancelSetPasscode() }) { + SetPinView( + cancelAction: { viewModel.cancelSetPasscode() } + ).edgesIgnoringSafeArea(.all) + } + .sheet(isPresented: $viewModel.unlockPasscodePresented, onDismiss: { viewModel.cancelUnlock() }) { + UnlockPinView( + unlockAction: { viewModel.onUnlock() }, + cancelAction: { viewModel.cancelUnlock() } + ).edgesIgnoringSafeArea(.all) + } + + if viewModel.passcodeEnabled { + ClickableRow(action: { editPasscodePresented = true }) { + Text("settings_security.change_pin".localized).themeBody() + Image.disclosureIcon + } + .sheet(isPresented: $editPasscodePresented) { + EditPinView().edgesIgnoringSafeArea(.all) + } + } + } + + if viewModel.passcodeEnabled && viewModel.biometryAvailable { + ListSection { + ListRow { + Image(viewModel.biometryIconName) + Toggle(isOn: $viewModel.biometryEnabled) { + Text(viewModel.biometryTitle).themeBody() + } + } + } + } + } + .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) + } + } +} + +struct SetPinView: UIViewControllerRepresentable, ISetPinDelegate { + typealias UIViewControllerType = UIViewController + + let cancelAction: () -> () + + func makeUIViewController(context: Context) -> UIViewController { + App.shared.pinKit.setPinModule(delegate: self) + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} + + func didCancelSetPin() { + cancelAction() + } +} + +struct EditPinView: UIViewControllerRepresentable { + typealias UIViewControllerType = UIViewController + + func makeUIViewController(context: Context) -> UIViewController { + return App.shared.pinKit.editPinModule + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct UnlockPinView: UIViewControllerRepresentable { + typealias UIViewControllerType = UIViewController + + let unlockAction: () -> () + let cancelAction: () -> () + + func makeUIViewController(context: Context) -> UIViewController { + return App.shared.pinKit.unlockPinModule( + biometryUnlockMode: .disabled, + insets: .zero, + cancellable: true, + autoDismiss: true, + onUnlock: unlockAction, + onCancelUnlock: cancelAction + ) + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewController.swift deleted file mode 100644 index bfbb45e171..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewController.swift +++ /dev/null @@ -1,196 +0,0 @@ -import UIKit -import RxSwift -import ThemeKit -import ComponentKit -import SectionsTableView -import PinKit - -class SecuritySettingsViewController: ThemeViewController { - private let viewModel: SecuritySettingsViewModel - private let disposeBag = DisposeBag() - - private let tableView = SectionsTableView(style: .grouped) - - private var pinViewItem = SecuritySettingsViewModel.PinViewItem(enabled: false, editVisible: false, biometryViewItem: nil) - private var loaded = false - - init(viewModel: SecuritySettingsViewModel) { - self.viewModel = viewModel - - super.init() - - hidesBottomBarWhenPushed = true - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = "settings_security.title".localized - navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil) - - view.addSubview(tableView) - tableView.snp.makeConstraints { maker in - maker.edges.equalToSuperview() - } - - tableView.backgroundColor = .clear - tableView.separatorStyle = .none - - tableView.sectionDataSource = self - - subscribe(disposeBag, viewModel.pinViewItemDriver) { [weak self] in self?.sync(pinViewItem: $0) } - subscribe(disposeBag, viewModel.showErrorSignal) { [weak self] in self?.show(error: $0) } - subscribe(disposeBag, viewModel.openSetPinSignal) { [weak self] in self?.openSetPin() } - subscribe(disposeBag, viewModel.openUnlockSignal) { [weak self] in self?.openUnlock() } - - loaded = true - } - - private func sync(pinViewItem: SecuritySettingsViewModel.PinViewItem) { - self.pinViewItem = pinViewItem - reloadTable() - } - - private func reloadTable() { - if loaded { - tableView.reload(animated: true) - } else { - tableView.buildSections() - } - } - - private func show(error: String) { - HudHelper.instance.show(banner: .error(string: error)) - } - - private func openSetPin() { - present(App.shared.pinKit.setPinModule(delegate: self), animated: true) - } - - private func openEditPin() { - present(App.shared.pinKit.editPinModule, animated: true) - } - - private func openUnlock() { - present(App.shared.pinKit.unlockPinModule( - biometryUnlockMode: .disabled, - insets: .zero, - cancellable: true, - autoDismiss: true, - onUnlock: { [weak self] in - self?.handleUnlock() - }, - onCancelUnlock: { [weak self] in - self?.tableView.reloadData() - } - ), animated: true) - } - - private func handleUnlock() { - let success = viewModel.onUnlock() - - if !success { - tableView.reloadData() - } - } - -} - -extension SecuritySettingsViewController: SectionsDataSource { - - private func passcodeRows(viewItem: SecuritySettingsViewModel.PinViewItem) -> [RowProtocol] { - var elements = tableView.universalImage24Elements( - image: .local(UIImage(named: "dialpad_alt_2_24")?.withTintColor(.themeGray)), - title: .body("settings_security.passcode".localized), - accessoryType: .switch(isOn: viewItem.enabled) { [weak self] in self?.viewModel.onTogglePin(isOn: $0) } - ) - elements.insert(.image20 { (component: ImageComponent) -> () in - component.isHidden = viewItem.enabled - component.imageView.image = UIImage(named: "warning_2_20")?.withTintColor(.themeLucian) - }, at: 2) - - let passcodeRow = CellBuilderNew.row( - rootElement: .hStack(elements), - tableView: tableView, - id: "passcode", - hash: "\(viewItem.enabled)", - height: .heightCell48, - bind: { cell in - cell.set(backgroundStyle: .lawrence, isFirst: true, isLast: !viewItem.editVisible) - } - ) - - var rows: [RowProtocol] = [passcodeRow] - - if viewItem.editVisible { - let editRow = tableView.universalRow48( - id: "edit-passcode", - title: .body("settings_security.change_pin".localized), - accessoryType: .disclosure, - autoDeselect: true, - isLast: true, - action: { [weak self] in - self?.openEditPin() - } - ) - - rows.append(editRow) - } - - return rows - } - - private func biometryRow(viewItem: SecuritySettingsViewModel.BiometryViewItem) -> RowProtocol { - tableView.universalRow48( - id: "biometry", - image: .local(UIImage(named: viewItem.icon)?.withTintColor(.themeGray)), - title: .body(viewItem.title), - accessoryType: .switch( - isOn: viewItem.enabled, - onSwitch: { [weak self] isOn in - self?.viewModel.onToggleBiometry(isOn: isOn) - }), - hash: "\(viewItem.enabled)", - isFirst: true, - isLast: true - ) - } - - func buildSections() -> [SectionProtocol] { - var sections = [SectionProtocol]() - - let passcodeSection = Section( - id: "passcode", - headerState: .margin(height: .margin12), - footerState: .margin(height: .margin24), - rows: passcodeRows(viewItem: pinViewItem) - ) - sections.append(passcodeSection) - - if let biometryViewItem = pinViewItem.biometryViewItem { - let biometrySection = Section( - id: "biometry", - footerState: .margin(height: .margin32), - rows: [ - biometryRow(viewItem: biometryViewItem) - ] - ) - sections.append(biometrySection) - } - - return sections - } - -} - -extension SecuritySettingsViewController: ISetPinDelegate { - - func didCancelSetPin() { - tableView.reloadData() - } - -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift index 091a3e9d1e..33eb0e247b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift @@ -1,106 +1,105 @@ -import RxSwift -import RxRelay -import RxCocoa - -class SecuritySettingsViewModel { - private let service: SecuritySettingsService - private let disposeBag = DisposeBag() - - private let pinViewItemRelay = BehaviorRelay(value: PinViewItem(enabled: false, editVisible: false, biometryViewItem: nil)) - private let showErrorRelay = PublishRelay() - private let openSetPinRelay = PublishRelay<()>() - private let openUnlockRelay = PublishRelay<()>() - - init(service: SecuritySettingsService) { - self.service = service - - subscribe(disposeBag, service.pinItemObservable) { [weak self] in self?.sync(pinItem: $0) } - - sync(pinItem: service.pinItem) - } - - private func sync(pinItem: SecuritySettingsService.PinItem) { - let viewItem = PinViewItem( - enabled: pinItem.enabled, - editVisible: pinItem.enabled, - biometryViewItem: biometryViewItem(pinItem: pinItem) - ) - - pinViewItemRelay.accept(viewItem) +import Combine +import ComponentKit +import PinKit +import SwiftUI + +class SecuritySettingsViewModel: ObservableObject { + private let pinKit: PinKit.Kit + private var cancellables = Set() + + @Published var passcodeEnabled: Bool = false { + didSet { + passcodeSwitchOn = passcodeEnabled + } } - private func biometryViewItem(pinItem: SecuritySettingsService.PinItem) -> BiometryViewItem? { - guard pinItem.enabled else { - return nil + @Published var passcodeSwitchOn: Bool = false { + didSet { + guard oldValue != passcodeSwitchOn else { + return + } + + if passcodeSwitchOn { + if !passcodeEnabled { + setPasscodePresented = true + } + } else { + if passcodeEnabled { + unlockPasscodePresented = true + } + } } + } - guard let biometryType = pinItem.biometryType else { - return nil - } + @Published var setPasscodePresented: Bool = false + @Published var unlockPasscodePresented: Bool = false - switch biometryType { - case .faceId: return BiometryViewItem(enabled: pinItem.biometryEnabled, icon: "face_id_24", title: "settings_security.face_id".localized) - case .touchId: return BiometryViewItem(enabled: pinItem.biometryEnabled, icon: "touch_id_2_24", title: "settings_security.touch_id".localized) - case .none: return nil + @Published var biometryEnabled: Bool = false { + didSet { + if pinKit.biometryEnabled != biometryEnabled { + pinKit.biometryEnabled = biometryEnabled + } } } -} + @Published var biometryAvailable: Bool = true -extension SecuritySettingsViewModel { + var biometryTitle: String = "" + var biometryIconName: String = "" - var pinViewItemDriver: Driver { - pinViewItemRelay.asDriver() - } + init(pinKit: PinKit.Kit) { + self.pinKit = pinKit - var showErrorSignal: Signal { - showErrorRelay.asSignal() - } + pinKit.isPinSetPublisher + .sink { [weak self] _ in self?.sync() } + .store(in: &cancellables) - var openSetPinSignal: Signal<()> { - openSetPinRelay.asSignal() - } + pinKit.biometryTypePublisher + .sink { [weak self] _ in self?.sync() } + .store(in: &cancellables) - var openUnlockSignal: Signal<()> { - openUnlockRelay.asSignal() + sync() } - func onTogglePin(isOn: Bool) { - if service.pinItem.enabled { - openUnlockRelay.accept(()) - } else { - openSetPinRelay.accept(()) + private func sync() { + passcodeEnabled = pinKit.isPinSet + biometryEnabled = pinKit.biometryEnabled + + switch pinKit.biometryType { + case .faceId: + biometryAvailable = true + biometryTitle = "settings_security.face_id".localized + biometryIconName = "face_id_24" + case .touchId: + biometryAvailable = true + biometryTitle = "settings_security.touch_id".localized + biometryIconName = "touch_id_2_24" + default: + biometryAvailable = false + biometryTitle = "" + biometryIconName = "" } } +} - func onToggleBiometry(isOn: Bool) { - service.toggleBiometry(isOn: isOn) - } - - func onUnlock() -> Bool { +extension SecuritySettingsViewModel { + func onUnlock() { do { - try service.disablePin() - return true + try pinKit.clear() } catch { - showErrorRelay.accept(error.smartDescription) - return false + HudHelper.instance.show(banner: .error(string: error.smartDescription)) } } -} - -extension SecuritySettingsViewModel { - - struct PinViewItem { - let enabled: Bool - let editVisible: Bool - let biometryViewItem: BiometryViewItem? + func cancelSetPasscode() { + if !passcodeEnabled { + passcodeSwitchOn = false + } } - struct BiometryViewItem { - let enabled: Bool - let icon: String - let title: String + func cancelUnlock() { + if passcodeEnabled { + passcodeSwitchOn = true + } } - }