diff --git a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate index bcbd1d0..9842947 100644 Binary files a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate and b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AcaMate/0. Setup/SwiftUI_Prefix.swift b/AcaMate/0. Setup/SwiftUI_Prefix.swift index cbd8ffd..66faa09 100644 --- a/AcaMate/0. Setup/SwiftUI_Prefix.swift +++ b/AcaMate/0. Setup/SwiftUI_Prefix.swift @@ -40,6 +40,8 @@ public let WS_URL: String = "wss://acamate.ipstein.myds.me" #endif +public let API_HEADER = "iOS_AM_Connect_Key" + // MARK: - TYPEALIAS typealias VOID_TO_VOID = () -> () diff --git a/AcaMate/1. View/10. Common/NavigationView.swift b/AcaMate/1. View/10. Common/NavigationView.swift index af74041..3d25772 100644 --- a/AcaMate/1. View/10. Common/NavigationView.swift +++ b/AcaMate/1. View/10. Common/NavigationView.swift @@ -22,9 +22,9 @@ struct NavigationView: View { case .NONE: EmptyView() case .Intro: - IntroView() + IntroView(appVM: appVM) case .Login : - LoginView() + LoginView(appVM: appVM) case .Register(let type, let id): RegisterView(type: type, snsID: id) case .SelectAcademy: diff --git a/AcaMate/1. View/11. Intro & Login/IntroView.swift b/AcaMate/1. View/11. Intro & Login/IntroView.swift index a2fed0d..f169434 100644 --- a/AcaMate/1. View/11. Intro & Login/IntroView.swift +++ b/AcaMate/1. View/11. Intro & Login/IntroView.swift @@ -9,10 +9,15 @@ import SwiftUI import Combine struct IntroView: View { - @EnvironmentObject var appVM: AppViewModel +// @EnvironmentObject var appVM: AppViewModel + @StateObject private var introVM : IntroViewModel @State var cancellables: Set = [] -// @Binding var naviState : NaviState + + init(appVM: AppViewModel){ + _introVM = StateObject(wrappedValue: IntroViewModel(appVM: appVM)) +// self.introVM = IntroViewModel(appVM: appVM) + } var body: some View { VStack(spacing: 0) { @@ -39,95 +44,7 @@ struct IntroView: View { .onAppear { printLog("IntroView_onAppear") - #if LOCAL - appVM.naviState.set(act: .RESET, path: .Login) - #else - subscribeAlertAction() - loadVersion() - .sink { completion in - switch completion { - case .failure(let error): - printLog(error) - case .finished: break - } - } receiveValue: { version in - @UserDefault(key:"currentVer", defaultValue: "0.0.0") var currentVer - @UserDefault(key:"finalVer", defaultValue: "0.0.0") var finalVer - currentVer = currentVersion() - finalVer = version.final_ver - - let compareForce = compareVersion(version.force_ver, currentVer)//currentVersion()) - let compareChoice = compareVersion(version.final_ver, currentVer)//currentVersion()) - - if compareForce == .bigger { - appVM.alertData = SetAlertData().setForceUpdate( - action: appVM.alertAction - ) - appVM.showAlert.toggle() - } else if compareChoice == .bigger && version.choice_update_yn { - appVM.alertData = SetAlertData().setSelectUpdate( - action: appVM.alertAction - ) - appVM.showAlert.toggle() - } else { - // 정상 동작 넘어감 - appVM.naviState.set(act: .RESET, path: .Login) -// naviState.set(act: .RESET, path: .Login) - - - } - } - .store(in: &cancellables) - #endif - + introVM.appStart() } } - - private func subscribeAlertAction() { - appVM.alertAction - .compactMap { $0 } - .sink { action in - if action == "updateNow" { - exit(1) - //MARK: - TODO (앱스토어 이동 로직 넣을 것) - } else { - appVM.naviState.set(act: .RESET, path: .Login) - } - }.store(in: &cancellables) - } - - - private func loadVersion() -> Future { - return Future { promise in - let request = APIRequest(path: "/api/v1/in/app/version", - parameters: ["type":"I"], - decoding: APIResponse.self) - - APIManager.shared.loadAPIData(request) - .sink { completion in - switch completion { - case .failure(let error): - printLog("\(error)") - promise(.failure(error)) - case .finished: break - } - } receiveValue: { data in - guard let apiData = data as? APIResponse, let version = apiData.data else {return} - printLog("\(version.toStringDict())") - promise(.success(version)) - } - .store(in: &cancellables) - } - - } - - private func currentVersion() -> String { - guard let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return "" } - return currentVer - - } - - private func versionChange(ver: String) -> [Int] { - return ver.components(separatedBy: ["."]).map {Int($0) ?? 0} - } } diff --git a/AcaMate/1. View/11. Intro & Login/LoginView.swift b/AcaMate/1. View/11. Intro & Login/LoginView.swift index 8d06828..c06ad42 100644 --- a/AcaMate/1. View/11. Intro & Login/LoginView.swift +++ b/AcaMate/1. View/11. Intro & Login/LoginView.swift @@ -9,8 +9,11 @@ import SwiftUI import Combine struct LoginView: View { - @EnvironmentObject var appVM: AppViewModel - @StateObject private var loginVM = LoginViewModel() +// @EnvironmentObject var appVM: AppViewModel + @StateObject private var loginVM: LoginViewModel + init(appVM : AppViewModel) { + _loginVM = StateObject(wrappedValue: LoginViewModel(appVM: appVM)) + } var body: some View { VStack(spacing: 0) { @@ -25,7 +28,7 @@ struct LoginView: View { VStack(spacing: 16) { Button { // MARK: - TODO, 카카오 계정 로그인 구현 - loginVM.toggleLoading = true +// loginVM.toggleLoading = true loginVM.loginAction(type: .Kakao) } label: { @@ -37,7 +40,7 @@ struct LoginView: View { // appVM.naviState.set(act: .ADD, path: .SelectAcademy(bids: ["AA0000", "AA0001"])) // loginVM.toggleLoading = true // loginVM.loginTest(type: .Kakao, id: "TestSNSID1@#") - loginVM.USERPAITEST() +// loginVM.USERPAITEST() } label: { @@ -51,13 +54,14 @@ struct LoginView: View { } .frame(maxWidth: .infinity,maxHeight: .infinity) .fullDrawView(.Normal.normal) - .onChange(of: loginVM.pathName){ _, new in - appVM.naviState.set(act: .ADD, path: new) - } - .onChange(of: loginVM.toggleLoading) { _, new in - appVM.isLoading = new - } +// .onChange(of: loginVM.pathName){ _, new in +// appVM.naviState.set(act: .ADD, path: new) +// } +// +// .onChange(of: loginVM.toggleLoading) { _, new in +// appVM.isLoading = new +// } } diff --git a/AcaMate/2. Model/Alert.swift b/AcaMate/2. Model/Alert.swift index c5dcb47..9ef6f6f 100644 --- a/AcaMate/2. Model/Alert.swift +++ b/AcaMate/2. Model/Alert.swift @@ -82,6 +82,17 @@ struct SetAlertData { ]) } + /// 서버에서 발생한 크리티컬한 오류 - 앱 종료가 최선 + func setServerError(action: CurrentValueSubject) -> AlertData { + return AlertData(title: "시스템 오류", body: "시스템이 정상적이지 않습니다. \n확인 후 다시 시도해주세요.", + button: [ + ButtonType(name: "확인", role: .cancel, + function: { + action.send("exit") + }) + ]) + } + /// 로그인 문제 발생 func setErrorLogin() -> AlertData { return AlertData(title: "로그인", diff --git a/AcaMate/3. ViewModel/AppViewModel.swift b/AcaMate/3. ViewModel/AppViewModel.swift index 11c3a4f..ac3eec1 100644 --- a/AcaMate/3. ViewModel/AppViewModel.swift +++ b/AcaMate/3. ViewModel/AppViewModel.swift @@ -16,6 +16,7 @@ class AppViewModel: ObservableObject { var alertData: AlertData = .init(body: "") + /// 항상 최신값을 가지고 있다가 구독자 추가 되면 그 즉시 값을 전달하고 이후 업데이트 되는 값을 계속 보내주는 역할을 함 let alertAction = CurrentValueSubject(nil) } diff --git a/AcaMate/3. ViewModel/IntroViewModel.swift b/AcaMate/3. ViewModel/IntroViewModel.swift index cfb7666..c11cd61 100644 --- a/AcaMate/3. ViewModel/IntroViewModel.swift +++ b/AcaMate/3. ViewModel/IntroViewModel.swift @@ -5,4 +5,161 @@ // Created by TAnine on 3/20/25. // -import Foundation +import SwiftUI +import Combine + +class IntroViewModel: ObservableObject { + var appVM : AppViewModel + private var cancellables = Set() + +// @Published var toggleLoading: Bool = false +// @Published var pathName: PathName = .NONE + @UserDefault(key: "header", defaultValue: "headerValue") var headerValue + + init(appVM: AppViewModel) { + self.appVM = appVM + } + + func appStart() { + //#if LOCAL + // pathName = .Login + //#else + subscribeAlertAction() + searchHeader() + .flatMap { success -> Future in + return self.loadVersion() + } + .sink { completion in + switch completion { + case .failure(let error): + printLog(error) + case .finished: break + } + } receiveValue: { [weak self] version in + guard let self = self else {return} + @UserDefault(key:"currentVer", defaultValue: "0.0.0") var currentVer + @UserDefault(key:"finalVer", defaultValue: "0.0.0") var finalVer + currentVer = currentVersion() + finalVer = version.final_ver + + let compareForce = compareVersion(version.force_ver, currentVer)//currentVersion()) + let compareChoice = compareVersion(version.final_ver, currentVer)//currentVersion()) + + if compareForce == .bigger { + appVM.alertData = SetAlertData().setForceUpdate( + action: appVM.alertAction + ) + appVM.showAlert.toggle() + } else if compareChoice == .bigger && version.choice_update_yn { + appVM.alertData = SetAlertData().setSelectUpdate( + action: appVM.alertAction + ) + appVM.showAlert.toggle() + } else { + // 정상 동작 넘어감 + appVM.naviState.set(act: .RESET, path: .Login) + + } + } + .store(in: &cancellables) + } + + func searchHeader() -> Future { + return Future { [weak self] promise in + + guard let self = self else { + promise(.failure(ACA_ERROR("Self 전환 오류"))) + return + } + + guard let deviceId = UIDevice.current.identifierForVendor?.uuidString, + let bundleId = Bundle.main.bundleIdentifier else { + promise(.failure(ACA_ERROR("번들/디바이스 아이디 조회 불량"))) + return + } + + let request = APIRequest(path: "/api/v1/in/app", + parameters: ["type": "I", "specific": deviceId, "project": bundleId], + decoding: APIResponse
.self) + + + APIManager.shared.loadAPIData(request) + .sink { completion in + switch completion { + case .failure(let error): + printLog("최종 에러: \(error)") + promise(.failure(error)) + + case .finished: + break + } + } receiveValue: { [weak self] response in + guard let self = self else {return} + if let data = response.data { + self.headerValue = data.header + promise(.success(true)) + } + else { + promise(.failure(ACA_ERROR("데이터 없음"))) + } + } + .store(in: &self.cancellables) + } + // + } + + + private func subscribeAlertAction() { + appVM.alertAction + .compactMap { $0 } + .sink { [weak self] action in + guard let self = self else {return} + if action == "exit" { + exit(1) + } else if action == "updateNow" { + exit(1) + //MARK: - TODO (앱스토어 이동 로직 넣을 것) + } else { + appVM.naviState.set(act: .RESET, path: .Login) + } + }.store(in: &cancellables) + } + + private func loadVersion() -> Future { + return Future { [weak self] promise in + guard let self = self else {return} + let request = APIRequest(path: "/api/v1/in/app/version", + headers: [API_HEADER : self.headerValue], + parameters: ["type":"I"], + decoding: APIResponse.self) + + APIManager.shared.loadAPIData(request) + .sink { completion in + switch completion { + case .failure(let error): + printLog("\(error)") + promise(.failure(error)) + case .finished: break + } + } receiveValue: { versionData in + guard let version = versionData.data else {return} + printLog("\(version.toStringDict())") + promise(.success(version)) + } + .store(in: &cancellables) + } + + } + + private func currentVersion() -> String { + guard let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return "" } + return currentVer + + } + + private func versionChange(ver: String) -> [Int] { + return ver.components(separatedBy: ["."]).map {Int($0) ?? 0} + } + + +} diff --git a/AcaMate/3. ViewModel/LoginViewModel.swift b/AcaMate/3. ViewModel/LoginViewModel.swift index de3a22a..313c69c 100644 --- a/AcaMate/3. ViewModel/LoginViewModel.swift +++ b/AcaMate/3. ViewModel/LoginViewModel.swift @@ -10,113 +10,67 @@ import Combine class LoginViewModel: ObservableObject { + let appVM: AppViewModel private var cancellables = Set() - @Published var toggleLoading: Bool = false - @Published var pathName: PathName = .NONE +// @Published var toggleLoading: Bool = false +// @Published var pathName: PathName = .NONE + + @UserDefault(key: "token", defaultValue: "accToken") var accToken + @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh + @UserDefault(key: "header", defaultValue: "headerValue") var headerValue var bidArray: [String] = [] - func USERPAITEST() { - @UserDefault(key: "token", defaultValue: "accToken") var accToken - @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh - @UserDefault(key: "header", defaultValue: "headerValue") var headerValue - - // UIDevice의 identifierForVendor를 사용하여 고유 식별자 (UUID)를 문자열로 반환 - guard let deviceId = UIDevice.current.identifierForVendor?.uuidString else { return } - guard let bundleId = Bundle.main.bundleIdentifier else { return } - -// - let request = APIRequest(path: "/api/v1/in/app", - parameters: ["type": "I", "specific": deviceId, "project": bundleId], - decoding: APIResponse
.self) -// - APIManager.shared.loadAPIData(request) - .sink { completion in - switch completion { - case .failure(let error): - printLog("최종 에러: \(error)") - case .finished: - break - } - } receiveValue: { response in -// guard let response = response as? APIResponse
else { return } - printLog(response.data?.header) - } - .store(in: &cancellables) - /* - let acc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBTVRlc3RFbWFpbDIwMjUwMzE4IiwianRpIjoiMjgwMjIwZDMtYzUwNS00YjFjLTgwMzgtZjBlNGJjYzM4MTE3IiwiZXhwIjoxNzQyMjYzMzgwLCJpc3MiOiJBY2FNYXRlIiwiYXVkIjoiaHR0cHM6L2RldmFjYW1hdGUuaXBzdGVpbi5teWRzLm1lIn0.f6kLKnsWhzlllSuYKxpFNuXuV4vOtJ2ox4IGSnxE67Y" - - refresh = "MRo+1HIvaPgECXrvwmGvtUpxSk7Pip7KtGSoWDqmjVA=" - - let request = APIRequest(path: "/api/v1/in/user", - parameters: ["token": accToken], - decoding: APIResponse.self) - - APIManager.shared.loadUserAPIData(request: request) - .sink { completion in - switch completion { - case .failure(let error): - printLog("최종 에러: \(error)") - case .finished: - break - } - } receiveValue: { response in - guard let user = response.data as? User else { return } - printLog("최종 값 : \(user.name), \(user.birth)") - } - .store(in: &cancellables) - */ + init(appVM: AppViewModel) { + self.appVM = appVM } - func loginAction(type: SNSLoginType) { - + appVM.isLoading = true LoginController().login(type) .flatMap{ snsId in APIManager.shared.loadAPIData(APIRequest(path: "/api/v1/in/user/login", + headers: [API_HEADER : self.headerValue], parameters: [ - "acctype": "\(type == .Apple ? "ST00": "ST01")", - "sns_id": "\(snsId.snsId)" + "acctype": "\(type == .Apple ? "ST00": "ST01")", + "snsId": "\(snsId.snsId)" ], decoding: APIResponse.self)) .map { response in return (snsId: snsId.snsId, response: response) } } - .sink { [weak self] completion in + .sink { [weak self] completion in + guard let self = self else { return } // API 자체적으로 내보내는 에러는 여기서 거를거고 switch completion { case .failure(let error): - self?.toggleLoading = false + self.appVM.isLoading = false printLog("\(error)") case .finished: - self?.toggleLoading = false + self.appVM.isLoading = false } } receiveValue: { [weak self] response in guard let self = self else { return } - // let userToken = response.response // as? APIResponse else {return} let snsId = response.snsId - switch response.response.status.code { case .success(let code): if code == "000" { - @UserDefault(key: "token", defaultValue: "accToken") var accToken - @UserDefault(key: "refresh", defaultValue: "refreshToken") var refreshToken - if let token = response.response.data.toStringDict()["token"], let refresh = response.response.data.toStringDict()["refresh"] { printLog(token) printLog(refresh) - accToken = token - refreshToken = refresh - self.pathName = .SelectAcademy + + self.accToken = token + self.refresh = refresh + appVM.naviState.set(act: .ADD, path: .SelectAcademy) } } else { // 회원가입 진행 // 여기다가 타입도 추가해야 함 - self.pathName = .Register(type, id: "\(snsId)") + appVM.naviState.set(act: .ADD, path: .Register(type, id: "\(snsId)")) } case .anything(let apiCode): // MARK: TO-DO