diff --git a/Contributors.md b/Contributors.md index a8d29d2..a97940e 100644 --- a/Contributors.md +++ b/Contributors.md @@ -5,3 +5,4 @@ - [Zev Eisenberg](https://github.com/ZevEisenberg) - [Jay Wren](https://github.com/jrwren) - [Amzd](https://github.com/amzd) +- [lambdaclan](https://github.com/lambdaclan) diff --git a/Tests/Demo.swift b/Tests/Demo.swift index 212532a..4cca82c 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -55,6 +55,13 @@ struct Demo: App { .closeShortcut() .defaultSize(width: 400, height: 250) .title("Form Demo") + Window(id: "password-checker-demo", open: 0) { _ in + PasswordCheckerDemo.WindowContent() + } + .closeShortcut() + .defaultSize(width: 400, height: 250) + .title("Password Checker Demo") + Window(id: "navigation", open: 0) { _ in NavigationViewDemo.WindowContent() } diff --git a/Tests/Page.swift b/Tests/Page.swift index 04d5a7f..f05b99a 100644 --- a/Tests/Page.swift +++ b/Tests/Page.swift @@ -25,6 +25,7 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible case carousel case viewSwitcher case form + case passwordChecker case popover case flowBox case navigationView @@ -45,6 +46,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible return "Navigation View" case .alertDialog: return "Alert Dialog" + case .passwordChecker: + return "Password Checker" default: return rawValue.capitalized } @@ -87,6 +90,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible return "Switch the window's view" case .form: return "Group controls used for data entry" + case .passwordChecker: + return "Check the validity of a password" case .popover: return "Present content in a bubble-like context popup" case .flowBox: @@ -130,6 +135,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible ViewSwitcherDemo(app: app) case .form: FormDemo(app: app) + case .passwordChecker: + PasswordCheckerDemo(app: app) case .popover: PopoverDemo() case .flowBox: diff --git a/Tests/PasswordCheckerDemo.swift b/Tests/PasswordCheckerDemo.swift new file mode 100644 index 0000000..2b2a1a5 --- /dev/null +++ b/Tests/PasswordCheckerDemo.swift @@ -0,0 +1,191 @@ +// +// PasswordCheckerDemo.swift +// Adwaita +// +// Created by lambdaclan on 13.06.24. +// +// Adjusted by david-swift on 18.06.24. + +// swiftlint:disable missing_docs no_magic_numbers + +import Adwaita + +enum PasswordChecker: String, CaseIterable, CustomStringConvertible, Identifiable { + case length + case upper + case lower + case special + case numeric + + var id: String { + self.rawValue + } + + var label: String { + switch self { + case .length: + return "Password Length" + case .upper: + return "Password Uppercase Characters" + case .lower: + return "Password Lowercase Characters" + case .special: + return "Password Special Characters" + case .numeric: + return "Password Numeric Characters" + } + } + + var description: String { + switch self { + case .length: + return "Password needs to be greater than 8 characters long" + case .upper: + return "Password needs to contain at least one uppercase character" + case .lower: + return "Password needs to contain at least one lowercase character" + case .special: + return "Password needs to contain at least one special character `!&^%$#@()/`" + case .numeric: + return "Password needs to contain at least one numeric character" + } + } +} + +struct PasswordCheckerDemo: View { + + var app: GTUIApp + + var view: Body { + VStack { + Button("View Demo") { + app.showWindow("password-checker-demo") + } + .suggested() + .pill() + .frame(maxWidth: 100) + } + } + + struct WindowContent: View { + + @State private var password = "" + @State private var copied: Signal = .init() + + var checkStatus: [String: Bool] { + PasswordChecker.allCases.enumerated().reduce([String: Bool]()) { dict, checker in + var dict = dict + dict[checker.element.rawValue] = check(password: password, checker: checker.element).1 + return dict + } + } + + var view: Body { + VStack { + FormSection("") { + Form { + EntryRow("Password", text: $password) + .suffix { + Button(icon: .default(icon: .editCopy)) { + State.copy(password) + copied.signal() + } + .flat() + .verticalCenter() + .tooltip("Copy") + Button(icon: .default(icon: .editClear)) { + password = "" + } + .flat() + .verticalCenter() + .tooltip("Clear") + } + } + } + .padding() + ForEach(PasswordChecker.allCases) { checker in + CheckerButton( + isValid: .constant(checkStatus[checker.rawValue] ?? false), + checkerName: checker.label, + checkerInfo: checker.description + ) + .padding() + } + } + .padding() + .toast("Copied to clipboard", signal: copied) + .frame(minWidth: 340, minHeight: 400) + .topToolbar { + HeaderBar.empty() + } + } + + private func check(password: String, checker: PasswordChecker) -> (String, Bool) { + switch checker { + case .length: + return (PasswordChecker.length.rawValue, password.count > 8) + case .upper: + let result = password.range(of: ".*[A-Z]+.*", options: .regularExpression) + return (PasswordChecker.upper.rawValue, result != nil ? true : false) + case .lower: + let result = password.range(of: ".*[a-z]+.*", options: .regularExpression) + return (PasswordChecker.lower.rawValue, result != nil ? true : false) + case .special: + let result = password.range(of: ".*[!&^%$#@()/]+.*", options: .regularExpression) + return (PasswordChecker.special.rawValue, result != nil ? true : false) + case .numeric: + let result = password.range(of: ".*[0-9]+.*", options: .regularExpression) + return (PasswordChecker.numeric.rawValue, result != nil ? true : false) + } + } + } + + private struct CheckerButton: View { + + @Binding var isValid: Bool + @State var isInfoVisible = false + + var checkerName: String + var checkerInfo: String + + var view: Body { + if isValid { + Button("") { + isInfoVisible = true + } + .child { + ButtonContent() + .iconName(Icon.DefaultIcon.emblemOk.string) + .label(checkerName) + .halign(.start) + } + .success() + .alertDialog( + visible: $isInfoVisible, + heading: checkerName, + body: checkerInfo + ) + .response("OK", role: .close) {} + } else { + Button("") { + isInfoVisible = true + } + .child { + ButtonContent() + .iconName(Icon.DefaultIcon.faceAngry.string) + .label(checkerName) + .halign(.start) + } + .destructive() + .alertDialog( + visible: $isInfoVisible, + heading: checkerName, + body: checkerInfo + ) + .response("OK", role: .close) {} + } + } + } +} + +// swiftlint:enable missing_docs no_magic_numbers