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

merge :: 회원가입 - 인증 이메일 입력 Feature #96

Merged
merged 14 commits into from
Oct 27, 2022
Merged
3 changes: 3 additions & 0 deletions Projects/App/Sources/Application/DI/AppComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public extension AppComponent {
var signinComponent: SigninComponent {
SigninComponent(parent: self)
}
var signupEmailVerifyComponent: SignupEmailVerifyComponent {
SignupEmailVerifyComponent(parent: self)
}
}

// MARK: - Main
Expand Down
2 changes: 1 addition & 1 deletion Projects/App/Sources/Application/DMSApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct DMSApp: App {

var body: some Scene {
WindowGroup {
AppComponent().mainTabComponent.makeView()
AppComponent().signupEmailVerifyComponent.makeView()
}
}
}
20 changes: 20 additions & 0 deletions Projects/App/Sources/Application/NeedleGenerated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ private class SchoolCodeDependencyc0114744c1c8c7843672Provider: SchoolCodeDepend
private func factoryb65c1efbf06b87162473f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject {
return SchoolCodeDependencyc0114744c1c8c7843672Provider(appComponent: parent1(component) as! AppComponent)
}
private class SignupEmailVerifyDependencyf9d372ac752ee19b78caProvider: SignupEmailVerifyDependency {
var checkDuplicateEmailUseCase: any CheckDuplicateEmailUseCase {
return appComponent.checkDuplicateEmailUseCase
}
private let appComponent: AppComponent
init(appComponent: AppComponent) {
self.appComponent = appComponent
}
}
/// ^->AppComponent->SignupEmailVerifyComponent
private func factory3b1904c76335d70151ebf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject {
return SignupEmailVerifyDependencyf9d372ac752ee19b78caProvider(appComponent: parent1(component) as! AppComponent)
}
private class MainTabDependency2826cdb310ed0b17a725Provider: MainTabDependency {
var homeComponent: HomeComponent {
return appComponent.homeComponent
Expand Down Expand Up @@ -110,6 +123,7 @@ extension AppComponent: Registration {
localTable["schoolCodeComponent-SchoolCodeComponent"] = { self.schoolCodeComponent as Any }
localTable["findIDComponent-FindIDComponent"] = { self.findIDComponent as Any }
localTable["signinComponent-SigninComponent"] = { self.signinComponent as Any }
localTable["signupEmailVerifyComponent-SignupEmailVerifyComponent"] = { self.signupEmailVerifyComponent as Any }
localTable["mainTabComponent-MainTabComponent"] = { self.mainTabComponent as Any }
localTable["homeComponent-HomeComponent"] = { self.homeComponent as Any }
localTable["remoteStudentsDataSource-any RemoteStudentsDataSource"] = { self.remoteStudentsDataSource as Any }
Expand All @@ -136,6 +150,11 @@ extension SchoolCodeComponent: Registration {
keyPathToName[\SchoolCodeDependency.checkSchoolCodeUseCase] = "checkSchoolCodeUseCase-any CheckSchoolCodeUseCase"
}
}
extension SignupEmailVerifyComponent: Registration {
public func registerItems() {
keyPathToName[\SignupEmailVerifyDependency.checkDuplicateEmailUseCase] = "checkDuplicateEmailUseCase-any CheckDuplicateEmailUseCase"
}
}
extension MainTabComponent: Registration {
public func registerItems() {
keyPathToName[\MainTabDependency.homeComponent] = "homeComponent-HomeComponent"
Expand Down Expand Up @@ -175,6 +194,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi
private func register1() {
registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider)
registerProviderFactory("^->AppComponent->SchoolCodeComponent", factoryb65c1efbf06b87162473f47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->SignupEmailVerifyComponent", factory3b1904c76335d70151ebf47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->MainTabComponent", factory1ab5a747ddf21e1393f9f47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->HomeComponent", factory67229cdf0f755562b2b1f47b58f8f304c97af4d5)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI

public struct RootPresentationModeKey: EnvironmentKey {
public static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}

public extension EnvironmentValues {
var rootPresentationMode: Binding<RootPresentationMode> {
get { return self[RootPresentationModeKey.self] }
set { self[RootPresentationModeKey.self] = newValue }
}
}

public typealias RootPresentationMode = Bool

public extension RootPresentationMode {
mutating func dismiss() {
self.toggle()
}
}
4 changes: 2 additions & 2 deletions Projects/Features/SigninFeature/Sources/SigninView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ struct SigninView: View {
VStack(alignment: .leading, spacing: 8) {
Text("DMS")
.dmsFont(.title(.extraLarge), color: .PrimaryVariant.primary)
.padding(.top, 28)

Text("더 편한 기숙사 생활을 위해")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}

Spacer()
}
.padding(.top, 24)

VStack(spacing: 72) {
DMSFloatingTextField(
Expand Down Expand Up @@ -96,7 +96,7 @@ struct SigninView: View {
DMSWideButton(text: "로그인", color: .PrimaryVariant.primary) {
viewModel.signinButtonDidTap()
}
.disabled(!viewModel.isSigninButtonEnabled)
.disabled(!viewModel.isSigninEnabled)
.padding(.top, 24)
.frame(maxWidth: .infinity)
.padding(.bottom, 40)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class SigninViewModel: BaseViewModel {
@Published var isOnAutoSignin = true
@Published var isNavigateSignup = false
@Published var isSuccessSignin = false
var isSigninButtonEnabled: Bool {
var isSigninEnabled: Bool {
!id.isEmpty && !password.isEmpty
}

Expand All @@ -19,7 +19,7 @@ final class SigninViewModel: BaseViewModel {
}

func signinButtonDidTap() {
guard isSigninButtonEnabled else { return }
guard isSigninEnabled else { return }
addCancellable(signinUseCase.execute(req: .init(accountID: id, password: password))) { [weak self] _ in
self?.isSuccessSignin = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.to(beEmpty())
expect { sut.password }.to(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 ID만 입력했다면") {
Expand All @@ -30,7 +30,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.toNot(beEmpty())
expect { sut.password }.to(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 Password만 입력했다면") {
Expand All @@ -40,7 +40,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.to(beEmpty())
expect { sut.password }.toNot(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 ID와 Password를 모두 입력했다면") {
Expand All @@ -51,7 +51,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 true이다.") {
expect { sut.id }.toNot(beEmpty())
expect { sut.password }.toNot(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beTrue())
expect { sut.isSigninEnabled }.to(beTrue())
}
}
context("유저가 ID와 Password를 알맞게 입력하고 로그인을 시도한다면") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import DomainModule
import SwiftUI
import NeedleFoundation

public protocol SignupEmailVerifyDependency: Dependency {
var checkDuplicateEmailUseCase: any CheckDuplicateEmailUseCase { get }
}

public final class SignupEmailVerifyComponent: Component<SignupEmailVerifyDependency> {
public func makeView() -> some View {
SignupEmailVerifyView(
viewModel: .init(
checkDuplicateEmailUseCase: dependency.checkDuplicateEmailUseCase
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import DesignSystem
import SwiftUI

struct SignupEmailVerifyView: View {
@StateObject var viewModel: SignupEmailVerifyViewModel
@Environment(\.rootPresentationMode) var rootPresentationMode
@Environment(\.dismiss) var dismiss

public init(viewModel: SignupEmailVerifyViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
}

var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 8) {
Text("DMS")
.dmsFont(.title(.extraLarge), color: .PrimaryVariant.primary)

Text("이메일 주소 입력")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}

Spacer()
}
.padding(.top, 24)

DMSFloatingTextField(
"이메일 주소",
text: $viewModel.email,
isError: viewModel.isErrorOcuured,
errorMessage: viewModel.errorMessage
) {
viewModel.sendButtonDidTap()
}
.padding(.top, 68)

Spacer()

HStack(spacing: 6) {
Text("이미 계정이 있으신가요?")
.dmsFont(.text(.small), color: .GrayScale.gray5)

DMSButton(text: "로그인", style: .text, color: .GrayScale.gray6) {
rootPresentationMode.wrappedValue.toggle()
}
}

DMSWideButton(text: "인증코드 발송", color: .PrimaryVariant.primary) {
viewModel.sendButtonDidTap()
}
.disabled(!viewModel.isSendEnabled)
.padding(.top, 24)
.padding(.bottom, 40)
}
.dmsBackButton(dismiss: dismiss)
.padding(.horizontal, 24)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import BaseFeature
import Combine
import DomainModule
import Utility

final class SignupEmailVerifyViewModel: BaseViewModel {
@Published var email = "" {
didSet { isErrorOcuured = false }
}
var isSendEnabled: Bool {
!email.isEmpty
}

private let checkDuplicateEmailUseCase: any CheckDuplicateEmailUseCase

public init(
checkDuplicateEmailUseCase: any CheckDuplicateEmailUseCase
) {
self.checkDuplicateEmailUseCase = checkDuplicateEmailUseCase
}

func sendButtonDidTap() {
let emailExpression = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"
guard
email ~= emailExpression,
isSendEnabled
else {
isErrorOcuured = true
errorMessage = "올바른 이메일 형식이 아닙니다."
return
}
addCancellable(checkDuplicateEmailUseCase.execute(email: email)) { _ in }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ struct SchoolCodeView: View {
Text("학교 인증코드 입력")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}
.padding(.top, 28)

Spacer()
}
.padding(.top, 24)
.padding(.horizontal, 24)

Spacer()
Expand Down
26 changes: 26 additions & 0 deletions Projects/Modules/Utility/Sources/RegexUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

public extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
}

public extension NSRegularExpression {
func matches(_ string: String) -> Bool {
let range = NSRange(location: 0, length: string.count)
return firstMatch(in: string, options: [], range: range) != nil
}
}

public extension String {
static func ~= (lhs: String, rhs: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: rhs) else { return false }
let range = NSRange(location: 0, length: lhs.utf16.count)
return regex.firstMatch(in: lhs, options: [], range: range) != nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ public struct DMSFloatingTextField: View {
var errorMessage: String
var onCommit: () -> Void
@FocusState var isFocused: Bool
@Namespace var animation
private var isFloaintg: Bool {
isFocused || !text.isEmpty
}
Expand Down