Create password attempt when submitting
This commit is contained in:
parent
54b1cc1884
commit
f766e0893a
7 changed files with 183 additions and 47 deletions
|
@ -12,6 +12,7 @@ import SwiftData
|
|||
class PasswordManager {
|
||||
|
||||
let passwordRepository: PasswordRepository
|
||||
let passwordAttemptRepository: PasswordAttemptRepository
|
||||
let passwordKeychainRepository: PasswordKeychainRepository
|
||||
|
||||
private let context: ModelContext
|
||||
|
@ -19,10 +20,12 @@ class PasswordManager {
|
|||
init(
|
||||
context: ModelContext,
|
||||
passwordRepository: PasswordRepository,
|
||||
passwordAttemptRepository: PasswordAttemptRepository,
|
||||
passwordKeychainRepository: PasswordKeychainRepository
|
||||
) {
|
||||
self.context = context
|
||||
self.passwordRepository = passwordRepository
|
||||
self.passwordAttemptRepository = passwordAttemptRepository
|
||||
self.passwordKeychainRepository = passwordKeychainRepository
|
||||
}
|
||||
|
||||
|
@ -40,6 +43,10 @@ class PasswordManager {
|
|||
return passwordKeychainRepository.getPassword(withID: id)
|
||||
}
|
||||
|
||||
func getPasswordAttempts(passwordID id: UUID) -> [PasswordAttempt] {
|
||||
return passwordAttemptRepository.getPasswordAttempts(passwordID: id)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func createPassword(name: String, value: String) -> Password {
|
||||
|
||||
|
@ -65,6 +72,16 @@ class PasswordManager {
|
|||
return password
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func createPasswordAttempt(passwordID: UUID, isSuccessful: Bool, typingTime: Double) {
|
||||
let attempt = PasswordAttempt(
|
||||
password: passwordID,
|
||||
isSuccessful: isSuccessful,
|
||||
typingTime: typingTime
|
||||
)
|
||||
passwordAttemptRepository.createPasswordAttempt(attempt: attempt)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func deletePassword(_ password: Password) -> Bool {
|
||||
|
||||
|
@ -76,6 +93,7 @@ class PasswordManager {
|
|||
|
||||
try context.transaction {
|
||||
passwordRepository.deletePassword(password)
|
||||
try passwordAttemptRepository.deleteAllAttempts(withID: password.id)
|
||||
try passwordKeychainRepository.deletePassword(withID: password.id)
|
||||
}
|
||||
|
||||
|
|
54
password/data/repository/PasswordAttemptRepository.swift
Normal file
54
password/data/repository/PasswordAttemptRepository.swift
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// PasswordAttemptRepository.swift
|
||||
// password
|
||||
//
|
||||
// Created by Markus Thielker on 17.01.25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUICore
|
||||
import SwiftData
|
||||
|
||||
class PasswordAttemptRepository {
|
||||
|
||||
private let context: ModelContext
|
||||
|
||||
init(_ context: ModelContext) {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
func getPasswordAttempts(passwordID id: UUID) -> [PasswordAttempt] {
|
||||
|
||||
print("fetching attempts for password \(id)")
|
||||
|
||||
var attempts: [PasswordAttempt] = []
|
||||
do {
|
||||
|
||||
let request = FetchDescriptor<PasswordAttempt>(
|
||||
predicate: #Predicate { $0.password == id },
|
||||
sortBy: [SortDescriptor(\.timestamp)]
|
||||
)
|
||||
attempts = try context.fetch(request)
|
||||
|
||||
print("found \(attempts.count) attempts for password \(id)")
|
||||
|
||||
} catch {
|
||||
print("fetching attempts failed: \(error)")
|
||||
}
|
||||
|
||||
return attempts
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func createPasswordAttempt(attempt: PasswordAttempt) {
|
||||
context.insert(attempt)
|
||||
print("inserted attempt")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func deleteAllAttempts(withID id: UUID) throws {
|
||||
let predicate = #Predicate<PasswordAttempt> { $0.password == id }
|
||||
try context.delete(model: PasswordAttempt.self, where: predicate)
|
||||
print("deleted all attempts for password \(id)")
|
||||
}
|
||||
}
|
|
@ -9,10 +9,6 @@ import Foundation
|
|||
import SwiftUICore
|
||||
import SwiftData
|
||||
|
||||
enum PasswordRepositoryError: Error {
|
||||
case notFound
|
||||
}
|
||||
|
||||
class PasswordRepository {
|
||||
|
||||
private let context: ModelContext
|
||||
|
|
|
@ -9,47 +9,61 @@ import SwiftUI
|
|||
|
||||
struct DetailView: View {
|
||||
|
||||
let password: Password
|
||||
let passwordKC: PasswordKC
|
||||
@ObservedObject var viewModel: DetailViewModel
|
||||
|
||||
@State var value: String = ""
|
||||
|
||||
func validateInput(input: String, password: Password) -> Bool {
|
||||
return input == passwordKC.value
|
||||
@State private var startTime: Date?
|
||||
@State private var elapsedTime: Double = -1
|
||||
@State private var value: String = ""
|
||||
|
||||
init(viewModel: DetailViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
|
||||
func validateInput(input: String, password: Password) -> Bool {
|
||||
return input == viewModel.passwordKC.value
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Enter the password for \(password.name)")
|
||||
Text("Enter the password for \(viewModel.password.name) and submit with \"Enter\"")
|
||||
Form {
|
||||
SecureField("", text: $value)
|
||||
Button("Submit") {
|
||||
let correct = validateInput(input: value, password: password)
|
||||
|
||||
if (correct) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Correct"
|
||||
alert.informativeText = "That one was correct!"
|
||||
alert.addButton(withTitle: "Let's go!")
|
||||
alert.runModal()
|
||||
} else {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Not quite"
|
||||
alert.informativeText = " That one was not quite right! Try again!"
|
||||
alert.addButton(withTitle: "Okay")
|
||||
alert.runModal()
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.onChange(of: value) { _, _ in
|
||||
if (value.isEmpty){
|
||||
startTime = nil
|
||||
} else if startTime == nil {
|
||||
startTime = Date()
|
||||
}
|
||||
}
|
||||
.onSubmit {
|
||||
if let startTime = startTime {
|
||||
elapsedTime = Date().timeIntervalSince(startTime)
|
||||
}
|
||||
|
||||
let isSuccessful = validateInput(input: value, password: viewModel.password)
|
||||
viewModel.createPasswordAttempt(isSuccessful: isSuccessful, typingTime: elapsedTime)
|
||||
|
||||
if isSuccessful {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Correct"
|
||||
alert.informativeText = "That one was correct!"
|
||||
alert.addButton(withTitle: "Let's go!")
|
||||
alert.runModal()
|
||||
} else {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Not quite"
|
||||
alert.informativeText = " That one was not quite right! Try again!"
|
||||
alert.addButton(withTitle: "Okay")
|
||||
alert.runModal()
|
||||
}
|
||||
|
||||
value = ""
|
||||
startTime = nil
|
||||
elapsedTime = -1
|
||||
}
|
||||
|
||||
value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let password = Password(name: "macbook")
|
||||
let passwordKC = PasswordKC(id: password.id, value: "admin")
|
||||
DetailView(password: password, passwordKC: passwordKC)
|
||||
}
|
||||
|
|
58
password/view/detail/DetailViewModel.swift
Normal file
58
password/view/detail/DetailViewModel.swift
Normal file
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// ListViewModel.swift
|
||||
// password
|
||||
//
|
||||
// Created by Markus Thielker on 16.01.25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
class DetailViewModel: ObservableObject {
|
||||
|
||||
let passwordID: UUID
|
||||
|
||||
private let context: ModelContext
|
||||
private let passwordManager: PasswordManager
|
||||
|
||||
@Published var password: Password
|
||||
@Published var passwordKC: PasswordKC
|
||||
@Published var passwordAttempts: [PasswordAttempt]
|
||||
|
||||
@MainActor
|
||||
init(context: ModelContext, passwordID id: UUID) {
|
||||
self.context = context
|
||||
self.passwordID = id
|
||||
|
||||
let passwordRepository = PasswordRepository(context)
|
||||
let passwordAttemptRepository = PasswordAttemptRepository(context)
|
||||
let passwordKeychainRepository = PasswordKeychainRepository()
|
||||
|
||||
self.passwordManager = PasswordManager(
|
||||
context: context,
|
||||
passwordRepository: passwordRepository,
|
||||
passwordAttemptRepository: passwordAttemptRepository,
|
||||
passwordKeychainRepository: passwordKeychainRepository
|
||||
)
|
||||
|
||||
password = passwordManager.getPassword(withID: id)!
|
||||
passwordKC = passwordManager.getPasswordKeychain(withID: id)!
|
||||
passwordAttempts = passwordManager.getPasswordAttempts(passwordID: id)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func getPassword(withID id: UUID) -> Password? {
|
||||
return passwordManager.getPassword(withID: id)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func getPasswordKeychain(withID id: UUID) -> PasswordKC? {
|
||||
return passwordManager.getPasswordKeychain(withID: id)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func createPasswordAttempt(isSuccessful: Bool, typingTime: Double) {
|
||||
passwordManager.createPasswordAttempt(passwordID: password.id, isSuccessful: isSuccessful, typingTime: typingTime)
|
||||
passwordAttempts = passwordManager.getPasswordAttempts(passwordID: password.id)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import _SwiftData_SwiftUI
|
|||
|
||||
struct ListView: View {
|
||||
|
||||
@Environment(\.modelContext) var context
|
||||
|
||||
@ObservedObject var viewModel: ListViewModel
|
||||
@State var isAddingPassword: Bool = false
|
||||
|
||||
|
@ -17,10 +19,7 @@ struct ListView: View {
|
|||
NavigationView {
|
||||
List {
|
||||
ForEach(viewModel.passwords) { password in
|
||||
NavigationLink(destination: DetailView(
|
||||
password: password,
|
||||
passwordKC: viewModel.getPasswordKeychain(withID: password.id)!
|
||||
)) {
|
||||
NavigationLink(destination: DetailView(viewModel: DetailViewModel(context: context, passwordID: password.id))) {
|
||||
Text(password.name)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +31,7 @@ struct ListView: View {
|
|||
.frame(width: 20, height: 20)
|
||||
}
|
||||
Button(action: {
|
||||
viewModel.getAllPasswords()
|
||||
viewModel.passwords = viewModel.getAllPasswords()
|
||||
}) {
|
||||
Image(systemName: "arrow.trianglehead.clockwise")
|
||||
.imageScale(.medium)
|
||||
|
@ -45,7 +44,7 @@ struct ListView: View {
|
|||
AddPasswordView(viewModel: viewModel)
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getAllPasswords()
|
||||
viewModel.passwords = viewModel.getAllPasswords()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ class ListViewModel: ObservableObject {
|
|||
self.context = context
|
||||
|
||||
let passwordRepository = PasswordRepository(context)
|
||||
let passwordAttemptRepository = PasswordAttemptRepository(context)
|
||||
let passwordKeychainRepository = PasswordKeychainRepository()
|
||||
|
||||
self.passwordManager = PasswordManager(
|
||||
context: context,
|
||||
passwordRepository: passwordRepository,
|
||||
passwordAttemptRepository: passwordAttemptRepository,
|
||||
passwordKeychainRepository: passwordKeychainRepository
|
||||
)
|
||||
|
||||
passwords = getAllPasswords()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -55,8 +55,5 @@ class ListViewModel: ObservableObject {
|
|||
@MainActor
|
||||
func deletePassword(_ password: Password) {
|
||||
let success = passwordManager.deletePassword(password)
|
||||
if success {
|
||||
passwords = getAllPasswords()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue