diff --git a/DOKI.xcodeproj/project.pbxproj b/DOKI.xcodeproj/project.pbxproj
index 8b590991..2ad429f5 100644
--- a/DOKI.xcodeproj/project.pbxproj
+++ b/DOKI.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
766B2E492E254BD600F3A668 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 766B2E482E254BD600F3A668 /* Moya */; };
+ 76C154E62EE46DDF00A8ED40 /* NMapsMap in Frameworks */ = {isa = PBXBuildFile; productRef = 76C154E52EE46DDF00A8ED40 /* NMapsMap */; };
76ECCF2D2E05C4820056CAF7 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 76ECCF2C2E05C4820056CAF7 /* Kingfisher */; };
76ECCF302E05C4CC0056CAF7 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 76ECCF2F2E05C4CC0056CAF7 /* Lottie */; };
/* End PBXBuildFile section */
@@ -43,6 +44,7 @@
buildActionMask = 2147483647;
files = (
76ECCF2D2E05C4820056CAF7 /* Kingfisher in Frameworks */,
+ 76C154E62EE46DDF00A8ED40 /* NMapsMap in Frameworks */,
76ECCF302E05C4CC0056CAF7 /* Lottie in Frameworks */,
766B2E492E254BD600F3A668 /* Moya in Frameworks */,
);
@@ -90,6 +92,7 @@
76ECCF2C2E05C4820056CAF7 /* Kingfisher */,
76ECCF2F2E05C4CC0056CAF7 /* Lottie */,
766B2E482E254BD600F3A668 /* Moya */,
+ 76C154E52EE46DDF00A8ED40 /* NMapsMap */,
);
productName = DoggyWalker;
productReference = 76ECCDED2E05AFCC0056CAF7 /* DOKI.app */;
@@ -123,6 +126,7 @@
76ECCF2B2E05C4820056CAF7 /* XCRemoteSwiftPackageReference "Kingfisher" */,
76ECCF2E2E05C4CC0056CAF7 /* XCRemoteSwiftPackageReference "lottie-ios" */,
766B2E472E254BD600F3A668 /* XCRemoteSwiftPackageReference "Moya" */,
+ 76C154E42EE46DDF00A8ED40 /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 76ECCDEE2E05AFCC0056CAF7 /* Products */;
@@ -162,7 +166,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- BASE_URL = "www.pawkey.o-r.kr/api/v1/";
+ BASE_URL = "";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -228,7 +232,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- BASE_URL = "www.pawkey.o-r.kr/api/v1/";
+ BASE_URL = "";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -282,12 +286,15 @@
};
76ECCDF92E05AFCC0056CAF7 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 76ECCDEF2E05AFCC0056CAF7 /* DOKI */;
+ baseConfigurationReferenceRelativePath = Config.xcconfig;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = DOKI/DOKI.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = "";
+ DEVELOPMENT_TEAM = Z9PQC69UPK;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DOKI/Info.plist;
@@ -319,9 +326,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = DOKI/DOKI.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = "";
+ DEVELOPMENT_TEAM = Z9PQC69UPK;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DOKI/Info.plist;
@@ -380,6 +388,14 @@
minimumVersion = 15.0.3;
};
};
+ 76C154E42EE46DDF00A8ED40 /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/navermaps/SPM-NMapsMap";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 3.23.0;
+ };
+ };
76ECCF2B2E05C4820056CAF7 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
@@ -404,6 +420,11 @@
package = 766B2E472E254BD600F3A668 /* XCRemoteSwiftPackageReference "Moya" */;
productName = Moya;
};
+ 76C154E52EE46DDF00A8ED40 /* NMapsMap */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 76C154E42EE46DDF00A8ED40 /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */;
+ productName = NMapsMap;
+ };
76ECCF2C2E05C4820056CAF7 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 76ECCF2B2E05C4820056CAF7 /* XCRemoteSwiftPackageReference "Kingfisher" */;
diff --git a/DOKI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DOKI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 7a434681..deb29b09 100644
--- a/DOKI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/DOKI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
{
- "originHash" : "fc639fd733ced6b35537b4c9370e6e9a4b9aa79783e3568a6f68abcb40d4a8fa",
+ "originHash" : "5e223e348c5e7fc2156f51eba94fe4e748e00223d98a5ef154d32f929a170c17",
"pins" : [
{
"identity" : "alamofire",
@@ -54,6 +54,24 @@
"revision" : "5dd1907d64f0d36f158f61a466bab75067224893",
"version" : "6.9.0"
}
+ },
+ {
+ "identity" : "spm-nmapsgeometry",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/navermaps/SPM-NMapsGeometry.git",
+ "state" : {
+ "revision" : "436d5e2e684f557faf5ef5862fd6633a42d7af11",
+ "version" : "1.0.2"
+ }
+ },
+ {
+ "identity" : "spm-nmapsmap",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/navermaps/SPM-NMapsMap",
+ "state" : {
+ "revision" : "fb4eef37db9904c0a0dcdf7a828c892e13782cfa",
+ "version" : "3.23.0"
+ }
}
],
"version" : 3
diff --git a/DOKI/Application/DOKIApp.swift b/DOKI/Application/DOKIApp.swift
index 9839d122..c28d9b33 100644
--- a/DOKI/Application/DOKIApp.swift
+++ b/DOKI/Application/DOKIApp.swift
@@ -10,7 +10,7 @@ import SwiftUI
@main
struct DOKIApp: App {
@StateObject var appDIContainer = AppDIContainer()
- @StateObject var authManager = AuthManager()
+ @StateObject var authManager = AuthManager.shared
var body: some Scene {
WindowGroup {
diff --git a/DOKI/DOKI.entitlements b/DOKI/DOKI.entitlements
new file mode 100644
index 00000000..a812db50
--- /dev/null
+++ b/DOKI/DOKI.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.applesignin
+
+ Default
+
+
+
diff --git a/DOKI/Global/Manager/AuthManager.swift b/DOKI/Global/Manager/AuthManager.swift
index aabd9b2f..8988d5c4 100644
--- a/DOKI/Global/Manager/AuthManager.swift
+++ b/DOKI/Global/Manager/AuthManager.swift
@@ -7,6 +7,8 @@
import SwiftUI
+import Moya
+
enum AuthState: String, CaseIterable {
case loggedIn
case loggedOut
@@ -14,13 +16,64 @@ enum AuthState: String, CaseIterable {
}
class AuthManager: ObservableObject {
+ static let shared = AuthManager()
+
@Published var authStatus: AuthState = .loading
+ private(set) var accessToken: String?
+ private(set) var refreshToken: String?
+
+ private let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()])
+
+ private init() {}
+
func checkLogin() {
- authStatus = .loggedIn
+ do {
+ self.accessToken = try KeychainManager.read(.accessToken)
+ self.refreshToken = try KeychainManager.read(.refreshToken)
+ authStatus = .loggedIn
+ } catch {
+ authStatus = .loggedOut
+ print(error.localizedDescription)
+ }
}
- func login() {
- authStatus = .loggedIn
+ /// AppleLogin API
+ func loginWithApple(_ idToken: String, deviceId: String) async {
+ do {
+ let appleLoginReqDto = AppleLoginRequestDTO(idToken: idToken, deviceId: deviceId)
+ let response: AppleLoginResponseDTO = try await provider.async.request(.appleLogin(appleLoginReqDto: appleLoginReqDto))
+ try KeychainManager.create(.accessToken, response.accessToken)
+ try KeychainManager.create(.refreshToken, response.refreshToken)
+ self.accessToken = response.accessToken
+ self.refreshToken = response.refreshToken
+
+ authStatus = .loggedIn
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+
+ func logout() {
+ do {
+ try KeychainManager.delete(.accessToken)
+ try KeychainManager.delete(.refreshToken)
+ authStatus = .loggedOut
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+
+ func reissueToken(accessToken: String, refreshToken: String) {
+ do {
+ try KeychainManager.create(.accessToken, accessToken)
+ try KeychainManager.create(.refreshToken, refreshToken)
+ self.accessToken = accessToken
+ self.refreshToken = refreshToken
+ } catch {
+ print(error.localizedDescription)
+ }
}
}
+
+
diff --git a/DOKI/Global/Manager/KeychainManager.swift b/DOKI/Global/Manager/KeychainManager.swift
new file mode 100644
index 00000000..b0e9442d
--- /dev/null
+++ b/DOKI/Global/Manager/KeychainManager.swift
@@ -0,0 +1,81 @@
+//
+// KeychainManager.swift
+// DOKI
+//
+// Created by a on 12/7/25.
+//
+
+import Foundation
+
+enum KeychainError: Error {
+ case noPassword
+ case unhandledError(status: OSStatus)
+ case unexpectedPasswordData
+
+ var message: String {
+ switch self {
+ case .noPassword: return "No password available."
+ case .unexpectedPasswordData: return "Expected data, but found none."
+ case .unhandledError(let status): return "Unhandled error with status: \(status)"
+ }
+ }
+}
+
+enum KeychainName: String {
+ case accessToken
+ case refreshToken
+}
+
+struct KeychainManager {
+
+ /// Keychain 저장소에서 key에 해당하는 값을 추가
+ static func create(_ key: KeychainName, _ value: T) throws {
+ do {
+ let valueData = try JSONEncoder().encode(value)
+ let query: NSDictionary = [
+ kSecClass: kSecClassGenericPassword,
+ kSecAttrAccount: key.rawValue,
+ kSecValueData: valueData
+ ]
+ SecItemDelete(query)
+
+ let status = SecItemAdd(query, nil)
+ guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
+ } catch {
+ throw KeychainError.unexpectedPasswordData
+ }
+ }
+
+ /// Keychain 저장소에서 key에 해당하는 값을 검색
+ @discardableResult
+ static func read(_ key: KeychainName) throws -> String? {
+ let query: NSDictionary = [kSecClass: kSecClassGenericPassword,
+ kSecAttrAccount: key.rawValue,
+ kSecMatchLimit: kSecMatchLimitOne,
+ kSecReturnData: true]
+ var item: CFTypeRef?
+ let status = SecItemCopyMatching(query as CFDictionary, &item)
+ guard status != errSecItemNotFound else { throw KeychainError.noPassword }
+ if status == errSecSuccess {
+ if let retrievedItem = item as? Data {
+ let returnValue = String(data: retrievedItem, encoding: String.Encoding.utf8)
+ return returnValue
+ } else {
+ return nil
+ }
+ } else {
+ throw KeychainError.unexpectedPasswordData
+ }
+ }
+
+ /// key에 해당하는 값을 삭제
+ static func delete(_ key: KeychainName) throws {
+ let query: NSDictionary = [
+ kSecClass: kSecClassGenericPassword,
+ kSecAttrAccount: key.rawValue
+ ]
+ let status = SecItemDelete(query)
+ guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
+ }
+}
+
diff --git a/DOKI/Network/Base/AuthInterceptor.swift b/DOKI/Network/Base/AuthInterceptor.swift
new file mode 100644
index 00000000..5f0f5026
--- /dev/null
+++ b/DOKI/Network/Base/AuthInterceptor.swift
@@ -0,0 +1,88 @@
+//
+// AuthInterceptor.swift
+// DOKI
+//
+// Created by a on 12/9/25.
+//
+
+import Foundation
+
+import Moya
+import Alamofire
+
+final class AuthInterceptor: RequestInterceptor {
+ static let shared = AuthInterceptor()
+
+ private init() {}
+
+ // 네트워크 요청하기전 헤더에 accessToken 추가
+ func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) {
+ var request = urlRequest
+
+ if request.url?.absoluteString.contains("auth/refresh") == true {
+ completion(.success(request))
+ return
+ }
+ if let accessToken = AuthManager.shared.accessToken {
+ request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+ }
+
+ completion(.success(request))
+ }
+
+ func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) {
+ // 401인 경우가 아니라면 종료
+ guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
+ completion(.doNotRetryWithError(error))
+ return
+ }
+
+ // refreshToken 가져오기 없다면 종료
+ guard let refreshToken = AuthManager.shared.refreshToken?.replacingOccurrences(of: "\"", with: "") else {
+ completion(.doNotRetry)
+ AuthManager.shared.logout()
+ return
+ }
+
+ // 토큰 재발급 API 호출 & 토큰 교체
+ var refreshRequest = URLRequest(url: URL(string: Config.baseURL + "auth/refresh")!)
+ refreshRequest.httpMethod = "POST"
+ refreshRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
+
+ let requestBody = try? JSONSerialization.data(withJSONObject: ["refreshToken": refreshToken, "deviceId": "doki-service"])
+ refreshRequest.httpBody = requestBody
+ let defaultSession = URLSession(configuration: .default)
+
+ defaultSession.dataTask(with: refreshRequest) { (data: Data?, response: URLResponse?, error: Error?) in
+ // 에러 발생시 재요청x
+ guard error == nil else {
+ completion(.doNotRetry)
+ AuthManager.shared.logout()
+ return
+ }
+
+ guard let data, let response = response as? HTTPURLResponse, (200..<300) ~= response.statusCode else {
+ completion(.doNotRetry)
+ AuthManager.shared.logout()
+ return
+ }
+
+ // 토큰 재발급 요청 성공
+ do {
+ let response = try JSONDecoder().decode(AppleLoginResponseDTO.self, from: data)
+ // 토큰 재발급
+ AuthManager.shared.reissueToken(
+ accessToken: response.accessToken,
+ refreshToken: response.refreshToken
+ )
+ // 재요청
+ print("토큰 재발급 성공 - 재요청")
+ completion(.retry)
+ } catch {
+ print("토큰 재발급 실패 - 로그아웃")
+ completion(.doNotRetryWithError(error))
+ AuthManager.shared.logout()
+ }
+ }.resume()
+ }
+}
diff --git a/DOKI/Network/Base/BaseTargetType.swift b/DOKI/Network/Base/BaseTargetType.swift
index 1525152d..6537b90d 100644
--- a/DOKI/Network/Base/BaseTargetType.swift
+++ b/DOKI/Network/Base/BaseTargetType.swift
@@ -10,8 +10,7 @@ import Foundation
import Moya
enum HeaderType {
- case noneHeader
- case userHeader(userId: Int)
+ case defaultHeader
}
protocol BaseTargetType: TargetType {
@@ -28,13 +27,8 @@ extension BaseTargetType {
var headers: [String: String]? {
switch headerType {
- case .noneHeader:
- return nil
- case .userHeader(let userId):
- return [
- "Content-Type": "application/json",
- "X-USER-ID": String(userId),
- ]
+ case .defaultHeader:
+ return ["Content-Type": "application/json"]
}
}
}
diff --git a/DOKI/Network/Login/DTOs/AppleLoginRequestDTO.swift b/DOKI/Network/Login/DTOs/AppleLoginRequestDTO.swift
new file mode 100644
index 00000000..cb95a719
--- /dev/null
+++ b/DOKI/Network/Login/DTOs/AppleLoginRequestDTO.swift
@@ -0,0 +1,13 @@
+//
+// AppleLoginRequestDTO.swift
+// DOKI
+//
+// Created by a on 12/7/25.
+//
+
+import Foundation
+
+struct AppleLoginRequestDTO: Encodable {
+ let idToken: String
+ let deviceId: String
+}
diff --git a/DOKI/Network/Login/DTOs/AppleLoginResponseDTO.swift b/DOKI/Network/Login/DTOs/AppleLoginResponseDTO.swift
new file mode 100644
index 00000000..5019cd5d
--- /dev/null
+++ b/DOKI/Network/Login/DTOs/AppleLoginResponseDTO.swift
@@ -0,0 +1,13 @@
+//
+// AppleLoginResponseDTO.swift
+// DOKI
+//
+// Created by a on 12/7/25.
+//
+
+import Foundation
+
+struct AppleLoginResponseDTO: Codable {
+ let accessToken: String
+ let refreshToken: String
+}
diff --git a/DOKI/Network/Login/LoginAPI.swift b/DOKI/Network/Login/LoginAPI.swift
new file mode 100644
index 00000000..f1f84381
--- /dev/null
+++ b/DOKI/Network/Login/LoginAPI.swift
@@ -0,0 +1,44 @@
+//
+// LoginAPI.swift
+// DOKI
+//
+// Created by a on 12/7/25.
+//
+
+import Foundation
+
+import Moya
+
+enum LoginAPI {
+ case appleLogin(appleLoginReqDto: AppleLoginRequestDTO)
+}
+
+extension LoginAPI: BaseTargetType {
+ var headerType: HeaderType {
+ switch self {
+ case .appleLogin:
+ return .defaultHeader
+ }
+ }
+
+ var path: String {
+ switch self {
+ case .appleLogin:
+ return "auth/apple/login"
+ }
+ }
+
+ var method: Moya.Method {
+ switch self {
+ case .appleLogin:
+ return .post
+ }
+ }
+
+ var task: Task {
+ switch self {
+ case let .appleLogin(appleLoginReqDto):
+ return .requestJSONEncodable(appleLoginReqDto)
+ }
+ }
+}
diff --git a/DOKI/Network/Region/RegionAPI.swift b/DOKI/Network/Region/RegionAPI.swift
new file mode 100644
index 00000000..f97c33ab
--- /dev/null
+++ b/DOKI/Network/Region/RegionAPI.swift
@@ -0,0 +1,48 @@
+//
+// RegionAPI.swift
+// DOKI
+//
+// Created by a on 12/9/25.
+//
+
+import Foundation
+
+import Moya
+
+enum RegionAPI {
+ case getRegions
+}
+
+extension RegionAPI: BaseTargetType {
+ var validationType: ValidationType {
+ .successCodes
+ }
+
+ var headerType: HeaderType {
+ switch self {
+ case .getRegions:
+ return .defaultHeader
+ }
+ }
+
+ var path: String {
+ switch self {
+ case .getRegions:
+ return "regions"
+ }
+ }
+
+ var method: Moya.Method {
+ switch self {
+ case .getRegions:
+ return .get
+ }
+ }
+
+ var task: Task {
+ switch self {
+ case .getRegions:
+ return .requestPlain
+ }
+ }
+}
diff --git a/DOKI/Presentation/Home/View/HomeView.swift b/DOKI/Presentation/Home/View/HomeView.swift
index 1c80e8d0..82bb024e 100644
--- a/DOKI/Presentation/Home/View/HomeView.swift
+++ b/DOKI/Presentation/Home/View/HomeView.swift
@@ -7,10 +7,26 @@
import SwiftUI
+import Moya
+
struct HomeView: View {
@StateObject var viewModel: HomeViewModel
+ private let provider = MoyaProvider(session: .init(interceptor: AuthInterceptor.shared), plugins: [NetworkLoggerPlugin()])
+ @State var errorMessage = ""
+
var body: some View {
- Text("홈")
+ VStack {
+ Text("홈")
+ Text(errorMessage)
+ }
+ .task {
+ do {
+ let response: BaseDTO = try await provider.async.request(.getRegions)
+ } catch {
+ print(error.localizedDescription)
+ errorMessage = error.localizedDescription
+ }
+ }
}
}
diff --git a/DOKI/Presentation/Login/View/LoginView.swift b/DOKI/Presentation/Login/View/LoginView.swift
index 398f4959..4773a3fa 100644
--- a/DOKI/Presentation/Login/View/LoginView.swift
+++ b/DOKI/Presentation/Login/View/LoginView.swift
@@ -6,6 +6,7 @@
//
import SwiftUI
+import AuthenticationServices
struct LoginView: View {
@StateObject var viewModel: LoginViewModel
@@ -33,11 +34,17 @@ struct LoginView: View {
}
.padding(.horizontal, 16)
- AppleLoginButton {
-
- }
+ AppleLoginButton {}
.padding(.top, 8)
.padding(.horizontal, 16)
+ .overlay(
+ SignInWithAppleButton(
+ onRequest: viewModel.requestAppleLogin(_:),
+ onCompletion: viewModel.onCompleteAppleLogin(_:)
+ )
+ .frame(height: 50)
+ .blendMode(.destinationOver)
+ )
}
}
.overlay(alignment: .trailing) {
diff --git a/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift b/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift
index 52a6a65f..b1366567 100644
--- a/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift
+++ b/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift
@@ -6,15 +6,41 @@
//
import SwiftUI
+import AuthenticationServices
class LoginViewModel: ObservableObject {
private let loginCoordinator: Coordinator
+ private let authManager: AuthManager
- init(loginCoordinator: Coordinator) {
+ init(loginCoordinator: Coordinator,
+ authManager: AuthManager = .shared) {
self.loginCoordinator = loginCoordinator
+ self.authManager = authManager
}
- func navigateToRegister() {
+ /// 유저정보 등록화면으로 이동
+ func navigateToRegister() {
loginCoordinator.push(.register)
}
+
+ /// Apple 로그인 요청
+ func requestAppleLogin(_ request :ASAuthorizationAppleIDRequest) {
+ request.requestedScopes = [.fullName, .email]
+ }
+
+ /// Apple 로그인 요청 완료
+ func onCompleteAppleLogin(_ result: Result) {
+ switch result {
+ case .success(let authResult):
+ if let appleIDCredential = authResult.credential as? ASAuthorizationAppleIDCredential,
+ let identityTokenData = appleIDCredential.identityToken,
+ let identityToken = String(data: identityTokenData, encoding: .utf8) {
+ Task {
+ await authManager.loginWithApple(identityToken, deviceId: "doki-service")
+ }
+ }
+ case .failure(let error):
+ print(error.localizedDescription)
+ }
+ }
}
diff --git a/DOKI/Presentation/Register/View/RegisterView.swift b/DOKI/Presentation/Register/View/RegisterView.swift
index 0b6eb8cf..857f73ca 100644
--- a/DOKI/Presentation/Register/View/RegisterView.swift
+++ b/DOKI/Presentation/Register/View/RegisterView.swift
@@ -59,7 +59,7 @@ extension RegisterView {
private var mainButton: some View {
MainButton(text: viewModel.isLastStep ? "완료" : "다음", buttonState: viewModel.buttonDisabled ? .disabled : .active1) {
if viewModel.isLastStep {
- authManager.login()
+ // TODO: 홈으로 이동
} else {
viewModel.goToNextStep()
}