Skip to content

Commit

Permalink
Merge pull request #73 from Nexters/fix/#72-QA-UpdateProfile
Browse files Browse the repository at this point in the history
Fix/#72 QA - 프로필 업데이트 이슈 1차 해결
  • Loading branch information
longlivedrgn authored Aug 29, 2024
2 parents 95750bf + 2001c57 commit ae9e685
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 155 deletions.
2 changes: 1 addition & 1 deletion Projects/Data/Remote/Sources/Service/UserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension UserService: DependencyKey {
let response = await NetworkProvider.shared.sendRequest(endPoint, interceptor: interceptor)

if case .failure(let failure) = response {
throw failure
throw UserClientError.duplicateNickName
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,43 @@ import ComposableArchitecture

@Reducer
public struct UpdateProfileFeature: Reducer {

public init() {}

@ObservableState
public struct State: Equatable {
var selectedPiece: Character = .rabbit
var selectedCharacter: Character = .rabbit
var nickName: String = ""
var initialNickName: String = ""
var initialCharacter: Character = .rabbit

var noticeMessage: String? = "1~6자, 한글, 영문 또는 숫자를 입력하세요."
var isValidNickName: Bool = true
var isAllCompleted: Bool = false // 모든 Input이 알맞게 들어갔는 지 확인
var isUpdateSucceed: Bool = false
var isCheckingProfile: Bool = true // 앱 초기 진입 시 API 호출

public init() {}
}

public enum Action: BindableAction {
case binding(BindingAction<State>)

case pieceImageTapped(Character)
case saveButtonTapped
case backButtonTapped

case onAppear

case checkProfileResponse(Result<UserProfile, Error>)
case updateProfileResponse(Result<Void, Error>)

public enum Delegate {
case didUpdateProfileSucceed
}

case delegate(Delegate)
}

@Dependency(\.dismiss) var dismiss
@Dependency(UserClient.self) var userClient
@Dependency(UserService.self) var userService
Expand All @@ -64,47 +72,55 @@ public struct UpdateProfileFeature: Reducer {
case .binding(\.nickName):
state.noticeMessage = "1~6자, 한글, 영문 또는 숫자를 입력하세요."
state.isValidNickName = self.validate(state.nickName)
state.isAllCompleted = state.isValidNickName && !state.nickName.isEmpty
state.isAllCompleted = self.isAvailableToSave(with: &state)
return .none
case .pieceImageTapped(let piece):
state.selectedPiece = piece

case .pieceImageTapped(let character):
state.selectedCharacter = character
state.isAllCompleted = self.isAvailableToSave(with: &state)

return .none
case .backButtonTapped:
return .run { _ in
await self.dismiss()
await self.dismiss()
}
// MARK: 기존 프로필 체크

// MARK: 기존 프로필 체크
case .checkProfileResponse(.success(let userProfile)):
state.nickName = userProfile.nickname
state.selectedPiece = userProfile.character
state.selectedCharacter = userProfile.character
state.initialCharacter = userProfile.character
state.initialNickName = userProfile.nickname
state.isCheckingProfile = false
return .none
case .checkProfileResponse(.failure(let error)):
print("🚨 에러 발생!! \(error)")
return .none
// MARK: 프로필 업데이트
// MARK: 프로필 업데이트
case .saveButtonTapped:
let nickName = state.nickName
let piece = state.selectedPiece
let piece = state.selectedCharacter
return .run { send in
await send(.updateProfileResponse(
Result { try await self.userClient.createProfile(userService, nickName, piece) }
))
}
case .updateProfileResponse(.success(_)):
state.isUpdateSucceed = true
return .none
return .run { send in
await send(.delegate(.didUpdateProfileSucceed))
await self.dismiss()
}
case .updateProfileResponse(.failure(let error)):
guard let error = error as? UserClientError else { return .none }
switch error {
case .duplicateNickName:
state.noticeMessage = "이미 존재하는 회원 닉네임입니다."
state.isValidNickName = false
case .networkDisabled:
print("Network 에러")
default:
print("에러 발생")
case .duplicateNickName:
state.noticeMessage = "이미 존재하는 회원 닉네임입니다."
state.isValidNickName = false
state.isAllCompleted = false
case .networkDisabled:
print("Network 에러")
default:
print("에러 발생")
}
return .none
default:
Expand All @@ -127,4 +143,11 @@ extension UpdateProfileFeature {
let range = NSRange(location: 0, length: nickName.utf16.count)
return regex.firstMatch(in: nickName, options: [], range: range) != nil
}

private func isAvailableToSave(with state: inout State) -> Bool {
let isCharacterEquals = state.initialCharacter == state.selectedCharacter
let isNicknameEquals = state.initialNickName == state.nickName
let isAllEqual = isCharacterEquals && isNicknameEquals
return state.isValidNickName && !state.nickName.isEmpty && !isAllEqual
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public struct UpdateProfileView: View {

Spacer()

Image(uiImage: store.selectedPiece.roundImage.image)
Image(uiImage: store.selectedCharacter.roundImage.image)
.resizable()
.scaledToFit()
.frame(width: geometry.size.width * 0.5, height: geometry.size.height * 0.26)
Expand All @@ -51,7 +51,7 @@ public struct UpdateProfileView: View {
.resizable()
.scaledToFit()
.frame(width: geometry.size.width * 0.26, height: geometry.size.height * 0.11)
.opacity(piece == store.selectedPiece ? 1.0 : 0.3)
.opacity(piece == store.selectedCharacter ? 1.0 : 0.3)
.onTapGesture {
store.send(.pieceImageTapped(piece))
}
Expand All @@ -60,7 +60,7 @@ public struct UpdateProfileView: View {
.frame(width: geometry.size.width * 0.26, height: 24)
.foregroundColor(.mmGray2)
.background(Color.mmGray5)
.opacity(piece == store.selectedPiece ? 1.0 : 0.3)
.opacity(piece == store.selectedCharacter ? 1.0 : 0.3)
.cornerRadius(20)
}
}
Expand Down Expand Up @@ -103,25 +103,6 @@ public struct UpdateProfileView: View {
Spacer()
}
}

if showToastMessage {
VStack {
Spacer()
HStack(alignment: .center, spacing: 10) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
Text("닉네임이 저장되었어요")
.font(.pretendard(size: 14, type: .medium))
.foregroundColor(.white)
}
.frame(width: 203, height: 44)
.background(Color.mmGray2)
.cornerRadius(9)
.padding(.bottom, keyboardHeight + 111)
}
.transition(.opacity)
.zIndex(1)
}
}
}
.navigationBarHidden(true)
Expand All @@ -130,20 +111,6 @@ public struct UpdateProfileView: View {
.animation(.default, value: keyboardHeight)
.onAppear(perform: addKeyboardObserver)
.onDisappear(perform: removeKeyboardObserver)
.onChange(of: store.isUpdateSucceed, { _, isUpdateSucceed in
if isUpdateSucceed {
showToastMessage = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation(.easeOut(duration: 0.5)) {
showToastMessage = false
}

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
store.isUpdateSucceed = false
}
}
}
})
.task {
await store
.send(.onAppear)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public struct SettingFeature: Reducer {
@ObservableState
public struct State: Equatable {
@Presents var destination: Destination.State?
var isUpdateSucceed: Bool = false
var isNavigationPresented = false

public init() {}
Expand Down Expand Up @@ -67,6 +68,7 @@ public struct SettingFeature: Reducer {
}
case .navigateUpdateProfileViewTapped:
state.destination = .updateProfile(UpdateProfileFeature.State())
state.isUpdateSucceed = false
return .none
case .navigateTermsOfUseViewTapped:
state.destination = .termsOfUse
Expand All @@ -80,6 +82,9 @@ public struct SettingFeature: Reducer {
case .navigateProfileDeletionViewTapped:
state.destination = .profileDeletion(ProfileDeletionFeature.State())
return .none
case .destination(.presented(.updateProfile(.delegate(.didUpdateProfileSucceed)))):
state.isUpdateSucceed = true
return .none
case .destination(.presented(.logout(.delegate(.didLogoutSucceed)))):
return .send(.delegate(.didLogout))
case .destination(.presented(.profileDeletion(.delegate(.didDeleteProfileSucceed)))):
Expand Down
Loading

0 comments on commit ae9e685

Please sign in to comment.