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

πŸ”€ :: (#357) νšŒμ›κ°€μž… ν™”λ©΄ λ””μžμΈνŒ¨ν„΄μ„ TCA둜 λ³€κ²½ν•©λ‹ˆλ‹€. #359

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions Application/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ let project = Project(
.Service.UserService,
.SPM.FirebaseMessaging,
.SPM.SemicolonDesign,
.SPM.ComposableArchitecture,
.target(name: "XquareWidget")
] + TargetDependency.universalDependencies,
settings: .settings(base: ["OTHER_LDFLAGS": "-ObjC"])
Expand Down
13 changes: 9 additions & 4 deletions Application/Sources/Application/AppDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import Foundation
import AuthService
import MealDataService
import UserService
import ComposableArchitecture

struct AppDependency {
let launchScreenView: LaunchScreenView
let signupView: SignupView
}

// swiftlint:disable function_body_length
Expand All @@ -28,8 +30,10 @@ extension AppDependency {
let loginViewModel = LoginViewModel(
signInUseCase: authServiceDependency.signinUseCase
)
let signupViewModel = SignupViewModel(
signupUseCase: authServiceDependency.signupUseCase
let signupStore = Store(
initialState: SignupState(),
reducer: SignupReducer.reducer,
environment: SignupEnvironment(signupUseCase: authServiceDependency.signupUseCase)
)
let launchScreenViewModel = LaunchScreenViewModel(
refreshTokenUseCase: authServiceDependency.refreshTokenUseCase
Expand Down Expand Up @@ -61,7 +65,7 @@ extension AppDependency {
mainView: mainView
)
let signupView = SignupView(
viewModel: signupViewModel,
store: signupStore,
loginView: loginView
)
let onboardingView = OnboardingView(
Expand All @@ -75,7 +79,8 @@ extension AppDependency {
)

return AppDependency(
launchScreenView: launchScreenView
launchScreenView: launchScreenView,
signupView: signupView
)

}
Expand Down
16 changes: 16 additions & 0 deletions Application/Sources/Scene/Signup/SignupAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

import AuthService
import ComposableArchitecture

enum SignupAction {
case signup
case signupIsSuccess(Result<Void, AuthServiceError>)
case checkPasswordIsEqual
case checkButtonDisabled
case binding(BindingAction<SignupState>)
case authCodeChanged(String)
case idChanged(String)
case passwordChanged(String)
case reEnterPasswordChange(String)
}
7 changes: 7 additions & 0 deletions Application/Sources/Scene/Signup/SignupEnvironment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

import AuthService

struct SignupEnvironment {
let signupUseCase: SignupUseCase
}
79 changes: 79 additions & 0 deletions Application/Sources/Scene/Signup/SignupReducer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Foundation

import AuthService
import ComposableArchitecture

struct SignupReducer {

static let reducer = AnyReducer<SignupState, SignupAction, SignupEnvironment> { state, action, environemnt in
switch action {
case .authCodeChanged(let authCode):
state.authCode = authCode
return .none
case .idChanged(let id):
state.id = id
return .none
case .passwordChanged(let password):
state.password = password
return .none
case .reEnterPasswordChange(let password):
state.reEnterPassword = password
return .none
case .signup:
return environemnt.signupUseCase.excute(data: .init(
authCode: state.authCode,
id: state.id,
profileImageUrl: nil,
password: state.password
))
.catchToEffect(SignupAction.signupIsSuccess)
.eraseToEffect()
case .signupIsSuccess(.success(let success)):
state.isSuccess = true
return .none
case .signupIsSuccess(.failure(let error)):
if error == .duplicateId {
state.isSuccess = false
state.idErrorMessage = "아이디가 μ€‘λ³΅λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
} else if error == .networkNotWorking {
state.isInternetNotworking = true
state.isSuccess = false
}
return .none
case .checkPasswordIsEqual:
state.passwordErrorMessage = isPasswordEqual(
state.password,
state.reEnterPassword
) ? "" : "λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."

return .none
case .checkButtonDisabled:
state.isDisable = !isPasswordEqual(
state.password,
state.reEnterPassword
) || !checkId(state.id) || !checkAuthCode(state.authCode) || !checkPassword(state.password)
return .none
case .binding:
return .none
}
}

private static func isPasswordEqual(_ password: String, _ reEnterPassword: String) -> Bool {
return password == reEnterPassword
}
private static func checkId(_ id: String) -> Bool {
let strRegEx = "[A-Za-z0-9]{6,20}"
let pred = NSPredicate(format: "SELF MATCHES %@", strRegEx)

return pred.evaluate(with: id)
}
private static func checkPassword(_ password: String) -> Bool {
let strRegEx = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{6,30}"
let pred = NSPredicate(format: "SELF MATCHES %@", strRegEx)

return pred.evaluate(with: password)
}
private static func checkAuthCode(_ authCode: String) -> Bool {
return authCode.count == 6
}
}
13 changes: 13 additions & 0 deletions Application/Sources/Scene/Signup/SignupState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

struct SignupState: Equatable {
var authCode: String = ""
var id: String = ""
var password: String = ""
var reEnterPassword: String = ""
var idErrorMessage: String = ""
var passwordErrorMessage: String = ""
var isSuccess: Bool = false
var isDisable: Bool = true
var isInternetNotworking: Bool = false
}
158 changes: 90 additions & 68 deletions Application/Sources/Scene/Signup/SignupView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,108 @@ import SwiftUI

import SemicolonDesign
import AuthService
import ComposableArchitecture

struct SignupView: View {

@StateObject var viewModel: SignupViewModel
let store: Store<SignupState, SignupAction>
var loginView: LoginView

var body: some View {
NavigationView {
ZStack {
ScrollView {
VStack(spacing: 20) {
Spacer()
.frame(height: 16)
SDTextField(
title: "μΈμ¦μ½”λ“œ",
placeholder: "6자리λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”",
text: $viewModel.authCode
)
.padding(.horizontal, 16)
.onChange(of: viewModel.authCode) { _ in
viewModel.checkSignup()
}
SDTextField(
title: "아이디",
placeholder: "영문, 숫자 6~20자",
text: $viewModel.id,
errorMessage: viewModel.idErrorMessage
)
.padding(.horizontal, 16)
.onChange(of: viewModel.id) { _ in
viewModel.checkSignup()
}
SDTextField(
title: "λΉ„λ°€λ²ˆν˜Έ",
placeholder: "숫자, 영문, 특수문자 μ‘°ν•© μ΅œμ†Œ 6자",
text: $viewModel.password,
isSecure: true
)
.padding(.horizontal, 16)
.onChange(of: viewModel.password) { _ in
viewModel.equalPasswordError()
viewModel.checkSignup()
WithViewStore(self.store) { viewStore in
NavigationView {
ZStack {
ScrollView {
VStack(spacing: 20) {
Spacer()
.frame(height: 16)
SDTextField(
title: "μΈμ¦μ½”λ“œ",
placeholder: "6자리λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”",
text: viewStore.binding(
get: \.authCode,
send: SignupAction.authCodeChanged
)
)
.padding(.horizontal, 16)
.onChange(of: viewStore.state.authCode) { _ in
viewStore.send(.checkButtonDisabled)
}
SDTextField(
title: "아이디",
placeholder: "영문, 숫자 6~20자",
text: viewStore.binding(
get: \.id,
send: SignupAction.idChanged
),
errorMessage: viewStore.state.idErrorMessage
)
.padding(.horizontal, 16)
.onChange(of: viewStore.state.id) { _ in
viewStore.send(.checkButtonDisabled)
}
SDTextField(
title: "λΉ„λ°€λ²ˆν˜Έ",
placeholder: "숫자, 영문, 특수문자 μ‘°ν•© μ΅œμ†Œ 6자",
text: viewStore.binding(
get: \.password,
send: SignupAction.passwordChanged
),
isSecure: true
)
.padding(.horizontal, 16)
.onChange(of: viewStore.state.password) { _ in
viewStore.send(.checkPasswordIsEqual)
viewStore.send(.checkButtonDisabled)
}
SDTextField(
title: "λΉ„λ°€λ²ˆν˜Έ μž¬μž…λ ₯",
placeholder: "μž¬μž…λ ₯",
text: viewStore.binding(
get: \.reEnterPassword,
send: SignupAction.reEnterPasswordChange
),
errorMessage: viewStore.state.passwordErrorMessage,
isSecure: true
)
.padding(.horizontal, 16)
.onChange(of: viewStore.state.reEnterPassword) { _ in
viewStore.send(.checkPasswordIsEqual)
viewStore.send(.checkButtonDisabled)
}
TermsCaptionView()
Spacer().frame(height: 64)
}
SDTextField(
title: "λΉ„λ°€λ²ˆν˜Έ μž¬μž…λ ₯",
placeholder: "μž¬μž…λ ₯",
text: $viewModel.reEnterPassword,
errorMessage: viewModel.passwordErrorMessage,
isSecure: true
}
VStack {
Spacer()
FillButton(
isDisabled: viewStore.binding(
get: \.isDisable,
send: SignupAction.checkButtonDisabled
),
text: "μž…λ ₯ μ™„λ£Œ",
action: { viewStore.send(.signup) },
type: .rounded
)
.padding(.horizontal, 16)
.onChange(of: viewModel.reEnterPassword) { _ in
viewModel.equalPasswordError()
viewModel.checkSignup()
.fullScreenCover(isPresented: viewStore.binding(
get: \.isSuccess,
send: SignupAction.signup
)) {
loginView
}
TermsCaptionView()
Spacer().frame(height: 64)
}
}
VStack {
Spacer()
FillButton(
isDisabled: $viewModel.isDisabled,
text: "μž…λ ₯ μ™„λ£Œ",
action: {
viewModel.signup()
},
type: .rounded
)
.fullScreenCover(isPresented: $viewModel.isSuccess) {
loginView
}
}
.sdErrorAlert(isPresented: viewStore.binding(
get: \.isInternetNotworking,
send: SignupAction.signup
), sdAlert: {
SDErrorAlert(errerMessage: "λ„€νŠΈμ›Œν¬κ°€ μ›ν• ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
})
.navigationTitle("νšŒμ›κ°€μž…")
.setNavigationBackButton()
}
.sdErrorAlert(isPresented: $viewModel.isInternetNotWorking, sdAlert: {
SDErrorAlert(errerMessage: "λ„€νŠΈμ›Œν¬κ°€ μ›ν• ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
})
.navigationTitle("νšŒμ›κ°€μž…")
.setNavigationBackButton()
.accentColor(.GrayScale.gray800)
}
.accentColor(.GrayScale.gray800)
}
}
Loading