diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 458e56ee5..1b37c2b53 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -465,6 +465,7 @@ A5F929AF262C857D00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929AE262C857D00C3E60A /* MarkdownKit */; }; A5F929B6262C858700C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B5262C858700C3E60A /* MarkdownKit */; }; A5F929B8262C858F00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B7262C858F00C3E60A /* MarkdownKit */; }; + D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA7882D42745D0069BC73 /* PinPadView.swift */; }; E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */; }; E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F620EC200900D0CB2D /* SecurityViewController.swift */; }; E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */; }; @@ -1108,6 +1109,7 @@ A5E0422A282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcUnspentTransactionResponse.swift; sourceTree = ""; }; AD258997F050B24C0051CC8D /* Pods-Adamant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.release.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.release.xcconfig"; sourceTree = ""; }; ADDFD2FA17E41CCBD11A1733 /* Pods-Adamant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.debug.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.debug.xcconfig"; sourceTree = ""; }; + D39AA7882D42745D0069BC73 /* PinPadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinPadView.swift; sourceTree = ""; }; E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; E90055F620EC200900D0CB2D /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecurityViewController+StayIn.swift"; sourceTree = ""; }; @@ -2404,6 +2406,7 @@ E96D64C72295C44400CA5587 /* Data+utilites.swift */, 64A223D520F760BB005157CB /* Localization.swift */, E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, + D39AA7882D42745D0069BC73 /* PinPadView.swift */, E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, 6414C18D217DF43100373FA6 /* String+adamant.swift */, @@ -3618,6 +3621,7 @@ E908471B2196FE590095825D /* Adamant.xcdatamodeld in Sources */, E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */, 93294B902AAD2C6B00911109 /* SwiftyOnboardOverlay.swift in Sources */, + D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */, E948E03B20235E2300975D6B /* SettingsFactory.swift in Sources */, 4186B3302941E642006594A3 /* AdmWalletService+DynamicConstants.swift in Sources */, E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index 34c06bda6..ed3348e5d 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -81,3 +81,5 @@ extension PinpadViewController { return pinpad } } + + diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift new file mode 100644 index 000000000..21545c240 --- /dev/null +++ b/Adamant/Helpers/PinPadView.swift @@ -0,0 +1,158 @@ +// +// StyledPinpadView.swift +// Adamant +// +// Created by Brian on 23/01/2025. +// Copyright © 2025 Adamant. All rights reserved. +// + +import SwiftUI + +struct PinPadViewRepresentable: UIViewRepresentable { + @Binding var enteredPin: String + let pinLength: Int + let validatePin: (String) -> Bool + let onSuccess: () -> Void + let onCancel: () -> Void + + func makeUIView(context: Context) -> UIView { + let hostingController = UIHostingController( + rootView: PinPadView( + enteredPin: $enteredPin, + pinLength: pinLength, + validatePin: validatePin, + onSuccess: onSuccess, + onCancel: onCancel + ) + ) + return hostingController.view + } + + func updateUIView(_ uiView: UIView, context: Context) { + // Handle updates to the view if needed + } +} + +// swiftlint:disable multiple_closures_with_trailing_closure +struct PinPadView: View { + @Binding var enteredPin: String + @State var isPinpadVisible: Bool = true + let pinLength: Int + let validatePin: (String) -> Bool + let onSuccess: () -> Void + let onCancel: () -> Void + + var body: some View { + VStack { + Spacer() + Text("Login into ADAMANT") + .foregroundColor(.white) + .textCase(nil) + .font(.body) + .padding(.top, 30) + + HStack(spacing: 10) { + ForEach(0.. some View { + HStack { + if showsDeleteButton { + Circle() + .frame(width: 75, height: 75) + .foregroundColor(.clear) + } + ForEach(from...to, id: \.self) { number in + Button(action: { + handlePinInput("\(number)") + }) { + Circle() + .frame(width: 75, height: 75) + .overlay( + Text("\(number)") + .foregroundColor(.white) + .font(.title) + ) + .foregroundColor(.clear) + .overlay(Circle().stroke(Color.white, lineWidth: 1)) + } + } + if showsDeleteButton { + Button(action: deleteLastDigit) { + Circle() + .frame(width: 75, height: 75) + .overlay( + Image(systemName: "delete.left") + .foregroundColor(.white) + .font(.title) + ) + .foregroundColor(.clear) + .overlay(Circle().stroke(Color.white, lineWidth: 1)) + } + } + } + } + + private func handlePinInput(_ digit: String) { + guard enteredPin.count < pinLength else { return } + enteredPin.append(digit) + if enteredPin.count == pinLength { + if validatePin(enteredPin) { + onSuccess() + isPinpadVisible = false + } else { + enteredPin.removeAll() + } + } + } + + private func deleteLastDigit() { + guard !enteredPin.isEmpty else { return } + enteredPin.removeLast() + } +} + +#if DEBUG + +private struct Placeholder { + @State var enteredPin: String = "" +} + +#Preview { + PinPadView( + enteredPin: Placeholder().$enteredPin, + pinLength: 6 + ) { _ in + true + } onSuccess: { + + } onCancel: { + + } +} + +#endif diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 6783bb9f6..b7b33ebc5 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1528,7 +1528,7 @@ extension ChatListViewController { } } -private extension State { +private extension DataProviderState { var isUpdating: Bool { switch self { case .updating: true diff --git a/Adamant/ServiceProtocols/DataProviders/DataProvider.swift b/Adamant/ServiceProtocols/DataProviders/DataProvider.swift index 395453c24..1272b84f6 100644 --- a/Adamant/ServiceProtocols/DataProviders/DataProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/DataProvider.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -enum State { +enum DataProviderState { case empty case updating case upToDate @@ -17,8 +17,8 @@ enum State { } protocol DataProvider: AnyObject, Actor { - var state: State { get } - var stateObserver: AnyObservable { get } + var state: DataProviderState { get } + var stateObserver: AnyObservable { get } var isInitiallySynced: Bool { get } func reload() async @@ -26,10 +26,10 @@ protocol DataProvider: AnyObject, Actor { } // MARK: - Status Equatable -extension State: Equatable { +extension DataProviderState: Equatable { /// Simple equatable function. Does not checks associated values. - static func ==(lhs: State, rhs: State) -> Bool { + static func ==(lhs: DataProviderState, rhs: DataProviderState) -> Bool { switch (lhs, rhs) { case (.empty, .empty): return true case (.updating, .updating): return true diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index e75b27790..c2b78f1de 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -28,10 +28,10 @@ actor AdamantChatsProvider: ChatsProvider { let stack: CoreDataStack // MARK: Properties - @ObservableValue private var stateNotifier: State = .empty - var stateObserver: AnyObservable { $stateNotifier.eraseToAnyPublisher() } + @ObservableValue private var stateNotifier: DataProviderState = .empty + var stateObserver: AnyObservable { $stateNotifier.eraseToAnyPublisher() } - private(set) var state: State = .empty + private(set) var state: DataProviderState = .empty private(set) var receivedLastHeight: Int64? private(set) var readedLastHeight: Int64? private let apiTransactions = 100 @@ -228,7 +228,7 @@ actor AdamantChatsProvider: ChatsProvider { // MARK: Tools /// Free stateSemaphore before calling this method, or you will deadlock. - private func setState(_ state: State, previous prevState: State, notify: Bool = true) { + private func setState(_ state: DataProviderState, previous prevState: DataProviderState, notify: Bool = true) { self.state = state guard notify else { return } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index c64361867..158ef01fb 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -25,8 +25,8 @@ actor AdamantTransfersProvider: TransfersProvider { private let transactionService: ChatTransactionService weak var chatsProvider: ChatsProvider? - @ObservableValue private(set) var state: State = .empty - var stateObserver: AnyObservable { $state.eraseToAnyPublisher() } + @ObservableValue private(set) var state: DataProviderState = .empty + var stateObserver: AnyObservable { $state.eraseToAnyPublisher() } private(set) var isInitiallySynced: Bool = false private(set) var receivedLastHeight: Int64? private(set) var readedLastHeight: Int64? @@ -41,7 +41,7 @@ actor AdamantTransfersProvider: TransfersProvider { // MARK: Tools /// Free stateSemaphore before calling this method, or you will deadlock. - private func setState(_ state: State, previous prevState: State, notify: Bool = false) { + private func setState(_ state: DataProviderState, previous prevState: DataProviderState, notify: Bool = false) { self.state = state if notify {