diff --git a/SwiftUIBasics/Views/PlansView.swift b/SwiftUIBasics/Views/PlansView.swift index cb97876..4647418 100644 --- a/SwiftUIBasics/Views/PlansView.swift +++ b/SwiftUIBasics/Views/PlansView.swift @@ -1,18 +1,104 @@ // -// PlansView.swift -// SwiftUIBasics +// ContentView.swift +// ExercisesUI // -// Created by Diplomado on 09/12/23. +// Created by Eric Margay on 09/12/23. // import SwiftUI -struct PlansView: View { +struct ContentPlansView: View { var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack(alignment:.center){ + Text("Choose") + .font(.system(size: 32, weight: .black, design: .serif)) + .fontDesign(.rounded) + Text("Your Plan") + .font(.system(size: 32, weight: .black, design: .serif)) + .fontDesign(.rounded) + HStack { + CardView(iconName: "", title: "Basic", description: "Per month", price: "$9") + .fontWeight(.bold) + .foregroundStyle(.white) + .background(.purple) + .cornerRadius(15.0) + ZStack { + CardView(iconName: "", title: "Pro", description: "Per month", price: "$19") + .fontWeight(.bold) + .background(Color(white: 0.93)) + .cornerRadius(15.0) + BadgeView(text: "Best for designer", padding: 5) + .fontWeight(.bold) + .foregroundStyle(.white) + .background(.orange) + .padding(.top, 200) + } + } + + ZStack { + CardView(iconName: "sparkles", title: "Team", description: " Per month ", price: "299") + .fontWeight(.bold) + .background(Color(white: 0.3)) + .foregroundStyle(.white) + .cornerRadius(15.0) + BadgeView(text: "Perfect for teams with 20 memebers", padding: 5) + .fontWeight(.bold) + .foregroundStyle(.white) + .background(.orange) + .padding(.top, 250) + + } + } + + } } + #Preview { - PlansView() + ContentPlansView() +} + +struct CardView: View { + var iconName: String + var title: String + var description: String + var price: String + + var body: some View { + VStack(spacing: 10) { + Image(systemName: iconName) + .font(.system(size: 40)) + .padding(.top, 30) + .padding(.bottom, -15) + VStack(spacing: 10) { + Text(title) + .font(.system(.title, design: .rounded)) + .fontWeight(.black) + .padding(.horizontal) + .padding(.vertical) + .padding(.bottom, -25) + Text(price) + .fontWeight(.bold) + .font(.system(size: 40, design: .rounded)) + Text(description) + .padding(.horizontal,40) + .padding(.vertical) + .padding(.bottom) + } + } + } + +} + +struct BadgeView: View { + var text: String + var padding: CGFloat + + + var body: some View { + Text(text) + .padding(padding) + + } } diff --git a/SwiftUIBasics/Views/SignUpView.swift b/SwiftUIBasics/Views/SignUpView.swift index 4f8aeb9..ba6522c 100644 --- a/SwiftUIBasics/Views/SignUpView.swift +++ b/SwiftUIBasics/Views/SignUpView.swift @@ -6,40 +6,191 @@ // import SwiftUI +import Combine + +class SignUpViewModel: ObservableObject { + // inputs + @Published var email: String = "" + @Published var password: String = "" + @Published var passwordConfirm: String = "" + + // outputs + @Published var isValidUsernameLength: Bool = false + @Published var isValidPasswordLength: Bool = false + @Published var isValidPasswordUpperCase: Bool = false + @Published var isValidPasswordLowerCase: Bool = false + @Published var isValidPassWordOneSymbol: Bool = false + @Published var isValidPasswordOneNumber: Bool = false + @Published var isValidPasswordMatch: Bool = false + @Published var isValid: Bool = false + @Published var isValidPre: Bool = false + + private var cancelableSet: Set = [] + + init() { + $email + .receive(on: RunLoop.main) + .map { password in + let pattern = "[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*@[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[.][a-zA-Z]{2,5}" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidUsernameLength, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + return password.count >= 8 + } + .assign(to: \.isValidPasswordLength, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[A-Z]" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidPasswordUpperCase, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[a-z]" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidPasswordLowerCase, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[!\"#$%&'()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~]+" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidPassWordOneSymbol, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[0-9]" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidPasswordOneNumber, on: self) + .store(in: &cancelableSet) + + + + Publishers.CombineLatest($password, $passwordConfirm) + .receive(on: RunLoop.main) + .map { (password, passwordConfirm) in + return !password.isEmpty && !passwordConfirm.isEmpty && password == passwordConfirm + } + .assign(to: \.isValidPasswordMatch, on: self) + .store(in: &cancelableSet) + + Publishers.CombineLatest4($isValidPasswordLowerCase, $isValidPassWordOneSymbol, $isValidPasswordOneNumber, $isValidPre) + .map { (a, b, c, d) in + return a && b && c && d + } + .assign(to: \.isValidPre, on: self) + .store(in: &cancelableSet) + + Publishers.CombineLatest4($isValidUsernameLength, $isValidPasswordLength, $isValidPasswordUpperCase, + $isValidPasswordMatch) + .map { (a, b, c, d) in + return a && b && c && d + } + .assign(to: \.isValid, on: self) + .store(in: &cancelableSet) + + + } +} struct SignUpView: View { - @State var email: String = "" - @State var password: String = "" - @State var passwordConfirmation: String = "" - @State var terms: Bool = true + @ObservedObject var vm = SignUpViewModel() + var body: some View { - NavigationView { + VStack { + Text("Create an account") + .font(.system(.largeTitle, design: .rounded)) + .bold() + .foregroundStyle(.maryBlue) + .padding(.bottom, 30) + FormTextField(name: "Email", value: $vm.email) + .keyboardType(.emailAddress) + .autocapitalization(.none) + RequirementText(text: "Enter a valid email", isValid: vm.isValidUsernameLength) + .padding() + FormTextField(name: "Password", value: $vm.password, isSecure: true) VStack { - Form { - TextField(text: $email) { - Text("email") - } - .autocorrectionDisabled() - .textInputAutocapitalization(.never) - SecureField(text: $password) { - Text("Password") - } - .autocorrectionDisabled() - SecureField(text: $passwordConfirmation) { - Text("Password Confirmation") - } - .autocorrectionDisabled() - - Toggle("Accept Terms and conditions", isOn: $terms) - } - Button("Create Account") { - print("Create account") - } - .font(.system(size: 24)) - .buttonStyle(.borderedProminent) + RequirementText(text: "A minimum of 8 characters", isValid: vm.isValidPasswordLength) + RequirementText(text: "One uppercase letter", isValid: vm.isValidPasswordUpperCase) + RequirementText(text: "One lowercase letter", isValid: vm.isValidPasswordLowerCase) + RequirementText(text: "One symbol", isValid: vm.isValidPassWordOneSymbol) + RequirementText(text: "One number", isValid: vm.isValidPasswordOneNumber) } - .navigationTitle("Sign Up") + .padding() + FormTextField(name: "Confirm Password", value: $vm.passwordConfirm, isSecure: true) + RequirementText(text: "Your confirm password should be the same as password", isValid: vm.isValidPasswordMatch) + .padding() + .padding(.bottom, 50) + Button(action: { + print("Doing") + // Proceed to the next screen + }) { + Text("Sign Up") + .font(.system(.body, design: .rounded)) + .foregroundColor(.white) + .bold() + .padding() + .frame(minWidth: 0, maxWidth: .infinity) + .background(vm.isValid ? .maryBlue :.turquoise) + // .background(LinearGradient(gradient: Gradient(colors: [.turquoise, .maryBlue]), startPoint: .leading, endPoint: .trailing)) + .cornerRadius(10) + .padding(.horizontal) + } + .disabled(!vm.isValid) + + HStack { + Text("Already have an account?") + .font(.system(.body, design: .rounded)) + .bold() + Button(action: { + // Proceed to Sign in screen + }) { + Text("Sign in") + .font(.system(.body, design: .rounded)) + .bold() + .foregroundColor(.maryBlue) + } + }.padding(.top, 50) + Spacer() } + .padding() } }