Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: [trello.com/c/80ynmlyd] PinPadView in SwiftUI #669

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Adamant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1108,6 +1109,7 @@
A5E0422A282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcUnspentTransactionResponse.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
D39AA7882D42745D0069BC73 /* PinPadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinPadView.swift; sourceTree = "<group>"; };
E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
E90055F620EC200900D0CB2D /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = "<group>"; };
E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecurityViewController+StayIn.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
2 changes: 2 additions & 0 deletions Adamant/Helpers/MyLittlePinpad+adamant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ extension PinpadViewController {
return pinpad
}
}


158 changes: 158 additions & 0 deletions Adamant/Helpers/PinPadView.swift
Original file line number Diff line number Diff line change
@@ -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..<pinLength, id: \.self) { index in
Circle()
.frame(width: 15, height: 15)
.foregroundColor(index < enteredPin.count ? .white : .gray)
}
}
.padding(.vertical, 20)
Spacer()
.frame(height: 20)
VStack(alignment: .center) {
row(from: 1, to: 3)
row(from: 4, to: 6)
row(from: 7, to: 9)
row(from: 0, to: 0, showsDeleteButton: true)
}
Spacer()
Button(String.adamant.alert.cancel) {
onCancel()
isPinpadVisible = false
}
.foregroundColor(.white)
.padding(.top, 20)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.edgesIgnoringSafeArea(.all))
}

private func row(from: Int, to: Int, showsDeleteButton: Bool = false) -> 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
2 changes: 1 addition & 1 deletion Adamant/Modules/ChatsList/ChatListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ extension ChatListViewController {
}
}

private extension State {
private extension DataProviderState {
var isUpdating: Bool {
switch self {
case .updating: true
Expand Down
10 changes: 5 additions & 5 deletions Adamant/ServiceProtocols/DataProviders/DataProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@
import Foundation
import CommonKit

enum State {
enum DataProviderState {
case empty
case updating
case upToDate
case failedToUpdate(Error)
}

protocol DataProvider: AnyObject, Actor {
var state: State { get }
var stateObserver: AnyObservable<State> { get }
var state: DataProviderState { get }
var stateObserver: AnyObservable<DataProviderState> { get }
var isInitiallySynced: Bool { get }

func reload() async
func reset()
}

// 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
Expand Down
8 changes: 4 additions & 4 deletions Adamant/Services/DataProviders/AdamantChatsProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ actor AdamantChatsProvider: ChatsProvider {
let stack: CoreDataStack

// MARK: Properties
@ObservableValue private var stateNotifier: State = .empty
var stateObserver: AnyObservable<State> { $stateNotifier.eraseToAnyPublisher() }
@ObservableValue private var stateNotifier: DataProviderState = .empty
var stateObserver: AnyObservable<DataProviderState> { $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
Expand Down Expand Up @@ -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 }
Expand Down
6 changes: 3 additions & 3 deletions Adamant/Services/DataProviders/AdamantTransfersProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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> { $state.eraseToAnyPublisher() }
@ObservableValue private(set) var state: DataProviderState = .empty
var stateObserver: AnyObservable<DataProviderState> { $state.eraseToAnyPublisher() }
private(set) var isInitiallySynced: Bool = false
private(set) var receivedLastHeight: Int64?
private(set) var readedLastHeight: Int64?
Expand All @@ -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 {
Expand Down