diff --git a/Library/Generated/strings.swift b/Library/Generated/strings.swift index 1e65f3ccf..89c9b0465 100644 --- a/Library/Generated/strings.swift +++ b/Library/Generated/strings.swift @@ -700,6 +700,8 @@ internal enum L10n { internal static let lndLog = L10n.tr("Localizable", "scene.settings.item.lnd_log") /// Manage Channels internal static let manageChannels = L10n.tr("Localizable", "scene.settings.item.manage_channels") + /// Authenticate payments + internal static let paymentsAuthentication = L10n.tr("Localizable", "scene.settings.item.paymentsAuthentication") /// Privacy Policy internal static let privacyPolicy = L10n.tr("Localizable", "scene.settings.item.privacy_policy") /// Manage Wallets diff --git a/Library/Scenes/ModalDetail/Send/SendViewController.swift b/Library/Scenes/ModalDetail/Send/SendViewController.swift index a99c78520..c3ac7879e 100644 --- a/Library/Scenes/ModalDetail/Send/SendViewController.swift +++ b/Library/Scenes/ModalDetail/Send/SendViewController.swift @@ -160,6 +160,10 @@ final class SendViewController: ModalDetailViewController { } private func sendButtonTapped() { + if !Settings.shared.paymentsAuthentication.value { + return self.send() + } + authenticate { [weak self] result in switch result { case .success: diff --git a/Library/Scenes/Settings/GroupedTableViewController.swift b/Library/Scenes/Settings/GroupedTableViewController.swift index 8ea569d98..b800128ef 100644 --- a/Library/Scenes/Settings/GroupedTableViewController.swift +++ b/Library/Scenes/Settings/GroupedTableViewController.swift @@ -48,6 +48,15 @@ class GroupedTableViewController: UITableViewController { .bind(to: detailLabel.reactive.text) .dispose(in: reactive.bag) } + } else if let item = item as? ToggleSettingsItem { + cell = UITableViewCell(style: .default, reuseIdentifier: "ToggleSettingsCell") + + let toggle = UISwitch() + toggle.onTintColor = UIColor.Zap.lightningOrange + toggle.isOn = item.isToggled.value + toggle.reactive.isOn.bidirectionalBind(to: item.isToggled) + + cell.accessoryView = toggle } else { cell = UITableViewCell(style: .default, reuseIdentifier: "DefaultSettingsCell") } diff --git a/Library/Scenes/Settings/Items/PaymentsAuthenticationSettingsItem.swift b/Library/Scenes/Settings/Items/PaymentsAuthenticationSettingsItem.swift new file mode 100644 index 000000000..d59900d10 --- /dev/null +++ b/Library/Scenes/Settings/Items/PaymentsAuthenticationSettingsItem.swift @@ -0,0 +1,59 @@ +// +// Library +// +// Created by Ivan Kuznetsov on 11.05.2020. +// Copyright © 2020 Zap. All rights reserved. +// + +import Bond +import SwiftLnd +import UIKit + +final class PaymentsAuthenticationSettingsItem: NSObject, ToggleSettingsItem { + var isToggled = Observable(Settings.shared.paymentsAuthentication.value) + + let title = L10n.Scene.Settings.Item.paymentsAuthentication + + private let authenticationViewModel: AuthenticationViewModel + var fakeAuthViewController = UIViewController() + + init(authenticationViewModel: AuthenticationViewModel) { + self.authenticationViewModel = authenticationViewModel + + super.init() + + isToggled.observeNext { [weak self] isOn in + if !isOn && Settings.shared.paymentsAuthentication.value { + self?.authenticate { [weak self] result in + switch result { + case .success: + Settings.shared.paymentsAuthentication.value = isOn + case .failure: + self?.isToggled.send(!isOn) + } + } + } else { + Settings.shared.paymentsAuthentication.value = isOn + } + } + .dispose(in: reactive.bag) + } + + func didSelectItem(from fromViewController: UIViewController) { + } + + private func authenticate(completion: @escaping (Result) -> Void) { + if BiometricAuthentication.type == .none { + ModalPinViewController.authenticate(authenticationViewModel: authenticationViewModel) { completion($0) } + } else { + BiometricAuthentication.authenticate(viewController: fakeAuthViewController) { [authenticationViewModel] result in + if case .failure(let error) = result, + error == AuthenticationError.useFallback { + ModalPinViewController.authenticate(authenticationViewModel: authenticationViewModel) { completion($0) } + } else { + completion(result) + } + } + } + } +} diff --git a/Library/Scenes/Settings/Items/SettingsItem.swift b/Library/Scenes/Settings/Items/SettingsItem.swift index 5de8ec662..9069fd05d 100644 --- a/Library/Scenes/Settings/Items/SettingsItem.swift +++ b/Library/Scenes/Settings/Items/SettingsItem.swift @@ -24,3 +24,7 @@ protocol SubtitleSettingsItem: SettingsItem { protocol SelectableSettingsItem: SettingsItem { var isSelectedOption: Observable { get } } + +protocol ToggleSettingsItem: SettingsItem { + var isToggled: Observable { get } +} diff --git a/Library/Scenes/Settings/Settings.swift b/Library/Scenes/Settings/Settings.swift index fb7aa4336..11c2c0118 100644 --- a/Library/Scenes/Settings/Settings.swift +++ b/Library/Scenes/Settings/Settings.swift @@ -28,6 +28,7 @@ public final class Settings: NSObject, Persistable { var blockExplorer: BlockExplorer? var onChainRequestAddressType: OnChainRequestAddressType? var lightningRequestExpiry: ExpiryTime? + var paymentsPINProtection: Bool? } public let primaryCurrency: Observable @@ -38,6 +39,7 @@ public final class Settings: NSObject, Persistable { let blockExplorer: Observable let onChainRequestAddressType: Observable let lightningRequestExpiry: Observable + let paymentsAuthentication: Observable public static let shared = Settings() @@ -55,6 +57,7 @@ public final class Settings: NSObject, Persistable { blockExplorer = Observable(data?.blockExplorer ?? .blockstream) onChainRequestAddressType = Observable(data?.onChainRequestAddressType ?? .witnessPubkeyHash) lightningRequestExpiry = Observable(data?.lightningRequestExpiry ?? .oneHour) + paymentsAuthentication = Observable(data?.paymentsPINProtection ?? true) super.init() @@ -95,6 +98,11 @@ public final class Settings: NSObject, Persistable { .dropFirst(1) .observeNext { [weak self] in self?.data.lightningRequestExpiry = $0 + }, + paymentsAuthentication + .dropFirst(1) + .observeNext { [weak self] in + self?.data.paymentsPINProtection = $0 } ].dispose(in: reactive.bag) } diff --git a/Library/Scenes/Settings/SettingsViewController.swift b/Library/Scenes/Settings/SettingsViewController.swift index 85d6e230d..6a20e189c 100644 --- a/Library/Scenes/Settings/SettingsViewController.swift +++ b/Library/Scenes/Settings/SettingsViewController.swift @@ -26,7 +26,12 @@ final class SettingsViewController: GroupedTableViewController { ) { self.info = info + let paymentsAuthenticationSetting = PaymentsAuthenticationSettingsItem( + authenticationViewModel: authenticationViewModel + ) + var walletRows: [SettingsItem] = [ + paymentsAuthenticationSetting, ChangePinSettingsItem(authenticationViewModel: authenticationViewModel) ] @@ -55,6 +60,8 @@ final class SettingsViewController: GroupedTableViewController { ]), at: 0) } super.init(sections: sections) + + paymentsAuthenticationSetting.fakeAuthViewController = self } required init?(coder aDecoder: NSCoder) { diff --git a/Library/en.lproj/Localizable.strings b/Library/en.lproj/Localizable.strings index bb353538a..dcbeab8c7 100644 --- a/Library/en.lproj/Localizable.strings +++ b/Library/en.lproj/Localizable.strings @@ -91,6 +91,7 @@ "scene.settings.item.help" = "Need Help?"; "scene.settings.item.privacy_policy" = "Privacy Policy"; "scene.settings.item.lnd_log" = "Show lnd Log"; +"scene.settings.item.paymentsAuthentication" = "Authenticate payments"; "scene.request.title" = "Receive"; "scene.request.lightning_button" = "Lightning"; diff --git a/Zap.xcodeproj/project.pbxproj b/Zap.xcodeproj/project.pbxproj index 0cca64edc..f27246161 100644 --- a/Zap.xcodeproj/project.pbxproj +++ b/Zap.xcodeproj/project.pbxproj @@ -402,6 +402,7 @@ ADFEFA7120E628AC00FE1557 /* Lightning.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD536BE620E62763002279BC /* Lightning.framework */; }; ADFEFA7920E62B1800FE1557 /* BlockChainHeightUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0D3C97F20629B9D0015C5D7 /* BlockChainHeightUpdater.swift */; }; B7136A44ACFDB94442D05503 /* Pods_Zap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B5CE8ECCAF4EA900FB2EDE1 /* Pods_Zap.framework */; }; + B8C7C0FA246930DF00B4556A /* PaymentsAuthenticationSettingsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C7C0F9246930DF00B4556A /* PaymentsAuthenticationSettingsItem.swift */; }; CF11A5026DB63D2F0617B3CD /* Pods_LightningTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7944BECD4B9EE6982B31E9A /* Pods_LightningTests.framework */; }; D191A2ABA2BF895DAD01E919 /* Pods_RPC_Widget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C3A10DFB274187D3242E528 /* Pods_RPC_Widget.framework */; }; /* End PBXBuildFile section */ @@ -1028,6 +1029,7 @@ ADFE537F20D693F500B3BA2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ADFEFA7320E6294000FE1557 /* OnChainRequestAddressType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnChainRequestAddressType.swift; sourceTree = ""; }; B7944BECD4B9EE6982B31E9A /* Pods_LightningTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LightningTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B8C7C0F9246930DF00B4556A /* PaymentsAuthenticationSettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentsAuthenticationSettingsItem.swift; sourceTree = ""; }; BFEA939DE8A6736499979EAF /* Pods-RPC-Lightning.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPC-Lightning.release.xcconfig"; path = "Target Support Files/Pods-RPC-Lightning/Pods-RPC-Lightning.release.xcconfig"; sourceTree = ""; }; C3CC119794C04517509253C2 /* Pods-RPC-Widget.debugremote.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPC-Widget.debugremote.xcconfig"; path = "Target Support Files/Pods-RPC-Widget/Pods-RPC-Widget.debugremote.xcconfig"; sourceTree = ""; }; C480EBE1AF18EF4DEB4BA918 /* Pods-Zap.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Zap.debug.xcconfig"; path = "Target Support Files/Pods-Zap/Pods-Zap.debug.xcconfig"; sourceTree = ""; }; @@ -1302,6 +1304,7 @@ A091914A203B297D00FA525A /* CurrencySettingsItem.swift */, 5C18BB70234C09EB00BCF9D9 /* LightningRequestExpirySettingsItem.swift */, AD6FFCA520AB0A3700B57330 /* OnChainRequestAddressTypeSettingsItem.swift */, + B8C7C0F9246930DF00B4556A /* PaymentsAuthenticationSettingsItem.swift */, ADFE2E4B216CF4A800988243 /* PushViewControllerSettingsItem.swift */, AD81C21621B80FCD00FA3FA9 /* SafariSettingsItem.swift */, A0DDC06B2018C07100AEFF94 /* SettingsItem.swift */, @@ -3289,6 +3292,7 @@ AD967FF021062AB20048085B /* RPCConnectQRCodeError+Localizable.swift in Sources */, AD3DDF7122D39BC30031BFC8 /* Emoji.swift in Sources */, AD615CF7211CAB0900680790 /* PaddingLabel.swift in Sources */, + B8C7C0FA246930DF00B4556A /* PaymentsAuthenticationSettingsItem.swift in Sources */, AD391C6E20D16F89007EE22A /* SetupPinViewModel.swift in Sources */, ADA179C62152864E001401F3 /* Storage.swift in Sources */, ADA865BE20ECB88500B1B74B /* FilterViewController.swift in Sources */,