From fb85f6014a3014b85d2bf662de2bf1e80de02cf3 Mon Sep 17 00:00:00 2001 From: FreeDeveloper97 Date: Tue, 1 Oct 2024 17:40:50 +0900 Subject: [PATCH] =?UTF-8?q?feat=20#165=20=EC=9D=B8=EC=A6=9D=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20timer=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EA=B5=AC=ED=98=84,=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=AC=EC=A0=84=EC=86=A1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Signup/Email/SignupEmailModel.swift | 75 ++++++++++++++----- .../Signup/Email/SignupEmailView.swift | 31 +++++--- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/Project_Timer/Present/Signup/Email/SignupEmailModel.swift b/Project_Timer/Present/Signup/Email/SignupEmailModel.swift index 9f2160ab..c14acf79 100644 --- a/Project_Timer/Present/Signup/Email/SignupEmailModel.swift +++ b/Project_Timer/Present/Signup/Email/SignupEmailModel.swift @@ -12,6 +12,34 @@ import SwiftUI // MARK: State class SignupEmailModel: ObservableObject { + + // MARK: State + + @Published var contentWidth: CGFloat = .zero + @Published var focus: TTSignupTextFieldView.type? + @Published var isWarningEmail: Bool = false + @Published var validVerificationCode: Bool? + @Published var getVerificationSuccess: Bool = false + @Published var stage: Stage = .email + + @Published var email: String = "" + @Published var authCode: String = "" + @Published var authCodeRemainSeconds: Int? // authCode 만료까지 남은 초 + + // MARK: Action + + enum Action { + case resendAuthCode + } + public func action(_ action: Action) { + switch action { + case .resendAuthCode: + self.postAuthCode() + } + } + + // MARK: Properties + enum Stage { case email case verificationCode @@ -43,16 +71,9 @@ class SignupEmailModel: ObservableObject { } } } - @Published var contentWidth: CGFloat = .zero - @Published var focus: TTSignupTextFieldView.type? - @Published var isWarningEmail: Bool = false - @Published var validVerificationCode: Bool? - @Published var getVerificationSuccess: Bool = false - @Published var stage: Stage = .email - - @Published var email: String = "" - @Published var verificationCode: String = "" - private var verificationKey = "" + private var postAuthCodeTerminateDate: Date? // authCode 만료 시점 + private var authKey: String? // authCode 전송시 서버로부터 받은 5분간 유효한 authKey + private var timer: Timer? // authCode 전송 후 남은 시간, 1초간 업데이트 private let getUsernameNotExistUseCase: GetUsernameNotExistUseCase private let postAuthCodeUseCase: PostAuthCodeUseCase @@ -84,7 +105,7 @@ class SignupEmailModel: ObservableObject { // verificationCodeTextField underline 컬러 var authCodeTintColor: Color { - if validVerificationCode == false && verificationCode.isEmpty { + if validVerificationCode == false && authCode.isEmpty { return Colors.wrongTextField.toColor } else { return focus == .verificationCode ? Color.blue : UIColor.placeholderText.toColor @@ -98,7 +119,7 @@ class SignupEmailModel: ObservableObject { venderInfo: self.infos.venderInfo, emailInfo: SignupEmailInfo( email: self.email, - verificationKey: self.verificationCode) + verificationKey: self.authCode) ) } @@ -109,7 +130,7 @@ class SignupEmailModel: ObservableObject { venderInfo: self.infos.venderInfo, emailInfo: SignupEmailInfo( email: self.email, - verificationKey: self.verificationCode), + verificationKey: self.authCode), passwordInfo: nil ) } @@ -168,8 +189,9 @@ extension SignupEmailModel { } } - /// 인증코드 전송 + /// 인증코드 전송, 전송 시점 저장 및 authKey 수신 후 저장 private func postAuthCode() { + self.postAuthCodeTerminateDate = Calendar.current.date(byAdding: .minute, value: 1, to: Date()) self.postAuthCodeUseCase.execute(type: .signup(email: self.email)) .sink { [weak self] completion in guard case .failure(let networkError) = completion else { return } @@ -177,18 +199,35 @@ extension SignupEmailModel { self?.handleCheckEmailError(networkError) } receiveValue: { [weak self] postAuthCodeInfo in print("authKey: \(postAuthCodeInfo.authKey)") + self?.authKey = postAuthCodeInfo.authKey self?.resetVerificationCode() + self?.runTimer() } .store(in: &self.cancellables) } + // 인증코드 유효시간 표시 timer 동작 + func runTimer() { + DispatchQueue.main.async { [weak self] in + self?.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [weak self] timer in + guard let postAuthCodeTerminateDate = self?.postAuthCodeTerminateDate else { return } + let remainSeconds = Int(postAuthCodeTerminateDate.timeIntervalSinceNow) + if remainSeconds <= 0 { + self?.timer?.invalidate() + self?.timer = nil + } + self?.authCodeRemainSeconds = remainSeconds + }) + } + } + // 인증코드 done 액션 func checkVerificationCode() { - validVerificationCode = verificationCode.count > 7 + validVerificationCode = authCode.count > 7 // stage 변화 -> @StateFocus 반영 if validVerificationCode == true { // verificationKey 수신 필요 - verificationKey = "abcd1234" + authCode = "abcd1234" getVerificationSuccess = true } else { resetVerificationCode() @@ -201,7 +240,9 @@ extension SignupEmailModel { } private func resetVerificationCode() { - self.verificationCode = "" + self.authCode = "" + self.timer?.invalidate() + self.timer = nil self.stage = .verificationCode } } diff --git a/Project_Timer/Present/Signup/Email/SignupEmailView.swift b/Project_Timer/Present/Signup/Email/SignupEmailView.swift index 6026b736..cce91e19 100644 --- a/Project_Timer/Present/Signup/Email/SignupEmailView.swift +++ b/Project_Timer/Present/Signup/Email/SignupEmailView.swift @@ -149,28 +149,37 @@ struct SignupEmailView: View { .frame(height: 35) HStack(alignment: .center, spacing: 16) { - TTSignupTextFieldView(type: .verificationCode, keyboardType: .alphabet, text: $model.verificationCode, focus: $focus) { + TTSignupTextFieldView(type: .verificationCode, keyboardType: .alphabet, text: $model.authCode, focus: $focus) { model.checkVerificationCode() } .frame(maxWidth: .infinity) - // MARK: Timer 구현 필요 - Text("4 : 59") + + Text(remainTime(remainSeconds: model.authCodeRemainSeconds)) .font(Fonts.HGGGothicssiP40g(size: 18)) - // MARK: 재전송 구현 필요 - Button { - // MARK: ViewModel 내에서 네트워킹이 필요한 부분 - print("resend") - } label: { - Text(Localized.string(.SignUp_Button_Resend)) - .font(Typographys.font(.normal_3, size: 18)) + .monospacedDigit() + + if model.authCodeRemainSeconds == 0 { + Button { + model.action(.resendAuthCode) + } label: { + Text(Localized.string(.SignUp_Button_Resend)) + .font(Typographys.font(.normal_3, size: 18)) + } } } TTSignupTextFieldUnderlineView(color: model.authCodeTintColor) - TTSignupTextFieldWarning(warning: Localized.string(.SignUp_Error_WrongCode), visible: model.validVerificationCode == false && model.verificationCode.isEmpty) + TTSignupTextFieldWarning(warning: Localized.string(.SignUp_Error_WrongCode), visible: model.validVerificationCode == false && model.authCode.isEmpty) .id(TTSignupTextFieldView.type.verificationCode) } } + + func remainTime(remainSeconds: Int?) -> String { + guard let remainSeconds else { return "0:00" } + let minutes = Int(remainSeconds) / 60 + let seconds = Int(remainSeconds) % 60 + return String(format: "%d:%02d", minutes, seconds) + } } }