diff --git a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj index 6fb53a9..57858a4 100644 --- a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj +++ b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 320043762CDCA18E00D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; }; 320043782CDCA49100D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; }; 3200437C2CDCA6F300D08B6D /* (null) in Sources */ = {isa = PBXBuildFile; }; - 3200438B2CDF3BEF00D08B6D /* ProfileSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200438A2CDF3BE900D08B6D /* ProfileSetupView.swift */; }; + 3200438B2CDF3BEF00D08B6D /* ProfileCreateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200438A2CDF3BE900D08B6D /* ProfileCreateViewController.swift */; }; 320043912CDF3D7F00D08B6D /* KeywordButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200438E2CDF3D7F00D08B6D /* KeywordButton.swift */; }; 320043922CDF3D7F00D08B6D /* InputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200438D2CDF3D7F00D08B6D /* InputTextField.swift */; }; 320043932CDF3D7F00D08B6D /* KeywordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200438F2CDF3D7F00D08B6D /* KeywordView.swift */; }; @@ -22,8 +22,16 @@ 3200441A2CE48A3D00D08B6D /* MPCBroswer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044192CE48A3100D08B6D /* MPCBroswer.swift */; }; 3200441C2CE48C5C00D08B6D /* MPCAdvertiser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200441B2CE48C5600D08B6D /* MPCAdvertiser.swift */; }; 3200441E2CE48D3500D08B6D /* MPConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200441D2CE48D1F00D08B6D /* MPConnectionManager.swift */; }; + 3200444E2CE595EA00D08B6D /* ProfileCreatePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200444D2CE595DE00D08B6D /* ProfileCreatePresenter.swift */; }; + 320044502CE595F700D08B6D /* ProfileInputPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200444F2CE595F000D08B6D /* ProfileInputPresenter.swift */; }; + 320044532CE59A8A00D08B6D /* Dog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044522CE59A8100D08B6D /* Dog.swift */; }; + 320044562CE59DF500D08B6D /* ProfileInputRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044552CE59D5D00D08B6D /* ProfileInputRouter.swift */; }; + 320044582CE5B04C00D08B6D /* ProfileCreateRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044572CE5B04300D08B6D /* ProfileCreateRouter.swift */; }; + 3200445A2CE5B3AA00D08B6D /* ProfileCreateInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044592CE5B39A00D08B6D /* ProfileCreateInteractor.swift */; }; + 3200445E2CE5CA1B00D08B6D /* SaveInfoUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3200445D2CE5CA1A00D08B6D /* SaveInfoUseCase.swift */; }; + 320044672CE6125100D08B6D /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320044662CE6124900D08B6D /* DataManager.swift */; }; 995D94262CE32ECE005A47BF /* Extension + Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995D94252CE32EC1005A47BF /* Extension + Keyboard.swift */; }; - 99FA4E792CE0FBBF00553C2E /* ProfileInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FA4E782CE0FBBF00553C2E /* ProfileInputView.swift */; }; + 99FA4E792CE0FBBF00553C2E /* ProfileInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FA4E782CE0FBBF00553C2E /* ProfileInputViewController.swift */; }; A21435F72CE20D2500564A4D /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21435F62CE20D2500564A4D /* TabBarController.swift */; }; A288A63D2CDC595000D11C34 /* (null) in Resources */ = {isa = PBXBuildFile; }; A2C328862CE245AC00D255AB /* TabBarModuleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C328852CE245AC00D255AB /* TabBarModuleBuilder.swift */; }; @@ -66,7 +74,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 3200438A2CDF3BE900D08B6D /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = ""; }; + 3200438A2CDF3BE900D08B6D /* ProfileCreateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCreateViewController.swift; sourceTree = ""; }; 3200438C2CDF3D7F00D08B6D /* Extension + Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension + Navigation.swift"; sourceTree = ""; }; 3200438D2CDF3D7F00D08B6D /* InputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextField.swift; sourceTree = ""; }; 3200438E2CDF3D7F00D08B6D /* KeywordButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordButton.swift; sourceTree = ""; }; @@ -76,8 +84,16 @@ 320044192CE48A3100D08B6D /* MPCBroswer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCBroswer.swift; sourceTree = ""; }; 3200441B2CE48C5600D08B6D /* MPCAdvertiser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCAdvertiser.swift; sourceTree = ""; }; 3200441D2CE48D1F00D08B6D /* MPConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPConnectionManager.swift; sourceTree = ""; }; + 3200444D2CE595DE00D08B6D /* ProfileCreatePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCreatePresenter.swift; sourceTree = ""; }; + 3200444F2CE595F000D08B6D /* ProfileInputPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileInputPresenter.swift; sourceTree = ""; }; + 320044522CE59A8100D08B6D /* Dog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dog.swift; sourceTree = ""; }; + 320044552CE59D5D00D08B6D /* ProfileInputRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileInputRouter.swift; sourceTree = ""; }; + 320044572CE5B04300D08B6D /* ProfileCreateRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCreateRouter.swift; sourceTree = ""; }; + 320044592CE5B39A00D08B6D /* ProfileCreateInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCreateInteractor.swift; sourceTree = ""; }; + 3200445D2CE5CA1A00D08B6D /* SaveInfoUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveInfoUseCase.swift; sourceTree = ""; }; + 320044662CE6124900D08B6D /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 995D94252CE32EC1005A47BF /* Extension + Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension + Keyboard.swift"; sourceTree = ""; }; - 99FA4E782CE0FBBF00553C2E /* ProfileInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileInputView.swift; sourceTree = ""; }; + 99FA4E782CE0FBBF00553C2E /* ProfileInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileInputViewController.swift; sourceTree = ""; }; A21435F62CE20D2500564A4D /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; A2C328852CE245AC00D255AB /* TabBarModuleBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarModuleBuilder.swift; sourceTree = ""; }; A2C328872CE2481900D255AB /* HomeModuleBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModuleBuilder.swift; sourceTree = ""; }; @@ -143,6 +159,14 @@ path = MPC; sourceTree = ""; }; + 320044512CE5995F00D08B6D /* Entity */ = { + isa = PBXGroup; + children = ( + 320044522CE59A8100D08B6D /* Dog.swift */, + ); + path = Entity; + sourceTree = ""; + }; A21435F52CE20CF600564A4D /* Tab */ = { isa = PBXGroup; children = ( @@ -606,7 +630,7 @@ FDA491682CDA7C0300A36FB0 /* View */, FDA491662CDA7BE500A36FB0 /* Interactor */, FDA491642CDA7BD600A36FB0 /* Presenter */, - FDA491652CDA7BDF00A36FB0 /* Entity */, + 320044512CE5995F00D08B6D /* Entity */, FDA491672CDA7BEB00A36FB0 /* Router */, ); path = CreateProfile; @@ -658,20 +682,18 @@ FDA491642CDA7BD600A36FB0 /* Presenter */ = { isa = PBXGroup; children = ( + 3200444F2CE595F000D08B6D /* ProfileInputPresenter.swift */, + 3200444D2CE595DE00D08B6D /* ProfileCreatePresenter.swift */, ); path = Presenter; sourceTree = ""; }; - FDA491652CDA7BDF00A36FB0 /* Entity */ = { - isa = PBXGroup; - children = ( - ); - path = Entity; - sourceTree = ""; - }; FDA491662CDA7BE500A36FB0 /* Interactor */ = { isa = PBXGroup; children = ( + 320044662CE6124900D08B6D /* DataManager.swift */, + 320044592CE5B39A00D08B6D /* ProfileCreateInteractor.swift */, + 3200445D2CE5CA1A00D08B6D /* SaveInfoUseCase.swift */, ); path = Interactor; sourceTree = ""; @@ -679,6 +701,8 @@ FDA491672CDA7BEB00A36FB0 /* Router */ = { isa = PBXGroup; children = ( + 320044552CE59D5D00D08B6D /* ProfileInputRouter.swift */, + 320044572CE5B04300D08B6D /* ProfileCreateRouter.swift */, ); path = Router; sourceTree = ""; @@ -686,8 +710,8 @@ FDA491682CDA7C0300A36FB0 /* View */ = { isa = PBXGroup; children = ( - 99FA4E782CE0FBBF00553C2E /* ProfileInputView.swift */, - 3200438A2CDF3BE900D08B6D /* ProfileSetupView.swift */, + 99FA4E782CE0FBBF00553C2E /* ProfileInputViewController.swift */, + 3200438A2CDF3BE900D08B6D /* ProfileCreateViewController.swift */, ); path = View; sourceTree = ""; @@ -1098,9 +1122,11 @@ files = ( FDBFA9A12CE1E51500AA9220 /* SNMColor.swift in Sources */, A2C844D12CDBE1E5007F2970 /* (null) in Sources */, + 3200444E2CE595EA00D08B6D /* ProfileCreatePresenter.swift in Sources */, FD3A03422CD8CF460047B7ED /* AppDelegate.swift in Sources */, FD3A03432CD8CF460047B7ED /* SceneDelegate.swift in Sources */, - 99FA4E792CE0FBBF00553C2E /* ProfileInputView.swift in Sources */, + 99FA4E792CE0FBBF00553C2E /* ProfileInputViewController.swift in Sources */, + 320044582CE5B04C00D08B6D /* ProfileCreateRouter.swift in Sources */, FDEAEA7C2CE314A7005890FA /* BaseViewController.swift in Sources */, A21435F72CE20D2500564A4D /* TabBarController.swift in Sources */, 320043762CDCA18E00D08B6D /* (null) in Sources */, @@ -1112,6 +1138,7 @@ FD0634282CE596BD003C9D6B /* SNMRequestConvertible.swift in Sources */, 320043972CDF493C00D08B6D /* Extension + UIView.swift in Sources */, FD0634262CE596B5003C9D6B /* Endpoint.swift in Sources */, + 320044502CE595F700D08B6D /* ProfileInputPresenter.swift in Sources */, FD69B1B32CE3BC76009D71DC /* KeychainManager.swift in Sources */, 320043782CDCA49100D08B6D /* (null) in Sources */, FD06342A2CE596D0003C9D6B /* NetworkProvider.swift in Sources */, @@ -1120,11 +1147,15 @@ 320043922CDF3D7F00D08B6D /* InputTextField.swift in Sources */, 320043932CDF3D7F00D08B6D /* KeywordView.swift in Sources */, FD06342C2CE5984D003C9D6B /* SNMNetworkResponse.swift in Sources */, + 3200445E2CE5CA1B00D08B6D /* SaveInfoUseCase.swift in Sources */, 320043942CDF3D7F00D08B6D /* PrimaryButton.swift in Sources */, 320043952CDF3D7F00D08B6D /* Extension + Navigation.swift in Sources */, FDBFA99F2CE1E50C00AA9220 /* SNMFont.swift in Sources */, + 320044532CE59A8A00D08B6D /* Dog.swift in Sources */, + 320044562CE59DF500D08B6D /* ProfileInputRouter.swift in Sources */, A2C844D72CDBEF8B007F2970 /* (null) in Sources */, - 3200438B2CDF3BEF00D08B6D /* ProfileSetupView.swift in Sources */, + 3200438B2CDF3BEF00D08B6D /* ProfileCreateViewController.swift in Sources */, + 320044672CE6125100D08B6D /* DataManager.swift in Sources */, FDEAEA7A2CE3148D005890FA /* BaseView.swift in Sources */, A2C328862CE245AC00D255AB /* TabBarModuleBuilder.swift in Sources */, FD69B1B52CE3BC7E009D71DC /* UserDefaultsManager.swift in Sources */, @@ -1133,6 +1164,7 @@ FD0634312CE62578003C9D6B /* AnyEncodable.swift in Sources */, 3200441A2CE48A3D00D08B6D /* MPCBroswer.swift in Sources */, 3200437C2CDCA6F300D08B6D /* (null) in Sources */, + 3200445A2CE5B3AA00D08B6D /* ProfileCreateInteractor.swift in Sources */, A2C328922CE3BEA000D255AB /* AppRouter.swift in Sources */, 320043702CDC9A0D00D08B6D /* (null) in Sources */, ); diff --git a/SniffMeet/SniffMeet/Source/App/AppRouter.swift b/SniffMeet/SniffMeet/Source/App/AppRouter.swift index 4b32492..33c5873 100644 --- a/SniffMeet/SniffMeet/Source/App/AppRouter.swift +++ b/SniffMeet/SniffMeet/Source/App/AppRouter.swift @@ -13,8 +13,6 @@ final class AppRouter { init(window: UIWindow?) { self.window = window } - - // 첫 화면을 뭘 보여줄지는 회원 가입 여부에 따라 앱 플로우가 다르므로, UIWindow를 이용해야 합니다. func displayInitialScreen(isLoggedIn: Bool) { if isLoggedIn { displayTabBar() @@ -24,7 +22,7 @@ final class AppRouter { } private func displayTabBar() { let submodules = ( - home: HomeModuleBuilder.build(), + home: UINavigationController(rootViewController: HomeModuleBuilder.build()), walk: UIViewController(), mate: UIViewController() ) @@ -32,8 +30,18 @@ final class AppRouter { window?.makeKeyAndVisible() } private func displayProfileSetupView() { - let profileViewController = ProfileInputView() - window?.rootViewController = profileViewController + let navigationController = + UINavigationController(rootViewController: ProfileInputRouter.createProfileInputModule()) + window?.rootViewController = navigationController window?.makeKeyAndVisible() } + + func moveToHomeScreen() { + let submodules = ( + home: UINavigationController(rootViewController: HomeModuleBuilder.build()), + walk: UIViewController(), + mate: UIViewController() + ) + window?.rootViewController = TabBarModuleBuilder.build(usingSubmodules: submodules) + } } diff --git a/SniffMeet/SniffMeet/Source/App/SceneDelegate.swift b/SniffMeet/SniffMeet/Source/App/SceneDelegate.swift index 3a0f8f0..395d7b7 100644 --- a/SniffMeet/SniffMeet/Source/App/SceneDelegate.swift +++ b/SniffMeet/SniffMeet/Source/App/SceneDelegate.swift @@ -17,12 +17,10 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { options connectionOptions: UIScene.ConnectionOptions ) { guard let windowScene = (scene as? UIWindowScene) else { return } - let window = UIWindow(windowScene: windowScene) - let appRouter = AppRouter(window: window) - self.window = window - self.appRouter = appRouter + + window = UIWindow(windowScene: windowScene) + appRouter = AppRouter(window: window) - appRouter.displayInitialScreen(isLoggedIn: true) - window.makeKeyAndVisible() + appRouter?.displayInitialScreen(isLoggedIn: false) } } diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Entity/Dog.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Entity/Dog.swift new file mode 100644 index 0000000..0beee7f --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Entity/Dog.swift @@ -0,0 +1,38 @@ +// +// DogDTO.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// +import Foundation + +struct DogDetailInfo { + let name: String + let age: UInt8 + let size: Size + let keywords: [Keyword] +} + +enum Size: Codable { + case small + case medium + case big +} + +enum Keyword: String, Codable { + case energetic = "활발한" + case smart = "똑똑한" + case friendly = "친화력 좋은" + case shy = "소심한" + case independent = "독립적인" +} + + +struct Dog: Codable { + let name: String + let age: UInt8 + let size: Size + let keywords: [Keyword] + let nickname: String + let profileImage: Data? +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/DataManager.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/DataManager.swift new file mode 100644 index 0000000..74c2e04 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/DataManager.swift @@ -0,0 +1,28 @@ +// +// DataManager.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// + +import Foundation + +protocol DataStorable { + func storeData(data: Encodable) throws +} + +// TODO: - dataManager를 주입받을 수 있도록 수정 예정 +final class LocalDataManager: DataStorable { + private let dataManager = UserDefaultsManager(userDefaults: UserDefaults(suiteName: "demo")!, + jsonEncoder: JSONEncoder(), + jsonDecoder: JSONDecoder()) + func storeData(data: any Encodable) throws { + try dataManager.set(value: data, forKey: UserDefaultKey.dogInfo) + } +} + +extension LocalDataManager { + private enum UserDefaultKey { + static let dogInfo: String = "dogInfo" + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/ProfileCreateInteractor.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/ProfileCreateInteractor.swift new file mode 100644 index 0000000..8968623 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/ProfileCreateInteractor.swift @@ -0,0 +1,32 @@ +// +// ProfileCreateInteractable.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// + +protocol ProfileCreateInteractable: AnyObject { + var presenter: DogInfoInteractorOutput? { get set } + var storeDogInfoUsecase: StoreDogInfoUseCase { get set } + + func saveDogInfo(dogInfo: Dog) +} + +final class ProfileCreateInteractor: ProfileCreateInteractable { + weak var presenter: DogInfoInteractorOutput? + var storeDogInfoUsecase: StoreDogInfoUseCase + + init(presenter: DogInfoInteractorOutput? = nil, usecase: StoreDogInfoUseCase) { + self.presenter = presenter + storeDogInfoUsecase = usecase + } + + func saveDogInfo(dogInfo: Dog) { + do { + try storeDogInfoUsecase.execute(dog: dogInfo) + presenter?.didSaveDogInfo() + } catch (let error){ + presenter?.didFailToSaveDogInfo(error: error) + } + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/SaveInfoUseCase.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/SaveInfoUseCase.swift new file mode 100644 index 0000000..19330dc --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Interactor/SaveInfoUseCase.swift @@ -0,0 +1,24 @@ +// +// SaveInfoUseCase.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// +import Foundation + +protocol StoreDogInfoUseCase { + func execute(dog: Dog) throws +} + + +final class StoreDogInfoUserCaseImpl: StoreDogInfoUseCase { + let localDataManager: DataStorable + + init(localDataManager: DataStorable) { + self.localDataManager = localDataManager + } + + func execute(dog: Dog) throws { + try localDataManager.storeData(data: dog) + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileCreatePresenter.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileCreatePresenter.swift new file mode 100644 index 0000000..6b3a6ef --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileCreatePresenter.swift @@ -0,0 +1,61 @@ +// +// ProfileSetupPresenter.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// +import Foundation + +protocol ProfileCreatePresentable : AnyObject{ + var dogInfo: DogDetailInfo { get set } + var view: ProfileCreateViewable? { get set } + var interactor: ProfileCreateInteractable? { get set } + var router: ProfileCreateRoutable? { get set } + + func saveDogInfo(nickname: String, imageData: Data?) +} + +protocol DogInfoInteractorOutput: AnyObject { + func didSaveDogInfo() + func didFailToSaveDogInfo(error: Error) +} + + +final class ProfileCreatePresenter: ProfileCreatePresentable { + var dogInfo: DogDetailInfo + weak var view: ProfileCreateViewable? + var interactor: ProfileCreateInteractable? + var router: ProfileCreateRoutable? + + init(dogInfo: DogDetailInfo, + view: ProfileCreateViewable? = nil, + interactor: ProfileCreateInteractable? = nil, + router: ProfileCreateRoutable? = nil) + { + self.dogInfo = dogInfo + self.view = view + self.interactor = interactor + self.router = router + } + + func saveDogInfo(nickname: String, imageData: Data?) { + interactor?.saveDogInfo(dogInfo: Dog(name: dogInfo.name, + age: dogInfo.age, + size: dogInfo.size, + keywords: dogInfo.keywords, + nickname: nickname, + profileImage: imageData)) + + } +} + +extension ProfileCreatePresenter: DogInfoInteractorOutput { + func didSaveDogInfo() { + guard let view else { return } + router?.presentMainScreen(from: view) + } + + func didFailToSaveDogInfo(error: any Error) { + // TODO: - alert 올리는데 어떻게 올릴지 정하기 + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileInputPresenter.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileInputPresenter.swift new file mode 100644 index 0000000..fc045df --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Presenter/ProfileInputPresenter.swift @@ -0,0 +1,23 @@ +// +// ProfileInputPresenter.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// + +protocol ProfileInputPresentable { + var view: ProfileInputViewable? { get set } + var router: ProfileInputRoutable? { get set } + func moveToProfileCreateView(with newDogDetailInfo: DogDetailInfo) +} + + +final class ProfileInputPresenter: ProfileInputPresentable { + weak var view: ProfileInputViewable? + var router: ProfileInputRoutable? + + func moveToProfileCreateView(with newDogDetailInfo: DogDetailInfo) { + guard let view else { return } + router?.presentPostCreateScreen(from: view, with: newDogDetailInfo) + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileCreateRouter.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileCreateRouter.swift new file mode 100644 index 0000000..ddfcc6e --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileCreateRouter.swift @@ -0,0 +1,50 @@ +// +// ProfileCreateRouter.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// +import UIKit + +protocol ProfileCreateRoutable { + func presentMainScreen(from view: ProfileCreateViewable) +} + +protocol ProfileCreateBuildable { + static func createProfileCreateModule(dogDetailInfo: DogDetailInfo) -> UIViewController +} + +final class ProfileCreateRouter: ProfileCreateRoutable { + func presentMainScreen(from view: any ProfileCreateViewable) { + if let sceneDelegate = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive })? + .delegate as? SceneDelegate { + if let router = sceneDelegate.appRouter { + router.moveToHomeScreen() + } + } + } +} + +extension ProfileCreateRouter: ProfileCreateBuildable { + static func createProfileCreateModule(dogDetailInfo: DogDetailInfo) -> UIViewController { + let storeDogInfoUsecase: StoreDogInfoUseCase = + StoreDogInfoUserCaseImpl(localDataManager: LocalDataManager()) + + let view: ProfileCreateViewable & UIViewController = ProfileCreateViewController() + let presenter: ProfileCreatePresentable & DogInfoInteractorOutput + = ProfileCreatePresenter(dogInfo: dogDetailInfo) + let interactor: ProfileCreateInteractable = + ProfileCreateInteractor(usecase: storeDogInfoUsecase) + let router: ProfileCreateRoutable & ProfileCreateBuildable = ProfileCreateRouter() + + view.presenter = presenter + presenter.view = view + presenter.router = router + presenter.interactor = interactor + interactor.presenter = presenter + + return view + } + +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileInputRouter.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileInputRouter.swift new file mode 100644 index 0000000..d47ec86 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/Router/ProfileInputRouter.swift @@ -0,0 +1,41 @@ +// +// Untitled.swift +// SniffMeet +// +// Created by 윤지성 on 11/14/24. +// +import UIKit + +protocol ProfileInputRoutable { + func presentPostCreateScreen(from view: ProfileInputViewable, with dogDetail: DogDetailInfo) +} + +protocol ProfileInputBuildable { + static func createProfileInputModule() -> UIViewController +} + +final class ProfileInputRouter: ProfileInputRoutable { + func presentPostCreateScreen(from view: ProfileInputViewable, with dogDetail: DogDetailInfo) { + let profileCreateViewController = + ProfileCreateRouter.createProfileCreateModule(dogDetailInfo: dogDetail) + + if let sourceView = view as? UIViewController { + sourceView.navigationController?.pushViewController(profileCreateViewController, + animated: true) + } + } +} + +extension ProfileInputRouter: ProfileInputBuildable { + static func createProfileInputModule() -> UIViewController { + let view: ProfileInputViewable & UIViewController = ProfileInputViewController() + var presenter: ProfileInputPresentable = ProfileInputPresenter() + let router: ProfileInputRoutable & ProfileInputBuildable = ProfileInputRouter() + + view.presenter = presenter + presenter.view = view + presenter.router = router + + return view + } +} diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileSetupView.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileCreateViewController.swift similarity index 86% rename from SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileSetupView.swift rename to SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileCreateViewController.swift index 8c00fcb..2fa4eaf 100644 --- a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileSetupView.swift +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileCreateViewController.swift @@ -5,10 +5,16 @@ // Created by 윤지성 on 11/9/24. // -import UIKit import PhotosUI +import UIKit + +protocol ProfileCreateViewable: AnyObject { + var presenter: ProfileCreatePresentable? { get set } +} -final class ProfileSetupView: UIViewController { +final class ProfileCreateViewController: UIViewController, ProfileCreateViewable { + var presenter: ProfileCreatePresentable? + private var titleLabel: UILabel = { let label = UILabel() label.text = Context.mainTitle @@ -61,11 +67,11 @@ final class ProfileSetupView: UIViewController { } } -private extension ProfileSetupView { +private extension ProfileCreateViewController { enum Context { static let submitBtnTitle: String = "등록 완료" static let placeholder: String = "닉네임을 입력해주세요." - static let mainTitle: String = "마지막으로,\n사진과 닉네임을 등록해주세요." + static let mainTitle: String = "마지막으로,\n반려견 사진과 닉네임을 등록해주세요." static let horizontalPadding: CGFloat = 24 static let imageViewSize: CGFloat = 140 static let addPhotoButtonSize: CGFloat = 44 @@ -115,22 +121,30 @@ private extension ProfileSetupView { constant: Context.horizontalPadding), submitButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Context.horizontalPadding), - submitButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -32) + submitButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32) ]) } func setDelegate() { picker.delegate = self nicknameTextField.delegate = self } + func setButtonAction() { addPhotoButton.addAction(UIAction { [weak self] _ in guard let picker = self?.picker else { return } self?.present(picker, animated: true, completion: nil) }, for: .touchUpInside) + + submitButton.addAction(UIAction { [weak self] _ in + // approuter를 통해서 화면전환을 수행한다. window를 다르게 설정해야 할듯 + guard let nickname = self?.nicknameTextField.text else { return } + self?.presenter?.saveDogInfo(nickname: nickname, imageData: nil) + + }, for: .touchUpInside) } } -extension ProfileSetupView: PHPickerViewControllerDelegate { +extension ProfileCreateViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true, completion: nil) let itemProvider = results.first?.itemProvider @@ -146,7 +160,7 @@ extension ProfileSetupView: PHPickerViewControllerDelegate { } } -extension ProfileSetupView: UITextFieldDelegate { +extension ProfileCreateViewController: UITextFieldDelegate { func textFieldDidChangeSelection(_ textField: UITextField) { submitButton.isEnabled = (textField.text?.count ?? 0 > 1) } diff --git a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputView.swift b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputViewController.swift similarity index 90% rename from SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputView.swift rename to SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputViewController.swift index 742d7ad..73eea1d 100644 --- a/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputView.swift +++ b/SniffMeet/SniffMeet/Source/Auth/CreateProfile/View/ProfileInputViewController.swift @@ -7,7 +7,13 @@ import UIKit -final class ProfileInputView: UIViewController { +protocol ProfileInputViewable: AnyObject { + var presenter: ProfileInputPresentable? { get set } +} + +final class ProfileInputViewController: UIViewController, ProfileInputViewable { + var presenter: ProfileInputPresentable? + private var titleLabel: UILabel = { let label = UILabel() label.text = Context.titleLabel @@ -55,10 +61,26 @@ final class ProfileInputView: UIViewController { for: .editingChanged) ageTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + + setButtonAction() + } + + func setButtonAction() { + nextButton.addAction(UIAction { [weak self] _ in + guard let name = self?.nameTextField.text, + let ageText = self?.ageTextField.text, + let age = Int(ageText) else { return } + + let dogInfo = DogDetailInfo(name: name, + age: UInt8(age), + size: .small, + keywords: [.energetic]) + self?.presenter?.moveToProfileCreateView(with: dogInfo) + }, for: .touchUpInside) } } -private extension ProfileInputView { +private extension ProfileInputViewController { enum Context { static let nextBtnTitle: String = "다음으로" static let titleLabel: String = "반가워요!\n당신의 반려견을 소개해주세요." @@ -169,7 +191,7 @@ private extension ProfileInputView { } } -extension ProfileInputView: UITextFieldDelegate { +extension ProfileInputViewController: UITextFieldDelegate { @objc private func textFieldDidChange(_ textField: UITextField) { updateNextButtonState() } diff --git a/SniffMeet/SniffMeet/Source/Common/Extension + Keyboard.swift b/SniffMeet/SniffMeet/Source/Common/Extension + Keyboard.swift index 878d7ca..2991ba9 100644 --- a/SniffMeet/SniffMeet/Source/Common/Extension + Keyboard.swift +++ b/SniffMeet/SniffMeet/Source/Common/Extension + Keyboard.swift @@ -10,7 +10,7 @@ import UIKit extension UIViewController { func hideKeyboardWhenTappedAround() { let tap = UITapGestureRecognizer(target: self, - action: #selector(ProfileInputView.dismissKeyboard)) + action: #selector(self.dismissKeyboard)) tap.cancelsTouchesInView = false view.addGestureRecognizer(tap) } diff --git a/SniffMeet/SniffMeet/Source/Home/Main/HomeModuleBuilder.swift b/SniffMeet/SniffMeet/Source/Home/Main/HomeModuleBuilder.swift index e036b77..493fbdd 100644 --- a/SniffMeet/SniffMeet/Source/Home/Main/HomeModuleBuilder.swift +++ b/SniffMeet/SniffMeet/Source/Home/Main/HomeModuleBuilder.swift @@ -8,7 +8,7 @@ import UIKit enum HomeModuleBuilder { - static func build() -> UINavigationController { + static func build() -> UIViewController { let view = HomeViewController() view.title = "SniffMeet" @@ -16,6 +16,6 @@ enum HomeModuleBuilder { // let interactor = HomeInteractor() // view.presenter = HomePresenter(view: view, router: router, interactor: interactor) - return UINavigationController(rootViewController: view) + return view } }