diff --git a/DOKI.xcodeproj/project.pbxproj b/DOKI.xcodeproj/project.pbxproj index 7ff62590..3447776a 100644 --- a/DOKI.xcodeproj/project.pbxproj +++ b/DOKI.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 00232BF72EB4E32E00F26249 /* Exceptions for "DOKI" folder in "DOKI" target */ = { + 007D0E9E2F123D5600BCD675 /* Exceptions for "DOKI" folder in "DOKI" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, @@ -31,7 +31,7 @@ 76ECCDEF2E05AFCC0056CAF7 /* DOKI */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 00232BF72EB4E32E00F26249 /* Exceptions for "DOKI" folder in "DOKI" target */, + 007D0E9E2F123D5600BCD675 /* Exceptions for "DOKI" folder in "DOKI" target */, ); path = DOKI; sourceTree = ""; @@ -166,7 +166,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - BASE_URL = ""; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -232,7 +231,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - BASE_URL = ""; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -286,10 +284,13 @@ }; 76ECCDF92E05AFCC0056CAF7 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = 76ECCDEF2E05AFCC0056CAF7 /* DOKI */; + baseConfigurationReferenceRelativePath = Config.xcconfig; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; BASE_URL = ""; + CODE_SIGN_ENTITLEMENTS = DOKI/DOKI.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = Z9PQC69UPK; @@ -321,10 +322,13 @@ }; 76ECCDFA2E05AFCC0056CAF7 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = 76ECCDEF2E05AFCC0056CAF7 /* DOKI */; + baseConfigurationReferenceRelativePath = Config.xcconfig; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; BASE_URL = ""; + CODE_SIGN_ENTITLEMENTS = DOKI/DOKI.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = Z9PQC69UPK; diff --git a/DOKI/Application/DOKIApp.swift b/DOKI/Application/DOKIApp.swift index 9839d122..e5fea183 100644 --- a/DOKI/Application/DOKIApp.swift +++ b/DOKI/Application/DOKIApp.swift @@ -10,13 +10,17 @@ import SwiftUI @main struct DOKIApp: App { @StateObject var appDIContainer = AppDIContainer() - @StateObject var authManager = AuthManager() + @StateObject var authManager = AuthManager.shared var body: some Scene { WindowGroup { RootView() .environmentObject(authManager) .environmentObject(appDIContainer) + .onAppear { + try? KeychainManager.delete(.accessToken) + try? KeychainManager.delete(.refreshToken) + } } } } 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..22028717 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,73 @@ enum AuthState: String, CaseIterable { } class AuthManager: ObservableObject { + static let shared = AuthManager() + @Published var authStatus: AuthState = .loading + @Published var isNewUser: Bool = false + + 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(authorizationCode: 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 + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + isNewUser = response.isNewUser + + if !isNewUser { + 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..ed8f86a1 --- /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 authorizationCode: 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..bdb33488 --- /dev/null +++ b/DOKI/Network/Login/DTOs/AppleLoginResponseDTO.swift @@ -0,0 +1,14 @@ +// +// AppleLoginResponseDTO.swift +// DOKI +// +// Created by a on 12/7/25. +// + +import Foundation + +struct AppleLoginResponseDTO: Codable { + let accessToken: String + let refreshToken: String + let isNewUser: Bool +} 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/LoginCoordinatorView.swift b/DOKI/Presentation/Login/View/LoginCoordinatorView.swift index ff01572a..b79ebfe9 100644 --- a/DOKI/Presentation/Login/View/LoginCoordinatorView.swift +++ b/DOKI/Presentation/Login/View/LoginCoordinatorView.swift @@ -14,6 +14,7 @@ enum LoginRoute: Route { struct LoginCoordinatorView: View { @StateObject var loginCoordinator: Coordinator @StateObject var loginViewModel: LoginViewModel + @StateObject var authManager: AuthManager = .shared let viewModelFactory: AppDIContainer.ViewModelFactory @@ -34,6 +35,11 @@ struct LoginCoordinatorView: View { RegisterView(viewModel: viewModel) } } + .onReceive(authManager.$isNewUser) { isNewUser in + if isNewUser { + loginCoordinator.push(.register) + } + } } } } 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..e2399fdc 100644 --- a/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift +++ b/DOKI/Presentation/Login/ViewModel/LoginViewModel.swift @@ -6,15 +6,42 @@ // 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.authorizationCode, + let identityToken = String(data: identityTokenData, encoding: .utf8) { + + Task { + await authManager.loginWithApple(identityToken, deviceId: "device_abc123") + } + } + 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..4e7ca15d 100644 --- a/DOKI/Presentation/Register/View/RegisterView.swift +++ b/DOKI/Presentation/Register/View/RegisterView.swift @@ -8,6 +8,7 @@ import SwiftUI struct RegisterView: View { + @Environment(\.dismiss) private var dismiss @EnvironmentObject var authManager: AuthManager @ObservedObject var viewModel: RegisterViewModel @@ -26,7 +27,13 @@ struct RegisterView: View { } }) .topNavigationView { - BackButton { viewModel.goToPrevStep() } + BackButton { + if viewModel.isFirstStep { + dismiss() + } else { + viewModel.goToPrevStep() + } + } } center: { Text(viewModel.currentStep.navTitle) .subtitle() @@ -59,7 +66,8 @@ extension RegisterView { private var mainButton: some View { MainButton(text: viewModel.isLastStep ? "완료" : "다음", buttonState: viewModel.buttonDisabled ? .disabled : .active1) { if viewModel.isLastStep { - authManager.login() + // TODO: API 연동 전까지 임시 코드 + authManager.authStatus = .loggedIn } else { viewModel.goToNextStep() } diff --git a/DOKI/Presentation/Register/ViewModel/RegisterViewModel.swift b/DOKI/Presentation/Register/ViewModel/RegisterViewModel.swift index fdc0e29a..46f0190e 100644 --- a/DOKI/Presentation/Register/ViewModel/RegisterViewModel.swift +++ b/DOKI/Presentation/Register/ViewModel/RegisterViewModel.swift @@ -117,6 +117,7 @@ class RegisterViewModel: ObservableObject { } } var isLastStep: Bool { next == nil } + var isFirstStep: Bool { prev == nil } // MARK: - User Action @@ -125,7 +126,7 @@ class RegisterViewModel: ObservableObject { } func goToPrevStep() { - if let prev { currentStep = prev } + if let prev { currentStep = prev } } func selecteGender(_ gender: Gender) { diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Contents.json b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Contents.json index 7387a60e..a5929557 100644 --- a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Contents.json +++ b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "img_logindog.png", + "filename" : "Frame 2087330180.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "img_logindog@2x.png", + "filename" : "Frame 2087330180@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "img_logindog@3x.png", + "filename" : "Frame 2087330180@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180.png new file mode 100644 index 00000000..5207fc9e Binary files /dev/null and b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180.png differ diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@2x.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@2x.png new file mode 100644 index 00000000..ab18f06b Binary files /dev/null and b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@2x.png differ diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@3x.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@3x.png new file mode 100644 index 00000000..32474600 Binary files /dev/null and b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/Frame 2087330180@3x.png differ diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog.png deleted file mode 100644 index 80d5906a..00000000 Binary files a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog.png and /dev/null differ diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@2x.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@2x.png deleted file mode 100644 index b5c4f5f0..00000000 Binary files a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@2x.png and /dev/null differ diff --git a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@3x.png b/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@3x.png deleted file mode 100644 index 69db61c9..00000000 Binary files a/DOKI/Resource/Assets.xcassets/Images/img_logindog.imageset/img_logindog@3x.png and /dev/null differ