From 6758b8e0043349dc0ca5ed0f6d04fe24ed3cbef6 Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Thu, 31 Aug 2023 11:56:07 +0900 Subject: [PATCH 01/11] =?UTF-8?q?refactor=20#87:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B7=B0=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DSKit/Sources/Lottie/KeymeLottie.swift | 2 +- Projects/Features/Sources/Root/RootView.swift | 100 ++++++++++-------- .../Features/Sources/SignIn/SignInView.swift | 32 +++--- 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/Projects/DSKit/Sources/Lottie/KeymeLottie.swift b/Projects/DSKit/Sources/Lottie/KeymeLottie.swift index 24c3660e..169f09a7 100644 --- a/Projects/DSKit/Sources/Lottie/KeymeLottie.swift +++ b/Projects/DSKit/Sources/Lottie/KeymeLottie.swift @@ -27,7 +27,7 @@ public struct KeymeLottieView: UIViewRepresentable { lottieView.translatesAutoresizingMaskIntoConstraints = false lottieView.loopMode = loopMode - lottieView.contentMode = .scaleToFill + lottieView.contentMode = .scaleAspectFill lottieView.play(completion: { finished in if finished { completion?() diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index 56d26c43..13c4f981 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -7,7 +7,9 @@ // import SwiftUI +import Core import ComposableArchitecture +import DSKit public struct RootView: View { private let store: StoreOf @@ -22,51 +24,63 @@ public struct RootView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in - if viewStore.logInStatus == .notDetermined { - // 여기 걸리면 에러임. 조심하셈. - EmptyView() - } else if viewStore.logInStatus == .loggedOut { - // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 - let loginStore = store.scope( - state: \.$logInStatus, - action: RootFeature.Action.login) - - IfLetStore(loginStore) { store in - SignInView(store: store) - } - } else if viewStore.registrationState?.status == .notDetermined { - // 개인정보 등록 상태를 로딩 중 - ProgressView() - } else if viewStore.registrationState?.status == .needsRegister { - // 개인정보 등록 - let registrationStore = store.scope( - state: \.$registrationState, - action: RootFeature.Action.registration) - - IfLetStore(registrationStore) { store in - RegistrationView(store: store) - } - } else if viewStore.onboardingStatus?.status == .notDetermined { - // 온보딩 상태를 로딩 중 - ProgressView() - } else if viewStore.onboardingStatus?.status == .needsOnboarding { - // 가입했지만 온보딩을 하지 않고 종료했던 유저 - let onboardingStore = store.scope( - state: \.$onboardingStatus, - action: RootFeature.Action.onboarding) - - IfLetStore(onboardingStore) { store in - OnboardingView(store: store) - } - } else { - // 가입했고 온보딩을 진행한 유저 - let mainPageStore = store.scope(state: \.$mainPageState, action: RootFeature.Action.mainPage) + ZStack { + // 애니메이션 부웅.. 부웅.. + KeymeLottieView(asset: .background, loopMode: .autoReverse) + .ignoresSafeArea() - IfLetStore(mainPageStore) { store in - KeymeMainView(store: store) + if viewStore.logInStatus == .loggedIn { + BackgroundBlurringView(style: .dark) + .ignoresSafeArea() .transition(.opacity) - } else: { - Text("에러") + } + + if viewStore.logInStatus == .notDetermined { + // 여기 걸리면 에러임. 조심하셈. + EmptyView() + } else if viewStore.logInStatus == .loggedOut { + // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 + let loginStore = store.scope( + state: \.$logInStatus, + action: RootFeature.Action.login) + + IfLetStore(loginStore) { store in + SignInView(store: store) + } + } else if viewStore.registrationState?.status == .notDetermined { + // 개인정보 등록 상태를 로딩 중 + ProgressView() + } else if viewStore.registrationState?.status == .needsRegister { + // 개인정보 등록 + let registrationStore = store.scope( + state: \.$registrationState, + action: RootFeature.Action.registration) + + IfLetStore(registrationStore) { store in + RegistrationView(store: store) + } + } else if viewStore.onboardingStatus?.status == .notDetermined { + // 온보딩 상태를 로딩 중 + ProgressView() + } else if viewStore.onboardingStatus?.status == .needsOnboarding { + // 가입했지만 온보딩을 하지 않고 종료했던 유저 + let onboardingStore = store.scope( + state: \.$onboardingStatus, + action: RootFeature.Action.onboarding) + + IfLetStore(onboardingStore) { store in + OnboardingView(store: store) + } + } else { + // 가입했고 온보딩을 진행한 유저 + let mainPageStore = store.scope(state: \.$mainPageState, action: RootFeature.Action.mainPage) + + IfLetStore(mainPageStore) { store in + KeymeMainView(store: store) + .transition(.opacity) + } else: { + Text("에러") + } } } } diff --git a/Projects/Features/Sources/SignIn/SignInView.swift b/Projects/Features/Sources/SignIn/SignInView.swift index e18a953e..96574667 100644 --- a/Projects/Features/Sources/SignIn/SignInView.swift +++ b/Projects/Features/Sources/SignIn/SignInView.swift @@ -10,6 +10,7 @@ import AuthenticationServices import ComposableArchitecture +import DSKit import SwiftUI import Network @@ -21,16 +22,27 @@ public struct SignInView: View { } public var body: some View { - VStack(alignment: .center, spacing: 0) { - Spacer() + ZStack(alignment: .center) { + Text.keyme("KEYME", font: .checkResult) + .foregroundColor(.white) + .offset(y: -39) - KakaoLoginButton(store: store) - - AppleLoginButton(store: store) - - GuideMessageView() + VStack(alignment: .center, spacing: 30) { + Spacer() + + VStack(spacing: 16) { + KakaoLoginButton(store: store) + .frame(height: 48) + + AppleLoginButton(store: store) + .frame(height: 48) + } + + GuideMessageView() + } + .padding(.horizontal, 32) + .padding(.bottom, 56) } - .padding() } // 카카오 로그인 버튼 @@ -45,7 +57,6 @@ public struct SignInView: View { .resizable() .scaledToFill() } - .frame(width: 312, height: 48) .cornerRadius(6) } } @@ -68,9 +79,7 @@ public struct SignInView: View { } }) .signInWithAppleButtonStyle(.white) - .frame(width: 312, height: 48) .cornerRadius(6) - .padding(.vertical) } } @@ -99,7 +108,6 @@ public struct SignInView: View { } } .font(.system(size: 11)) - .frame(width: 265, height: 36) } } } From dfbc25fc251d4a06f55bc619391a648523f2b24c Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Thu, 31 Aug 2023 19:03:16 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor=20#88:=20=EA=B0=9C=EC=9D=B8?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=93=B1=EB=A1=9D=20=EB=B7=B0=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UI/TextField/TextFieldPlaceholder.swift | 22 ++++ .../Registration/RegistrationView.swift | 122 ++++++++++++------ Projects/Features/Sources/Root/RootView.swift | 2 +- 3 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 Projects/Core/Sources/Utility/UI/TextField/TextFieldPlaceholder.swift diff --git a/Projects/Core/Sources/Utility/UI/TextField/TextFieldPlaceholder.swift b/Projects/Core/Sources/Utility/UI/TextField/TextFieldPlaceholder.swift new file mode 100644 index 00000000..ab1e87bc --- /dev/null +++ b/Projects/Core/Sources/Utility/UI/TextField/TextFieldPlaceholder.swift @@ -0,0 +1,22 @@ +// +// TextFieldPlaceholder.swift +// Core +// +// Created by 이영빈 on 2023/08/31. +// Copyright © 2023 team.humanwave. All rights reserved. +// + +import SwiftUI + +public extension View { + func placeholder( + when shouldShow: Bool, + alignment: Alignment = .leading, + @ViewBuilder placeholder: () -> Content + ) -> some View { + ZStack(alignment: alignment) { + placeholder().opacity(shouldShow ? 1 : 0) + self + } + } +} diff --git a/Projects/Features/Sources/Registration/RegistrationView.swift b/Projects/Features/Sources/Registration/RegistrationView.swift index 377b654c..79c40520 100644 --- a/Projects/Features/Sources/Registration/RegistrationView.swift +++ b/Projects/Features/Sources/Registration/RegistrationView.swift @@ -7,6 +7,7 @@ // import Core +import DSKit import SwiftUI import PhotosUI @@ -22,6 +23,7 @@ public struct RegistrationView: View { } // 닉네임 관련 프로퍼티 + @FocusState private var isTextFieldFocused: Bool @State private var nickname = "" // 사용자가 새롭게 입력한 닉네임 @State private var beforeNickname = "" // 기존에 입력했던 닉네임 @State private var isShake = false // 최대 글자 수를 넘긴 경우 좌, 우로 떨리는 애니메이션 @@ -32,7 +34,12 @@ public struct RegistrationView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in - VStack(spacing: 12) { + VStack(spacing: 0) { + Text.keyme("회원가입", font: .body3Semibold) + .foregroundColor(.white) + .padding(.top, 16) + .padding(.bottom, 24) + // 프로필 이미지를 등록하는 Circle PhotosPicker(selection: $selectedImage, matching: .images, photoLibrary: .shared()) { profileImage(imageData: selectedImageData) @@ -49,57 +56,68 @@ public struct RegistrationView: View { } } - // 닉네임 관련 안내메세지 + Spacer().frame(height: 59) + HStack(alignment: .center, spacing: 4) { - Text("닉네임") - .font(.system(size: 14)) + Text.keyme("닉네임", font: .body3Regular) + .foregroundColor(.white) - Text("(\(nickname.count)/6)") - .font(.system(size: 12)) - .foregroundColor(.gray) + Text.keyme("(\(nickname.count)/6)", font: .caption1) + .foregroundColor(DSKitAsset.Color.keymeMediumgray.swiftUIColor) Spacer() - Text("2~6자리 한글, 영어, 숫자") - .font(.system(size: 12)) + Text.keyme("2~6자리 한글, 영어, 숫자", font: .caption1) + .foregroundColor(.white) } - .padding(.horizontal, 2) + + Spacer().frame(height: 12) // 닉네임을 입력하는 TextField - TextField("닉네임을 입력해주세요.", text: $nickname) - .font(.system(size: 16)) + TextField("Nickname", text: $nickname) + .focused($isTextFieldFocused) + .placeholder(when: nickname.isEmpty, placeholder: { + Text.keyme("닉네임을 입력해주세요.", font: .body3Regular) + .foregroundColor(.white.opacity(0.4)) + }) + .foregroundColor(.white) + .padding(.horizontal, 10) .frame(height: 50) - .padding(.horizontal) .overlay( RoundedRectangle(cornerRadius: 4) - .stroke(.gray, lineWidth: 1) + .stroke(.white.opacity(0.3), lineWidth: 1) ) + .background(.black.opacity(0.4)) .modifier(Shake(isShake: $isShake)) - if !nickname.isEmpty, let isValid = viewStore.isNicknameAvailable { + if + !nickname.isEmpty, + let isValid = viewStore.state.isNicknameAvailable + { ValidateNicknameView(isValid: isValid) + .padding(.top, 12) } + Spacer(minLength: 50) + // 닉네임 관련 안내메세지 Rectangle() .frame(height: 80) - .foregroundColor(.gray) + .foregroundColor( + DSKitAsset.Color.keymeBlack.swiftUIColor.opacity(0.8)) .cornerRadius(8) .overlay( - Text("친구들이 원할하게 문제를 풀 수 있도록, 나를 가장 잘 나타내는 닉네임으로 설정해주세요. \(viewStore.canRegister.description)") - .font(.system(size: 14)) - .fontWeight(.semibold) + Text.keyme("친구들이 원활하게 문제를 풀 수 있도록, 나를 가장 잘 나타내는 닉네임으로 설정해주세요.", font: .body4) + .lineSpacing(10) .foregroundColor(.white) - .padding(.horizontal, 8) ) - - Spacer() - + .padding(.vertical, 24) + // 다음 페이지로 넘어가기 위한 Button Button(action: { viewStore.send( .finishRegister( - nickname: viewStore.state.nicknameTextFieldString, + nickname: viewStore.nicknameTextFieldString, thumbnailURL: viewStore.thumbnailURL, originalImageURL: viewStore.originalImageURL)) }) { @@ -115,11 +133,14 @@ public struct RegistrationView: View { } } .foregroundColor(.white) - .background(viewStore.canRegister ? .black : .gray) + .background(viewStore.state.canRegister ? .black : .white.opacity(0.3)) .cornerRadius(16) - .disabled(viewStore.canRegister ? false : true) + .padding(.bottom, 20) + .disabled(viewStore.state.canRegister ? false : true) + } + .onTapGesture { + isTextFieldFocused = false } - .modifier(DismissKeyboardOnTap()) .padding(.horizontal, 16) .onChange(of: nickname) { newValue in guard 1 <= newValue.count, newValue.count <= 6 else { @@ -136,6 +157,8 @@ public struct RegistrationView: View { viewStore.send(.debouncedNicknameUpdate(text: newValue)) } + .fullFrame() + .ignoresSafeArea(.keyboard, edges: .bottom) } } } @@ -143,7 +166,15 @@ public struct RegistrationView: View { // 닉네임에 대한 검증 여부를 보여주는 뷰 extension RegistrationView { func profileImage(imageData: Data?) -> some View { - ZStack(alignment: .bottomTrailing) { + let outercircleSize = 160.0 + let iconSize = 33.3 + + return ZStack(alignment: .center) { + Circle() + .foregroundColor(.white.opacity(0.15)) + .overlay(Circle().stroke(.white.opacity(0.30), lineWidth: 1)) + .frame(width: outercircleSize, height: outercircleSize) + Group { if let selectedImageData = imageData, @@ -152,25 +183,22 @@ extension RegistrationView { Image(uiImage: profileImage) .resizable() .scaledToFill() + .frame(width: outercircleSize - 20, height: outercircleSize - 20) } else { - Circle() - .foregroundColor(.gray) - .overlay(Circle().stroke(.white, lineWidth: 1)) + ZStack { + Circle() + .foregroundColor(.black.opacity(0.8)) + .frame(width: outercircleSize - 20, height: outercircleSize - 20) + + Image(systemName: "photo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: iconSize, height: iconSize) + .foregroundColor(.white) + } } } - .frame(width: 160, height: 160) .clipShape(Circle()) - - ZStack { - Circle() - .foregroundColor(.black) - .frame(width: 50, height: 50) - Image(systemName: "photo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 33.3, height: 33.3) - .foregroundColor(.white) - } } } @@ -192,3 +220,11 @@ extension RegistrationView { } } } + +struct RegistrationView_preview: PreviewProvider { + static var previews: some View { + RegistrationView(store: Store(initialState: RegistrationFeature.State(), reducer: { + RegistrationFeature() + })) + } +} diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index 13c4f981..c6bd6821 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -43,7 +43,7 @@ public struct RootView: View { let loginStore = store.scope( state: \.$logInStatus, action: RootFeature.Action.login) - + IfLetStore(loginStore) { store in SignInView(store: store) } From 57651539345e85ee3a2ff1722dc6d01243ce4fcb Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Fri, 1 Sep 2023 23:39:31 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor=20#87:=20=EB=A3=A8=ED=8A=B8?= =?UTF-8?q?=EB=B7=B0=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit . --- .../ProjectDescriptionHelpers/InfoPlist.swift | 10 +- .../Registration/RegisterFeature.swift | 7 +- .../Registration/RegistrationView.swift | 2 +- .../Features/Sources/Root/RootFeature.swift | 251 ++++++++---------- Projects/Features/Sources/Root/RootView.swift | 44 +-- .../Network/Sources/DTO/MemberUpdateDTO.swift | 11 +- 6 files changed, 151 insertions(+), 174 deletions(-) diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift index 49b02f63..cc915eca 100644 --- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -20,7 +20,15 @@ public extension Project { ] ], "API_BASE_URL": "$(API_BASE_URL)", - "UIUserInterfaceStyle": "Light" + "UIUserInterfaceStyle": "Light", + "NSAppTransportSecurity": [ + "NSExceptionDomains": [ + "api.keyme.space": [ + "NSIncludesSubdomains": true, + "NSExceptionMinimumTLSVersion": "TLSv1.2", + ], + ] + ] ] static let baseUrlInfoPlist: [String: InfoPlist.Value] = [ diff --git a/Projects/Features/Sources/Registration/RegisterFeature.swift b/Projects/Features/Sources/Registration/RegisterFeature.swift index 9f34c452..5005361d 100644 --- a/Projects/Features/Sources/Registration/RegisterFeature.swift +++ b/Projects/Features/Sources/Registration/RegisterFeature.swift @@ -47,7 +47,7 @@ public struct RegistrationFeature: Reducer { case registerProfileImageResponse(thumbnailURL: URL, originalImageURL: URL) case finishRegister(nickname: String, thumbnailURL: URL?, originalImageURL: URL?) - case finishRegisterResponse(id: Int, friendCode: String) + case finishRegisterResponse(MemberUpdateDTO) } public var body: some Reducer { @@ -112,10 +112,7 @@ public struct RegistrationFeature: Reducer { profileThumbnail: originalImageURL?.absoluteString)), object: MemberUpdateDTO.self) - await send( - .finishRegisterResponse( - id: result.data.id, - friendCode: result.data.friendCode ?? "")) // TODO: 나중에 non-null 값 필요 + await send(.finishRegisterResponse(result)) } case .finishRegisterResponse: diff --git a/Projects/Features/Sources/Registration/RegistrationView.swift b/Projects/Features/Sources/Registration/RegistrationView.swift index 79c40520..0e174bf9 100644 --- a/Projects/Features/Sources/Registration/RegistrationView.swift +++ b/Projects/Features/Sources/Registration/RegistrationView.swift @@ -187,7 +187,7 @@ extension RegistrationView { } else { ZStack { Circle() - .foregroundColor(.black.opacity(0.8)) + .foregroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor.opacity(0.8)) .frame(width: outercircleSize - 20, height: outercircleSize - 20) Image(systemName: "photo") diff --git a/Projects/Features/Sources/Root/RootFeature.swift b/Projects/Features/Sources/Root/RootFeature.swift index ea4e001d..8c2b064d 100644 --- a/Projects/Features/Sources/Root/RootFeature.swift +++ b/Projects/Features/Sources/Root/RootFeature.swift @@ -23,161 +23,58 @@ public struct RootFeature: Reducer { public struct State: Equatable { @PresentationState public var logInStatus: SignInFeature.State? @PresentationState public var registrationState: RegistrationFeature.State? - @PresentationState public var onboardingStatus: OnboardingFeature.State? + @PresentationState public var onboardingState: OnboardingFeature.State? @PresentationState public var mainPageState: MainPageFeature.State? - public init( - isLoggedIn: Bool? = nil, - doneRegistration: Bool? = nil, - doneOnboarding: Bool? = nil - ) { - registrationState = .init() - onboardingStatus = .init() - - if let isLoggedIn { - logInStatus = isLoggedIn ? .loggedIn : .loggedOut - } else { - logInStatus = .notDetermined - } - - if let doneRegistration { - registrationState?.status = doneRegistration ? .complete : .needsRegister - } else { - registrationState?.status = .notDetermined - } - - if let doneOnboarding { - onboardingStatus?.status = doneOnboarding ? .completed : .needsOnboarding - } else { - onboardingStatus?.status = .notDetermined - } + var userStatus: UserStatus = .notDetermined + + public enum UserStatus: Equatable { + case notDetermined + case needSignIn + case needRegistration + case needOnboarding + case canUseApp(userId: Int, nickname: String) } } public enum Action { + public enum View { + case checkUserStatus + } + case view(View) + case login(PresentationAction) case registration(PresentationAction) case onboarding(PresentationAction) case mainPage(PresentationAction) - case onboardingChecked(TaskResult) - - case checkUserStatus - case checkLoginStatus - case checkRegistrationStatus - case checkOnboardingStatus - - case updateMemberInformation - case startMainPage(userId: Int, nickname: String) + case updateState(State.UserStatus) + case updateMemberInformation(withMemberData: MemberUpdateDTO.MemberData?) } public var body: some ReducerOf { Reduce { state, action in switch action { - case .login(.presented(.signInWithAppleResponse(let response))): - switch response { - case .success(let body): - let token = body.data.token.accessToken - userStorage.accessToken = token - network.registerAuthorizationToken(token) - - if body.data.nickname == nil { - state.registrationState?.status = .needsRegister - } else { - state.registrationState?.status = .complete - } - return .none - - case .failure: - return .none - } - - case .login(.presented(.signInWithKakaoResponse(let response))): - switch response { - case .success(let body): - let token = body.data.token.accessToken - userStorage.accessToken = token - network.registerAuthorizationToken(token) - - if body.data.nickname == nil { - state.registrationState?.status = .needsRegister - } else { - state.registrationState?.status = .complete - } - return .none - - case .failure: - return .none - } - - case .checkUserStatus: + case .view(.checkUserStatus): let accessToken = userStorage.accessToken - if accessToken == nil { - state.logInStatus = .loggedOut - - return .none - } else { - state.logInStatus = .loggedIn + if accessToken == nil { // 로그 아웃 상태 + return .send(.updateState(.needSignIn)) + } else { // 로그인 상태 network.registerAuthorizationToken(accessToken) - - return .run { send in - await send(.updateMemberInformation) - await send(.checkRegistrationStatus) - await send(.checkOnboardingStatus) - } - } - - case .checkLoginStatus: - let accessToken = userStorage.accessToken - if accessToken == nil { - state.logInStatus = .loggedOut - } else { - state.logInStatus = .loggedIn - network.registerAuthorizationToken(accessToken) - } - - return .none - - case .registration(.presented(.finishRegisterResponse)): - // Do nothing currently - return .none - - case .checkRegistrationStatus: - let nickname: String? = userStorage.nickname - - if nickname == nil { - state.registrationState?.status = .needsRegister - } else { - state.registrationState?.status = .complete + return .send(.updateMemberInformation(withMemberData: nil)) } - - return .none - - case .checkOnboardingStatus: - return .run(priority: .userInitiated) { send in - await send(.onboardingChecked( - TaskResult { - // TODO: API 갈아끼우기 -// try await Task.sleep(until: .now + .seconds(0.1), clock: .continuous) - return false - } - )) - } - - case .onboardingChecked(.success(let result)): - switch result { - case true: - state.onboardingStatus?.status = .completed - case false: - state.onboardingStatus?.status = .needsOnboarding - } - return .none - - case .updateMemberInformation: + + case .updateMemberInformation(let receviedMemberData): return .run(priority: .userInitiated) { send in - let memberInformation = try await network.request( - .member(.fetch), - object: MemberUpdateDTO.self).data + let memberInformation: MemberUpdateDTO.MemberData + if let receviedMemberData { + memberInformation = receviedMemberData + } else { + memberInformation = try await network.request( + .member(.fetch), + object: MemberUpdateDTO.self + ).data + } userStorage.userId = memberInformation.id userStorage.nickname = memberInformation.nickname @@ -194,25 +91,83 @@ public struct RootFeature: Reducer { userStorage.profileThumbnailURL = profileThumbnailURL } + if memberInformation.nickname == nil { + await send(.updateState(.needRegistration)) + } else if memberInformation.isOnboardingClear == false { + await send(.updateState(.needOnboarding)) + } else { + await send(.updateState(.canUseApp(userId: memberInformation.id, nickname: memberInformation.nickname))) + } + Task.detached(priority: .low) { let notificationDelegate = UserNotificationCenterDelegateManager() + guard let token = await notificationDelegate.waitForToken() else { - print("ERROR TOEKN PUSH") + print("푸시토큰 등록 중 에러 발생") return } _ = try await network.request(.registerPushToken(.register(token))) } - - await send(.startMainPage( - userId: memberInformation.id, - nickname: memberInformation.nickname)) } - case .startMainPage(let userId, let nickname): - state.mainPageState = MainPageFeature.State(userId: userId, nickname: nickname) + case .updateState(let status): + state.userStatus = status + + switch status { + case .notDetermined: + break + case .needSignIn: + state.logInStatus = .loggedIn + case .needRegistration: + state.registrationState = .init() + case .needOnboarding: + state.onboardingState = .init() + case let .canUseApp(userId, nickname): + state.mainPageState = .init(userId: userId, nickname: nickname) + } + return .none + // MARK: - Presentation actions + case .login(.presented(.signInWithAppleResponse(let response))): + switch response { + case .success(let body): + let token = body.data.token.accessToken + userStorage.accessToken = token + network.registerAuthorizationToken(token) + + return .send(.updateMemberInformation(withMemberData: nil)) + + case .failure: + return .none + } + + case .login(.presented(.signInWithKakaoResponse(let response))): + switch response { + case .success(let body): + let token = body.data.token.accessToken + userStorage.accessToken = token + network.registerAuthorizationToken(token) + + return .send(.updateMemberInformation(withMemberData: nil)) + + case .failure: + return .none + } + + case .registration(.presented(.finishRegisterResponse(let response))): + return .send(.updateMemberInformation(withMemberData: response.data)) + + case .onboarding(.presented(.testResult(.closeButtonDidTap))): + guard let userId = userStorage.userId, let nickname = userStorage.nickname else { + // 멤버 정보 수신 재시도 + // TODO: and show alert. 사실 있을 수 없는 케이스긴 함 + return .send(.updateMemberInformation(withMemberData: nil)) + } + + return .send(.updateState(.canUseApp(userId: userId, nickname: nickname))) + default: return .none } @@ -223,7 +178,7 @@ public struct RootFeature: Reducer { .ifLet(\.$registrationState, action: /Action.registration) { RegistrationFeature() } - .ifLet(\.$onboardingStatus, action: /Action.onboarding) { + .ifLet(\.$onboardingState, action: /Action.onboarding) { OnboardingFeature() } .ifLet(\.$mainPageState, action: /Action.mainPage) { @@ -231,3 +186,13 @@ public struct RootFeature: Reducer { } } } + +private extension RootFeature { + func needsRegistration(forNickname nickname: String?) -> Bool { + if nickname == nil { + return true + } else { + return false + } + } +} diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index c6bd6821..0d8a98d0 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -16,41 +16,37 @@ public struct RootView: View { public init() { self.store = Store(initialState: RootFeature.State()) { - RootFeature() + RootFeature()._printChanges() } - store.send(.checkUserStatus) + store.send(.view(.checkUserStatus)) } public var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in + WithViewStore(store, observe: { $0.userStatus }, send: RootFeature.Action.view) { viewStore in ZStack { // 애니메이션 부웅.. 부웅.. KeymeLottieView(asset: .background, loopMode: .autoReverse) .ignoresSafeArea() - if viewStore.logInStatus == .loggedIn { + if viewStore.state != .needSignIn { BackgroundBlurringView(style: .dark) .ignoresSafeArea() - .transition(.opacity) + .transition(.opacity.animation(.easeInOut)) } - if viewStore.logInStatus == .notDetermined { - // 여기 걸리면 에러임. 조심하셈. - EmptyView() - } else if viewStore.logInStatus == .loggedOut { + switch viewStore.state { + case .needSignIn: // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 let loginStore = store.scope( state: \.$logInStatus, action: RootFeature.Action.login) - + IfLetStore(loginStore) { store in SignInView(store: store) } - } else if viewStore.registrationState?.status == .notDetermined { - // 개인정보 등록 상태를 로딩 중 - ProgressView() - } else if viewStore.registrationState?.status == .needsRegister { + + case .needRegistration: // 개인정보 등록 let registrationStore = store.scope( state: \.$registrationState, @@ -59,28 +55,32 @@ public struct RootView: View { IfLetStore(registrationStore) { store in RegistrationView(store: store) } - } else if viewStore.onboardingStatus?.status == .notDetermined { - // 온보딩 상태를 로딩 중 - ProgressView() - } else if viewStore.onboardingStatus?.status == .needsOnboarding { + + case .needOnboarding: // 가입했지만 온보딩을 하지 않고 종료했던 유저 let onboardingStore = store.scope( - state: \.$onboardingStatus, + state: \.$onboardingState, action: RootFeature.Action.onboarding) IfLetStore(onboardingStore) { store in OnboardingView(store: store) } - } else { + + case .canUseApp: // 가입했고 온보딩을 진행한 유저 - let mainPageStore = store.scope(state: \.$mainPageState, action: RootFeature.Action.mainPage) + let mainPageStore = store.scope( + state: \.$mainPageState, + action: RootFeature.Action.mainPage) IfLetStore(mainPageStore) { store in KeymeMainView(store: store) - .transition(.opacity) + .transition(.opacity.animation(.easeInOut)) } else: { Text("에러") } + + case .notDetermined: + EmptyView() } } } diff --git a/Projects/Network/Sources/DTO/MemberUpdateDTO.swift b/Projects/Network/Sources/DTO/MemberUpdateDTO.swift index cac54c30..a9486163 100644 --- a/Projects/Network/Sources/DTO/MemberUpdateDTO.swift +++ b/Projects/Network/Sources/DTO/MemberUpdateDTO.swift @@ -8,16 +8,23 @@ import Foundation -public struct MemberUpdateDTO: Codable { +public struct MemberUpdateDTO: Codable, Equatable { let code: Int public let data: MemberData let message: String - public struct MemberData: Codable { + public struct MemberData: Codable, Equatable { public let friendCode: String? public let id: Int + public let isOnboardingClear: Bool public let nickname: String public let profileImage: String public let profileThumbnail: String } } + +extension MemberUpdateDTO { + public static func == (lhs: MemberUpdateDTO, rhs: MemberUpdateDTO) -> Bool { + lhs.data.id == rhs.data.id + } +} From 039c7b3660cb2a8e41c338145e9ee8e0270f9a6d Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 09:39:52 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor=20#87:=20=EB=A3=A8=ED=8A=B8?= =?UTF-8?q?=EB=B7=B0=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=ED=91=B8=EC=8B=9C?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=93=B1=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GoogleService-Info.plist.encrypted | Bin 1104 -> 1136 bytes .../Features/Sources/Root/RootFeature+.swift | 30 +----------------- .../Features/Sources/Root/RootFeature.swift | 17 +++++----- Projects/Keyme/Sources/KeymeApp.swift | 12 +++++++ .../Network/Sources/DTO/MemberUpdateDTO.swift | 4 +-- 5 files changed, 23 insertions(+), 40 deletions(-) diff --git a/Encrypted/Secrets/GoogleService-Info.plist.encrypted b/Encrypted/Secrets/GoogleService-Info.plist.encrypted index dbe4cb732ff1ca1e93aa85b1713bd24bea66e0ca..58ede7d001cfafc39c7cc8ab647682ca495f8654 100644 GIT binary patch delta 942 zcmV;f15y0Y2=EAy(0}v|%swyi*4DPG?KSL5(rX{T^?qHv#E{vP9%e4XBh0I=X`y%_ zXAtQxf20rCPj^&hB_dzMw}1C^b{3FPLT7T|wOV6O z3xMs)8k+x53px32IC19*{^m|q~5vQHB~0)@iUdx-VX?xnLu5g zEH%~P%!t*(Rnnm`_9-zzb8h(S6zOo;D6iS#&#DkA3E=@T}e>^fwT9@ zpwea;(3hN>g^%ZCXq=O^2kmbP)Kh?vG`PAQo%JU4_{j-blCst#tZhh|%_}k4m}gml z9lRCDn~ctd5Vgs~0_?>=fjs{^lth#1+FLt3k=gB^U>&R8M6VsgWIJUiP-R^SHUHUuT`p_hmwztB?fML zUVs1j0PHs7e4orm!2dEJ%le2@(N1R8rIw^VoA@0@C{EtjoBl>mY10blHOYMc@Jwph z7$BliN*3kD&-m+%99_+=$^Sf&qmFS{`o{om7zEb3e~a4i0w2`tUfxe-nSrK>BN6(1 z@9cv{5;lM@#yPmhS%;btXbXi!ROJV7n|+dV=w7lVj3sbmhC>2LJ#7 delta 910 zcmV;919AND2+#{Z z|HRnX>JDy2`Vh}tG66C1F>bQZ1$6@sGEq+-*$w6j9l)uOLLxT?>+jKjRZ$8ur;hDotqED;&CGzW*9hOh_@l(FY5!frsxewvWZ z!wwaG=*SmIO8+sFXSyc3ll~!Pr3by1uy^RQ=vFzP%YSDRC+f_E*MgDB8O*Y5ifRUL z`pD$pFG7b*ReBXItx2^%6?zvXFO@3kD~nr{)Jm>rVmjVGL=6sBm!R{5Hc{WoQKI23 z=sN%eFIyLI4peX3NZE(c1NYM!O^PYlek$G6edcWv@cw(wH_cyQkp;4-;~nr^)yP3Q zARFgHCx3|z47Y+rkE}*Uw~#A*K=#@clCb@L6)9Epb$h(GGNOe5r#`f+Mz!k-Vm9Hr zFZl!j3@%yCNA(JnY`qYlS`-EIimA&IO2aXjUSX^ZhpmO~1@YNcLYeoz;8r<43~?_` z^L^_lv`C)>QLEi3Bu-GpCkk3XquC@dT?3O_`G2RqN4UtbE!dzT47xtnhSxqF-!)&0Av-@h5}csr&jJ5dj%HuW%4LJ#3QbWKbVJ;Wxw8y5N3-B)8Abe4 zC@F8Uh}FxHtCFYLm&y$iZG6(`{Jgk5V*o7vJsIp#$~IZbKlKFd95u%WM(Ozyy|ZIM zUVk4wn+PA$D?JR?^^*Q1a_#I#1pqnEE_Krn4(`wIJ Date: Sat, 2 Sep 2023 12:07:26 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor=20#87:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20APi=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Home/KeymeTestHomeFeature.swift | 8 ++--- .../Sources/Home/KeymeTestHomeView.swift | 6 +++- .../Sources/MainPage/MainPageFeature.swift | 21 ++++++++---- .../Sources/MainPage/MainPageView.swift | 33 +++++++------------ .../Sources/MyPage/MyPageFeature.swift | 24 +++++++------- .../Features/Sources/MyPage/MyPageView.swift | 11 ++++--- .../MyPage/ScoreList/ScoreListFeature.swift | 2 +- 7 files changed, 54 insertions(+), 51 deletions(-) diff --git a/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift index 2a80651c..c56da995 100644 --- a/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift +++ b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift @@ -10,11 +10,11 @@ import ComposableArchitecture import Domain import Network -struct KeymeTestsHomeFeature: Reducer { +public struct KeymeTestsHomeFeature: Reducer { @Dependency(\.keymeAPIManager) private var network // 테스트를 아직 풀지 않았거나, 풀었거나 2가지 케이스만 존재 - struct State: Equatable { + public struct State: Equatable { @PresentationState var testStartViewState: KeymeTestsStartFeature.State? var view: View @@ -28,7 +28,7 @@ struct KeymeTestsHomeFeature: Reducer { } } - enum Action { + public enum Action { case fetchDailyTests case showTestStartView(testData: KeymeTestsModel) case startTest(PresentationAction) @@ -36,7 +36,7 @@ struct KeymeTestsHomeFeature: Reducer { enum View {} } - var body: some ReducerOf { + public var body: some ReducerOf { Reduce { state, action in switch action { case .fetchDailyTests: diff --git a/Projects/Features/Sources/Home/KeymeTestHomeView.swift b/Projects/Features/Sources/Home/KeymeTestHomeView.swift index a4bae6ab..4ba42556 100644 --- a/Projects/Features/Sources/Home/KeymeTestHomeView.swift +++ b/Projects/Features/Sources/Home/KeymeTestHomeView.swift @@ -15,7 +15,6 @@ struct KeymeTestsHomeView: View { init(store: StoreOf) { self.store = store - store.send(.fetchDailyTests) } var body: some View { @@ -41,6 +40,11 @@ struct KeymeTestsHomeView: View { // 결과 화면 표시도 생각 } + .onAppear { + if viewStore.dailyTestId == nil { + viewStore.send(.fetchDailyTests) + } + } } } } diff --git a/Projects/Features/Sources/MainPage/MainPageFeature.swift b/Projects/Features/Sources/MainPage/MainPageFeature.swift index 80fd4e0b..f832dafe 100644 --- a/Projects/Features/Sources/MainPage/MainPageFeature.swift +++ b/Projects/Features/Sources/MainPage/MainPageFeature.swift @@ -11,23 +11,32 @@ import ComposableArchitecture public struct MainPageFeature: Reducer { public struct State: Equatable { - let userId: Int - let nickname: String + var home: KeymeTestsHomeFeature.State + var myPage: MyPageFeature.State public init(userId: Int, nickname: String) { - self.userId = userId - self.nickname = nickname + self.home = .init(nickname: nickname) + self.myPage = .init(userId: userId, nickname: nickname) } } public enum Action { - case logout - case changeNickname(String) + case home(KeymeTestsHomeFeature.Action) + case myPage(MyPageFeature.Action) } public var body: some Reducer { Reduce { _, _ in return .none } + + Scope(state: \.home, action: /Action.home) { + KeymeTestsHomeFeature() + } + + Scope(state: \.myPage, action: /Action.myPage) { + MyPageFeature() + } + } } diff --git a/Projects/Features/Sources/MainPage/MainPageView.swift b/Projects/Features/Sources/MainPage/MainPageView.swift index e8d2b9c8..5880737f 100644 --- a/Projects/Features/Sources/MainPage/MainPageView.swift +++ b/Projects/Features/Sources/MainPage/MainPageView.swift @@ -25,30 +25,19 @@ struct KeymeMainView: View { } var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in + WithViewStore(store, observe: { $0 }) { _ in TabView(selection: $selectedTab) { - KeymeTestsHomeView(store: Store( - initialState: KeymeTestsHomeFeature.State( - nickname: viewStore.nickname) - ) { - KeymeTestsHomeFeature() - }) - .tabItem { - homeTabImage - } - .tag(Tab.home) + KeymeTestsHomeView(store: store.scope(state: \.home, action: MainPageFeature.Action.home)) + .tabItem { + homeTabImage + } + .tag(Tab.home) - MyPageView(store: Store( - initialState: MyPageFeature.State( - userId: viewStore.state.userId, - nickname: viewStore.state.nickname) - ) { - MyPageFeature() - }) - .tabItem { - myPageTabImage - } - .tag(Tab.myPage) + MyPageView(store: store.scope(state: \.myPage, action: MainPageFeature.Action.myPage)) + .tabItem { + myPageTabImage + } + .tag(Tab.myPage) } .introspect(.tabView, on: .iOS(.v16, .v17)) { tabViewController in let tabBar = tabViewController.tabBar diff --git a/Projects/Features/Sources/MyPage/MyPageFeature.swift b/Projects/Features/Sources/MyPage/MyPageFeature.swift index 23a3b48c..5dd4b8c6 100644 --- a/Projects/Features/Sources/MyPage/MyPageFeature.swift +++ b/Projects/Features/Sources/MyPage/MyPageFeature.swift @@ -14,17 +14,10 @@ import Foundation import SwiftUI import Network -struct Coordinate { - var x: Double - var y: Double - var r: Double - var color: Color -} - -struct MyPageFeature: Reducer { +public struct MyPageFeature: Reducer { @Dependency(\.keymeAPIManager) private var network - struct State: Equatable { + public struct State: Equatable { var similarCircleDataList: [CircleData] = [] var differentCircleDataList: [CircleData] = [] var view: View @@ -45,14 +38,14 @@ struct MyPageFeature: Reducer { } } - enum Action: Equatable { + public enum Action: Equatable { case saveCircle([CircleData], MatchRate) case showCircle(MyPageSegment) case requestCircle(MatchRate) case view(View) case scoreListAction(ScoreListFeature.Action) - enum View: Equatable { + public enum View: Equatable { case markViewAsShown case circleTapped case circleDismissed @@ -138,9 +131,16 @@ struct MyPageFeature: Reducer { } } -extension MyPageFeature { +public extension MyPageFeature { enum MatchRate { case top5 case low5 } + + struct Coordinate { + var x: Double + var y: Double + var r: Double + var color: Color + } } diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index c630bc45..1f8be83b 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -18,11 +18,6 @@ struct MyPageView: View { init(store: StoreOf) { self.store = store - - store.send(.requestCircle(.top5)) - store.send(.requestCircle(.low5)) - - store.send(.view(.selectSegement(.similar))) } public var body: some View { @@ -90,5 +85,11 @@ struct MyPageView: View { } } } + .onAppear { + store.send(.requestCircle(.top5)) + store.send(.requestCircle(.low5)) + + store.send(.view(.selectSegement(.similar))) + } } } diff --git a/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift b/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift index ff3c7dd6..20265277 100644 --- a/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift +++ b/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift @@ -11,7 +11,7 @@ import ComposableArchitecture import Domain import Network -struct ScoreListFeature: Reducer { +public struct ScoreListFeature: Reducer { @Dependency(\.keymeAPIManager) private var network public struct State: Equatable { From 5d05d6aea0be97088f069e2b0f33ab45fae6d72b Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 13:29:31 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor=20#87:=20=EB=A3=A8=ED=8A=B8?= =?UTF-8?q?=EB=B7=B0=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Sources/Root/RootFeature.swift | 2 +- Projects/Features/Sources/Root/RootView.swift | 24 +++++++++++++++---- .../Network/Sources/DTO/MemberUpdateDTO.swift | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Projects/Features/Sources/Root/RootFeature.swift b/Projects/Features/Sources/Root/RootFeature.swift index 8a0e1389..89c3738b 100644 --- a/Projects/Features/Sources/Root/RootFeature.swift +++ b/Projects/Features/Sources/Root/RootFeature.swift @@ -89,7 +89,7 @@ public struct RootFeature: Reducer { } if let userId = memberInformation.id, let nickname = memberInformation.nickname { - if memberInformation.isOnboardingClear == false { + if memberInformation.isOnboardingClear != true { await send(.updateState(.needOnboarding)) } else { await send(.updateState(.canUseApp(userId: userId, nickname: nickname))) diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index 0d8a98d0..9e0e79cc 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -32,7 +32,7 @@ public struct RootView: View { if viewStore.state != .needSignIn { BackgroundBlurringView(style: .dark) .ignoresSafeArea() - .transition(.opacity.animation(.easeInOut)) + .transition(.opacity.animation(Animation.customInteractiveSpring())) } switch viewStore.state { @@ -45,7 +45,9 @@ public struct RootView: View { IfLetStore(loginStore) { store in SignInView(store: store) } - + .transition(.opacity.animation(Animation.customInteractiveSpring())) + .zIndex(ViewZIndex.siginIn.rawValue) + case .needRegistration: // 개인정보 등록 let registrationStore = store.scope( @@ -55,7 +57,9 @@ public struct RootView: View { IfLetStore(registrationStore) { store in RegistrationView(store: store) } - + .transition(.opacity.animation(Animation.customInteractiveSpring())) + .zIndex(ViewZIndex.registration.rawValue) + case .needOnboarding: // 가입했지만 온보딩을 하지 않고 종료했던 유저 let onboardingStore = store.scope( @@ -65,6 +69,8 @@ public struct RootView: View { IfLetStore(onboardingStore) { store in OnboardingView(store: store) } + .transition(.opacity.animation(Animation.customInteractiveSpring())) + .zIndex(ViewZIndex.onboarding.rawValue) case .canUseApp: // 가입했고 온보딩을 진행한 유저 @@ -74,10 +80,11 @@ public struct RootView: View { IfLetStore(mainPageStore) { store in KeymeMainView(store: store) - .transition(.opacity.animation(.easeInOut)) } else: { Text("에러") } + .transition(.opacity.animation(Animation.customInteractiveSpring())) + .zIndex(ViewZIndex.main.rawValue) case .notDetermined: EmptyView() @@ -87,6 +94,15 @@ public struct RootView: View { } } +private extension RootView { + enum ViewZIndex: CGFloat { + case siginIn = 4 + case registration = 3 + case onboarding = 2 + case main = 1 + } +} + struct RootView_Previews: PreviewProvider { static var previews: some View { RootView() diff --git a/Projects/Network/Sources/DTO/MemberUpdateDTO.swift b/Projects/Network/Sources/DTO/MemberUpdateDTO.swift index 1c6a9276..140cf8a0 100644 --- a/Projects/Network/Sources/DTO/MemberUpdateDTO.swift +++ b/Projects/Network/Sources/DTO/MemberUpdateDTO.swift @@ -16,7 +16,7 @@ public struct MemberUpdateDTO: Codable, Equatable { public struct MemberData: Codable, Equatable { public let friendCode: String? public let id: Int? - public let isOnboardingClear: Bool + public let isOnboardingClear: Bool? public let nickname: String? public let profileImage: String public let profileThumbnail: String From 22bba19937a9b2b97b12856d53aa30b16daebf6c Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 13:47:09 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor=20#87:=20=ED=96=85=ED=8B=B1=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Utility/Interact/Haptic/HapticManager.swift | 7 +++++++ Projects/Features/Sources/Onboarding/OnboardingView.swift | 2 +- .../Features/Sources/Registration/RegistrationView.swift | 1 + Projects/Features/Sources/SignIn/SignInView.swift | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Projects/Core/Sources/Utility/Interact/Haptic/HapticManager.swift b/Projects/Core/Sources/Utility/Interact/Haptic/HapticManager.swift index b3d65ace..a19e943b 100644 --- a/Projects/Core/Sources/Utility/Interact/Haptic/HapticManager.swift +++ b/Projects/Core/Sources/Utility/Interact/Haptic/HapticManager.swift @@ -54,6 +54,13 @@ public class HapticManager { generator.impactOccurred() } + /// 붕 + public func boong() { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } + /// 패턴 재생(패턴은 추후 협의 후 개발해서 추가) public func playHapticPattern() { do { diff --git a/Projects/Features/Sources/Onboarding/OnboardingView.swift b/Projects/Features/Sources/Onboarding/OnboardingView.swift index 2a9fcf71..8da8784f 100644 --- a/Projects/Features/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Features/Sources/Onboarding/OnboardingView.swift @@ -116,7 +116,7 @@ public struct OnboardingView: View { .foregroundColor(.black) } .onTapGesture { - HapticManager.shared.tok() + HapticManager.shared.boong() viewStore.send(.nextButtonDidTap) } .padding(Padding.insets(leading: 16, trailing: 16)) diff --git a/Projects/Features/Sources/Registration/RegistrationView.swift b/Projects/Features/Sources/Registration/RegistrationView.swift index 0e174bf9..48b3ed57 100644 --- a/Projects/Features/Sources/Registration/RegistrationView.swift +++ b/Projects/Features/Sources/Registration/RegistrationView.swift @@ -115,6 +115,7 @@ public struct RegistrationView: View { // 다음 페이지로 넘어가기 위한 Button Button(action: { + HapticManager.shared.boong() viewStore.send( .finishRegister( nickname: viewStore.nicknameTextFieldString, diff --git a/Projects/Features/Sources/SignIn/SignInView.swift b/Projects/Features/Sources/SignIn/SignInView.swift index 96574667..8953e2be 100644 --- a/Projects/Features/Sources/SignIn/SignInView.swift +++ b/Projects/Features/Sources/SignIn/SignInView.swift @@ -9,6 +9,7 @@ // import AuthenticationServices +import Core import ComposableArchitecture import DSKit import SwiftUI @@ -52,6 +53,7 @@ public struct SignInView: View { var body: some View { Button(action: { store.send(.signInWithKakao) + HapticManager.shared.boong() }) { Image("kakao_login") .resizable() @@ -68,6 +70,7 @@ public struct SignInView: View { var body: some View { SignInWithAppleButton( onRequest: { request in + HapticManager.shared.boong() request.requestedScopes = [.fullName, .email] }, onCompletion: { completion in From 2df9aa312e885d700fbad4f4682385d6777ea8da Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 18:29:10 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix=20#88:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Sources/Registration/RegisterFeature.swift | 8 +++++++- .../Features/Sources/Registration/RegistrationView.swift | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Projects/Features/Sources/Registration/RegisterFeature.swift b/Projects/Features/Sources/Registration/RegisterFeature.swift index 5005361d..0965b63f 100644 --- a/Projects/Features/Sources/Registration/RegisterFeature.swift +++ b/Projects/Features/Sources/Registration/RegisterFeature.swift @@ -54,13 +54,19 @@ public struct RegistrationFeature: Reducer { Reduce { state, action in switch action { case .debouncedNicknameUpdate(let nicknameString): + guard !nicknameString.isEmpty else { + return .none + } + state.nicknameTextFieldString = nicknameString + state.isNicknameAvailable = nil + return .run { send in try await withTaskCancellation( id: CancelID.debouncedNicknameUpdate, cancelInFlight: true ) { - try await self.clock.sleep(for: .seconds(0.7)) + try await self.clock.sleep(for: .seconds(0.3)) await send(.checkDuplicatedNickname(nicknameString)) } diff --git a/Projects/Features/Sources/Registration/RegistrationView.swift b/Projects/Features/Sources/Registration/RegistrationView.swift index 48b3ed57..2d449645 100644 --- a/Projects/Features/Sources/Registration/RegistrationView.swift +++ b/Projects/Features/Sources/Registration/RegistrationView.swift @@ -87,7 +87,10 @@ public struct RegistrationView: View { RoundedRectangle(cornerRadius: 4) .stroke(.white.opacity(0.3), lineWidth: 1) ) - .background(.black.opacity(0.4)) + .background( + RoundedRectangle(cornerRadius: 4) + .fill(.black.opacity(0.4)) + ) .modifier(Shake(isShake: $isShake)) if @@ -111,8 +114,8 @@ public struct RegistrationView: View { .lineSpacing(10) .foregroundColor(.white) ) - .padding(.vertical, 24) - + .padding(.bottom, 64) + // 다음 페이지로 넘어가기 위한 Button Button(action: { HapticManager.shared.boong() From 7ea76c829eebecfdc4f42f7f6a67f496a899776d Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 18:57:32 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor=20#87:=20=EC=95=BD=EA=B4=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Onboarding/OnboardingView.swift | 2 +- .../Sources/Registration/RegistrationView.swift | 7 ++++--- .../Features/Sources/SignIn/SignInView.swift | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Projects/Features/Sources/Onboarding/OnboardingView.swift b/Projects/Features/Sources/Onboarding/OnboardingView.swift index 8da8784f..5dc5a9aa 100644 --- a/Projects/Features/Sources/Onboarding/OnboardingView.swift +++ b/Projects/Features/Sources/Onboarding/OnboardingView.swift @@ -99,7 +99,7 @@ public struct OnboardingView: View { Color.clear .contentShape(Circle()) .onTapGesture { - HapticManager.shared.tongtong() + HapticManager.shared.boong() viewStore.send(.startButtonDidTap) } } diff --git a/Projects/Features/Sources/Registration/RegistrationView.swift b/Projects/Features/Sources/Registration/RegistrationView.swift index 2d449645..420b4e84 100644 --- a/Projects/Features/Sources/Registration/RegistrationView.swift +++ b/Projects/Features/Sources/Registration/RegistrationView.swift @@ -142,9 +142,6 @@ public struct RegistrationView: View { .padding(.bottom, 20) .disabled(viewStore.state.canRegister ? false : true) } - .onTapGesture { - isTextFieldFocused = false - } .padding(.horizontal, 16) .onChange(of: nickname) { newValue in guard 1 <= newValue.count, newValue.count <= 6 else { @@ -164,6 +161,10 @@ public struct RegistrationView: View { .fullFrame() .ignoresSafeArea(.keyboard, edges: .bottom) } + .contentShape(Rectangle()) + .onTapGesture { + isTextFieldFocused = false + } } } diff --git a/Projects/Features/Sources/SignIn/SignInView.swift b/Projects/Features/Sources/SignIn/SignInView.swift index 8953e2be..9ba1d816 100644 --- a/Projects/Features/Sources/SignIn/SignInView.swift +++ b/Projects/Features/Sources/SignIn/SignInView.swift @@ -87,13 +87,21 @@ public struct SignInView: View { } struct GuideMessageView: View { + let serviceTermURLString = "https://keyme.notion.site/Keyme-b1f3902d8fe04b97be6d8835119887cd?pvs=4" + let privacyTermURLString = "https://keyme.notion.site/Keyme-46bef61be1204fc594a49e85e5913a39?pvs=4" + var body: some View { VStack(spacing: 8) { Text("가입 시, 키미의 다음 사항에 동의하는 것으로 간주합니다.") .foregroundColor(.gray) HStack(spacing: 4) { - Button(action: {}) { + Button(action: { + guard let serviceTermURL = URL(string: serviceTermURLString) else { + return + } + UIApplication.shared.open(serviceTermURL) + }) { Text("서비스 이용약관") .fontWeight(.bold) .foregroundColor(.white) @@ -102,7 +110,12 @@ public struct SignInView: View { Text("및") .foregroundColor(.gray) - Button(action: {}) { + Button(action: { + guard let privacyTermURL = URL(string: privacyTermURLString) else { + return + } + UIApplication.shared.open(privacyTermURL) + }) { Text("개인정보 정책") .fontWeight(.bold) .foregroundColor(.white) From 58cc61fa873ca4d68e0595ebb7f39e213b0c81c0 Mon Sep 17 00:00:00 2001 From: enebin Date: Sat, 2 Sep 2023 19:20:52 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor=20#87:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectDescriptionHelpers/InfoPlist.swift | 3 ++- Projects/Keyme/Sources/KeymeApp.swift | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift index cc915eca..1c75b7a6 100644 --- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -28,7 +28,8 @@ public extension Project { "NSExceptionMinimumTLSVersion": "TLSv1.2", ], ] - ] + ], + "LSApplicationQueriesSchemes": ["kakaokompassauth", "kakaolink"] ] static let baseUrlInfoPlist: [String: InfoPlist.Value] = [ diff --git a/Projects/Keyme/Sources/KeymeApp.swift b/Projects/Keyme/Sources/KeymeApp.swift index bf26f2cf..8c704728 100644 --- a/Projects/Keyme/Sources/KeymeApp.swift +++ b/Projects/Keyme/Sources/KeymeApp.swift @@ -24,11 +24,6 @@ struct KeymeApp: App { var body: some Scene { WindowGroup { RootView() - .onOpenURL(perform: { url in - if (AuthApi.isKakaoTalkLoginUrl(url)) { - AuthController.handleOpenUrl(url: url) - } - }) } } } @@ -56,6 +51,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD return true } + func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey : Any] = [:] + ) -> Bool { + if (AuthApi.isKakaoTalkLoginUrl(url)) { + return AuthController.handleOpenUrl(url: url) + } + + return false + } + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() From 0dfe8d361b1f45d2275c584f3c9049b8396b612b Mon Sep 17 00:00:00 2001 From: enebin Date: Sun, 3 Sep 2023 07:58:18 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor=20#87:=20xcconfig=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Encrypted/XCConfig/App/DEV.xcconfig.encrypted | Bin 160 -> 256 bytes .../XCConfig/App/PROD.xcconfig.encrypted | 2 +- .../ProjectDescriptionHelpers/InfoPlist.swift | 6 +++- Projects/Keyme/Sources/KeymeApp.swift | 30 +++++++----------- .../Sources/Network/API/ShortUrlAPI.swift | 9 ++++-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Encrypted/XCConfig/App/DEV.xcconfig.encrypted b/Encrypted/XCConfig/App/DEV.xcconfig.encrypted index 44249a24f6a477e745f4fbc590d1999cbd440ddf..da673e05876f366546d7faf65f0265ce1f670dec 100644 GIT binary patch delta 184 zcmV;p07w6z0e}LKP=E3E)Epm1kx_yI>O>D==bhaxG3!J!MHs2ZBt#_ixOiEF#tZZI zwhcRbPYe~rgeM6hJHbbnj`KJy(py>Qlg%G-5LC4Q5Cl^)WaiWYwasiXByz0c5aW)A zhmE|cNrksLX%?&1zNd&9_&rLZS|kPECI@cEN6^hfH^yErpD|EL$Gb#Jm?fixXhd9~ mod-^-BH+oc`hw<0o}>O68I4S8Cj2^;0O*W~P^Ea~kH|m5u2j$f delta 87 zcmV-d0I2_f0-ynqP*93q(j>+Jh}nIbbO)OT9|#D^UrMp%bj+fr|9<;>9(%E7tMS@J twFget`4QZ?860O4KuhLLbX5+|gf(e(0J0og0L6(|!Swn%MgJmHEW*$QC@}y4 diff --git a/Encrypted/XCConfig/App/PROD.xcconfig.encrypted b/Encrypted/XCConfig/App/PROD.xcconfig.encrypted index 702bced4..5a5edbbd 100644 --- a/Encrypted/XCConfig/App/PROD.xcconfig.encrypted +++ b/Encrypted/XCConfig/App/PROD.xcconfig.encrypted @@ -1 +1 @@ -dBJؠLH 8v2cmH "?^F1Xno^ |т2x>SFgtmz \ԌuQ&uD0p>y|/N&UcŞ;RV$HKX6W]nu.i}Dş?~Jod >wR \ No newline at end of file +dBJؠLH 8v2cmH "?^F1Xno^ |т2x>SFgtmz \ԌuQ&=OpVfazuc)!wW0+SXl>7{mD@Z]增o_{ZEx6 l}5`W.?ho>Hˈ4: SѢC^7L \ No newline at end of file diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift index 1c75b7a6..154fa3a6 100644 --- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -16,10 +16,14 @@ public extension Project { "CFBundleURLTypes": [ [ "CFBundleTypeRole": "Editor", - "CFBundleURLSchemes": ["keyme"] + "CFBundleURLSchemes": [ + "keyme", + "kakao$(KAKAO_API_KEY)" + ] ] ], "API_BASE_URL": "$(API_BASE_URL)", + "KAKAO_API_KEY": "$(KAKAO_API_KEY)", "UIUserInterfaceStyle": "Light", "NSAppTransportSecurity": [ "NSExceptionDomains": [ diff --git a/Projects/Keyme/Sources/KeymeApp.swift b/Projects/Keyme/Sources/KeymeApp.swift index 8c704728..fff8c7f6 100644 --- a/Projects/Keyme/Sources/KeymeApp.swift +++ b/Projects/Keyme/Sources/KeymeApp.swift @@ -8,22 +8,22 @@ import FirebaseMessaging import Features import Network -import KakaoSDKAuth import KakaoSDKCommon +import KakaoSDKAuth @main struct KeymeApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - let KAKAO_PRIVATE_KEY = "" // 🚨 SECRET 🚨 - - init() { - KakaoSDK.initSDK(appKey: KAKAO_PRIVATE_KEY) - } - var body: some Scene { WindowGroup { RootView() + .onOpenURL { url in + print(url) + if (AuthApi.isKakaoTalkLoginUrl(url)) { + _ = AuthController.handleOpenUrl(url: url) + } + } } } } @@ -33,6 +33,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + if let kakaoAPIKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_API_KEY") as? String { + KakaoSDK.initSDK(appKey: kakaoAPIKey) + } + FirebaseApp.configure() UNUserNotificationCenter.current().delegate = self @@ -51,18 +55,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD return true } - func application( - _ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey : Any] = [:] - ) -> Bool { - if (AuthApi.isKakaoTalkLoginUrl(url)) { - return AuthController.handleOpenUrl(url: url) - } - - return false - } - func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() diff --git a/Projects/Network/Sources/Network/API/ShortUrlAPI.swift b/Projects/Network/Sources/Network/API/ShortUrlAPI.swift index b42ab6d0..2a30eb9d 100644 --- a/Projects/Network/Sources/Network/API/ShortUrlAPI.swift +++ b/Projects/Network/Sources/Network/API/ShortUrlAPI.swift @@ -44,7 +44,12 @@ extension ShortUrlAPI: TargetType { } public var headers: [String : String]? { - let accessToken = "e9a1ab0011a56327138c36652c2242cdff37ee1b" // TODO: 밖으로 - return ["Authorization": "Bearer \(accessToken)", "Content-Type": "application/json"] + var header = ["Content-Type": "application/json"] + + if let accessToken = Bundle.main.object(forInfoDictionaryKey: "BITLY_API_KEY") as? String { + header["Authorization"] = "Bearer \(accessToken)" + } + + return header } }