From a0d791f1565eb3036d64781dc20ed28000be2f3e Mon Sep 17 00:00:00 2001 From: JunYoung Date: Wed, 8 Apr 2026 20:34:49 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat/#316:=20auth=20feature=20example=20?= =?UTF-8?q?=ED=83=80=EA=B9=83=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS.xcodeproj/project.pbxproj | 140 ++++++++++++++++++ MLS/MLSAuthFeatureExample/AppDelegate.swift | 36 +++++ .../AccentColor.colorset/Contents.json | 11 ++ .../AppIcon.appiconset/Contents.json | 35 +++++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 ++++ .../Base.lproj/Main.storyboard | 24 +++ MLS/MLSAuthFeatureExample/Info.plist | 25 ++++ MLS/MLSAuthFeatureExample/SceneDelegate.swift | 52 +++++++ .../ViewController.swift | 19 +++ 10 files changed, 373 insertions(+) create mode 100644 MLS/MLSAuthFeatureExample/AppDelegate.swift create mode 100644 MLS/MLSAuthFeatureExample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 MLS/MLSAuthFeatureExample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 MLS/MLSAuthFeatureExample/Assets.xcassets/Contents.json create mode 100644 MLS/MLSAuthFeatureExample/Base.lproj/LaunchScreen.storyboard create mode 100644 MLS/MLSAuthFeatureExample/Base.lproj/Main.storyboard create mode 100644 MLS/MLSAuthFeatureExample/Info.plist create mode 100644 MLS/MLSAuthFeatureExample/SceneDelegate.swift create mode 100644 MLS/MLSAuthFeatureExample/ViewController.swift diff --git a/MLS/MLS.xcodeproj/project.pbxproj b/MLS/MLS.xcodeproj/project.pbxproj index c59a4da2..295fd5c0 100644 --- a/MLS/MLS.xcodeproj/project.pbxproj +++ b/MLS/MLS.xcodeproj/project.pbxproj @@ -135,6 +135,7 @@ 087D3EE82DA7972C002F924D /* MLS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MLS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 08DA58A62E1E5BE3009097A6 /* DictionaryFeature.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DictionaryFeature.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 08DA58A92E1E5BEB009097A6 /* DictionaryFeatureInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DictionaryFeatureInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 08F7A9232F86745C00EF5C06 /* MLSAuthFeatureExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MLSAuthFeatureExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 772199F12E0E7EC800A7B58C /* AuthFeatureInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AuthFeatureInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7721A5032E0EE7AE00A7B58C /* BaseFeature.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BaseFeature.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77660AD12DD0D361007A4EF3 /* KakaoConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = KakaoConfig.xcconfig; sourceTree = ""; }; @@ -159,6 +160,13 @@ ); target = 087D3EE72DA7972C002F924D /* MLS */; }; + 08F7A9362F86745D00EF5C06 /* Exceptions for "MLSAuthFeatureExample" folder in "MLSAuthFeatureExample" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 08F7A9222F86745C00EF5C06 /* MLSAuthFeatureExample */; + }; 77FA688B2F72C7380064B6EB /* Exceptions for "MLSDesignSystemExample" folder in "MLSDesignSystemExample" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -177,6 +185,14 @@ path = MLS; sourceTree = ""; }; + 08F7A9242F86745C00EF5C06 /* MLSAuthFeatureExample */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 08F7A9362F86745D00EF5C06 /* Exceptions for "MLSAuthFeatureExample" folder in "MLSAuthFeatureExample" target */, + ); + path = MLSAuthFeatureExample; + sourceTree = ""; + }; 77BEB0412DBA84B0002FFCFC /* MLSTests */ = { isa = PBXFileSystemSynchronizedRootGroup; path = MLSTests; @@ -228,6 +244,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 08F7A9202F86745C00EF5C06 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 77BEB03D2DBA84B0002FFCFC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -330,6 +353,7 @@ 087D3EEA2DA7972C002F924D /* MLS */, 77BEB0412DBA84B0002FFCFC /* MLSTests */, 77FA687B2F72C7360064B6EB /* MLSDesignSystemExample */, + 08F7A9242F86745C00EF5C06 /* MLSAuthFeatureExample */, 084A25312DB93A5400C395C0 /* Frameworks */, 087D3EE92DA7972C002F924D /* Products */, ); @@ -341,6 +365,7 @@ 087D3EE82DA7972C002F924D /* MLS.app */, 77BEB0402DBA84B0002FFCFC /* MLSTests.xctest */, 77FA687A2F72C7360064B6EB /* MLSDesignSystemExample.app */, + 08F7A9232F86745C00EF5C06 /* MLSAuthFeatureExample.app */, ); name = Products; sourceTree = ""; @@ -383,6 +408,28 @@ productReference = 087D3EE82DA7972C002F924D /* MLS.app */; productType = "com.apple.product-type.application"; }; + 08F7A9222F86745C00EF5C06 /* MLSAuthFeatureExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 08F7A9372F86745D00EF5C06 /* Build configuration list for PBXNativeTarget "MLSAuthFeatureExample" */; + buildPhases = ( + 08F7A91F2F86745C00EF5C06 /* Sources */, + 08F7A9202F86745C00EF5C06 /* Frameworks */, + 08F7A9212F86745C00EF5C06 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 08F7A9242F86745C00EF5C06 /* MLSAuthFeatureExample */, + ); + name = MLSAuthFeatureExample; + packageProductDependencies = ( + ); + productName = MLSAuthFeatureExample; + productReference = 08F7A9232F86745C00EF5C06 /* MLSAuthFeatureExample.app */; + productType = "com.apple.product-type.application"; + }; 77BEB03F2DBA84B0002FFCFC /* MLSTests */ = { isa = PBXNativeTarget; buildConfigurationList = 77BEB0482DBA84B0002FFCFC /* Build configuration list for PBXNativeTarget "MLSTests" */; @@ -446,6 +493,9 @@ 087D3EE72DA7972C002F924D = { CreatedOnToolsVersion = 16.2; }; + 08F7A9222F86745C00EF5C06 = { + CreatedOnToolsVersion = 26.1.1; + }; 77BEB03F2DBA84B0002FFCFC = { CreatedOnToolsVersion = 16.2; TestTargetID = 087D3EE72DA7972C002F924D; @@ -507,6 +557,7 @@ 087D3EE72DA7972C002F924D /* MLS */, 77BEB03F2DBA84B0002FFCFC /* MLSTests */, 77FA68792F72C7360064B6EB /* MLSDesignSystemExample */, + 08F7A9222F86745C00EF5C06 /* MLSAuthFeatureExample */, ); }; /* End PBXProject section */ @@ -522,6 +573,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 08F7A9212F86745C00EF5C06 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 77BEB03E2DBA84B0002FFCFC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -567,6 +625,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 08F7A91F2F86745C00EF5C06 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 77BEB03C2DBA84B0002FFCFC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -795,6 +860,72 @@ }; name = Release; }; + 08F7A9342F86745D00EF5C06 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MLSAuthFeatureExample/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.donggle.MLSAuthFeatureExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 08F7A9352F86745D00EF5C06 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MLSAuthFeatureExample/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.donggle.MLSAuthFeatureExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 77BEB0462DBA84B0002FFCFC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -924,6 +1055,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 08F7A9372F86745D00EF5C06 /* Build configuration list for PBXNativeTarget "MLSAuthFeatureExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 08F7A9342F86745D00EF5C06 /* Debug */, + 08F7A9352F86745D00EF5C06 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 77BEB0482DBA84B0002FFCFC /* Build configuration list for PBXNativeTarget "MLSTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MLS/MLSAuthFeatureExample/AppDelegate.swift b/MLS/MLSAuthFeatureExample/AppDelegate.swift new file mode 100644 index 00000000..25efac65 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// MLSAuthFeatureExample +// +// Created by SeoJunYoung on 4/8/26. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/MLS/MLSAuthFeatureExample/Assets.xcassets/AccentColor.colorset/Contents.json b/MLS/MLSAuthFeatureExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MLS/MLSAuthFeatureExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/MLS/MLSAuthFeatureExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..23058801 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MLS/MLSAuthFeatureExample/Assets.xcassets/Contents.json b/MLS/MLSAuthFeatureExample/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MLS/MLSAuthFeatureExample/Base.lproj/LaunchScreen.storyboard b/MLS/MLSAuthFeatureExample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MLS/MLSAuthFeatureExample/Base.lproj/Main.storyboard b/MLS/MLSAuthFeatureExample/Base.lproj/Main.storyboard new file mode 100644 index 00000000..25a76385 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MLS/MLSAuthFeatureExample/Info.plist b/MLS/MLSAuthFeatureExample/Info.plist new file mode 100644 index 00000000..dd3c9afd --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift new file mode 100644 index 00000000..38c00710 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// MLSAuthFeatureExample +// +// Created by SeoJunYoung on 4/8/26. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/MLS/MLSAuthFeatureExample/ViewController.swift b/MLS/MLSAuthFeatureExample/ViewController.swift new file mode 100644 index 00000000..c2bf4daf --- /dev/null +++ b/MLS/MLSAuthFeatureExample/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// MLSAuthFeatureExample +// +// Created by SeoJunYoung on 4/8/26. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .red + } + + +} + From 8518c8dac18b25969600d03eb2f57dc01fb6e222 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Wed, 8 Apr 2026 20:44:16 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat/#316:=20authfeature=20pakage=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS.xcworkspace/contents.xcworkspacedata | 13 +- MLS/MLSAuthFeature/.gitignore | 8 + MLS/MLSAuthFeature/Package.swift | 52 ++++ .../Presentation/Login/LoginFactoryImpl.swift | 65 +++++ .../Presentation/Login/LoginReactor.swift | 196 +++++++++++++++ .../Presentation/Login/LoginView.swift | 238 ++++++++++++++++++ .../Login/LoginViewController.swift | 168 +++++++++++++ .../OnBoardingNotificationFactoryImpl.swift | 19 ++ .../OnBoardingNotificationReactor.swift | 60 +++++ .../OnBoardingNotificationView.swift | 82 ++++++ ...OnBoardingNotificationViewController.swift | 112 +++++++++ .../OnBoarding/OnBoardingBaseView.swift | 44 ++++ .../OnBoardingInputFactoryImpl.swift | 40 +++ .../OnBoardingInputReactor.swift | 115 +++++++++ .../OnBoardingInput/OnBoardingInputView.swift | 57 +++++ .../OnBoardingInputViewController.swift | 161 ++++++++++++ ...BoardingNotificationSheetFactoryImpl.swift | 40 +++ .../OnBoardingNotificationSheetReactor.swift | 113 +++++++++ .../OnBoardingNotificationSheetView.swift | 100 ++++++++ ...rdingNotificationSheetViewController.swift | 157 ++++++++++++ .../OnBoardingQuestionFactoryImpl.swift | 18 ++ .../OnBoardingQuestionReactor.swift | 65 +++++ .../OnBoardingQuestionView.swift | 94 +++++++ .../OnBoardingQuestionViewController.swift | 119 +++++++++ .../TermsAgreementFactoryImpl.swift | 39 +++ .../TermsAgreementReactor.swift | 155 ++++++++++++ .../TermsAgreement/TermsAgreementView.swift | 148 +++++++++++ .../TermsAgreementViewController.swift | 181 +++++++++++++ ...343\205\201\343\204\264\343\205\207.swift" | 7 + ...343\205\201\343\204\264\343\205\207.swift" | 7 + .../MLSAuthFeatureTests.swift | 6 + 31 files changed, 2674 insertions(+), 5 deletions(-) create mode 100644 MLS/MLSAuthFeature/.gitignore create mode 100644 MLS/MLSAuthFeature/Package.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift create mode 100644 "MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" create mode 100644 "MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" create mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift diff --git a/MLS/MLS.xcworkspace/contents.xcworkspacedata b/MLS/MLS.xcworkspace/contents.xcworkspacedata index c5c30238..99fde6a0 100644 --- a/MLS/MLS.xcworkspace/contents.xcworkspacedata +++ b/MLS/MLS.xcworkspace/contents.xcworkspacedata @@ -36,11 +36,14 @@ location = "group:Core/Core.xcodeproj"> - - - + location = "group:MLSDesignSystem"> + + + + + diff --git a/MLS/MLSAuthFeature/.gitignore b/MLS/MLSAuthFeature/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/MLS/MLSAuthFeature/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift new file mode 100644 index 00000000..5dd517d1 --- /dev/null +++ b/MLS/MLSAuthFeature/Package.swift @@ -0,0 +1,52 @@ +// swift-tools-version: 6.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MLSAuthFeature", + platforms: [.iOS(.v15)], + products: [ + // Interface: 외부 인터페이스와 모델을 제공하는 모듈 + .library( + name: "MLSAuthFeatureInterface", + targets: ["MLSAuthFeatureInterface"] + ), + // Feature: 실제 기능이 구현된 모듈 + .library( + name: "MLSAuthFeature", + targets: ["MLSAuthFeature"] + ), + // Testing: 단위 테스트나 Example 앱에서 사용될 Mock 데이터를 제공하는 모듈 + .library( + name: "MLSAuthFeatureTesting", + targets: ["MLSAuthFeatureTesting"] + ) + ], + targets: [ + // Interface 모듈 (도메인 모델 및 프로토콜) + .target( + name: "MLSAuthFeatureInterface", + dependencies: [] + ), + // Feature 모듈 (실제 구현) + .target( + name: "MLSAuthFeature", + dependencies: ["MLSAuthFeatureInterface"] + ), + // Testing 모듈 (Mock 객체) + .target( + name: "MLSAuthFeatureTesting", + dependencies: ["MLSAuthFeatureInterface"] + ), + // Tests 모듈 + .testTarget( + name: "MLSAuthFeatureTests", + dependencies: [ + "MLSAuthFeature", + "MLSAuthFeatureInterface", + "MLSAuthFeatureTesting" + ] + ) + ] +) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift new file mode 100644 index 00000000..a75289b0 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift @@ -0,0 +1,65 @@ +import AuthFeatureInterface +import BaseFeature +import DomainInterface + +import RxSwift + +public struct LoginFactoryImpl: LoginFactory { + private let termsAgreementsFactory: TermsAgreementFactory + private let appleLoginUseCase: FetchSocialCredentialUseCase + private let kakaoLoginUseCase: FetchSocialCredentialUseCase + private let loginWithAppleUseCase: LoginWithAppleUseCase + private let loginWithKakaoUseCase: LoginWithKakaoUseCase + private let fetchTokenUseCase: FetchTokenFromLocalUseCase + private let putFCMTokenUseCase: PutFCMTokenUseCase + private let fetchPlatformUseCase: FetchPlatformUseCase + + public init( + termsAgreementsFactory: TermsAgreementFactory, + appleLoginUseCase: FetchSocialCredentialUseCase, + kakaoLoginUseCase: FetchSocialCredentialUseCase, + loginWithAppleUseCase: LoginWithAppleUseCase, + loginWithKakaoUseCase: LoginWithKakaoUseCase, + fetchTokenUseCase: FetchTokenFromLocalUseCase, + putFCMTokenUseCase: PutFCMTokenUseCase, + fetchPlatformUseCase: FetchPlatformUseCase + ) { + self.termsAgreementsFactory = termsAgreementsFactory + self.appleLoginUseCase = appleLoginUseCase + self.kakaoLoginUseCase = kakaoLoginUseCase + self.loginWithAppleUseCase = loginWithAppleUseCase + self.loginWithKakaoUseCase = loginWithKakaoUseCase + self.fetchTokenUseCase = fetchTokenUseCase + self.putFCMTokenUseCase = putFCMTokenUseCase + self.fetchPlatformUseCase = fetchPlatformUseCase + } + + public func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController { + let viewController = LoginViewController(termsAgreementsFactory: termsAgreementsFactory) + viewController.isBottomTabbarHidden = true + + viewController.reactor = LoginReactor( + fetchAppleCredentialUseCase: appleLoginUseCase, + fetchKakaoCredentialUseCase: kakaoLoginUseCase, + loginWithAppleUseCase: loginWithAppleUseCase, + loginWithKakaoUseCase: loginWithKakaoUseCase, + fetchTokenUseCase: fetchTokenUseCase, + putFCMTokenUseCase: putFCMTokenUseCase, + fetchPlatformUseCase: fetchPlatformUseCase + ) + + viewController.routeToHome + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak viewController] in + switch exitRoute { + case .home: + onLoginCompleted?() + case .pop: + viewController?.navigationController?.popViewController(animated: true) + } + }) + .disposed(by: viewController.disposeBag) + + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift new file mode 100644 index 00000000..7bf0b45d --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -0,0 +1,196 @@ +import DomainInterface +import NotificationCenter + +import ReactorKit +import RxSwift + +public final class LoginReactor: Reactor { + public enum Route { + case none + case termsAgreements(credential: Credential, platform: LoginPlatform) + case error + case home + case dismiss + } + + // MARK: - Reactor + public enum Action { + case viewWillAppear + case kakaoLoginButtonTapped + case appleLoginButtonTapped + case guestLoginButtonTapped + case backButtonTapped + } + + public enum Mutation { + case navigateTo(route: Route) + case setRelogin(LoginPlatform?) + } + + public struct State { + @Pulse var route: Route = .none + var platform: LoginPlatform? + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + private let fetchAppleCredentialUseCase: FetchSocialCredentialUseCase + private let fetchKakaoCredentialUseCase: FetchSocialCredentialUseCase + private let loginWithAppleUseCase: LoginWithAppleUseCase + private let loginWithKakaoUseCase: LoginWithKakaoUseCase + private let fetchTokenUseCase: FetchTokenFromLocalUseCase + private let putFCMTokenUseCase: PutFCMTokenUseCase + private let fetchPlatformUseCase: FetchPlatformUseCase + + // MARK: - init + public init( + fetchAppleCredentialUseCase: FetchSocialCredentialUseCase, + fetchKakaoCredentialUseCase: FetchSocialCredentialUseCase, + loginWithAppleUseCase: LoginWithAppleUseCase, + loginWithKakaoUseCase: LoginWithKakaoUseCase, + fetchTokenUseCase: FetchTokenFromLocalUseCase, + putFCMTokenUseCase: PutFCMTokenUseCase, + fetchPlatformUseCase: FetchPlatformUseCase + ) { + self.fetchAppleCredentialUseCase = fetchAppleCredentialUseCase + self.fetchKakaoCredentialUseCase = fetchKakaoCredentialUseCase + self.loginWithAppleUseCase = loginWithAppleUseCase + self.loginWithKakaoUseCase = loginWithKakaoUseCase + self.fetchTokenUseCase = fetchTokenUseCase + self.putFCMTokenUseCase = putFCMTokenUseCase + self.fetchPlatformUseCase = fetchPlatformUseCase + self.initialState = State() + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .viewWillAppear: + return fetchPlatformUseCase.execute() + .map { Mutation.setRelogin($0) } + case .kakaoLoginButtonTapped: + return handleKakaoLogin() + case .appleLoginButtonTapped: + return handleAppleLogin() + case .guestLoginButtonTapped: + return .just(.navigateTo(route: .home)) + case .backButtonTapped: + return .just(.navigateTo(route: .dismiss)) + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + switch mutation { + case .navigateTo(let route): + newState.route = route + case .setRelogin(let platform): + newState.platform = platform + } + return newState + } +} + +// MARK: - Methods +private extension LoginReactor { + func handleKakaoLogin() -> Observable { + return checkNotificationPermissionAndFetchFCMToken() + .withUnretained(self) + .flatMap { owner, fcmToken in + // 1. 카카오 로그인 자격 증명 가져오기 + owner.fetchKakaoCredentialUseCase.execute() + .flatMap { credential in + // 2. 자격 증명을 바탕으로 로그인 요청 + owner.loginWithKakaoUseCase.execute(credential: credential) + .flatMap { response -> Observable in + if response.isRegister { + // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 + return owner.putFCMTokenUseCase.execute(fcmToken: fcmToken) + .andThen(.just(.navigateTo(route: .home))) + } else { + // 4. 미가입 유저면 약관 동의 화면으로 이동 + return .just(.navigateTo(route: .termsAgreements( + credential: credential, + platform: .kakao + ))) + } + } + .catch { error in + // 5. 로그인 실패 예외 처리 + if case AuthError.userNotFound = error { + return .just(.navigateTo(route: .termsAgreements( + credential: credential, + platform: .kakao + ))) + } else { + return .just(.navigateTo(route: .error)) + } + } + } + } + } + + func handleAppleLogin() -> Observable { + return checkNotificationPermissionAndFetchFCMToken() + .withUnretained(self) + .flatMap { owner, fcmToken in + // 1. 애플 로그인 자격 증명 가져오기 + owner.fetchAppleCredentialUseCase.execute() + .flatMap { credential in + // 2. 자격 증명을 바탕으로 로그인 요청 + owner.loginWithAppleUseCase.execute(credential: credential) + .flatMap { response -> Observable in + if response.isRegister { + // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 + return owner.putFCMTokenUseCase.execute(fcmToken: fcmToken) + .andThen(.just(.navigateTo(route: .home))) + } else { + // 4. 미가입 유저면 약관 동의 화면으로 이동 + return .just(.navigateTo(route: .termsAgreements( + credential: credential, + platform: .apple + ))) + } + } + .catch { error in + // 5. 로그인 실패 예외 처리 + if case AuthError.userNotFound = error { + return .just(.navigateTo(route: .termsAgreements( + credential: credential, + platform: .apple + ))) + } else { + return .just(.navigateTo(route: .error)) + } + } + } + } + } + + func checkNotificationPermissionAndFetchFCMToken() -> Observable { + return Observable.create { [weak self] observer in + guard let self else { + observer.onNext(nil) + observer.onCompleted() + return Disposables.create() + } + + UNUserNotificationCenter.current().getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized, .provisional, .ephemeral: + let result = self.fetchTokenUseCase.execute(type: .fcmToken) + switch result { + case .success(let token): observer.onNext(token) + case .failure: observer.onNext(nil) + } + default: + observer.onNext(nil) + } + observer.onCompleted() + } + + return Disposables.create() + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift new file mode 100644 index 00000000..f6f1fa32 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift @@ -0,0 +1,238 @@ +import UIKit + +import DesignSystem +import DomainInterface + +import SnapKit + +final class LoginView: UIView { + // MARK: - Type + private enum Constant { + static let buttonLogoImageSize: CGFloat = 18 + static let buttonLogoImageLeadingInset: CGFloat = 14 + static let buttonHeight: CGFloat = 44 + static let buttonCornerRadius: CGFloat = 8 + static let buttonSpacing: CGFloat = 8 + static let buttonStackViewBottomInset: CGFloat = 16 + static let horizontalInset: CGFloat = 16 + static let buttonCenterXInset: CGFloat = buttonLogoImageLeadingInset + buttonLogoImageSize + static let labelHeight: CGFloat = 28 + static let subTitleBottomSpacing: CGFloat = -25 + static let recentLogoWidth: CGFloat = 82 + static let recentLogoHeight: CGFloat = 30 + static let recentLogoInset: CGFloat = 8 + } + + // MARK: - Properties + public let header = NavigationBar(type: .arrowLeft) + + private let loginImageView: UIImageView = { + let image = DesignSystemAsset.image(named: "Login_KV_img") + let view = UIImageView(image: image) + return view + }() + + private let buttonStackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = Constant.buttonSpacing + return view + }() + + let kakaoLoginButton: UIButton = { + let button = UIButton() + button.backgroundColor = .init(hexCode: "#FEE500", alpha: 1) + button.layer.cornerRadius = Constant.buttonCornerRadius + return button + }() + + private let kakaoLogoImageView: UIImageView = { + let image = DesignSystemAsset.image(named: "kakaoLogo") + let view = UIImageView(image: image) + view.contentMode = .scaleAspectFit + return view + }() + + private let kakaoLoginLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .korFont(style: .semiBold, size: 15), text: "카카오로 계속하기", color: .init(hexCode: "#000000", alpha: 0.85)) + return label + }() + + let appleLoginButton: UIButton = { + let button = UIButton() + button.backgroundColor = .init(hexCode: "#000000", alpha: 1) + button.layer.cornerRadius = Constant.buttonCornerRadius + return button + }() + + private let appleLogoImageView: UIImageView = { + let image = DesignSystemAsset.image(named: "appleLogo") + let view = UIImageView(image: image) + view.contentMode = .scaleAspectFit + return view + }() + + let appleLoginLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .korFont(style: .semiBold, size: 15), text: "Apple로 계속하기", color: .init(hexCode: "#FFFFFF")) + return label + }() + + let guestLoginButton: CommonButton = { + let button = CommonButton(style: .text, title: "가입 없이 둘러보기", disabledTitle: "가입 없이 둘러보기") + return button + }() + + private let mainTitleLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .h_xl_b, text: "모험가님,") + return label + }() + + private let subTitleLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .h_xl_r, text: "다시 오신 걸 환영해요!") + return label + }() + + private let recentLoginImageView: UIImageView = { + let view = UIImageView(image: DesignSystemAsset.image(named: "recentLoginLogo")) + view.isHidden = true + return view + }() + + // MARK: - init + init() { + super.init(frame: .zero) + + addViews() + setupConstraints() + configureUI() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension LoginView { + func addViews() { + addSubview(loginImageView) + addSubview(buttonStackView) + addSubview(recentLoginImageView) + + buttonStackView.addArrangedSubview(kakaoLoginButton) + buttonStackView.addArrangedSubview(appleLoginButton) + + kakaoLoginButton.addSubview(kakaoLogoImageView) + kakaoLoginButton.addSubview(kakaoLoginLabel) + appleLoginButton.addSubview(appleLogoImageView) + appleLoginButton.addSubview(appleLoginLabel) + + addSubview(header) + } + + func setupConstraints() { + header.snp.makeConstraints { make in + make.top.horizontalEdges.equalTo(safeAreaLayoutGuide) + } + + loginImageView.snp.makeConstraints { make in + make.width.equalToSuperview() + make.height.equalTo(UIScreen.main.bounds.width * 1.49) + make.top.horizontalEdges.equalToSuperview() + } + + buttonStackView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + make.bottom.equalToSuperview().inset(Constant.buttonStackViewBottomInset) + } + + kakaoLoginButton.snp.makeConstraints { make in + make.height.equalTo(Constant.buttonHeight) + } + + kakaoLogoImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().inset(Constant.buttonLogoImageLeadingInset) + make.size.equalTo(Constant.buttonLogoImageSize) + } + + kakaoLoginLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.centerX.equalToSuperview().inset(Constant.buttonCenterXInset) + } + + appleLoginButton.snp.makeConstraints { make in + make.height.equalTo(Constant.buttonHeight) + } + + appleLogoImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().inset(Constant.buttonLogoImageLeadingInset) + make.size.equalTo(Constant.buttonLogoImageSize) + } + + appleLoginLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.centerX.equalToSuperview().inset(Constant.buttonCenterXInset) + } + } + + func configureUI() {} +} + +extension LoginView { + func update(loginPlatform: LoginPlatform?) { + mainTitleLabel.removeFromSuperview() + subTitleLabel.removeFromSuperview() + guestLoginButton.removeFromSuperview() + + switch loginPlatform { + case .kakao, .apple: + // 최근로그인 라벨 추가 + addSubview(mainTitleLabel) + addSubview(subTitleLabel) + + subTitleLabel.snp.remakeConstraints { make in + make.bottom.equalTo(buttonStackView.snp.top).offset(Constant.subTitleBottomSpacing) + make.centerX.equalToSuperview() + make.height.equalTo(Constant.labelHeight) + } + mainTitleLabel.snp.remakeConstraints { make in + make.bottom.equalTo(subTitleLabel.snp.top) + make.centerX.equalToSuperview() + make.height.equalTo(Constant.labelHeight) + } + + recentLoginImageView.isHidden = false + + recentLoginImageView.snp.remakeConstraints { make in + switch loginPlatform { + case .apple: + make.leading.equalTo(appleLoginButton).offset(Constant.recentLogoInset) + make.bottom.equalTo(appleLoginButton.snp.top).offset(Constant.recentLogoInset) + case .kakao: + make.leading.equalTo(kakaoLoginButton).offset(Constant.recentLogoInset) + make.bottom.equalTo(kakaoLoginButton.snp.top).offset(Constant.recentLogoInset) + default: + break + } + make.width.equalTo(Constant.recentLogoWidth) + make.height.equalTo(Constant.recentLogoHeight) + } + case nil: + buttonStackView.addArrangedSubview(guestLoginButton) + guestLoginButton.snp.remakeConstraints { make in + make.height.equalTo(Constant.buttonHeight) + } + recentLoginImageView.isHidden = true + } + + setNeedsLayout() + layoutIfNeeded() + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift new file mode 100644 index 00000000..0fc4c2ba --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift @@ -0,0 +1,168 @@ +import UIKit + +import AuthFeatureInterface +import BaseFeature + +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit + +public final class LoginViewController: BaseViewController, View { + public typealias Reactor = LoginReactor + + // MARK: - Properties + public var disposeBag = DisposeBag() + + public let routeToHome = PublishRelay() + + private let mainView: LoginView + + private let termsAgreementsFactory: TermsAgreementFactory + + public init(termsAgreementsFactory: TermsAgreementFactory) { + self.mainView = LoginView() + self.termsAgreementsFactory = termsAgreementsFactory + super.init() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Life Cycle +public extension LoginViewController { + override func viewDidLoad() { + super.viewDidLoad() + + addViews() + setupConstraints() + configureUI() + } +} + +// MARK: - SetUp +private extension LoginViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + make.bottom.equalTo(view.safeAreaLayoutGuide) + } + } + + func configureUI() { + view.backgroundColor = .systemBackground + + if let navigationController = navigationController, + navigationController.viewControllers.count > 1 { + mainView.header.leftButton.isHidden = false + } else { + mainView.header.leftButton.isHidden = true + } + } +} + +public extension LoginViewController { + func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.kakaoLoginButton.rx.tap + .map { Reactor.Action.kakaoLoginButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.kakaoLoginButton.rx.controlEvent(.touchDown) + .withUnretained(self) + .subscribe { owner, _ in + owner.mainView.kakaoLoginButton.backgroundColor = .init(hexCode: "#E5CE00") + } + .disposed(by: disposeBag) + + mainView.kakaoLoginButton.rx.controlEvent([.touchUpInside, .touchUpOutside, .touchCancel]) + .withUnretained(self) + .subscribe { owner, _ in + owner.mainView.kakaoLoginButton.backgroundColor = .init(hexCode: "#FEE500") + } + .disposed(by: disposeBag) + + mainView.appleLoginButton.rx.tap + .map { Reactor.Action.appleLoginButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.appleLoginButton.rx.controlEvent(.touchDown) + .withUnretained(self) + .subscribe { owner, _ in + owner.mainView.appleLoginLabel.textColor = .init(hexCode: "#E5E5E5") + } + .disposed(by: disposeBag) + + mainView.appleLoginButton.rx.controlEvent([.touchUpInside, .touchUpOutside, .touchCancel]) + .withUnretained(self) + .subscribe { owner, _ in + owner.mainView.appleLoginLabel.textColor = .whiteMLS + } + .disposed(by: disposeBag) + + mainView.guestLoginButton.rx.tap + .map { Reactor.Action.guestLoginButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.header.leftButton.rx.tap + .map { Reactor.Action.backButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + reactor.state + .observe(on: MainScheduler.instance) + .map { $0.platform } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, platform in + owner.mainView.update(loginPlatform: platform) + } + .disposed(by: disposeBag) + + rx.viewDidAppear + .take(1) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .observe(on: MainScheduler.instance) + .withUnretained(self) + .subscribe { owner, route in + switch route { + case .termsAgreements(let credential, let platform): + let controller = owner.termsAgreementsFactory.make(credential: credential, platform: platform) + owner.navigationController?.pushViewController(controller, animated: true) + case .home: + owner.routeToHome.accept(()) + case .error: + DispatchQueue.main.async { + let controller = BaseErrorViewController() + owner.present(controller, animated: true) + } + case .dismiss: + owner.navigationController?.popViewController(animated: true) + default: + break + } + } + .disposed(by: disposeBag) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift new file mode 100644 index 00000000..dbccc528 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift @@ -0,0 +1,19 @@ +import AuthFeatureInterface +import BaseFeature + +public struct OnBoardingNotificationFactoryImpl: OnBoardingNotificationFactory { + private let onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory + private let appCoordinator: () -> AppCoordinatorProtocol + + public init(onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory, appCoordinator: @escaping () -> AppCoordinatorProtocol) { + self.onBoardingNotificationSheetFactory = onBoardingNotificationSheetFactory + self.appCoordinator = appCoordinator + } + + public func make(selectedLevel: Int, selectedJobID: Int) -> BaseViewController { + let viewController = OnBoardingNotificationViewController(onBoardingNotificationSheetFactory: onBoardingNotificationSheetFactory, appCoordinator: appCoordinator()) + viewController.isBottomTabbarHidden = true + viewController.reactor = OnBoardingNotificationReactor(selectedLevel: selectedLevel, selectedJobID: selectedJobID) + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift new file mode 100644 index 00000000..042dc04e --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift @@ -0,0 +1,60 @@ +import ReactorKit +import RxSwift + +public final class OnBoardingNotificationReactor: Reactor { + // MARK: - Reactor + public enum Route { + case none + case notificationAlert + case pop + case home + } + + public enum Action { + case nextButtonTapped + case skipButtonTapped + case backButtonTapped + } + + public enum Mutation { + case navigateTo(route: Route) + } + + public struct State { + @Pulse var route: Route = .none + let selectedLevel: Int + let selectedJobID: Int + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + + // MARK: - init + public init(selectedLevel: Int, selectedJobID: Int) { + self.initialState = State(selectedLevel: selectedLevel, selectedJobID: selectedJobID) + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .nextButtonTapped: + return .just(.navigateTo(route: .notificationAlert)) + case .skipButtonTapped: + return .just(.navigateTo(route: .home)) + case .backButtonTapped: + return .just(.navigateTo(route: .pop)) + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + + switch mutation { + case .navigateTo(let route): + newState.route = route + } + + return newState + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift new file mode 100644 index 00000000..2be88be7 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift @@ -0,0 +1,82 @@ +import UIKit + +import DesignSystem + +import SnapKit + +public final class OnBoardingNotificationView: OnBoardingBaseView { + // MARK: - Type + private enum Constant { + static let horizontalInset = 16 + static let verticalInset = 16 + static let imgSize = 220 + static let resizeCenterY = 70 + } + + // MARK: - Components + private let imageView: UIImageView = { + let view = UIImageView() + view.image = DesignSystemAsset.image(named: "getNotify") + return view + }() + + private let boldTextLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .h_xxl_b, text: "메이플랜드에서 이벤트가 생기면\n알림을 보내드리고 있어요") + label.numberOfLines = 2 + return label + }() + + private lazy var contentView: UIView = { + let view = UIView() + view.addSubview(imageView) + view.addSubview(boldTextLabel) + + imageView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.centerX.equalToSuperview() + make.size.equalTo(Constant.imgSize) + } + + boldTextLabel.snp.makeConstraints { make in + make.top.equalTo(imageView.snp.bottom).offset(4) + make.horizontalEdges.bottom.equalToSuperview() + } + + return view + }() + + public let nextButton = CommonButton(style: .normal, title: "다음", disabledTitle: "") + + // MARK: - init + init() { + super.init() + addViews() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension OnBoardingNotificationView { + func addViews() { + addSubview(contentView) + addSubview(nextButton) + } + + func setupConstraints() { + contentView.snp.makeConstraints { make in + make.centerY.equalToSuperview().offset(-Constant.resizeCenterY) + make.horizontalEdges.equalToSuperview() + } + + nextButton.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + make.bottom.equalTo(safeAreaLayoutGuide).inset(Constant.verticalInset) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift new file mode 100644 index 00000000..08566c23 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -0,0 +1,112 @@ +import UIKit +import UserNotifications + +import AuthFeatureInterface +import BaseFeature +import DictionaryFeatureInterface + +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit + +public class OnBoardingNotificationViewController: BaseViewController, View { + // MARK: - Properties + public typealias Reactor = OnBoardingNotificationReactor + + public var disposeBag = DisposeBag() + + private let onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory + private let appCoordinator: AppCoordinatorProtocol + + // MARK: - Components + + private var mainView = OnBoardingNotificationView() + + public init(onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory, appCoordinator: AppCoordinatorProtocol) { + self.onBoardingNotificationSheetFactory = onBoardingNotificationSheetFactory + self.appCoordinator = appCoordinator + super.init() + } + + @available(*, unavailable) + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Life Cycle +public extension OnBoardingNotificationViewController { + override func viewDidLoad() { + super.viewDidLoad() + configureUI() + } +} + +// MARK: - SetUp +private extension OnBoardingNotificationViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + } + + func configureUI() { + addViews() + setupConstraints() + } +} + +// MARK: - Private Methods +private extension OnBoardingNotificationViewController {} + +// MARK: - Bind +public extension OnBoardingNotificationViewController { + func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + mainView.nextButton.rx.tap + .map { Reactor.Action.nextButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.headerView.leftButton.rx.tap + .map { Reactor.Action.backButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.headerView.underlineTextButton.rx.tap + .map { Reactor.Action.skipButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + rx.viewDidAppear + .take(1) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .withUnretained(self) + .observe(on: MainScheduler.instance) + .subscribe { owner, route in + switch route { + case .notificationAlert: + let viewController = owner.onBoardingNotificationSheetFactory.make(selectedLevel: reactor.currentState.selectedLevel, selectedJobID: reactor.currentState.selectedJobID) + owner.presentModal(viewController, hideTabBar: true) + case .home: + owner.appCoordinator.showMainTab() + case .pop: + owner.navigationController?.popViewController(animated: true) + default: + break + } + } + .disposed(by: disposeBag) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift new file mode 100644 index 00000000..3f25b5f4 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift @@ -0,0 +1,44 @@ +import UIKit + +import DesignSystem + +/// 온보딩 화면에서 사용하기 위한 헤더(타이틀 버튼 포함)를 가진 뷰 +public class OnBoardingBaseView: UIView { + // MARK: - Components + public let headerView: NavigationBar = { + let view = NavigationBar(type: .withUnderLine("다음에 하기")) + return view + }() + + // MARK: - init + init(leftButtonIsHidden: Bool = false, underlineTextButtonIsHidden: Bool = false) { + super.init(frame: .zero) + addViews() + setupConstraints() + configureUI() + if leftButtonIsHidden { headerView.leftButton.isHidden = true } + if underlineTextButtonIsHidden { headerView.underlineTextButton.isHidden = true } + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension OnBoardingBaseView { + func addViews() { + addSubview(headerView) + } + + func setupConstraints() { + headerView.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + } + } + + func configureUI() { + backgroundColor = .clearMLS + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift new file mode 100644 index 00000000..19243aeb --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift @@ -0,0 +1,40 @@ +import AuthFeatureInterface +import BaseFeature +import DictionaryFeatureInterface +import DomainInterface + +public struct OnBoardingInputFactoryImpl: OnBoardingInputFactory { + private let checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase + private let checkValidLevelUseCase: CheckValidLevelUseCase + private let fetchJobListUseCase: FetchJobListUseCase + private let updateUserInfoUseCase: UpdateUserInfoUseCase + private let onBoardingNotificationFactory: OnBoardingNotificationFactory + private let appCoordinator: () -> AppCoordinatorProtocol + + public init( + checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase, + checkValidLevelUseCase: CheckValidLevelUseCase, + fetchJobListUseCase: FetchJobListUseCase, + updateUserInfoUseCase: UpdateUserInfoUseCase, + onBoardingNotificationFactory: OnBoardingNotificationFactory, + appCoordinator: @escaping () -> AppCoordinatorProtocol + ) { + self.checkEmptyUseCase = checkEmptyUseCase + self.checkValidLevelUseCase = checkValidLevelUseCase + self.fetchJobListUseCase = fetchJobListUseCase + self.updateUserInfoUseCase = updateUserInfoUseCase + self.onBoardingNotificationFactory = onBoardingNotificationFactory + self.appCoordinator = appCoordinator + } + + public func make() -> BaseViewController { + let viewController = OnBoardingInputViewController(onBoardingNotificationFactory: onBoardingNotificationFactory, appCoordinator: appCoordinator()) + viewController.isBottomTabbarHidden = true + viewController.reactor = OnBoardingInputReactor( + checkEmptyUseCase: checkEmptyUseCase, + checkValidLevelUseCase: checkValidLevelUseCase, + fetchJobListUseCase: fetchJobListUseCase + ) + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift new file mode 100644 index 00000000..84b244c8 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift @@ -0,0 +1,115 @@ +import DomainInterface + +import ReactorKit +import RxSwift + +public final class OnBoardingInputReactor: Reactor { + // MARK: - Reactor + public enum Route { + case none + case dismiss + case home + case notification + case error + } + + public enum Action { + case viewWillAppear + case backButtonTapped + case skipButtonTapped + case nextButtonTapped + case inputLevel(Int?) + case inputRole(Job?) + } + + public enum Mutation { + case setJobList(jobList: [Job]) + case setButtonEnabled(Bool) + case setLevelValid(Bool?) + case setLevel(Int?) + case setRole(Job?) + case navigateTo(route: Route) + } + + public struct State { + @Pulse var route: Route = .none + + var level: Int? + var job: Job? + var isButtonEnabled: Bool = false + var isLevelValid: Bool? + var jobList: [Job] = [] + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + + private let checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase + private let checkValidLevelUseCase: CheckValidLevelUseCase + private let fetchJobListUseCase: FetchJobListUseCase + + // MARK: - init + public init( + checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase, + checkValidLevelUseCase: CheckValidLevelUseCase, + fetchJobListUseCase: FetchJobListUseCase + ) { + self.checkEmptyUseCase = checkEmptyUseCase + self.checkValidLevelUseCase = checkValidLevelUseCase + self.fetchJobListUseCase = fetchJobListUseCase + self.initialState = State() + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .viewWillAppear: + return fetchJobListUseCase.execute() + .map { response in + .setJobList(jobList: response.jobList) + } + .catchAndReturn(.navigateTo(route: .error)) + case .backButtonTapped: + return Observable.just(.navigateTo(route: .dismiss)) + case .skipButtonTapped: + return Observable.just(.navigateTo(route: .home)) + case .nextButtonTapped: + return Observable.just(.navigateTo(route: .notification)) + case .inputLevel(let level): + let changeLevel = Observable.just(Mutation.setLevel(level)) + let validateJob = checkEmptyUseCase.execute(level: level, job: currentState.job?.name) + .map(Mutation.setButtonEnabled) + let validateLevel = checkValidLevelUseCase.execute(level: level) + .map(Mutation.setLevelValid) + return .merge(changeLevel, validateJob, validateLevel) + case .inputRole(let job): + return checkEmptyUseCase.execute(level: currentState.level, job: job?.name) + .map { isValid in + [.setRole(job), .setButtonEnabled(isValid)] + } + .flatMap { Observable.from($0) } + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + + switch mutation { + case .setJobList(let jobList): + newState.jobList = jobList + case .setButtonEnabled(let isEnabled): + newState.isButtonEnabled = isEnabled + case .setLevelValid(let isValid): + newState.isLevelValid = isValid + case .setLevel(let level): + newState.level = level + case .setRole(let role): + newState.job = role + case .navigateTo(let route): + newState.route = route + } + + return newState + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift new file mode 100644 index 00000000..e21e0f05 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift @@ -0,0 +1,57 @@ +import UIKit + +import BaseFeature +import DesignSystem + +import RxCocoa +import RxSwift +import SnapKit + +public final class OnBoardingInputView: CharacterInputView { + // MARK: - Type + + // MARK: - Properties + + // MARK: - Components + public let headerView: NavigationBar = { + let view = NavigationBar(type: .withUnderLine("다음에 하기")) + return view + }() + + // MARK: - init + init(leftButtonIsHidden: Bool = false, underlineTextButtonIsHidden: Bool = false) { + super.init() + addViews() + setupConstraints() + configureUI() + if leftButtonIsHidden { headerView.leftButton.isHidden = true } + if underlineTextButtonIsHidden { headerView.underlineTextButton.isHidden = true } + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension OnBoardingInputView { + func addViews() { + addSubview(headerView) + } + + func setupConstraints() { + headerView.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + } + + descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(headerView.snp.bottom).offset(Constant.verticalInset) + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + } + } + + func configureUI() { + backgroundColor = .clearMLS + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift new file mode 100644 index 00000000..1da435e4 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift @@ -0,0 +1,161 @@ +import UIKit + +import AuthFeatureInterface +import BaseFeature +import DesignSystem +import DictionaryFeatureInterface + +import ReactorKit +import RxCocoa +import RxKeyboard +import RxSwift +import SnapKit + +public class OnBoardingInputViewController: BaseViewController, View { + // MARK: - Properties + public typealias Reactor = OnBoardingInputReactor + + public var disposeBag = DisposeBag() + + private let onBoardingNotificationFactory: OnBoardingNotificationFactory + private let appCoordinator: AppCoordinatorProtocol + + // MARK: - Components + + private var mainView = OnBoardingInputView() + + init(onBoardingNotificationFactory: OnBoardingNotificationFactory, appCoordinator: AppCoordinatorProtocol) { + self.onBoardingNotificationFactory = onBoardingNotificationFactory + self.appCoordinator = appCoordinator + super.init() + } +} + +// MARK: - Life Cycle +public extension OnBoardingInputViewController { + override func viewDidLoad() { + super.viewDidLoad() + addViews() + setupConstraints() + configureUI() + } +} + +// MARK: - SetUp +private extension OnBoardingInputViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + } + + func configureUI() { + setupKeyboard() + } + + func setupKeyboard() { + setupKeyboard(inset: OnBoardingInputView.Constant.bottomInset) { [weak self] height in + self?.mainView.nextButtonBottomConstraint?.update(inset: height) + } + } +} + +// MARK: - Bind +public extension OnBoardingInputViewController { + func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.nextButton.rx.tap + .map { Reactor.Action.nextButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.inputBox.textField.rx.text.orEmpty + .map { text -> Int? in + Int(text) + } + .map { Reactor.Action.inputLevel($0) } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.dropDownBox.onItemSelected = { [weak self] job in + guard let self = self else { return } + self.reactor?.action.onNext(.inputRole(.init(name: job.name, id: job.id))) + } + + mainView.headerView.leftButton.rx.tap + .map { Reactor.Action.backButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.headerView.underlineTextButton.rx.tap + .map { Reactor.Action.skipButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + reactor.state + .map { $0.jobList } + .observe(on: MainScheduler.instance) + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, list in + owner.mainView.dropDownBox.items = list.map { .init(name: $0.name, id: $0.id) } + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isLevelValid } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isLevelValid in + guard let isLevelValid = isLevelValid else { return } + owner.mainView.inputBox.setType(type: isLevelValid ? InputBoxType.edit : InputBoxType.error) + owner.mainView.errorMessage.isHidden = isLevelValid + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isButtonEnabled } + .bind(to: mainView.nextButton.rx.isEnabled) + .disposed(by: disposeBag) + + rx.viewDidAppear + .take(1) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .observe(on: MainScheduler.instance) + .withUnretained(self) + .subscribe(onNext: { owner, route in + switch route { + case .dismiss: + owner.navigationController?.popViewController(animated: true) + case .home: + owner.appCoordinator.showMainTab() + case .error: + let errorViewController = BaseErrorViewController() + owner.present(errorViewController, animated: true) + case .notification: + guard let selecteLevel = reactor.currentState.level, + let selectedJobID = reactor.currentState.job?.id else { return } + let viewController = owner.onBoardingNotificationFactory.make(selectedLevel: selecteLevel, selectedJobID: selectedJobID) + owner.navigationController?.pushViewController(viewController, animated: true) + default: + break + } + }) + .disposed(by: disposeBag) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift new file mode 100644 index 00000000..97613d4e --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift @@ -0,0 +1,40 @@ +import AuthFeatureInterface +import BaseFeature +import DictionaryFeatureInterface +import DomainInterface + +public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationSheetFactory { + private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase + private let openNotificationSettingUseCase: OpenNotificationSettingUseCase + private let updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase + private let updateUserInfoUseCase: UpdateUserInfoUseCase + private let appCoordinator: () -> AppCoordinatorProtocol + + public init( + checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, + openNotificationSettingUseCase: OpenNotificationSettingUseCase, + updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, + updateUserInfoUseCase: UpdateUserInfoUseCase, + appCoordinator: @escaping () -> AppCoordinatorProtocol + ) { + self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase + self.openNotificationSettingUseCase = openNotificationSettingUseCase + self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase + self.updateUserInfoUseCase = updateUserInfoUseCase + self.appCoordinator = appCoordinator + } + + public func make(selectedLevel: Int, selectedJobID: Int) -> BaseViewController & ModalPresentable { + let viewController = OnBoardingNotificationSheetViewController(appCoordinator: appCoordinator()) + viewController.isBottomTabbarHidden = true + viewController.reactor = OnBoardingNotificationSheetReactor( + selectedLevel: selectedLevel, + selectedJobID: selectedJobID, + checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, + openNotificationSettingUseCase: openNotificationSettingUseCase, + updateNotificationAgreementUseCase: updateNotificationAgreementUseCase, + updateUserInfoUseCase: updateUserInfoUseCase + ) + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift new file mode 100644 index 00000000..b9e1f8ce --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift @@ -0,0 +1,113 @@ +import DomainInterface + +import ReactorKit +import RxCocoa +import RxSwift + +public final class OnBoardingNotificationSheetReactor: Reactor { + public enum Route { + case none + case dismiss + case home + case setting + } + + // MARK: - Reactor + public enum Action { + case viewWillAppear + case toggleSwitchButton(Bool) + case setButtonTapped + case cancelButtonTapped + case applyButtonTapped + case skipButtonTapped + case updateAuthorization(Bool) + case appWillEnterForeground + } + + public enum Mutation { + case navigateTo(route: Route) + case setLocalNotification(Bool) + case setRemoteNotification(Bool) + case setAuthorized(Bool) + } + + public struct State { + @Pulse var route: Route = .none + var selectedLevel: Int + var selectedJobID: Int + var isAgreeLocalNotification = false + var isAgreeRemoteNotification = true + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + + private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase + private let openNotificationSettingUseCase: OpenNotificationSettingUseCase + private let updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase + private let updateUserInfoUseCase: UpdateUserInfoUseCase + + // MARK: - init + public init( + selectedLevel: Int, + selectedJobID: Int, + checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, + openNotificationSettingUseCase: OpenNotificationSettingUseCase, + updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, + updateUserInfoUseCase: UpdateUserInfoUseCase + ) { + self.initialState = State(selectedLevel: selectedLevel, selectedJobID: selectedJobID) + self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase + self.openNotificationSettingUseCase = openNotificationSettingUseCase + self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase + self.updateUserInfoUseCase = updateUserInfoUseCase + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .viewWillAppear, .appWillEnterForeground: + return checkNotificationPermissionUseCase.execute() + .asObservable() + .map { .setLocalNotification($0) } + case .toggleSwitchButton(let isAgree): + return .just(.setRemoteNotification(isAgree)) + case .setButtonTapped: + openNotificationSettingUseCase.execute() + return .just(.navigateTo(route: .setting)) + case .applyButtonTapped: + return updateUserInfoUseCase.execute(level: currentState.selectedLevel, selectedJobID: currentState.selectedJobID) + .andThen(updateNotificationAgreementUseCase.execute( + noticeAgreement: true, + patchNoteAgreement: true, + eventAgreement: true + )) + .andThen(Observable.just(.navigateTo(route: .home))) + .catchAndReturn(.navigateTo(route: .dismiss)) + case .cancelButtonTapped: + return .just(.navigateTo(route: .dismiss)) + case .skipButtonTapped: + return .just(.navigateTo(route: .home)) + case .updateAuthorization(let authorized): + return .just(.setAuthorized(authorized)) + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + + switch mutation { + case .navigateTo(let route): + newState.route = route + case .setLocalNotification(let isAgree): + newState.isAgreeLocalNotification = isAgree + case .setRemoteNotification(let isAgree): + newState.isAgreeRemoteNotification = isAgree + case let .setAuthorized(isAuthorized): + newState.isAgreeLocalNotification = isAuthorized + } + + return newState + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift new file mode 100644 index 00000000..aacb3853 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift @@ -0,0 +1,100 @@ +import UIKit + +import DesignSystem + +import SnapKit + +final class OnBoardingNotificationSheetView: UIView { + private enum Constant { + static let inset: CGFloat = 16 + static let spacing: CGFloat = 14 + static let buttonTopMargin: CGFloat = 20 + } + + // MARK: - Properties + let header: Header = { + let header = Header(style: .filter, title: "신규 이벤트 알림 설정") + return header + }() + + private let descriptionLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 2 + return label + }() + + private let buttonStackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = Constant.spacing + return view + }() + + let notificationToggleBox = ToggleBox(text: "알림 설정") + let applyButton = CommonButton(style: .normal, title: "적용", disabledTitle: nil) + let settingButton = CommonButton(style: .normal, title: "변경하기", disabledTitle: nil) + let skipButton = CommonButton(style: .border, title: "나중에 하기", disabledTitle: nil) + + // MARK: - init + init() { + super.init(frame: .zero) + + addViews() + setupConstraints() + configureUI() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension OnBoardingNotificationSheetView { + func addViews() { + addSubview(header) + addSubview(descriptionLabel) + addSubview(buttonStackView) + } + + func setupConstraints() { + header.snp.makeConstraints { make in + make.top.equalToSuperview().inset(Constant.inset) + make.horizontalEdges.equalToSuperview() + } + + descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(header.snp.bottom).offset(Constant.spacing) + make.horizontalEdges.equalToSuperview().inset(Constant.inset) + } + + buttonStackView.snp.makeConstraints { make in + make.top.equalTo(descriptionLabel.snp.bottom).offset(Constant.buttonTopMargin) + make.horizontalEdges.bottom.equalToSuperview().inset(Constant.inset) + } + } + + func configureUI() { + notificationToggleBox.toggle.isOn = true + } +} + +// MARK: - Methods +extension OnBoardingNotificationSheetView { + func setUI(isAgree: Bool) { + buttonStackView.arrangedSubviews.forEach { view in + buttonStackView.removeArrangedSubview(view) + view.removeFromSuperview() + } + + descriptionLabel.attributedText = .makeStyledString(font: .cp_s_r, text: isAgree ? "메이플랜드 이벤트 소식을\n푸시 알림으로 빠르게 받아보세요." : "기기 알림 설정을 변경해야 이벤트 소식을 받을 수 있어요.", color: .neutral700, alignment: .left) + if isAgree { + buttonStackView.addArrangedSubview(notificationToggleBox) + buttonStackView.addArrangedSubview(applyButton) + } else { + buttonStackView.addArrangedSubview(settingButton) + buttonStackView.addArrangedSubview(skipButton) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift new file mode 100644 index 00000000..ba30890a --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -0,0 +1,157 @@ +import UIKit + +import BaseFeature +import DesignSystem +import DictionaryFeatureInterface + +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit + +public final class OnBoardingNotificationSheetViewController: BaseViewController, ModalPresentable, View { + public var modalHeight: CGFloat? + + public typealias Reactor = OnBoardingNotificationSheetReactor + + public var disposeBag = DisposeBag() + + // MARK: - Properties + + private let appCoordinator: AppCoordinatorProtocol + + // MARK: - Components + private var mainView = OnBoardingNotificationSheetView() + + init(appCoordinator: AppCoordinatorProtocol) { + self.appCoordinator = appCoordinator + super.init() + } +} + +// MARK: - Life Cycle +public extension OnBoardingNotificationSheetViewController { + override func viewDidLoad() { + super.viewDidLoad() + + addViews() + setupConstraints() + } +} + +// MARK: - SetUp +private extension OnBoardingNotificationSheetViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} + +extension OnBoardingNotificationSheetViewController { + public func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .take(1) + .do(onNext: { [weak self] _ in + self?.checkNotificationAuthorization() + }) + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + NotificationCenter.default.rx.notification(UIApplication.willEnterForegroundNotification) + .map { _ in Reactor.Action.appWillEnterForeground } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.header.firstIconButton.rx.tap + .map { Reactor.Action.cancelButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.skipButton.rx.tap + .map { Reactor.Action.skipButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.settingButton.rx.tap + .map { Reactor.Action.setButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.applyButton.rx.tap + .map { Reactor.Action.applyButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.notificationToggleBox.toggle.rx.isOn + .map { Reactor.Action.toggleSwitchButton($0) } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + reactor.state + .observe(on: MainScheduler.instance) + .map { $0.isAgreeLocalNotification } + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.setUI(isAgree: isAgree) + } + .disposed(by: disposeBag) + + rx.viewDidAppear + .take(1) + .observe(on: MainScheduler.instance) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .withUnretained(self) + .subscribe { owner, route in + DispatchQueue.main.async { + switch route { + case .dismiss: + owner.dismissCurrentModal() + case .home: + owner.appCoordinator.showMainTab() + case .setting: + guard let url = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(url) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil) + default: + break + } + } + } + .disposed(by: disposeBag) + } +} + +// MARK: - Notification Authorization +private extension OnBoardingNotificationSheetViewController { + func checkNotificationAuthorization() { + guard let reactor = reactor else { return } + + NotificationPermissionManager.shared.getStatus { status in + switch status { + case .authorized, .provisional: + reactor.action.onNext(.updateAuthorization(true)) + case .denied: + reactor.action.onNext(.updateAuthorization(false)) + case .notDetermined: + NotificationPermissionManager.shared.requestIfNeeded { granted in + reactor.action.onNext(.updateAuthorization(granted)) + } + default: + reactor.action.onNext(.updateAuthorization(false)) + } + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift new file mode 100644 index 00000000..051cb8ba --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift @@ -0,0 +1,18 @@ +import AuthFeatureInterface +import BaseFeature + +public struct OnBoardingQuestionFactoryImpl: OnBoardingQuestionFactory { + + private let onBoardingInputFactory: OnBoardingInputFactory + + public init(onBoardingInputFactory: OnBoardingInputFactory) { + self.onBoardingInputFactory = onBoardingInputFactory + } + + public func make() -> BaseViewController { + let viewController = OnBoardingQuestionViewController(factory: onBoardingInputFactory) + viewController.isBottomTabbarHidden = true + viewController.reactor = OnBoardingQuestionReactor() + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionReactor.swift new file mode 100644 index 00000000..746674fc --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionReactor.swift @@ -0,0 +1,65 @@ +import ReactorKit +import RxSwift + +public final class OnBoardingQuestionReactor: Reactor { + // MARK: - Reactor + public enum Route { + case none + case dismiss + case home + case input + } + + public enum Action { + case viewDidLoad + case nextButtonTapped + case backButtonTapped + case skipButtonTapped + } + + public enum Mutation { + case showToast + case navigateTo(route: Route) + } + + public struct State { + @Pulse var route: Route = .none + @Pulse var isShowToast: Bool = false + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + + // MARK: - init + public init() { + self.initialState = State() + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .viewDidLoad: + return Observable.just(.showToast) + case .nextButtonTapped: + return Observable.just(.navigateTo(route: .input)) + case .backButtonTapped: + return Observable.just(.navigateTo(route: .dismiss)) + case .skipButtonTapped: + return Observable.just(.navigateTo(route: .home)) + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + + switch mutation { + case .showToast: + newState.isShowToast.toggle() + case .navigateTo(let route): + newState.route = route + } + + return newState + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift new file mode 100644 index 00000000..160c0e83 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift @@ -0,0 +1,94 @@ +import UIKit + +import DesignSystem + +import SnapKit + +public final class OnBoardingQuestionView: OnBoardingBaseView { + // MARK: - Type + private enum Constant { + static let horizontalInset = 16 + static let verticalInset = 16 + static let imgSize = 220 + static let resizeCenterY = 70 + } + + // MARK: - Components + private let imageView: UIImageView = { + let view = UIImageView() + view.image = DesignSystemAsset.image(named: "questionNotify") + return view + }() + + private let boldTextLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .h_xxxl_b, text: "효율적인 메이플랜드 플레이를\n위해 몇가지만 물어볼게요") + label.numberOfLines = 2 + return label + }() + + private let regularTeextLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .b_m_r, text: "나도 예티를 잡을 수 있을까?", color: .neutral700) + return label + }() + + private lazy var contentView: UIView = { + let view = UIView() + view.addSubview(imageView) + view.addSubview(boldTextLabel) + view.addSubview(regularTeextLabel) + + imageView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.centerX.equalToSuperview() + make.size.equalTo(Constant.imgSize) + } + + boldTextLabel.snp.makeConstraints { make in + make.top.equalTo(imageView.snp.bottom).offset(4) + make.horizontalEdges.equalToSuperview() + } + + regularTeextLabel.snp.makeConstraints { make in + make.top.equalTo(boldTextLabel.snp.bottom).offset(Constant.verticalInset) + make.horizontalEdges.bottom.equalToSuperview() + } + + return view + }() + + public let nextButton = CommonButton(style: .normal, title: "다음", disabledTitle: "") + + // MARK: - init + init() { + super.init(leftButtonIsHidden: true, underlineTextButtonIsHidden: true) + addViews() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension OnBoardingQuestionView { + func addViews() { + addSubview(contentView) + addSubview(nextButton) + } + + func setupConstraints() { + contentView.snp.makeConstraints { make in + make.centerY.equalToSuperview().offset(-Constant.resizeCenterY) + make.horizontalEdges.equalToSuperview() + } + + nextButton.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + make.bottom.equalTo(safeAreaLayoutGuide).inset(Constant.verticalInset) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift new file mode 100644 index 00000000..c1dbcf47 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift @@ -0,0 +1,119 @@ +import UIKit + +import AuthFeatureInterface +import BaseFeature + +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit + +public class OnBoardingQuestionViewController: BaseViewController, View { + // MARK: - Properties + public typealias Reactor = OnBoardingQuestionReactor + + public var disposeBag = DisposeBag() + + private let onBoardingInputFactory: OnBoardingInputFactory + + // MARK: - Components + + private var mainView = OnBoardingQuestionView() + + public init(factory: OnBoardingInputFactory) { + self.onBoardingInputFactory = factory + super.init() + } + + @available(*, unavailable) + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Life Cycle +public extension OnBoardingQuestionViewController { + override func viewDidLoad() { + super.viewDidLoad() + addViews() + setupConstraints() + } +} + +// MARK: - SetUp +private extension OnBoardingQuestionViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + } +} + +// MARK: - Bind +public extension OnBoardingQuestionViewController { + func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + rx.viewDidLoad + .map { Reactor.Action.viewDidLoad } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.nextButton.rx.tap + .map { Reactor.Action.nextButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.headerView.leftButton.rx.tap + .map { Reactor.Action.backButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.headerView.underlineTextButton.rx.tap + .map { Reactor.Action.skipButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + reactor.pulse(\.$isShowToast) + .subscribe { isShowToast in + if isShowToast { + let currentDate = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy.MM.dd" + let formattedDate = dateFormatter.string(from: currentDate) + ToastFactory.createToast(message: "\(formattedDate) 약관에 동의했어요.") + } + } + .disposed(by: disposeBag) + + rx.viewDidAppear + .take(1) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .withUnretained(self) + .subscribe { owner, route in + switch route { + case .dismiss: + owner.navigationController?.popViewController(animated: true) + case .home: + let homeViewController = UIViewController() + homeViewController.view.backgroundColor = .green + owner.navigationController?.pushViewController(homeViewController, animated: true) + case .input: + let inputViewController = owner.onBoardingInputFactory.make() + owner.navigationController?.pushViewController(inputViewController, animated: true) + default: + break + } + } + .disposed(by: disposeBag) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift new file mode 100644 index 00000000..e069897f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift @@ -0,0 +1,39 @@ +import AuthFeatureInterface +import BaseFeature +import DomainInterface + +public struct TermsAgreementFactoryImpl: TermsAgreementFactory { + private let onBoardingQuestionFactory: OnBoardingQuestionFactory + + private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase + private let signUpWithAppleUseCase: SignUpWithAppleUseCase + private let fetchTokenUseCase: FetchTokenFromLocalUseCase + private let updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + + public init( + onBoardingQuestionFactory: OnBoardingQuestionFactory, + signUpWithKakaoUseCase: SignUpWithKakaoUseCase, + signUpWithAppleUseCase: SignUpWithAppleUseCase, + fetchTokenUseCase: FetchTokenFromLocalUseCase, + updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + ) { + self.onBoardingQuestionFactory = onBoardingQuestionFactory + self.signUpWithKakaoUseCase = signUpWithKakaoUseCase + self.signUpWithAppleUseCase = signUpWithAppleUseCase + self.fetchTokenUseCase = fetchTokenUseCase + self.updateMarketingAgreementUseCase = updateMarketingAgreementUseCase + } + + public func make(credential: Credential, platform: LoginPlatform) -> BaseViewController { + let viewController = TermsAgreementViewController(onBoardingQuestionFactory: onBoardingQuestionFactory) + viewController.isBottomTabbarHidden = true + viewController.reactor = TermsAgreementReactor( + credential: credential, + socialPlatform: platform, + signUpWithKakaoUseCase: signUpWithKakaoUseCase, + signUpWithAppleUseCase: signUpWithAppleUseCase, + fetchTokenUseCase: fetchTokenUseCase, updateMarketingAgreementUseCase: updateMarketingAgreementUseCase + ) + return viewController + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift new file mode 100644 index 00000000..3aeb36ce --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift @@ -0,0 +1,155 @@ +import DomainInterface +import NotificationCenter + +import ReactorKit +import RxSwift + +public final class TermsAgreementReactor: Reactor { + // MARK: - Type + public enum AgreeType { + case total, age, serviceTerms, personalInfo, marketing + } + + public enum Route { + case none + case dismiss + case onBoarding + case error + case ageAgreement + case serviceAgreement + case personalAgreement + case marketingAgreement + } + + // MARK: - Reactor + public enum Action { + case backButtonTapped + case toggleAgree(type: AgreeType) + case bottomButtonTapped + case navigateTo(route: Route) + } + + public enum Mutation { + case setAgreeState(type: AgreeType, isOn: Bool) + case navigateTo(route: Route) + } + + public struct State { + @Pulse var route: Route = .none + + var isTotalAgree: Bool = false + var isAgeAgree: Bool = false + var isServiceTermsAgree: Bool = false + var isPersonalInformationAgree: Bool = false + var isMarketingAgree: Bool = false + var bottomButtonIsEnabled: Bool = false + } + + // MARK: - properties + public var initialState: State + var disposeBag = DisposeBag() + private let credential: Credential + private let socialPlatform: LoginPlatform + private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase + private let signUpWithAppleUseCase: SignUpWithAppleUseCase + private let fetchTokenUseCase: FetchTokenFromLocalUseCase + private let updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + + // MARK: - init + public init( + credential: Credential, + socialPlatform: LoginPlatform, + signUpWithKakaoUseCase: SignUpWithKakaoUseCase, + signUpWithAppleUseCase: SignUpWithAppleUseCase, + fetchTokenUseCase: FetchTokenFromLocalUseCase, + updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + ) { + self.credential = credential + self.socialPlatform = socialPlatform + self.signUpWithKakaoUseCase = signUpWithKakaoUseCase + self.signUpWithAppleUseCase = signUpWithAppleUseCase + self.fetchTokenUseCase = fetchTokenUseCase + self.updateMarketingAgreementUseCase = updateMarketingAgreementUseCase + self.initialState = State() + } + + // MARK: - Reactor Methods + public func mutate(action: Action) -> Observable { + switch action { + case .backButtonTapped: + return .just(.navigateTo(route: .dismiss)) + case .toggleAgree(let type): + let isOn: Bool + switch type { + case .total: + isOn = !currentState.isTotalAgree + case .age: + isOn = !currentState.isAgeAgree + case .serviceTerms: + isOn = !currentState.isServiceTermsAgree + case .personalInfo: + isOn = !currentState.isPersonalInformationAgree + case .marketing: + isOn = !currentState.isMarketingAgree + } + return .just(.setAgreeState(type: type, isOn: isOn)) + case .bottomButtonTapped: + let fcmToken: String? = { + if case .success(let token) = fetchTokenUseCase.execute(type: .fcmToken) { + return token + } else { + return nil + } + }() + + switch socialPlatform { + case .kakao: + return signUpWithKakaoUseCase + .execute(credential: credential, isMarketingAgreement: currentState.isMarketingAgree, fcmToken: fcmToken) + .map { _ in .navigateTo(route: .onBoarding) } + .catchAndReturn(.navigateTo(route: .error)) + + case .apple: + return signUpWithAppleUseCase + .execute(credential: credential, isMarketingAgreement: currentState.isMarketingAgree, fcmToken: fcmToken) + .map { _ in .navigateTo(route: .onBoarding) } + .catchAndReturn(.navigateTo(route: .error)) + } + + case .navigateTo(let route): + return .just(.navigateTo(route: route)) + } + } + + public func reduce(state: State, mutation: Mutation) -> State { + var newState = state + switch mutation { + case .setAgreeState(let type, let isOn): + switch type { + case .total: + newState.isTotalAgree = isOn + newState.isAgeAgree = isOn + newState.isServiceTermsAgree = isOn + newState.isPersonalInformationAgree = isOn + newState.isMarketingAgree = isOn + case .age: + newState.isAgeAgree = isOn + case .serviceTerms: + newState.isServiceTermsAgree = isOn + case .personalInfo: + newState.isPersonalInformationAgree = isOn + case .marketing: + newState.isMarketingAgree = isOn + } + case .navigateTo(let route): + newState.route = route + } + + // bottomButton 활성화 체크 + let allRequiredAgreed = newState.isAgeAgree && newState.isServiceTermsAgree && newState.isPersonalInformationAgree + newState.bottomButtonIsEnabled = allRequiredAgreed + newState.isTotalAgree = allRequiredAgreed && newState.isMarketingAgree + + return newState + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift new file mode 100644 index 00000000..bd55eff5 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift @@ -0,0 +1,148 @@ +import UIKit + +import DesignSystem + +import SnapKit + +public final class TermsAgreementView: UIView { + // MARK: - Type + private enum Constant { + static let imageTopSpacing: CGFloat = 20 + static let imageSize: CGFloat = 60 + static let horizontalInset: CGFloat = 16 + static let totalButtonBottomSpacing: CGFloat = -14 + static let titleLabelTopSpacing: CGFloat = 16 + static let titleLabelHeight: CGFloat = 30 + static let subTitleLabelTopSpacing: CGFloat = 4 + static let subTitleLabelHeight: CGFloat = 21 + static let stackViewBottomSpacing: CGFloat = -26 + static let bottomButtonBottomSpacing: CGFloat = 16 + static let termsSpacing: CGFloat = 4 + } + + // MARK: - Properties + let headerView: NavigationBar = { + let view = NavigationBar(type: .arrowLeft) + view.rightButton.isHidden = true + return view + }() + + private let logoImageView: UIImageView = { + let image = DesignSystemAsset.image(named: "logo") + let view = UIImageView(image: image) + view.contentMode = .scaleAspectFill + return view + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .h_xxxl_b, text: "필수약관에 동의해주세요") + return label + }() + + private let subTitleLabel: UILabel = { + let label = UILabel() + label.attributedText = .makeStyledString(font: .sub_m_m, text: "메랜사를 더 편하게 즐기기 위해 필요한 항목이에요", color: .neutral700) + return label + }() + + public let totalAgreeButton: CheckBoxButton = { + let button = CheckBoxButton(style: .normal, mainTitle: "전체동의", subTitle: "(선택 약관 포함)") + return button + }() + + private let termsStackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.isUserInteractionEnabled = true + view.spacing = Constant.termsSpacing + return view + }() + + public let ageAgreeButton: CheckBoxButton = { + let button = CheckBoxButton(style: .listMedium, mainTitle: "(필수) 만 14세 이상", subTitle: nil) + return button + }() + + public let serviceTermsAgreeButton: CheckBoxButton = { + let button = CheckBoxButton(style: .listMedium, mainTitle: "(필수) 메랜사 서비스 이용약관 동의", subTitle: nil) + return button + }() + + public let personalInformationAgreeButton: CheckBoxButton = { + let button = CheckBoxButton(style: .listMedium, mainTitle: "(필수) 개인정보 수집 및 이용 동의", subTitle: nil) + return button + }() + + public let marketingAgreeButton: CheckBoxButton = { + let button = CheckBoxButton(style: .listMedium, mainTitle: "(선택) 마케팅 정보 수신 동의", subTitle: nil) + return button + }() + + public let bottomButton: CommonButton = { + let button = CommonButton(style: .normal, title: "다음", disabledTitle: "다음") + return button + }() + + // MARK: - init + init() { + super.init(frame: .zero) + addViews() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("\(#file), \(#function) Error") + } +} + +// MARK: - SetUp +private extension TermsAgreementView { + func addViews() { + addSubview(headerView) + addSubview(logoImageView) + addSubview(titleLabel) + addSubview(subTitleLabel) + addSubview(totalAgreeButton) + addSubview(bottomButton) + addSubview(termsStackView) + termsStackView.addArrangedSubview(ageAgreeButton) + termsStackView.addArrangedSubview(serviceTermsAgreeButton) + termsStackView.addArrangedSubview(personalInformationAgreeButton) + termsStackView.addArrangedSubview(marketingAgreeButton) + } + + func setupConstraints() { + headerView.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + } + logoImageView.snp.makeConstraints { make in + make.top.equalTo(headerView.snp.bottom).offset(Constant.imageTopSpacing) + make.size.equalTo(Constant.imageSize) + make.leading.equalToSuperview().inset(Constant.horizontalInset) + } + titleLabel.snp.makeConstraints { make in + make.top.equalTo(logoImageView.snp.bottom).offset(Constant.titleLabelTopSpacing) + make.height.equalTo(Constant.titleLabelHeight) + make.leading.equalTo(logoImageView) + } + subTitleLabel.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(Constant.subTitleLabelTopSpacing) + make.height.equalTo(Constant.subTitleLabelHeight) + make.leading.equalTo(logoImageView) + } + bottomButton.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + make.bottom.equalToSuperview().inset(Constant.bottomButtonBottomSpacing) + } + termsStackView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + make.bottom.equalTo(bottomButton.snp.top).offset(Constant.stackViewBottomSpacing) + } + totalAgreeButton.snp.makeConstraints { make in + make.bottom.equalTo(termsStackView.snp.top).offset(Constant.totalButtonBottomSpacing) + make.horizontalEdges.equalToSuperview().inset(Constant.horizontalInset) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift new file mode 100644 index 00000000..bb7d2325 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift @@ -0,0 +1,181 @@ +import UIKit + +import AuthFeatureInterface +import BaseFeature + +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit + +public class TermsAgreementViewController: BaseViewController, View { + public typealias Reactor = TermsAgreementReactor + + // MARK: - Properties + public var disposeBag = DisposeBag() + + private let onBoardingQuestionFactory: OnBoardingQuestionFactory + + private var mainView = TermsAgreementView() + + public init(onBoardingQuestionFactory: OnBoardingQuestionFactory) { + self.onBoardingQuestionFactory = onBoardingQuestionFactory + super.init() + } + + @available(*, unavailable) + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Life Cycle +public extension TermsAgreementViewController { + override func viewDidLoad() { + super.viewDidLoad() + addViews() + setupConstraints() + configureUI() + } +} + +// MARK: - SetUp +private extension TermsAgreementViewController { + func addViews() { + view.addSubview(mainView) + } + + func setupConstraints() { + mainView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + } + + func configureUI() { + view.backgroundColor = .systemBackground + navigationController?.navigationBar.isHidden = true + } +} + +public extension TermsAgreementViewController { + func bind(reactor: Reactor) { + bindUserActions(reactor: reactor) + bindViewState(reactor: reactor) + } + + func bindUserActions(reactor: Reactor) { + mainView.headerView.leftButton.rx.tap + .map { Reactor.Action.backButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + let agreeButtons: [(button: UIButton, type: TermsAgreementReactor.AgreeType, isRightButton: Bool)] = [ + (mainView.totalAgreeButton, .total, false), + (mainView.ageAgreeButton, .age, false), + (mainView.ageAgreeButton.rightButton, .age, true), + (mainView.serviceTermsAgreeButton, .serviceTerms, false), + (mainView.serviceTermsAgreeButton.rightButton, .serviceTerms, true), + (mainView.personalInformationAgreeButton, .personalInfo, false), + (mainView.personalInformationAgreeButton.rightButton, .personalInfo, true), + (mainView.marketingAgreeButton, .marketing, false), + (mainView.marketingAgreeButton.rightButton, .marketing, true) + ] + + agreeButtons.forEach { button, type, _ in + button.rx.tap + .map { Reactor.Action.toggleAgree(type: type) } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + mainView.bottomButton.rx.tap + .map { Reactor.Action.bottomButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + } + + func bindViewState(reactor: Reactor) { + reactor.state + .map { $0.isTotalAgree } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.totalAgreeButton.isSelected = isAgree + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isAgeAgree } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.ageAgreeButton.isSelected = isAgree + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isServiceTermsAgree } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.serviceTermsAgreeButton.isSelected = isAgree + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isPersonalInformationAgree } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.personalInformationAgreeButton.isSelected = isAgree + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.isMarketingAgree } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isAgree in + owner.mainView.marketingAgreeButton.isSelected = isAgree + } + .disposed(by: disposeBag) + + reactor.state + .map { $0.bottomButtonIsEnabled } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, isEnabled in + owner.mainView.bottomButton.isEnabled = isEnabled + } + .disposed(by: disposeBag) + + rx.viewDidAppear + .take(1) + .flatMapLatest { _ in reactor.pulse(\.$route) } + .withUnretained(self) + .observe(on: MainScheduler.instance) + .subscribe { owner, route in + switch route { + case .dismiss: + owner.navigationController?.popViewController(animated: true) + case .onBoarding: + let questionViewController = owner.onBoardingQuestionFactory.make() + owner.navigationController?.setViewControllers([questionViewController], animated: true) + case .error: + let errorViewController = BaseErrorViewController() + owner.present(errorViewController, animated: true) + case .ageAgreement: + break + case .serviceAgreement: + break + case .personalAgreement: + break + case .marketingAgreement: + break + default: + break + } + } + .disposed(by: disposeBag) + } +} diff --git "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" "b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" new file mode 100644 index 00000000..af15126e --- /dev/null +++ "b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" @@ -0,0 +1,7 @@ +// +// ㅁㄴㅇ.swift +// MLSAuthFeature +// +// Created by SeoJunYoung on 4/8/26. +// + diff --git "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" "b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" new file mode 100644 index 00000000..af15126e --- /dev/null +++ "b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" @@ -0,0 +1,7 @@ +// +// ㅁㄴㅇ.swift +// MLSAuthFeature +// +// Created by SeoJunYoung on 4/8/26. +// + diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift new file mode 100644 index 00000000..ab6a9846 --- /dev/null +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift @@ -0,0 +1,6 @@ +import Testing +@testable import MLSAuthFeature + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} From 70b703686980b10c6c9c6c11f8edb64585e56050 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Wed, 8 Apr 2026 21:36:20 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat/#316:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=EC=83=81=ED=83=9C=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLSAuthFeature/Package.swift | 45 +++-- .../Data/Network/DTO/AuthResponseDTO.swift | 24 +++ .../Data/Network/DTO/JobsDTO.swift | 23 +++ .../Data/Network/DTO/MemberDTO.swift | 29 +++ .../Data/Network/Endpoint/AuthEndPoint.swift | 143 ++++++++++++++ .../Providers/Apple/AppleCredential.swift | 11 ++ .../Apple/AppleLoginProviderImpl.swift | 60 ++++++ .../Providers/Kakao/KakaoCredential.swift | 11 ++ .../Kakao/KakaoLoginProviderImpl.swift | 56 ++++++ .../Repository/AuthAPIRepositoryImpl.swift | 178 ++++++++++++++++++ .../AuthAPI/CheckLoginUseCaseImpl.swift | 45 +++++ ...eckNotificationPermissionUseCaseImpl.swift | 23 +++ .../AuthAPI/FetchJobListUseCaseImpl.swift | 16 ++ .../AuthAPI/FetchPlatformUseCaseImpl.swift | 16 ++ .../AuthAPI/LoginWithAppleUseCaseImpl.swift | 52 +++++ .../AuthAPI/LoginWithKakaoUseCaseImpl.swift | 52 +++++ .../UseCase/AuthAPI/LogoutUseCaseImpl.swift | 36 ++++ .../OpenNotificationSettingUseCaseImpl.swift | 12 ++ .../AuthAPI/PutFCMTokenUseCaseImpl.swift | 16 ++ .../UseCase/AuthAPI/ReissueUseCaseImpl.swift | 31 +++ .../AuthAPI/SignUpWithAppleUseCaseImpl.swift | 39 ++++ .../AuthAPI/SignUpWithKakaoUseCaseImpl.swift | 39 ++++ .../UpdateMarketingAgreementUseCaseImpl.swift | 16 ++ ...dateNotificationAgreementUseCaseImpl.swift | 16 ++ .../AuthAPI/UpdateUserInfoUseCaseImpl.swift | 16 ++ .../UseCase/AuthAPI/WithdrawUseCaseImpl.swift | 29 +++ .../FetchTokenFromLocalUseCaseImpl.swift | 15 ++ .../CheckEmptyLevelAndRoleUseCaseImpl.swift | 12 ++ .../OnBoarding/CheckNickNameUseCaseImpl.swift | 15 ++ .../CheckValidLevelUseCaseImpl.swift | 11 ++ .../Shared/SocialLoginUseCaseImpl.swift | 16 ++ .../MLSAuthFeature/Entity/Credential.swift | 4 + .../Entity/JobListResponse.swift | 19 ++ .../MLSAuthFeature/Entity/LoginPlatform.swift | 6 + .../MLSAuthFeature/Entity/LoginResponse.swift | 13 ++ .../Entity/MyPageResponse.swift | 33 ++++ .../Entity/SignUpResponse.swift | 11 ++ .../MLSAuthFeature/Error/AuthError.swift | 5 + .../Error/TokenRepositoryError.swift | 14 ++ .../Presentation/Login/LoginFactoryImpl.swift | 6 +- .../Presentation/Login/LoginReactor.swift | 2 +- .../Presentation/Login/LoginView.swift | 4 +- .../Login/LoginViewController.swift | 4 +- .../OnBoardingNotificationFactoryImpl.swift | 4 +- .../OnBoardingNotificationView.swift | 2 +- ...OnBoardingNotificationViewController.swift | 6 +- .../OnBoarding/OnBoardingBaseView.swift | 2 +- .../OnBoardingInputFactoryImpl.swift | 8 +- .../OnBoardingInputReactor.swift | 2 +- .../OnBoardingInput/OnBoardingInputView.swift | 4 +- .../OnBoardingInputViewController.swift | 8 +- ...BoardingNotificationSheetFactoryImpl.swift | 8 +- .../OnBoardingNotificationSheetReactor.swift | 2 +- .../OnBoardingNotificationSheetView.swift | 2 +- ...rdingNotificationSheetViewController.swift | 6 +- .../OnBoardingQuestionFactoryImpl.swift | 4 +- .../OnBoardingQuestionView.swift | 2 +- .../OnBoardingQuestionViewController.swift | 4 +- .../TermsAgreementFactoryImpl.swift | 6 +- .../TermsAgreementReactor.swift | 2 +- .../TermsAgreement/TermsAgreementView.swift | 2 +- .../TermsAgreementViewController.swift | 4 +- .../SocialAuthenticatableProvider.swift | 7 + .../Repository/AuthAPIRepository.swift | 21 +++ .../Repository/TokenRepository.swift | 7 + .../Repository/UserDefaultsRepository.swift | 6 + .../UseCase/AuthAPI/CheckLoginUseCase.swift | 5 + .../CheckNotificationPermissionUseCase.swift | 5 + .../UseCase/AuthAPI/FetchJobListUseCase.swift | 5 + .../AuthAPI/FetchPlatformUseCase.swift | 5 + .../AuthAPI/LoginWithAppleUseCase.swift | 5 + .../AuthAPI/LoginWithKakaoUseCase.swift | 5 + .../UseCase/AuthAPI/LogoutUseCase.swift | 5 + .../OpenNotificationSettingUseCase.swift | 3 + .../UseCase/AuthAPI/PutFCMTokenUseCase.swift | 5 + .../UseCase/AuthAPI/ReissueUseCase.swift | 5 + .../AuthAPI/SignUpWithAppleUseCase.swift | 5 + .../AuthAPI/SignUpWithKakaoUseCase.swift | 5 + .../UpdateMarketingAgreementUseCase.swift | 5 + .../UpdateNotificationAgreementUseCase.swift | 5 + .../AuthAPI/UpdateUserInfoUseCase.swift | 5 + .../UseCase/AuthAPI/WithdrawUseCase.swift | 5 + .../FetchTokenFromLocalUseCase.swift | 5 + .../CheckEmptyLevelAndRoleUseCase.swift | 5 + .../OnBoarding/CheckNickNameUseCase.swift | 5 + .../OnBoarding/CheckValidLevelUseCase.swift | 5 + .../Shared/FetchSocialCredentialUseCase.swift | 7 + .../AppCoordinatorProtocol.swift | 7 + .../LoginFactory.swift | 16 ++ .../OnBoadingNotificationFactory.swift | 5 + .../OnBoardingInputFactory.swift | 5 + .../OnBoardingModalFactory.swift | 6 + .../OnBoardingNotificationSheetFactory.swift | 6 + .../OnBoardingQuestionFactory.swift | 5 + .../TermsAgreementFactory.swift | 6 + .../MLSAuthFeatureTesting.swift | 2 + .../Sources/MLSAuthFeatureTesting/temp.swift | 2 +- ...343\205\201\343\204\264\343\205\207.swift" | 7 - MLS/MLSCore/Package.swift | 6 +- 99 files changed, 1452 insertions(+), 69 deletions(-) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/AppCoordinatorProtocol.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift rename "MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" => MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift (77%) delete mode 100644 "MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index 5dd517d1..6c3cab63 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -1,18 +1,16 @@ // swift-tools-version: 6.2 -// The swift-tools-version declares the minimum version of Swift required to build this package. - import PackageDescription let package = Package( name: "MLSAuthFeature", platforms: [.iOS(.v15)], products: [ - // Interface: 외부 인터페이스와 모델을 제공하는 모듈 + // Interface: Presentation 팩토리 프로토콜 .library( name: "MLSAuthFeatureInterface", targets: ["MLSAuthFeatureInterface"] ), - // Feature: 실제 기능이 구현된 모듈 + // Feature: Presentation + Domain + Data 구현체 .library( name: "MLSAuthFeature", targets: ["MLSAuthFeature"] @@ -21,23 +19,46 @@ let package = Package( .library( name: "MLSAuthFeatureTesting", targets: ["MLSAuthFeatureTesting"] - ) + ), + ], + dependencies: [ + .package(path: "../MLSCore"), + .package(path: "../MLSDesignSystem"), + .package(url: "https://github.com/ReactorKit/ReactorKit.git", from: "3.2.0"), + .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.22.0"), + .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"), + .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0"), ], targets: [ - // Interface 모듈 (도메인 모델 및 프로토콜) + // Interface 모듈 (Presentation 팩토리 프로토콜) .target( name: "MLSAuthFeatureInterface", - dependencies: [] + dependencies: [ + .product(name: "MLSCore", package: "MLSCore"), + .product(name: "MLSDesignSystem", package: "MLSDesignSystem"), + .product(name: "RxSwift", package: "RxSwift"), + ] ), - // Feature 모듈 (실제 구현) + // Feature 모듈 (Presentation + Domain + Data 구현체) .target( name: "MLSAuthFeature", - dependencies: ["MLSAuthFeatureInterface"] + dependencies: [ + "MLSAuthFeatureInterface", + .product(name: "MLSCore", package: "MLSCore"), + .product(name: "MLSDesignSystem", package: "MLSDesignSystem"), + .product(name: "ReactorKit", package: "ReactorKit"), + .product(name: "RxSwift", package: "RxSwift"), + .product(name: "RxKeyboard", package: "RxKeyboard"), + .product(name: "KakaoSDKAuth", package: "kakao-ios-sdk"), + .product(name: "KakaoSDKUser", package: "kakao-ios-sdk"), + ] ), // Testing 모듈 (Mock 객체) .target( name: "MLSAuthFeatureTesting", - dependencies: ["MLSAuthFeatureInterface"] + dependencies: [ + "MLSAuthFeatureInterface", + ] ), // Tests 모듈 .testTarget( @@ -45,8 +66,8 @@ let package = Package( dependencies: [ "MLSAuthFeature", "MLSAuthFeatureInterface", - "MLSAuthFeatureTesting" + "MLSAuthFeatureTesting", ] - ) + ), ] ) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift new file mode 100644 index 00000000..9fae13dc --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift @@ -0,0 +1,24 @@ + + +public struct AuthResponseDTO: Decodable { + public let accessToken: String + public let refreshToken: String + public let member: MemberDTO? +} + +public extension AuthResponseDTO { + func toLoginDomain() -> LoginResponse { + return .init( + isRegister: member != nil, + accessToken: accessToken, + refreshToken: refreshToken + ) + } + + func toSignUpDomain() -> SignUpResponse { + return .init( + accessToken: accessToken, + refreshToken: refreshToken + ) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift new file mode 100644 index 00000000..c81985b9 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift @@ -0,0 +1,23 @@ + + +public struct JobsDTO: Decodable { + public let jobId: Int + public let jobName: String + public let jobLevel: Int + public let parentJobId: Int? +} + +public extension JobsDTO { + func toDomain() -> Job { + return Job(name: jobName, id: jobId) + } +} + +public extension Array where Element == JobsDTO { + func toDomain() -> JobListResponse { + let jobs = self + .filter { $0.jobLevel == 0 } + .map { Job(name: $0.jobName, id: $0.jobId) } + return JobListResponse(jobList: jobs) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift new file mode 100644 index 00000000..509078f8 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift @@ -0,0 +1,29 @@ + + +public struct MemberDTO: Decodable { + public let id: String + public let provider: String + public let nickname: String + public let fcmToken: String? + public let marketingAgreement: Bool? + public let noticeAgreement: Bool? + public let patchNoteAgreement: Bool? + public let eventAgreement: Bool? + public let jobId: Int? + public let level: Int? + public let profileImageUrl: String + + func toMyPageDomain() -> MyPageResponse { + return .init( + nickname: nickname, + jobId: jobId, + jobName: "", + level: level, + profileUrl: profileImageUrl, + platform: provider == "APPLE" ? .apple : .kakao, + noticeAgreement: noticeAgreement, + patchNoteAgreement: patchNoteAgreement, + eventAgreement: eventAgreement + ) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift new file mode 100644 index 00000000..106c26f0 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift @@ -0,0 +1,143 @@ + +import MLSCore + +public enum AuthEndPoint { + static let base = "https://mapleland.2megabytes.me" + + public static func fetchProfile() -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/me", + method: .GET + ) + } + + public static func loginWithKakao(credential: Credential) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/login/kakao", + method: .POST, + headers: ["access-token": credential.token] + ) + } + + public static func loginWithApple(credential: Credential) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/login/apple", + method: .POST, + headers: ["id-token": credential.token] + ) + } + + public static func signupWithKakao(credential: String, body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/signup/kakao", + method: .POST, + headers: ["access-token": credential], + body: body + ) + } + + public static func signupWithApple(credential: String, body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/signup/apple", + method: .POST, + headers: ["id-token": credential], + body: body + ) + } + + public static func reIssueToken(refreshToken: String) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/reissue", + method: .POST, + headers: [ + "accept": "*/*", + "refresh-token": refreshToken, + ] + ) + } + + public static func fcmToken(body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/fcm-token", + method: .PUT, + body: body + ) + } + + public static func withdraw() -> EndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member", + method: .DELETE + ) + } + + public static func updateMarketingAgreement(credential: String, body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/marketing-agreement", + method: .PUT, + headers: ["Authorization": "Bearer \(credential)"], + body: body + ) + } + + public static func fetchJobs() -> ResponsableEndPoint<[JobsDTO]> { + .init( + baseURL: base, + path: "/api/v1/jobs", + method: .GET + ) + } + + public static func fetchJob(jobId: String) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/jobs/\(jobId)", + method: .GET + ) + } + + public static func updateCharacterInfo(body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/profile", + method: .PUT, + body: body + ) + } + + public static func updateNotification(body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/alert-agreement", + method: .PUT, + body: body + ) + } + + public static func updateNickName(body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/nickname", + method: .PUT, + body: body + ) + } + + public static func updateProfileImage(body: Encodable) -> ResponsableEndPoint { + .init( + baseURL: base, + path: "/api/v1/auth/member/profile-image", + method: .PUT, + body: body + ) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift new file mode 100644 index 00000000..6d27b3da --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift @@ -0,0 +1,11 @@ + + +public struct AppleCredential: Credential { + public let token: String + public let providerID: String + + public init(token: String, providerID: String) { + self.token = token + self.providerID = providerID + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift new file mode 100644 index 00000000..736eb281 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift @@ -0,0 +1,60 @@ +import AuthenticationServices +import Foundation +import UIKit + + +import RxSwift + +public final class AppleLoginProviderImpl: NSObject, SocialAuthenticatableProvider { + override public init() {} + + private var authServiceResponse = PublishSubject() + + public func getCredential() -> Observable { + let subject = PublishSubject() + authServiceResponse = subject + performRequest() + return subject + } + + private func performRequest() { + let provider = ASAuthorizationAppleIDProvider() + let request = provider.createRequest() + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = self + controller.presentationContextProvider = self + controller.performRequests() + } +} + +extension AppleLoginProviderImpl: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { + public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + let scenes = UIApplication.shared.connectedScenes + let windowScene = scenes.first as? UIWindowScene + return windowScene?.windows.first ?? UIWindow() + } + + public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else { + authServiceResponse.onError(AuthError.unknown(message: "Invalid Apple credential")) + return + } + + guard let idTokenData = appleIDCredential.identityToken, + let idToken = String(data: idTokenData, encoding: .utf8), + let codeData = appleIDCredential.authorizationCode, + let authCode = String(data: codeData, encoding: .utf8) + else { + authServiceResponse.onError(AuthError.unknown(message: "Failed to parse Apple token or code")) + return + } + + let credential = AppleCredential(token: idToken, providerID: authCode) + authServiceResponse.onNext(credential) + authServiceResponse.onCompleted() + } + + public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + authServiceResponse.onError(error) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift new file mode 100644 index 00000000..1c78151d --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift @@ -0,0 +1,11 @@ + + +public struct KakaoCredential: Credential { + public let token: String + public let providerID: String + + public init(token: String, providerID: String) { + self.token = token + self.providerID = providerID + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift new file mode 100644 index 00000000..ec43e228 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift @@ -0,0 +1,56 @@ +import Foundation + + +import KakaoSDKAuth +import KakaoSDKUser +import RxSwift + +public final class KakaoLoginProviderImpl: SocialAuthenticatableProvider { + public init() {} + + public func getCredential() -> Observable { + return Observable.create { [weak self] observer in + let disposable = Disposables.create() + + let handleLogin: (OAuthToken?, Error?) -> Void = { oauthToken, error in + self?.fetchEmailAfterDelay(oauthToken: oauthToken, error: error, observer: observer) + } + + DispatchQueue.main.async { + if UserApi.isKakaoTalkLoginAvailable() { + UserApi.shared.loginWithKakaoTalk(completion: handleLogin) + } else { + UserApi.shared.loginWithKakaoAccount(completion: handleLogin) + } + } + + return disposable + } + } + + private func fetchEmailAfterDelay(oauthToken: OAuthToken?, error: Error?, observer: AnyObserver) { + if let error { + observer.onError(error) + return + } + + guard let accessToken = oauthToken?.accessToken else { + observer.onError(AuthError.unknown(message: "토큰이 없어요")) + return + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + UserApi.shared.me { user, error in + if let error { + observer.onError(error) + return + } + + let id = user?.id ?? 0 + let credential = KakaoCredential(token: accessToken, providerID: String(id)) + observer.onNext(credential) + observer.onCompleted() + } + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift new file mode 100644 index 00000000..c4ab162c --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift @@ -0,0 +1,178 @@ +import Foundation + + +import MLSCore +import RxSwift + +public class AuthAPIRepositoryImpl: AuthAPIRepository { + private let provider: NetworkProvider + private let tokenInterceptor: Interceptor + private let authInterceptor: Interceptor + + public init(provider: NetworkProvider, tokenInterceptor: Interceptor, authInterceptor: Interceptor) { + self.provider = provider + self.tokenInterceptor = tokenInterceptor + self.authInterceptor = authInterceptor + } + + public func fetchProfile() -> Observable { + let endpoint = AuthEndPoint.fetchProfile() + return provider.requestData(endPoint: endpoint, interceptor: tokenInterceptor) + .map { $0.toMyPageDomain() } + } + + public func loginWithKakao(credential: Credential) -> Observable { + let endpoint = AuthEndPoint.loginWithKakao(credential: credential) + return provider.requestData(endPoint: endpoint, interceptor: authInterceptor) + .map { $0.toLoginDomain() } + .catch { error in + if case NetworkError.statusError(let code, _) = error, code == 404 { + return Observable.error(AuthError.userNotFound(credential: credential)) + } else { + return Observable.error(error) + } + } + } + + public func loginWithApple(credential: Credential) -> Observable { + let endpoint = AuthEndPoint.loginWithApple(credential: credential) + return provider.requestData(endPoint: endpoint, interceptor: authInterceptor) + .map { $0.toLoginDomain() } + .catch { error in + if case NetworkError.statusError(let code, _) = error, code == 404 { + return Observable.error(AuthError.userNotFound(credential: credential)) + } else { + return Observable.error(error) + } + } + } + + public func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + let endpoint = AuthEndPoint.signupWithKakao( + credential: credential.token, + body: KakaoBody( + providerId: credential.providerID, + fcmToken: fcmToken, + marketingAgreement: isMarketingAgreement + ) + ) + return provider.requestData(endPoint: endpoint, interceptor: nil).map { $0.toSignUpDomain() } + } + + public func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + let endpoint = AuthEndPoint.signupWithApple( + credential: credential.token, + body: AppleBody( + providerId: credential.providerID, + fcmToken: fcmToken, + marketingAgreement: isMarketingAgreement + ) + ) + return provider.requestData(endPoint: endpoint, interceptor: nil).map { $0.toSignUpDomain() } + } + + public func withdraw() -> Completable { + let endPoint = AuthEndPoint.withdraw() + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + } + + public func reissueToken(refreshToken: String) -> Observable { + let endPoint = AuthEndPoint.reIssueToken(refreshToken: refreshToken) + return provider.requestData(endPoint: endPoint, interceptor: nil).map { $0.toLoginDomain() } + } + + public func fcmToken(fcmToken: String?) -> Completable { + let endPoint = AuthEndPoint.fcmToken(body: FCMTokenBody(fcmToken: fcmToken)) + return provider.requestData(endPoint: endPoint, interceptor: authInterceptor) + } + + public func fetchJobList() -> Observable { + let endPoint = AuthEndPoint.fetchJobs() + return provider.requestData(endPoint: endPoint, interceptor: nil).map { $0.toDomain() } + } + + public func fetchJob(jobId: String) -> Observable { + let endPoint = AuthEndPoint.fetchJob(jobId: jobId) + return provider.requestData(endPoint: endPoint, interceptor: nil).map { $0.toDomain() } + } + + public func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { + let endPoint = AuthEndPoint.updateCharacterInfo(body: UpdateInfoBody(level: level, jobId: selectedJobID)) + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + } + + public func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { + let endPoint = AuthEndPoint.updateMarketingAgreement( + credential: credential, + body: MarketingAgreementBody(marketingAgreement: isMarketingAgreement) + ) + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + } + + public func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { + let endPoint = AuthEndPoint.updateNotification( + body: NotificationAgreementBody( + noticeAgreement: noticeAgreement, + patchNoteAgreement: patchNoteAgreement, + eventAgreement: eventAgreement + ) + ) + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + } + + public func updateNickName(nickName: String) -> Observable { + let endPoint = AuthEndPoint.updateNickName(body: NickNameBody(nickname: nickName)) + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + .map { $0.toMyPageDomain() } + } + + public func updateProfileImage(url: String) -> Completable { + let endPoint = AuthEndPoint.updateProfileImage(body: UpdateProfileImageBody(profileImageUrl: url)) + return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + } +} + +private extension AuthAPIRepositoryImpl { + struct KakaoBody: Encodable { + let provider = "KAKAO" + let providerId: String + let nickname: String? = nil + let fcmToken: String? + let marketingAgreement: Bool + } + + struct AppleBody: Encodable { + let provider = "APPLE" + let providerId: String + let nickname: String? = nil + let fcmToken: String? + let marketingAgreement: Bool + } + + struct FCMTokenBody: Encodable { + let fcmToken: String? + } + + struct MarketingAgreementBody: Encodable { + let marketingAgreement: Bool + } + + struct NotificationAgreementBody: Encodable { + let noticeAgreement: Bool + let patchNoteAgreement: Bool + let eventAgreement: Bool + } + + struct NickNameBody: Encodable { + let nickname: String + } + + struct UpdateInfoBody: Encodable { + let level: Int + let jobId: Int + } + + struct UpdateProfileImageBody: Encodable { + let profileImageUrl: String + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift new file mode 100644 index 00000000..dd749cf5 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift @@ -0,0 +1,45 @@ +import Foundation + + +import RxSwift + +public final class CheckLoginUseCaseImpl: CheckLoginUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + + public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + } + + public func execute() -> Observable { + switch tokenRepository.fetchToken(type: .refreshToken) { + case .success(let token): + guard !token.isEmpty else { return .just(false) } + + return authRepository.reissueToken(refreshToken: token) + .map { [weak self] response in + guard let self else { return false } + + let accessResult = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let refreshResult = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + + switch (accessResult, refreshResult) { + case (.success, .success): + return true + case (.failure(let error), _), + (_, .failure(let error)): + print("Token 저장 실패:", error.localizedDescription) + return false + } + } + .catch { error in + print("reissueToken 실패:", error.localizedDescription) + return .just(false) + } + + case .failure: + return .just(false) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift new file mode 100644 index 00000000..dca095b1 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift @@ -0,0 +1,23 @@ +import Foundation +import UserNotifications + + +import RxSwift + +public final class CheckNotificationPermissionUseCaseImpl: CheckNotificationPermissionUseCase { + public init() {} + + public func execute() -> Single { + return Single.create { observer in + UNUserNotificationCenter.current().getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized, .provisional, .ephemeral: + observer(.success(true)) + default: + observer(.success(false)) + } + } + return Disposables.create() + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift new file mode 100644 index 00000000..d98dfa56 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class FetchJobListUseCaseImpl: FetchJobListUseCase { + private let repository: AuthAPIRepository + + public init(repository: AuthAPIRepository) { + self.repository = repository + } + + public func execute() -> Observable { + return repository.fetchJobList() + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift new file mode 100644 index 00000000..d4b6bf4f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class FetchPlatformUseCaseImpl: FetchPlatformUseCase { + private let repository: UserDefaultsRepository + + public init(repository: UserDefaultsRepository) { + self.repository = repository + } + + public func execute() -> Observable { + return repository.fetchPlatform() + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift new file mode 100644 index 00000000..8f60e6a4 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift @@ -0,0 +1,52 @@ +import Foundation + + +import RxSwift + +public class LoginWithAppleUseCaseImpl: LoginWithAppleUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private let userDefaultsRepository: UserDefaultsRepository + + public init( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository, + userDefaultsRepository: UserDefaultsRepository + ) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository + } + + public func execute(credential: Credential) -> Observable { + return authRepository.loginWithApple(credential: credential) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) + + guard case (.success, .success) = (saveAccess, saveRefresh) else { + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + + var fcmToken: String? + if case .success(let token) = self.tokenRepository.fetchToken(type: .fcmToken) { + fcmToken = token + } + + let fcmUpdate = if let fcmToken { + self.authRepository.fcmToken(fcmToken: fcmToken) + .catch { error in + print("FCM token update failed: \(error)") + return .empty() + } + } else { + Completable.empty() + } + return fcmUpdate.andThen(savePlatform).andThen(Observable.just(response)) + } + .catch { error in + Observable.error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift new file mode 100644 index 00000000..76d9507a --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift @@ -0,0 +1,52 @@ +import Foundation + + +import RxSwift + +public class LoginWithKakaoUseCaseImpl: LoginWithKakaoUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private let userDefaultsRepository: UserDefaultsRepository + + public init( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository, + userDefaultsRepository: UserDefaultsRepository + ) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository + } + + public func execute(credential: Credential) -> Observable { + return authRepository.loginWithKakao(credential: credential) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) + + guard case (.success, .success) = (saveAccess, saveRefresh) else { + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + + var fcmToken: String? + if case .success(let token) = self.tokenRepository.fetchToken(type: .fcmToken) { + fcmToken = token + } + + let fcmUpdate = if let fcmToken { + self.authRepository.fcmToken(fcmToken: fcmToken) + .catch { error in + print("FCM token update failed: \(error)") + return .empty() + } + } else { + Completable.empty() + } + return fcmUpdate.andThen(savePlatform).andThen(Observable.just(response)) + } + .catch { error in + Observable.error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift new file mode 100644 index 00000000..af53b181 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift @@ -0,0 +1,36 @@ +import Foundation + + +import RxSwift + +public class LogoutUseCaseImpl: LogoutUseCase { + private let repository: TokenRepository + + public init(repository: TokenRepository) { + self.repository = repository + } + + public func execute() -> Completable { + return Completable.create { [weak self] completable in + guard let self else { + completable(.completed) + return Disposables.create() + } + + let deleteAccess = self.repository.deleteToken(type: .accessToken) + let deleteRefresh = self.repository.deleteToken(type: .refreshToken) + + guard case .success = deleteAccess, case .success = deleteRefresh else { + completable(.error(NSError(domain: "LogoutError", code: -1, userInfo: nil))) + return Disposables.create() + } + + if case .success = self.repository.fetchToken(type: .fcmToken) { + _ = self.repository.deleteToken(type: .fcmToken) + } + + completable(.completed) + return Disposables.create() + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift new file mode 100644 index 00000000..3496e877 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift @@ -0,0 +1,12 @@ +import UIKit + + + +public class OpenNotificationSettingUseCaseImpl: OpenNotificationSettingUseCase { + public init() {} + + public func execute() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift new file mode 100644 index 00000000..42fba104 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class PutFCMTokenUseCaseImpl: PutFCMTokenUseCase { + private let repository: AuthAPIRepository + + public init(repository: AuthAPIRepository) { + self.repository = repository + } + + public func execute(fcmToken: String?) -> Completable { + return repository.fcmToken(fcmToken: fcmToken) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift new file mode 100644 index 00000000..7ddf0ade --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift @@ -0,0 +1,31 @@ +import Foundation + + +import RxSwift + +public final class ReissueUseCaseImpl: ReissueUseCase { + private let repository: AuthAPIRepository + private let tokenRepository: TokenRepository + + public init(repository: AuthAPIRepository, tokenRepository: TokenRepository) { + self.repository = repository + self.tokenRepository = tokenRepository + } + + public func execute(refreshToken: String) -> Observable { + return repository.reissueToken(refreshToken: refreshToken) + .flatMap { [weak self] response -> Observable in + guard let self else { return .empty() } + + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + + switch (saveAccess, saveRefresh) { + case (.success, .success): + return .just(response) + default: + return .error(TokenRepositoryError.dataConversionError(message: "Failed to save new tokens")) + } + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift new file mode 100644 index 00000000..1db0815f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift @@ -0,0 +1,39 @@ +import Foundation + + +import RxSwift + +public final class SignUpWithAppleUseCaseImpl: SignUpWithAppleUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private let userDefaultsRepository: UserDefaultsRepository + + public init( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository, + userDefaultsRepository: UserDefaultsRepository + ) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository + } + + public func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return authRepository.signUpWithApple(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) + + switch (saveAccess, saveRefresh) { + case (.success, .success): + return savePlatform.andThen(Observable.just(response)) + default: + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + } + .catch { error in + Observable.error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift new file mode 100644 index 00000000..73d976bd --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift @@ -0,0 +1,39 @@ +import Foundation + + +import RxSwift + +public final class SignUpWithKakaoUseCaseImpl: SignUpWithKakaoUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private let userDefaultsRepository: UserDefaultsRepository + + public init( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository, + userDefaultsRepository: UserDefaultsRepository + ) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository + } + + public func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return authRepository.signUpWithKakao(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) + + switch (saveAccess, saveRefresh) { + case (.success, .success): + return savePlatform.andThen(Observable.just(response)) + default: + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + } + .catch { error in + Observable.error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift new file mode 100644 index 00000000..4578bee0 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class UpdateMarketingAgreementUseCaseImpl: UpdateMarketingAgreementUseCase { + private let repository: AuthAPIRepository + + public init(repository: AuthAPIRepository) { + self.repository = repository + } + + public func execute(credential: String, isMarketingAgreement: Bool) -> Completable { + return repository.updateMarketingAgreement(credential: credential, isMarketingAgreement: isMarketingAgreement) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift new file mode 100644 index 00000000..cb01bd9f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class UpdateNotificationAgreementUseCaseImpl: UpdateNotificationAgreementUseCase { + private let repository: AuthAPIRepository + + public init(repository: AuthAPIRepository) { + self.repository = repository + } + + public func execute(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { + return repository.updateNotificationAgreement(noticeAgreement: noticeAgreement, patchNoteAgreement: patchNoteAgreement, eventAgreement: eventAgreement) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift new file mode 100644 index 00000000..fb3ec223 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class UpdateUserInfoUseCaseImpl: UpdateUserInfoUseCase { + private let repository: AuthAPIRepository + + public init(repository: AuthAPIRepository) { + self.repository = repository + } + + public func execute(level: Int, selectedJobID: Int) -> Completable { + return repository.updateUserInfo(level: level, selectedJobID: selectedJobID) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift new file mode 100644 index 00000000..9196ad0e --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift @@ -0,0 +1,29 @@ +import Foundation + + +import RxSwift + +public class WithdrawUseCaseImpl: WithdrawUseCase { + private let authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + + public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + } + + public func execute() -> Completable { + return authRepository.withdraw() + .andThen(Completable.create { [weak self] completable in + guard let self else { + completable(.completed) + return Disposables.create() + } + _ = self.tokenRepository.deleteToken(type: .accessToken) + _ = self.tokenRepository.deleteToken(type: .refreshToken) + _ = self.tokenRepository.deleteToken(type: .fcmToken) + completable(.completed) + return Disposables.create() + }) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift new file mode 100644 index 00000000..a5cc67c5 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift @@ -0,0 +1,15 @@ +import Foundation + + + +public class FetchTokenFromLocalUseCaseImpl: FetchTokenFromLocalUseCase { + private let repository: TokenRepository + + public init(repository: TokenRepository) { + self.repository = repository + } + + public func execute(type: TokenType) -> Result { + return repository.fetchToken(type: type) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift new file mode 100644 index 00000000..41e15acf --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift @@ -0,0 +1,12 @@ + +import RxSwift + +public class CheckEmptyLevelAndRoleUseCaseImpl: CheckEmptyLevelAndRoleUseCase { + public init() {} + + public func execute(level: Int?, job: String?) -> Observable { + let isValidLevel = level.map { (1 ... 200).contains($0) } ?? false + let isValidRole = job != nil && job != "" + return .just(isValidLevel && isValidRole) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift new file mode 100644 index 00000000..070feb34 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift @@ -0,0 +1,15 @@ +import Foundation + + +import RxSwift + +public class CheckNickNameUseCaseImpl: CheckNickNameUseCase { + public init() {} + + public func execute(nickName: String) -> Observable { + let pattern = "^[가-힣ㄱ-ㅎㅏ-ㅣ]{2,15}$" + let trimmed = nickName.trimmingCharacters(in: .whitespacesAndNewlines) + let isValid = NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: trimmed) + return .just(isValid) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift new file mode 100644 index 00000000..293b3946 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift @@ -0,0 +1,11 @@ + +import RxSwift + +public class CheckValidLevelUseCaseImpl: CheckValidLevelUseCase { + public init() {} + + public func execute(level: Int?) -> Observable { + guard let level else { return .just(nil) } + return .just((1 ... 200).contains(level)) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift new file mode 100644 index 00000000..2d8cdcd8 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift @@ -0,0 +1,16 @@ +import Foundation + + +import RxSwift + +public class SocialLoginUseCaseImpl: FetchSocialCredentialUseCase { + public var provider: any SocialAuthenticatableProvider + + public init(provider: any SocialAuthenticatableProvider) { + self.provider = provider + } + + public func execute() -> Observable { + return provider.getCredential() + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift new file mode 100644 index 00000000..7abf603a --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift @@ -0,0 +1,4 @@ +public protocol Credential: Encodable { + var token: String { get } + var providerID: String { get } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift new file mode 100644 index 00000000..44e49789 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift @@ -0,0 +1,19 @@ +import Foundation + +public struct JobListResponse { + public var jobList: [Job] + + public init(jobList: [Job]) { + self.jobList = jobList + } +} + +public struct Job: Equatable { + public let name: String + public let id: Int + + public init(name: String, id: Int) { + self.name = name + self.id = id + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift new file mode 100644 index 00000000..35c4f7ff --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum LoginPlatform: String { + case kakao + case apple +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift new file mode 100644 index 00000000..2d2e0105 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift @@ -0,0 +1,13 @@ +import Foundation + +public struct LoginResponse { + public var isRegister: Bool + public var accessToken: String + public var refreshToken: String + + public init(isRegister: Bool, accessToken: String, refreshToken: String) { + self.isRegister = isRegister + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift new file mode 100644 index 00000000..562ec23b --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift @@ -0,0 +1,33 @@ +public struct MyPageResponse: Equatable { + public var nickname: String + public let jobId: Int? + public var jobName: String + public let level: Int? + public let profileUrl: String + public let platform: LoginPlatform + public let noticeAgreement: Bool + public let patchNoteAgreement: Bool + public let eventAgreement: Bool + + public init( + nickname: String, + jobId: Int?, + jobName: String, + level: Int?, + profileUrl: String, + platform: LoginPlatform, + noticeAgreement: Bool?, + patchNoteAgreement: Bool?, + eventAgreement: Bool? + ) { + self.nickname = nickname + self.jobId = jobId + self.jobName = jobName + self.level = level + self.profileUrl = profileUrl + self.platform = platform + self.noticeAgreement = noticeAgreement ?? false + self.patchNoteAgreement = patchNoteAgreement ?? false + self.eventAgreement = eventAgreement ?? false + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift new file mode 100644 index 00000000..1f4091e4 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct SignUpResponse { + public var accessToken: String + public var refreshToken: String + + public init(accessToken: String, refreshToken: String) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift new file mode 100644 index 00000000..abc34978 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift @@ -0,0 +1,5 @@ +public enum AuthError: Error { + case unknown(message: String) + case userNotFound(credential: Credential) + case tokenExpired +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift new file mode 100644 index 00000000..d0281877 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift @@ -0,0 +1,14 @@ +import Foundation +import Security + +public enum TokenRepositoryError: Error { + case noValueFound(message: String) + case unhandledError(status: OSStatus) + case dataConversionError(message: String) +} + +public enum TokenType: String { + case accessToken + case refreshToken + case fcmToken +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift index a75289b0..741eecd0 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift @@ -1,6 +1,6 @@ -import AuthFeatureInterface -import BaseFeature -import DomainInterface +import MLSAuthFeatureInterface +import MLSCore + import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 7bf0b45d..8ed84bd9 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -1,4 +1,4 @@ -import DomainInterface + import NotificationCenter import ReactorKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift index f6f1fa32..ef8395c6 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift @@ -1,7 +1,7 @@ import UIKit -import DesignSystem -import DomainInterface +import MLSDesignSystem + import SnapKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift index 0fc4c2ba..d28fff81 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift @@ -1,7 +1,7 @@ import UIKit -import AuthFeatureInterface -import BaseFeature +import MLSAuthFeatureInterface +import MLSCore import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift index dbccc528..bddcc1aa 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift @@ -1,5 +1,5 @@ -import AuthFeatureInterface -import BaseFeature +import MLSAuthFeatureInterface +import MLSCore public struct OnBoardingNotificationFactoryImpl: OnBoardingNotificationFactory { private let onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift index 2be88be7..c727b032 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift @@ -1,6 +1,6 @@ import UIKit -import DesignSystem +import MLSDesignSystem import SnapKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift index 08566c23..5c3a71f1 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -1,9 +1,9 @@ import UIKit import UserNotifications -import AuthFeatureInterface -import BaseFeature -import DictionaryFeatureInterface +import MLSAuthFeatureInterface +import MLSCore + import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift index 3f25b5f4..d375e951 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoarding/OnBoardingBaseView.swift @@ -1,6 +1,6 @@ import UIKit -import DesignSystem +import MLSDesignSystem /// 온보딩 화면에서 사용하기 위한 헤더(타이틀 버튼 포함)를 가진 뷰 public class OnBoardingBaseView: UIView { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift index 19243aeb..96e9ba6a 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift @@ -1,7 +1,7 @@ -import AuthFeatureInterface -import BaseFeature -import DictionaryFeatureInterface -import DomainInterface +import MLSAuthFeatureInterface +import MLSCore + + public struct OnBoardingInputFactoryImpl: OnBoardingInputFactory { private let checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift index 84b244c8..d2164c97 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift @@ -1,4 +1,4 @@ -import DomainInterface + import ReactorKit import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift index e21e0f05..2629b4fb 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputView.swift @@ -1,7 +1,7 @@ import UIKit -import BaseFeature -import DesignSystem +import MLSCore +import MLSDesignSystem import RxCocoa import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift index 1da435e4..27a03ff2 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift @@ -1,9 +1,9 @@ import UIKit -import AuthFeatureInterface -import BaseFeature -import DesignSystem -import DictionaryFeatureInterface +import MLSAuthFeatureInterface +import MLSCore +import MLSDesignSystem + import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift index 97613d4e..fcf2e103 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift @@ -1,7 +1,7 @@ -import AuthFeatureInterface -import BaseFeature -import DictionaryFeatureInterface -import DomainInterface +import MLSAuthFeatureInterface +import MLSCore + + public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationSheetFactory { private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift index b9e1f8ce..07e7aebf 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift @@ -1,4 +1,4 @@ -import DomainInterface + import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift index aacb3853..d0fe6065 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetView.swift @@ -1,6 +1,6 @@ import UIKit -import DesignSystem +import MLSDesignSystem import SnapKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift index ba30890a..4d239d60 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -1,8 +1,8 @@ import UIKit -import BaseFeature -import DesignSystem -import DictionaryFeatureInterface +import MLSCore +import MLSDesignSystem + import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift index 051cb8ba..585129e3 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionFactoryImpl.swift @@ -1,5 +1,5 @@ -import AuthFeatureInterface -import BaseFeature +import MLSAuthFeatureInterface +import MLSCore public struct OnBoardingQuestionFactoryImpl: OnBoardingQuestionFactory { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift index 160c0e83..04c58616 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionView.swift @@ -1,6 +1,6 @@ import UIKit -import DesignSystem +import MLSDesignSystem import SnapKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift index c1dbcf47..4c9236e0 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift @@ -1,7 +1,7 @@ import UIKit -import AuthFeatureInterface -import BaseFeature +import MLSAuthFeatureInterface +import MLSCore import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift index e069897f..e6d735b3 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift @@ -1,6 +1,6 @@ -import AuthFeatureInterface -import BaseFeature -import DomainInterface +import MLSAuthFeatureInterface +import MLSCore + public struct TermsAgreementFactoryImpl: TermsAgreementFactory { private let onBoardingQuestionFactory: OnBoardingQuestionFactory diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift index 3aeb36ce..8d5dafc0 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift @@ -1,4 +1,4 @@ -import DomainInterface + import NotificationCenter import ReactorKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift index bd55eff5..18a4099f 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementView.swift @@ -1,6 +1,6 @@ import UIKit -import DesignSystem +import MLSDesignSystem import SnapKit diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift index bb7d2325..713c29a5 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift @@ -1,7 +1,7 @@ import UIKit -import AuthFeatureInterface -import BaseFeature +import MLSAuthFeatureInterface +import MLSCore import ReactorKit import RxCocoa diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift new file mode 100644 index 00000000..090ccb67 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift @@ -0,0 +1,7 @@ +import Foundation + +import RxSwift + +public protocol SocialAuthenticatableProvider { + func getCredential() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift new file mode 100644 index 00000000..9197a0ea --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift @@ -0,0 +1,21 @@ +import Foundation + +import RxSwift + +public protocol AuthAPIRepository { + func loginWithKakao(credential: Credential) -> Observable + func loginWithApple(credential: Credential) -> Observable + func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable + func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable + func withdraw() -> Completable + func fetchJobList() -> Observable + func fetchJob(jobId: String) -> Observable + func updateUserInfo(level: Int, selectedJobID: Int) -> Completable + func reissueToken(refreshToken: String) -> Observable + func fcmToken(fcmToken: String?) -> Completable + func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable + func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable + func updateNickName(nickName: String) -> Observable + func updateProfileImage(url: String) -> Completable + func fetchProfile() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift new file mode 100644 index 00000000..695342f8 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift @@ -0,0 +1,7 @@ +import Foundation + +public protocol TokenRepository { + func fetchToken(type: TokenType) -> Result + func saveToken(type: TokenType, value: String) -> Result + func deleteToken(type: TokenType) -> Result +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift new file mode 100644 index 00000000..42cea260 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift @@ -0,0 +1,6 @@ +import RxSwift + +public protocol UserDefaultsRepository { + func fetchPlatform() -> Observable + func savePlatform(platform: LoginPlatform) -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift new file mode 100644 index 00000000..fc8d8347 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol CheckLoginUseCase { + func execute() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift new file mode 100644 index 00000000..04ae921c --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol CheckNotificationPermissionUseCase { + func execute() -> Single +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift new file mode 100644 index 00000000..12ab91bb --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol FetchJobListUseCase { + func execute() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift new file mode 100644 index 00000000..ce1113f9 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol FetchPlatformUseCase { + func execute() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift new file mode 100644 index 00000000..d938224b --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol LoginWithAppleUseCase { + func execute(credential: Credential) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift new file mode 100644 index 00000000..8f856400 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol LoginWithKakaoUseCase { + func execute(credential: Credential) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift new file mode 100644 index 00000000..10cf0abc --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol LogoutUseCase { + func execute() -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift new file mode 100644 index 00000000..b2f485ca --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift @@ -0,0 +1,3 @@ +public protocol OpenNotificationSettingUseCase { + func execute() +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift new file mode 100644 index 00000000..283f676c --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol PutFCMTokenUseCase { + func execute(fcmToken: String?) -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift new file mode 100644 index 00000000..4ab43bb7 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol ReissueUseCase { + func execute(refreshToken: String) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift new file mode 100644 index 00000000..77c0033c --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol SignUpWithAppleUseCase { + func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift new file mode 100644 index 00000000..480bc0d9 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol SignUpWithKakaoUseCase { + func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift new file mode 100644 index 00000000..5311731f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol UpdateMarketingAgreementUseCase { + func execute(credential: String, isMarketingAgreement: Bool) -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift new file mode 100644 index 00000000..f7d052be --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol UpdateNotificationAgreementUseCase { + func execute(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift new file mode 100644 index 00000000..8e4e8cb9 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol UpdateUserInfoUseCase { + func execute(level: Int, selectedJobID: Int) -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift new file mode 100644 index 00000000..0f8869db --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol WithdrawUseCase { + func execute() -> Completable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift new file mode 100644 index 00000000..bc2578cf --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol FetchTokenFromLocalUseCase { + func execute(type: TokenType) -> Result +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift new file mode 100644 index 00000000..83646065 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol CheckEmptyLevelAndRoleUseCase { + func execute(level: Int?, job: String?) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift new file mode 100644 index 00000000..05f6eff2 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol CheckNickNameUseCase { + func execute(nickName: String) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift new file mode 100644 index 00000000..d7c90322 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol CheckValidLevelUseCase { + func execute(level: Int?) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift new file mode 100644 index 00000000..c883ebd9 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift @@ -0,0 +1,7 @@ +import Foundation + +import RxSwift + +public protocol FetchSocialCredentialUseCase { + func execute() -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/AppCoordinatorProtocol.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/AppCoordinatorProtocol.swift new file mode 100644 index 00000000..24f9b2ca --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/AppCoordinatorProtocol.swift @@ -0,0 +1,7 @@ +import UIKit + +public protocol AppCoordinatorProtocol: AnyObject { + var window: UIWindow? { get set } + func showMainTab() + func showLogin(exitRoute: LoginExitRoute) +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift new file mode 100644 index 00000000..7b697eaf --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift @@ -0,0 +1,16 @@ +import MLSCore + +public enum LoginExitRoute { + case home + case pop +} + +public protocol LoginFactory { + func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController +} + +public extension LoginFactory { + func make(exitRoute: LoginExitRoute) -> BaseViewController { + make(exitRoute: exitRoute, onLoginCompleted: nil) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift new file mode 100644 index 00000000..e2672997 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift @@ -0,0 +1,5 @@ +import MLSCore + +public protocol OnBoardingNotificationFactory { + func make(selectedLevel: Int, selectedJobID: Int) -> BaseViewController +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift new file mode 100644 index 00000000..2963bab2 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift @@ -0,0 +1,5 @@ +import MLSCore + +public protocol OnBoardingInputFactory { + func make() -> BaseViewController +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift new file mode 100644 index 00000000..22269e8f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift @@ -0,0 +1,6 @@ +import MLSCore +import MLSDesignSystem + +public protocol OnBoardingModalFactory { + func make() -> BaseViewController & ModalPresentable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift new file mode 100644 index 00000000..a5acd2c3 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift @@ -0,0 +1,6 @@ +import MLSCore +import MLSDesignSystem + +public protocol OnBoardingNotificationSheetFactory { + func make(selectedLevel: Int, selectedJobID: Int) -> BaseViewController & ModalPresentable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift new file mode 100644 index 00000000..6424bd06 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift @@ -0,0 +1,5 @@ +import MLSCore + +public protocol OnBoardingQuestionFactory { + func make() -> BaseViewController +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift new file mode 100644 index 00000000..114aa95e --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift @@ -0,0 +1,6 @@ + +import MLSCore + +public protocol TermsAgreementFactory { + func make(credential: Credential, platform: LoginPlatform) -> BaseViewController +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift new file mode 100644 index 00000000..447293b8 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift @@ -0,0 +1,2 @@ +// MLSAuthFeatureTesting +// Mock 객체를 이 모듈에 추가하세요. diff --git "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift similarity index 77% rename from "MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift index af15126e..9c4c333f 100644 --- "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/\343\205\201\343\204\264\343\205\207.swift" +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift @@ -1,5 +1,5 @@ // -// ㅁㄴㅇ.swift +// temp.swift // MLSAuthFeature // // Created by SeoJunYoung on 4/8/26. diff --git "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" "b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" deleted file mode 100644 index af15126e..00000000 --- "a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/\343\205\201\343\204\264\343\205\207.swift" +++ /dev/null @@ -1,7 +0,0 @@ -// -// ㅁㄴㅇ.swift -// MLSAuthFeature -// -// Created by SeoJunYoung on 4/8/26. -// - diff --git a/MLS/MLSCore/Package.swift b/MLS/MLSCore/Package.swift index 4910c6a6..7360aafe 100644 --- a/MLS/MLSCore/Package.swift +++ b/MLS/MLSCore/Package.swift @@ -14,7 +14,8 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0") + .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"), + .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -24,7 +25,8 @@ let package = Package( dependencies: [ .product(name: "RxSwift", package: "RxSwift"), .product(name: "RxCocoa", package: "RxSwift"), - .product(name: "RxRelay", package: "RxSwift") + .product(name: "RxRelay", package: "RxSwift"), + .product(name: "RxKeyboard", package: "RxKeyboard"), ] ), .testTarget( From 2625b9230d7767c0ad979435a1c23f0e8a8f33c4 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Wed, 8 Apr 2026 23:21:35 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat/#316:=20example=20app=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EA=B0=80=EB=8A=A5=20=EC=83=81=ED=83=9C=EB=A1=9C=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 --- MLS/MLS.xcodeproj/project.pbxproj | 14 ++ MLS/MLSAuthFeature/Package.swift | 10 +- .../Kakao/KakaoLoginProviderImpl.swift | 2 +- .../MLSAuthFeature/MLSAuthFeature.swift | 1 + .../Presentation/Login/LoginReactor.swift | 1 + .../Login/LoginViewController.swift | 3 +- ...OnBoardingNotificationViewController.swift | 2 +- .../OnBoardingInputViewController.swift | 2 +- ...BoardingNotificationSheetFactoryImpl.swift | 1 + ...rdingNotificationSheetViewController.swift | 3 +- .../OnBoardingQuestionViewController.swift | 7 +- .../TermsAgreementReactor.swift | 1 + .../TermsAgreementViewController.swift | 3 +- .../Credential.swift | 0 .../LoginPlatform.swift | 0 MLS/MLSAuthFeatureExample/Info.plist | 2 - .../Mock/MockAuthAPIRepository.swift | 57 +++++++ .../Mock/MockTermsAgreementFactory.swift | 12 ++ .../Mock/MockTokenRepository.swift | 22 +++ .../Mock/MockUserDefaultsRepository.swift | 15 ++ MLS/MLSAuthFeatureExample/SceneDelegate.swift | 143 ++++++++++++++---- .../ViewController.swift | 19 +-- MLS/MLSCore/Package.swift | 3 +- .../BaseController/BaseViewController.swift | 2 + .../Utils/NotificationPermissionManager.swift | 50 ++++++ 25 files changed, 311 insertions(+), 64 deletions(-) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/Entity => MLSAuthFeatureInterface}/Credential.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/Entity => MLSAuthFeatureInterface}/LoginPlatform.swift (100%) create mode 100644 MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift create mode 100644 MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift create mode 100644 MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift create mode 100644 MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift create mode 100644 MLS/MLSCore/Sources/MLSCore/Utils/NotificationPermissionManager.swift diff --git a/MLS/MLS.xcodeproj/project.pbxproj b/MLS/MLS.xcodeproj/project.pbxproj index 295fd5c0..a2bb441b 100644 --- a/MLS/MLS.xcodeproj/project.pbxproj +++ b/MLS/MLS.xcodeproj/project.pbxproj @@ -30,6 +30,8 @@ 08ED492A2DCFDED4002C21A2 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 08ED49292DCFDED4002C21A2 /* RxRelay */; }; 08ED492C2DCFDED4002C21A2 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 08ED492B2DCFDED4002C21A2 /* RxSwift */; }; 08ED4DB12DCFE098002C21A2 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 08ED4DB02DCFE098002C21A2 /* SnapKit */; }; + 08F7AA012F86745C00EF5C06 /* MLSAuthFeature in Frameworks */ = {isa = PBXBuildFile; productRef = 08F7AA022F86745C00EF5C06 /* MLSAuthFeature */; }; + 08F7AA032F86745C00EF5C06 /* MLSAuthFeatureInterface in Frameworks */ = {isa = PBXBuildFile; productRef = 08F7AA042F86745C00EF5C06 /* MLSAuthFeatureInterface */; }; 770ADB1F2E433EDA00270506 /* RxKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 770ADB1E2E433EDA00270506 /* RxKeyboard */; }; 772199F22E0E7EC800A7B58C /* AuthFeatureInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 772199F12E0E7EC800A7B58C /* AuthFeatureInterface.framework */; }; 772199F32E0E7EC800A7B58C /* AuthFeatureInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 772199F12E0E7EC800A7B58C /* AuthFeatureInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -248,6 +250,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 08F7AA012F86745C00EF5C06 /* MLSAuthFeature in Frameworks */, + 08F7AA032F86745C00EF5C06 /* MLSAuthFeatureInterface in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -425,6 +429,8 @@ ); name = MLSAuthFeatureExample; packageProductDependencies = ( + 08F7AA022F86745C00EF5C06 /* MLSAuthFeature */, + 08F7AA042F86745C00EF5C06 /* MLSAuthFeatureInterface */, ); productName = MLSAuthFeatureExample; productReference = 08F7A9232F86745C00EF5C06 /* MLSAuthFeatureExample.app */; @@ -1174,6 +1180,14 @@ package = 08ED4DAF2DCFE098002C21A2 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; + 08F7AA022F86745C00EF5C06 /* MLSAuthFeature */ = { + isa = XCSwiftPackageProductDependency; + productName = MLSAuthFeature; + }; + 08F7AA042F86745C00EF5C06 /* MLSAuthFeatureInterface */ = { + isa = XCSwiftPackageProductDependency; + productName = MLSAuthFeatureInterface; + }; 770ADB1E2E433EDA00270506 /* RxKeyboard */ = { isa = XCSwiftPackageProductDependency; package = 770ADB1D2E433EDA00270506 /* XCRemoteSwiftPackageReference "RxKeyboard" */; diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index 6c3cab63..ca4ee130 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -28,6 +28,7 @@ let package = Package( .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.22.0"), .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"), .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0"), + .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"), ], targets: [ // Interface 모듈 (Presentation 팩토리 프로토콜) @@ -37,7 +38,8 @@ let package = Package( .product(name: "MLSCore", package: "MLSCore"), .product(name: "MLSDesignSystem", package: "MLSDesignSystem"), .product(name: "RxSwift", package: "RxSwift"), - ] + ], + swiftSettings: [.swiftLanguageMode(.v5)] ), // Feature 모듈 (Presentation + Domain + Data 구현체) .target( @@ -48,10 +50,14 @@ let package = Package( .product(name: "MLSDesignSystem", package: "MLSDesignSystem"), .product(name: "ReactorKit", package: "ReactorKit"), .product(name: "RxSwift", package: "RxSwift"), + .product(name: "RxCocoa", package: "RxSwift"), + .product(name: "RxRelay", package: "RxSwift"), .product(name: "RxKeyboard", package: "RxKeyboard"), .product(name: "KakaoSDKAuth", package: "kakao-ios-sdk"), .product(name: "KakaoSDKUser", package: "kakao-ios-sdk"), - ] + .product(name: "SnapKit", package: "SnapKit"), + ], + swiftSettings: [.swiftLanguageMode(.v5)] ), // Testing 모듈 (Mock 객체) .target( diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift index ec43e228..55a7325f 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift @@ -5,7 +5,7 @@ import KakaoSDKAuth import KakaoSDKUser import RxSwift -public final class KakaoLoginProviderImpl: SocialAuthenticatableProvider { +public final class KakaoLoginProviderImpl: SocialAuthenticatableProvider, @unchecked Sendable { public init() {} public func getCredential() -> Observable { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift new file mode 100644 index 00000000..2d35540d --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift @@ -0,0 +1 @@ +@_exported import MLSAuthFeatureInterface diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 8ed84bd9..2f1a1605 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -1,6 +1,7 @@ import NotificationCenter +import MLSAuthFeatureInterface import ReactorKit import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift index d28fff81..183550ed 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginViewController.swift @@ -2,13 +2,14 @@ import UIKit import MLSAuthFeatureInterface import MLSCore +import MLSDesignSystem import ReactorKit import RxCocoa import RxSwift import SnapKit -public final class LoginViewController: BaseViewController, View { +public final class LoginViewController: BaseViewController, @preconcurrency View { public typealias Reactor = LoginReactor // MARK: - Properties diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift index 5c3a71f1..70b8cc0c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -10,7 +10,7 @@ import RxCocoa import RxSwift import SnapKit -public class OnBoardingNotificationViewController: BaseViewController, View { +public class OnBoardingNotificationViewController: BaseViewController, @preconcurrency View { // MARK: - Properties public typealias Reactor = OnBoardingNotificationReactor diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift index 27a03ff2..0ff945af 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift @@ -11,7 +11,7 @@ import RxKeyboard import RxSwift import SnapKit -public class OnBoardingInputViewController: BaseViewController, View { +public class OnBoardingInputViewController: BaseViewController, @preconcurrency View { // MARK: - Properties public typealias Reactor = OnBoardingInputReactor diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift index fcf2e103..d1fc161c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift @@ -1,5 +1,6 @@ import MLSAuthFeatureInterface import MLSCore +import MLSDesignSystem diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift index 4d239d60..fda492ff 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -1,5 +1,6 @@ import UIKit +import MLSAuthFeatureInterface import MLSCore import MLSDesignSystem @@ -9,7 +10,7 @@ import RxCocoa import RxSwift import SnapKit -public final class OnBoardingNotificationSheetViewController: BaseViewController, ModalPresentable, View { +public final class OnBoardingNotificationSheetViewController: BaseViewController, @preconcurrency ModalPresentable, @preconcurrency View { public var modalHeight: CGFloat? public typealias Reactor = OnBoardingNotificationSheetReactor diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift index 4c9236e0..36c5b735 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingQuestion/OnBoardingQuestionViewController.swift @@ -2,13 +2,14 @@ import UIKit import MLSAuthFeatureInterface import MLSCore +import MLSDesignSystem import ReactorKit import RxCocoa import RxSwift import SnapKit -public class OnBoardingQuestionViewController: BaseViewController, View { +public class OnBoardingQuestionViewController: BaseViewController, @preconcurrency View { // MARK: - Properties public typealias Reactor = OnBoardingQuestionReactor @@ -84,7 +85,7 @@ public extension OnBoardingQuestionViewController { func bindViewState(reactor: Reactor) { reactor.pulse(\.$isShowToast) - .subscribe { isShowToast in + .subscribe(onNext: { isShowToast in if isShowToast { let currentDate = Date() let dateFormatter = DateFormatter() @@ -92,7 +93,7 @@ public extension OnBoardingQuestionViewController { let formattedDate = dateFormatter.string(from: currentDate) ToastFactory.createToast(message: "\(formattedDate) 약관에 동의했어요.") } - } + }) .disposed(by: disposeBag) rx.viewDidAppear diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift index 8d5dafc0..21d7d116 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift @@ -1,6 +1,7 @@ import NotificationCenter +import MLSAuthFeatureInterface import ReactorKit import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift index 713c29a5..d97ed6cc 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementViewController.swift @@ -2,13 +2,14 @@ import UIKit import MLSAuthFeatureInterface import MLSCore +import MLSDesignSystem import ReactorKit import RxCocoa import RxSwift import SnapKit -public class TermsAgreementViewController: BaseViewController, View { +public class TermsAgreementViewController: BaseViewController, @preconcurrency View { public typealias Reactor = TermsAgreementReactor // MARK: - Properties diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Credential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/Credential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Credential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginPlatform.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginPlatform.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginPlatform.swift diff --git a/MLS/MLSAuthFeatureExample/Info.plist b/MLS/MLSAuthFeatureExample/Info.plist index dd3c9afd..0eb786dc 100644 --- a/MLS/MLSAuthFeatureExample/Info.plist +++ b/MLS/MLSAuthFeatureExample/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift new file mode 100644 index 00000000..1ff2e6a1 --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift @@ -0,0 +1,57 @@ +import MLSAuthFeature +import RxSwift + +final class MockAuthAPIRepository: AuthAPIRepository { + + func loginWithKakao(credential: Credential) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + func loginWithApple(credential: Credential) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + func withdraw() -> Completable { return .empty() } + + func fetchJobList() -> Observable { + return .just(JobListResponse(jobList: [ + Job(name: "전사", id: 1), + Job(name: "마법사", id: 2), + Job(name: "궁수", id: 3), + Job(name: "도적", id: 4), + Job(name: "해적", id: 5), + ])) + } + + func fetchJob(jobId: String) -> Observable { + return .just(Job(name: "전사", id: 1)) + } + + func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { return .empty() } + + func reissueToken(refreshToken: String) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + func fcmToken(fcmToken: String?) -> Completable { return .empty() } + + func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { return .empty() } + + func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { return .empty() } + + func updateNickName(nickName: String) -> Observable { + return .just(MyPageResponse(nickname: nickName, jobId: nil, jobName: "", level: nil, profileUrl: "", platform: .kakao, noticeAgreement: nil, patchNoteAgreement: nil, eventAgreement: nil)) + } + + func updateProfileImage(url: String) -> Completable { return .empty() } + + func fetchProfile() -> Observable { return .just(nil) } +} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift b/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift new file mode 100644 index 00000000..8e68496a --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift @@ -0,0 +1,12 @@ +import UIKit +import MLSAuthFeature +import MLSAuthFeatureInterface +import MLSCore + +final class MockTermsAgreementFactory: TermsAgreementFactory { + func make(credential: Credential, platform: LoginPlatform) -> BaseViewController { + let vc = BaseViewController() + vc.view.backgroundColor = .systemBackground + return vc + } +} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift b/MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift new file mode 100644 index 00000000..d44a382b --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift @@ -0,0 +1,22 @@ +import MLSAuthFeature + +final class MockTokenRepository: TokenRepository { + private var storage: [String: String] = [:] + + func fetchToken(type: TokenType) -> Result { + if let value = storage[type.rawValue] { + return .success(value) + } + return .failure(TokenRepositoryError.noValueFound(message: "\(type.rawValue) not found")) + } + + func saveToken(type: TokenType, value: String) -> Result { + storage[type.rawValue] = value + return .success(()) + } + + func deleteToken(type: TokenType) -> Result { + storage[type.rawValue] = nil + return .success(()) + } +} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift b/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift new file mode 100644 index 00000000..a64d9e6d --- /dev/null +++ b/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift @@ -0,0 +1,15 @@ +import MLSAuthFeature +import RxSwift + +final class MockUserDefaultsRepository: UserDefaultsRepository { + private var platform: LoginPlatform? + + func fetchPlatform() -> Observable { + return .just(platform) + } + + func savePlatform(platform: LoginPlatform) -> Completable { + self.platform = platform + return .empty() + } +} diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index 38c00710..f9af4616 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -1,52 +1,129 @@ -// -// SceneDelegate.swift -// MLSAuthFeatureExample -// -// Created by SeoJunYoung on 4/8/26. -// - import UIKit +import MLSAuthFeature +import MLSAuthFeatureInterface -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol { var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } + guard let windowScene = (scene as? UIWindowScene) else { return } + + let window = UIWindow(windowScene: windowScene) + self.window = window - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + let loginVC = makeLoginViewController() + let nav = UINavigationController(rootViewController: loginVC) + nav.navigationBar.isHidden = true + window.rootViewController = nav + window.makeKeyAndVisible() } - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + // MARK: - AppCoordinatorProtocol + + func showMainTab() { + let alert = UIAlertController(title: "로그인 성공 🎉", message: "메인 화면으로 이동합니다.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .default)) + window?.rootViewController?.present(alert, animated: true) } - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). + func showLogin(exitRoute: LoginExitRoute) { + guard let nav = window?.rootViewController as? UINavigationController else { return } + let loginVC = makeLoginViewController() + nav.setViewControllers([loginVC], animated: true) } - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. + // MARK: - Private + + private func makeLoginViewController() -> UIViewController { + let tokenRepository = MockTokenRepository() + let userDefaultsRepository = MockUserDefaultsRepository() + let authRepository = MockAuthAPIRepository() + + let appleProvider = AppleLoginProviderImpl() + let kakaoProvider = KakaoLoginProviderImpl() + + let factory = LoginFactoryImpl( + termsAgreementsFactory: makeTermsAgreementFactory( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ), + appleLoginUseCase: SocialLoginUseCaseImpl(provider: appleProvider), + kakaoLoginUseCase: SocialLoginUseCaseImpl(provider: kakaoProvider), + loginWithAppleUseCase: LoginWithAppleUseCaseImpl( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ), + loginWithKakaoUseCase: LoginWithKakaoUseCaseImpl( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ), + fetchTokenUseCase: FetchTokenFromLocalUseCaseImpl(repository: tokenRepository), + putFCMTokenUseCase: PutFCMTokenUseCaseImpl(repository: authRepository), + fetchPlatformUseCase: FetchPlatformUseCaseImpl(repository: userDefaultsRepository) + ) + + return factory.make(exitRoute: .home, onLoginCompleted: { [weak self] in + self?.showMainTab() + }) } - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. + private func makeTermsAgreementFactory( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository, + userDefaultsRepository: UserDefaultsRepository + ) -> TermsAgreementFactory { + let onBoardingInputFactory = OnBoardingInputFactoryImpl( + checkEmptyUseCase: CheckEmptyLevelAndRoleUseCaseImpl(), + checkValidLevelUseCase: CheckValidLevelUseCaseImpl(), + fetchJobListUseCase: FetchJobListUseCaseImpl(repository: authRepository), + updateUserInfoUseCase: UpdateUserInfoUseCaseImpl(repository: authRepository), + onBoardingNotificationFactory: makeOnBoardingNotificationFactory( + authRepository: authRepository, + tokenRepository: tokenRepository + ), + appCoordinator: { [weak self] in self! } + ) + + let onBoardingQuestionFactory = OnBoardingQuestionFactoryImpl( + onBoardingInputFactory: onBoardingInputFactory + ) + + return TermsAgreementFactoryImpl( + onBoardingQuestionFactory: onBoardingQuestionFactory, + signUpWithKakaoUseCase: SignUpWithKakaoUseCaseImpl( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ), + signUpWithAppleUseCase: SignUpWithAppleUseCaseImpl( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ), + fetchTokenUseCase: FetchTokenFromLocalUseCaseImpl(repository: tokenRepository), + updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCaseImpl(repository: authRepository) + ) } + private func makeOnBoardingNotificationFactory( + authRepository: AuthAPIRepository, + tokenRepository: TokenRepository + ) -> OnBoardingNotificationFactory { + let sheetFactory = OnBoardingNotificationSheetFactoryImpl( + checkNotificationPermissionUseCase: CheckNotificationPermissionUseCaseImpl(), + openNotificationSettingUseCase: OpenNotificationSettingUseCaseImpl(), + updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCaseImpl(repository: authRepository), + updateUserInfoUseCase: UpdateUserInfoUseCaseImpl(repository: authRepository), + appCoordinator: { [weak self] in self! } + ) + return OnBoardingNotificationFactoryImpl( + onBoardingNotificationSheetFactory: sheetFactory, + appCoordinator: { [weak self] in self! } + ) + } } - diff --git a/MLS/MLSAuthFeatureExample/ViewController.swift b/MLS/MLSAuthFeatureExample/ViewController.swift index c2bf4daf..4fcaa0d4 100644 --- a/MLS/MLSAuthFeatureExample/ViewController.swift +++ b/MLS/MLSAuthFeatureExample/ViewController.swift @@ -1,19 +1,4 @@ -// -// ViewController.swift -// MLSAuthFeatureExample -// -// Created by SeoJunYoung on 4/8/26. -// - import UIKit -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .red - } - - -} - +// SceneDelegate에서 직접 LoginViewController를 띄우기 때문에 사용하지 않습니다. +class ViewController: UIViewController {} diff --git a/MLS/MLSCore/Package.swift b/MLS/MLSCore/Package.swift index 7360aafe..d016ff9b 100644 --- a/MLS/MLSCore/Package.swift +++ b/MLS/MLSCore/Package.swift @@ -27,7 +27,8 @@ let package = Package( .product(name: "RxCocoa", package: "RxSwift"), .product(name: "RxRelay", package: "RxSwift"), .product(name: "RxKeyboard", package: "RxKeyboard"), - ] + ], + swiftSettings: [.swiftLanguageMode(.v5)] ), .testTarget( name: "MLSCoreTests", diff --git a/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift b/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift index aa5e7877..b39b28bc 100644 --- a/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift +++ b/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift @@ -7,6 +7,8 @@ import RxSwift open class BaseViewController: UIViewController { private let disposeBag = DisposeBag() + public var isBottomTabbarHidden: Bool = false + public init() { super.init(nibName: nil, bundle: nil) os_log("➕init: \(String(describing: self))") diff --git a/MLS/MLSCore/Sources/MLSCore/Utils/NotificationPermissionManager.swift b/MLS/MLSCore/Sources/MLSCore/Utils/NotificationPermissionManager.swift new file mode 100644 index 00000000..87b184c6 --- /dev/null +++ b/MLS/MLSCore/Sources/MLSCore/Utils/NotificationPermissionManager.swift @@ -0,0 +1,50 @@ +import UIKit +import UserNotifications + +public final class NotificationPermissionManager: @unchecked Sendable { + + public static let shared = NotificationPermissionManager() + private init() {} + + public func getStatus(completion: @escaping (UNAuthorizationStatus) -> Void) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + completion(settings.authorizationStatus) + } + } + + public func requestIfNeeded(completion: ((Bool) -> Void)? = nil) { + let center = UNUserNotificationCenter.current() + center.getNotificationSettings { settings in + switch settings.authorizationStatus { + case .notDetermined: + center.requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in + if let error = error { + print("error: \(error.localizedDescription)") + completion?(false) + return + } + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + completion?(true) + } else { + completion?(false) + } + } + + case .authorized, .provisional: + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + completion?(true) + + case .denied: + completion?(false) + + default: + completion?(false) + } + } + } +} From 652e4f118e6746a8defb182ce69dd43fd49596f8 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Wed, 8 Apr 2026 23:38:28 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat/#316:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS.xcodeproj/project.pbxproj | 7 +++ MLS/MLSAuthFeature/Package.swift | 6 +- .../{ => Domain}/Entity/JobListResponse.swift | 0 .../{ => Domain}/Entity/LoginResponse.swift | 0 .../{ => Domain}/Entity/MyPageResponse.swift | 0 .../{ => Domain}/Entity/SignUpResponse.swift | 0 .../{ => Domain}/Error/AuthError.swift | 0 .../Error/TokenRepositoryError.swift | 0 .../SocialAuthenticatableProvider.swift | 0 .../Repository/AuthAPIRepository.swift | 0 .../Repository/TokenRepository.swift | 0 .../Repository/UserDefaultsRepository.swift | 0 .../UseCase/AuthAPI/CheckLoginUseCase.swift | 0 .../CheckNotificationPermissionUseCase.swift | 0 .../UseCase/AuthAPI/FetchJobListUseCase.swift | 0 .../AuthAPI/FetchPlatformUseCase.swift | 0 .../AuthAPI/LoginWithAppleUseCase.swift | 0 .../AuthAPI/LoginWithKakaoUseCase.swift | 0 .../UseCase/AuthAPI/LogoutUseCase.swift | 0 .../OpenNotificationSettingUseCase.swift | 0 .../UseCase/AuthAPI/PutFCMTokenUseCase.swift | 0 .../UseCase/AuthAPI/ReissueUseCase.swift | 0 .../AuthAPI/SignUpWithAppleUseCase.swift | 0 .../AuthAPI/SignUpWithKakaoUseCase.swift | 0 .../UpdateMarketingAgreementUseCase.swift | 0 .../UpdateNotificationAgreementUseCase.swift | 0 .../AuthAPI/UpdateUserInfoUseCase.swift | 0 .../UseCase/AuthAPI/WithdrawUseCase.swift | 0 .../FetchTokenFromLocalUseCase.swift | 0 .../CheckEmptyLevelAndRoleUseCase.swift | 0 .../OnBoarding/CheckNickNameUseCase.swift | 0 .../OnBoarding/CheckValidLevelUseCase.swift | 0 .../Shared/FetchSocialCredentialUseCase.swift | 0 .../MLSAuthFeature/MLSAuthFeature.swift | 1 - .../MLSAuthFeatureTesting.swift | 2 - .../Mock/MockAuthAPIRepository.swift | 59 +++++++++++++++++++ .../Mock/MockTermsAgreementFactory.swift | 13 ++++ .../Mock/MockTokenRepository.swift | 10 ++-- .../Mock/MockUserDefaultsRepository.swift | 17 ++++++ .../Sources/MLSAuthFeatureTesting/temp.swift | 7 --- .../Mock/MockAuthAPIRepository.swift | 57 ------------------ .../Mock/MockTermsAgreementFactory.swift | 12 ---- .../Mock/MockUserDefaultsRepository.swift | 15 ----- MLS/MLSAuthFeatureExample/SceneDelegate.swift | 2 +- 44 files changed, 107 insertions(+), 101 deletions(-) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Entity/JobListResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Entity/LoginResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Entity/MyPageResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Entity/SignUpResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Error/AuthError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Error/TokenRepositoryError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Providers => Domain/Provider}/SocialAuthenticatableProvider.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Repository/AuthAPIRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Repository/TokenRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/Repository/UserDefaultsRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/CheckLoginUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/FetchJobListUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/FetchPlatformUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/LoginWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/LoginWithKakaoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/LogoutUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/PutFCMTokenUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/ReissueUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/SignUpWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/UpdateUserInfoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/AuthAPI/WithdrawUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/OnBoarding/CheckNickNameUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/OnBoarding/CheckValidLevelUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{ => Domain}/UseCase/Shared/FetchSocialCredentialUseCase.swift (100%) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift rename MLS/{MLSAuthFeatureExample => MLSAuthFeature/Sources/MLSAuthFeatureTesting}/Mock/MockTokenRepository.swift (57%) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift delete mode 100644 MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift delete mode 100644 MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift delete mode 100644 MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift diff --git a/MLS/MLS.xcodeproj/project.pbxproj b/MLS/MLS.xcodeproj/project.pbxproj index a2bb441b..250c07a5 100644 --- a/MLS/MLS.xcodeproj/project.pbxproj +++ b/MLS/MLS.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 08ED4DB12DCFE098002C21A2 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 08ED4DB02DCFE098002C21A2 /* SnapKit */; }; 08F7AA012F86745C00EF5C06 /* MLSAuthFeature in Frameworks */ = {isa = PBXBuildFile; productRef = 08F7AA022F86745C00EF5C06 /* MLSAuthFeature */; }; 08F7AA032F86745C00EF5C06 /* MLSAuthFeatureInterface in Frameworks */ = {isa = PBXBuildFile; productRef = 08F7AA042F86745C00EF5C06 /* MLSAuthFeatureInterface */; }; + 08F7AA052F86745C00EF5C06 /* MLSAuthFeatureTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 08F7AA062F86745C00EF5C06 /* MLSAuthFeatureTesting */; }; 770ADB1F2E433EDA00270506 /* RxKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 770ADB1E2E433EDA00270506 /* RxKeyboard */; }; 772199F22E0E7EC800A7B58C /* AuthFeatureInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 772199F12E0E7EC800A7B58C /* AuthFeatureInterface.framework */; }; 772199F32E0E7EC800A7B58C /* AuthFeatureInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 772199F12E0E7EC800A7B58C /* AuthFeatureInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -252,6 +253,7 @@ files = ( 08F7AA012F86745C00EF5C06 /* MLSAuthFeature in Frameworks */, 08F7AA032F86745C00EF5C06 /* MLSAuthFeatureInterface in Frameworks */, + 08F7AA052F86745C00EF5C06 /* MLSAuthFeatureTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -431,6 +433,7 @@ packageProductDependencies = ( 08F7AA022F86745C00EF5C06 /* MLSAuthFeature */, 08F7AA042F86745C00EF5C06 /* MLSAuthFeatureInterface */, + 08F7AA062F86745C00EF5C06 /* MLSAuthFeatureTesting */, ); productName = MLSAuthFeatureExample; productReference = 08F7A9232F86745C00EF5C06 /* MLSAuthFeatureExample.app */; @@ -1188,6 +1191,10 @@ isa = XCSwiftPackageProductDependency; productName = MLSAuthFeatureInterface; }; + 08F7AA062F86745C00EF5C06 /* MLSAuthFeatureTesting */ = { + isa = XCSwiftPackageProductDependency; + productName = MLSAuthFeatureTesting; + }; 770ADB1E2E433EDA00270506 /* RxKeyboard */ = { isa = XCSwiftPackageProductDependency; package = 770ADB1D2E433EDA00270506 /* XCRemoteSwiftPackageReference "RxKeyboard" */; diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index ca4ee130..f2d7b076 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -63,8 +63,10 @@ let package = Package( .target( name: "MLSAuthFeatureTesting", dependencies: [ - "MLSAuthFeatureInterface", - ] + "MLSAuthFeature", + .product(name: "RxSwift", package: "RxSwift"), + ], + swiftSettings: [.swiftLanguageMode(.v5)] ), // Tests 모듈 .testTarget( diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/JobListResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/JobListResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/JobListResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/LoginResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/LoginResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/LoginResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/MyPageResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/SignUpResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Entity/SignUpResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/SignUpResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/AuthError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/TokenRepositoryError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Error/TokenRepositoryError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/TokenRepositoryError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Providers/SocialAuthenticatableProvider.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/AuthAPIRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/TokenRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/TokenRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/TokenRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Repository/UserDefaultsRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckLoginUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchJobListUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/FetchPlatformUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LoginWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/LogoutUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/PutFCMTokenUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/ReissueUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/UpdateUserInfoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/AuthAPI/WithdrawUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckNickNameUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/OnBoarding/CheckValidLevelUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/UseCase/Shared/FetchSocialCredentialUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift deleted file mode 100644 index 2d35540d..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/MLSAuthFeature.swift +++ /dev/null @@ -1 +0,0 @@ -@_exported import MLSAuthFeatureInterface diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift deleted file mode 100644 index 447293b8..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/MLSAuthFeatureTesting.swift +++ /dev/null @@ -1,2 +0,0 @@ -// MLSAuthFeatureTesting -// Mock 객체를 이 모듈에 추가하세요. diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift new file mode 100644 index 00000000..9f4d9379 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift @@ -0,0 +1,59 @@ +import MLSAuthFeature +import RxSwift + +public final class MockAuthAPIRepository: AuthAPIRepository { + + public init() {} + + public func loginWithKakao(credential: Credential) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + public func loginWithApple(credential: Credential) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + public func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + public func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + public func withdraw() -> Completable { return .empty() } + + public func fetchJobList() -> Observable { + return .just(JobListResponse(jobList: [ + Job(name: "전사", id: 1), + Job(name: "마법사", id: 2), + Job(name: "궁수", id: 3), + Job(name: "도적", id: 4), + Job(name: "해적", id: 5), + ])) + } + + public func fetchJob(jobId: String) -> Observable { + return .just(Job(name: "전사", id: 1)) + } + + public func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { return .empty() } + + public func reissueToken(refreshToken: String) -> Observable { + return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + } + + public func fcmToken(fcmToken: String?) -> Completable { return .empty() } + + public func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { return .empty() } + + public func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { return .empty() } + + public func updateNickName(nickName: String) -> Observable { + return .just(MyPageResponse(nickname: nickName, jobId: nil, jobName: "", level: nil, profileUrl: "", platform: .kakao, noticeAgreement: nil, patchNoteAgreement: nil, eventAgreement: nil)) + } + + public func updateProfileImage(url: String) -> Completable { return .empty() } + + public func fetchProfile() -> Observable { return .just(nil) } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift new file mode 100644 index 00000000..f7a3c5e3 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift @@ -0,0 +1,13 @@ +import UIKit +import MLSAuthFeature +import MLSCore + +public final class MockTermsAgreementFactory: TermsAgreementFactory { + public init() {} + + public func make(credential: Credential, platform: LoginPlatform) -> BaseViewController { + let vc = BaseViewController() + vc.view.backgroundColor = .systemBackground + return vc + } +} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift similarity index 57% rename from MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift index d44a382b..e1d5d1f9 100644 --- a/MLS/MLSAuthFeatureExample/Mock/MockTokenRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift @@ -1,21 +1,23 @@ import MLSAuthFeature -final class MockTokenRepository: TokenRepository { +public final class MockTokenRepository: TokenRepository { private var storage: [String: String] = [:] - func fetchToken(type: TokenType) -> Result { + public init() {} + + public func fetchToken(type: TokenType) -> Result { if let value = storage[type.rawValue] { return .success(value) } return .failure(TokenRepositoryError.noValueFound(message: "\(type.rawValue) not found")) } - func saveToken(type: TokenType, value: String) -> Result { + public func saveToken(type: TokenType, value: String) -> Result { storage[type.rawValue] = value return .success(()) } - func deleteToken(type: TokenType) -> Result { + public func deleteToken(type: TokenType) -> Result { storage[type.rawValue] = nil return .success(()) } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift new file mode 100644 index 00000000..9e66586d --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift @@ -0,0 +1,17 @@ +import MLSAuthFeature +import RxSwift + +public final class MockUserDefaultsRepository: UserDefaultsRepository { + private var platform: LoginPlatform? + + public init() {} + + public func fetchPlatform() -> Observable { + return .just(platform) + } + + public func savePlatform(platform: LoginPlatform) -> Completable { + self.platform = platform + return .empty() + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift deleted file mode 100644 index 9c4c333f..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/temp.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// temp.swift -// MLSAuthFeature -// -// Created by SeoJunYoung on 4/8/26. -// - diff --git a/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift deleted file mode 100644 index 1ff2e6a1..00000000 --- a/MLS/MLSAuthFeatureExample/Mock/MockAuthAPIRepository.swift +++ /dev/null @@ -1,57 +0,0 @@ -import MLSAuthFeature -import RxSwift - -final class MockAuthAPIRepository: AuthAPIRepository { - - func loginWithKakao(credential: Credential) -> Observable { - return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) - } - - func loginWithApple(credential: Credential) -> Observable { - return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) - } - - func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { - return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) - } - - func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { - return .just(SignUpResponse(accessToken: "mock_access", refreshToken: "mock_refresh")) - } - - func withdraw() -> Completable { return .empty() } - - func fetchJobList() -> Observable { - return .just(JobListResponse(jobList: [ - Job(name: "전사", id: 1), - Job(name: "마법사", id: 2), - Job(name: "궁수", id: 3), - Job(name: "도적", id: 4), - Job(name: "해적", id: 5), - ])) - } - - func fetchJob(jobId: String) -> Observable { - return .just(Job(name: "전사", id: 1)) - } - - func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { return .empty() } - - func reissueToken(refreshToken: String) -> Observable { - return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) - } - - func fcmToken(fcmToken: String?) -> Completable { return .empty() } - - func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { return .empty() } - - func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { return .empty() } - - func updateNickName(nickName: String) -> Observable { - return .just(MyPageResponse(nickname: nickName, jobId: nil, jobName: "", level: nil, profileUrl: "", platform: .kakao, noticeAgreement: nil, patchNoteAgreement: nil, eventAgreement: nil)) - } - - func updateProfileImage(url: String) -> Completable { return .empty() } - - func fetchProfile() -> Observable { return .just(nil) } -} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift b/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift deleted file mode 100644 index 8e68496a..00000000 --- a/MLS/MLSAuthFeatureExample/Mock/MockTermsAgreementFactory.swift +++ /dev/null @@ -1,12 +0,0 @@ -import UIKit -import MLSAuthFeature -import MLSAuthFeatureInterface -import MLSCore - -final class MockTermsAgreementFactory: TermsAgreementFactory { - func make(credential: Credential, platform: LoginPlatform) -> BaseViewController { - let vc = BaseViewController() - vc.view.backgroundColor = .systemBackground - return vc - } -} diff --git a/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift b/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift deleted file mode 100644 index a64d9e6d..00000000 --- a/MLS/MLSAuthFeatureExample/Mock/MockUserDefaultsRepository.swift +++ /dev/null @@ -1,15 +0,0 @@ -import MLSAuthFeature -import RxSwift - -final class MockUserDefaultsRepository: UserDefaultsRepository { - private var platform: LoginPlatform? - - func fetchPlatform() -> Observable { - return .just(platform) - } - - func savePlatform(platform: LoginPlatform) -> Completable { - self.platform = platform - return .empty() - } -} diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index f9af4616..a4dab287 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -1,6 +1,6 @@ import UIKit import MLSAuthFeature -import MLSAuthFeatureInterface +import MLSAuthFeatureTesting class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol { From 6dc027736b9303f23ad7e7c27d7fb63df50cfcca Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 22:38:16 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat/#316:=20=EC=9E=84=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=20=EC=88=9C=EC=84=9C=20=EB=B0=B0=EC=B9=98,=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/DTO/AuthResponseDTO.swift | 2 - .../Data/Network/DTO/JobsDTO.swift | 2 - .../Data/Network/DTO/MemberDTO.swift | 16 ------- .../Data/Network/Endpoint/AuthEndPoint.swift | 2 +- .../Providers/Apple/AppleCredential.swift | 2 +- .../Apple/AppleLoginProviderImpl.swift | 4 +- .../Providers/Kakao/KakaoCredential.swift | 2 +- .../Kakao/KakaoLoginProviderImpl.swift | 1 + .../Repository/AuthAPIRepositoryImpl.swift | 45 +----------------- .../Domain/Entity/MyPageResponse.swift | 33 ------------- .../Domain/Error/AuthError.swift | 2 + .../SocialAuthenticatableProvider.swift | 2 + .../Domain/Repository/AuthAPIRepository.swift | 7 +-- .../Repository/UserDefaultsRepository.swift | 2 + .../UseCase/AuthAPI/CheckLoginUseCase.swift | 5 -- .../AuthAPI/CheckLoginUseCaseImpl.swift | 45 ------------------ .../CheckNotificationPermissionUseCase.swift | 5 -- ...eckNotificationPermissionUseCaseImpl.swift | 23 ---------- .../UseCase/AuthAPI/FetchJobListUseCase.swift | 5 -- .../AuthAPI/FetchJobListUseCaseImpl.swift | 16 ------- .../AuthAPI/FetchPlatformUseCase.swift | 5 -- .../AuthAPI/FetchPlatformUseCaseImpl.swift | 16 ------- .../AuthAPI/LoginWithAppleUseCase.swift | 2 + .../AuthAPI/LoginWithAppleUseCaseImpl.swift | 1 + .../AuthAPI/LoginWithKakaoUseCase.swift | 2 + .../AuthAPI/LoginWithKakaoUseCaseImpl.swift | 1 + .../UseCase/AuthAPI/LogoutUseCase.swift | 5 -- .../UseCase/AuthAPI/LogoutUseCaseImpl.swift | 36 --------------- .../OpenNotificationSettingUseCase.swift | 3 -- .../OpenNotificationSettingUseCaseImpl.swift | 12 ----- .../UseCase/AuthAPI/PutFCMTokenUseCase.swift | 5 -- .../AuthAPI/PutFCMTokenUseCaseImpl.swift | 16 ------- .../UseCase/AuthAPI/ReissueUseCase.swift | 5 -- .../UseCase/AuthAPI/ReissueUseCaseImpl.swift | 31 ------------- .../AuthAPI/SignUpWithAppleUseCase.swift | 2 + .../AuthAPI/SignUpWithAppleUseCaseImpl.swift | 1 + .../AuthAPI/SignUpWithKakaoUseCase.swift | 2 + .../AuthAPI/SignUpWithKakaoUseCaseImpl.swift | 1 + .../UpdateMarketingAgreementUseCase.swift | 5 -- .../UpdateMarketingAgreementUseCaseImpl.swift | 16 ------- .../UpdateNotificationAgreementUseCase.swift | 5 -- ...dateNotificationAgreementUseCaseImpl.swift | 16 ------- .../AuthAPI/UpdateUserInfoUseCase.swift | 5 -- .../AuthAPI/UpdateUserInfoUseCaseImpl.swift | 16 ------- .../UseCase/AuthAPI/WithdrawUseCase.swift | 5 -- .../UseCase/AuthAPI/WithdrawUseCaseImpl.swift | 29 ------------ .../FetchTokenFromLocalUseCase.swift | 5 -- .../FetchTokenFromLocalUseCaseImpl.swift | 15 ------ .../CheckEmptyLevelAndRoleUseCaseImpl.swift | 1 - .../OnBoarding/CheckNickNameUseCase.swift | 5 -- .../OnBoarding/CheckNickNameUseCaseImpl.swift | 15 ------ .../CheckValidLevelUseCaseImpl.swift | 1 - .../Shared/FetchSocialCredentialUseCase.swift | 7 --- .../Shared/SocialLoginUseCaseImpl.swift | 16 ------- .../Presentation/Login/LoginFactoryImpl.swift | 41 ++++++++--------- .../Presentation/Login/LoginReactor.swift | 46 +++++++++---------- .../Presentation/Login/LoginView.swift | 2 +- ...OnBoardingNotificationViewController.swift | 1 - .../OnBoardingInputFactoryImpl.swift | 13 ++---- .../OnBoardingInputReactor.swift | 10 ++-- .../OnBoardingInputViewController.swift | 1 - ...BoardingNotificationSheetFactoryImpl.swift | 22 ++------- .../OnBoardingNotificationSheetReactor.swift | 39 ++++++++-------- .../TermsAgreementFactoryImpl.swift | 12 ++--- .../TermsAgreementReactor.swift | 15 ++---- .../TermsAgreementFactory.swift | 1 - .../Mock/MockAuthAPIRepository.swift | 16 +------ .../Mock/MockTermsAgreementFactory.swift | 2 +- .../Mock/MockTokenRepository.swift | 1 + .../Mock/MockUserDefaultsRepository.swift | 2 + MLS/MLSAuthFeatureExample/SceneDelegate.swift | 29 +++++------- 71 files changed, 130 insertions(+), 652 deletions(-) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift index 9fae13dc..4257d695 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift @@ -1,5 +1,3 @@ - - public struct AuthResponseDTO: Decodable { public let accessToken: String public let refreshToken: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift index c81985b9..7d457828 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift @@ -1,5 +1,3 @@ - - public struct JobsDTO: Decodable { public let jobId: Int public let jobName: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift index 509078f8..f7f39224 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift @@ -1,5 +1,3 @@ - - public struct MemberDTO: Decodable { public let id: String public let provider: String @@ -12,18 +10,4 @@ public struct MemberDTO: Decodable { public let jobId: Int? public let level: Int? public let profileImageUrl: String - - func toMyPageDomain() -> MyPageResponse { - return .init( - nickname: nickname, - jobId: jobId, - jobName: "", - level: level, - profileUrl: profileImageUrl, - platform: provider == "APPLE" ? .apple : .kakao, - noticeAgreement: noticeAgreement, - patchNoteAgreement: patchNoteAgreement, - eventAgreement: eventAgreement - ) - } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift index 106c26f0..c3fd7bc9 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift @@ -1,4 +1,4 @@ - +import MLSAuthFeatureInterface import MLSCore public enum AuthEndPoint { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift index 6d27b3da..027b7d06 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift @@ -1,4 +1,4 @@ - +import MLSAuthFeatureInterface public struct AppleCredential: Credential { public let token: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift index 736eb281..788d62ad 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift @@ -1,7 +1,7 @@ -import AuthenticationServices -import Foundation import UIKit +import MLSAuthFeatureInterface +import AuthenticationServices import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift index 1c78151d..4044f742 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift @@ -1,4 +1,4 @@ - +import MLSAuthFeatureInterface public struct KakaoCredential: Credential { public let token: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift index 55a7325f..d30d61e9 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift @@ -1,5 +1,6 @@ import Foundation +import MLSAuthFeatureInterface import KakaoSDKAuth import KakaoSDKUser diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift index c4ab162c..a567d2ed 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift @@ -1,7 +1,8 @@ import Foundation - +import MLSAuthFeatureInterface import MLSCore + import RxSwift public class AuthAPIRepositoryImpl: AuthAPIRepository { @@ -15,12 +16,6 @@ public class AuthAPIRepositoryImpl: AuthAPIRepository { self.authInterceptor = authInterceptor } - public func fetchProfile() -> Observable { - let endpoint = AuthEndPoint.fetchProfile() - return provider.requestData(endPoint: endpoint, interceptor: tokenInterceptor) - .map { $0.toMyPageDomain() } - } - public func loginWithKakao(credential: Credential) -> Observable { let endpoint = AuthEndPoint.loginWithKakao(credential: credential) return provider.requestData(endPoint: endpoint, interceptor: authInterceptor) @@ -91,24 +86,11 @@ public class AuthAPIRepositoryImpl: AuthAPIRepository { return provider.requestData(endPoint: endPoint, interceptor: nil).map { $0.toDomain() } } - public func fetchJob(jobId: String) -> Observable { - let endPoint = AuthEndPoint.fetchJob(jobId: jobId) - return provider.requestData(endPoint: endPoint, interceptor: nil).map { $0.toDomain() } - } - public func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { let endPoint = AuthEndPoint.updateCharacterInfo(body: UpdateInfoBody(level: level, jobId: selectedJobID)) return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) } - public func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { - let endPoint = AuthEndPoint.updateMarketingAgreement( - credential: credential, - body: MarketingAgreementBody(marketingAgreement: isMarketingAgreement) - ) - return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) - } - public func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { let endPoint = AuthEndPoint.updateNotification( body: NotificationAgreementBody( @@ -119,17 +101,6 @@ public class AuthAPIRepositoryImpl: AuthAPIRepository { ) return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) } - - public func updateNickName(nickName: String) -> Observable { - let endPoint = AuthEndPoint.updateNickName(body: NickNameBody(nickname: nickName)) - return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) - .map { $0.toMyPageDomain() } - } - - public func updateProfileImage(url: String) -> Completable { - let endPoint = AuthEndPoint.updateProfileImage(body: UpdateProfileImageBody(profileImageUrl: url)) - return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) - } } private extension AuthAPIRepositoryImpl { @@ -153,26 +124,14 @@ private extension AuthAPIRepositoryImpl { let fcmToken: String? } - struct MarketingAgreementBody: Encodable { - let marketingAgreement: Bool - } - struct NotificationAgreementBody: Encodable { let noticeAgreement: Bool let patchNoteAgreement: Bool let eventAgreement: Bool } - struct NickNameBody: Encodable { - let nickname: String - } - struct UpdateInfoBody: Encodable { let level: Int let jobId: Int } - - struct UpdateProfileImageBody: Encodable { - let profileImageUrl: String - } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift deleted file mode 100644 index 562ec23b..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/MyPageResponse.swift +++ /dev/null @@ -1,33 +0,0 @@ -public struct MyPageResponse: Equatable { - public var nickname: String - public let jobId: Int? - public var jobName: String - public let level: Int? - public let profileUrl: String - public let platform: LoginPlatform - public let noticeAgreement: Bool - public let patchNoteAgreement: Bool - public let eventAgreement: Bool - - public init( - nickname: String, - jobId: Int?, - jobName: String, - level: Int?, - profileUrl: String, - platform: LoginPlatform, - noticeAgreement: Bool?, - patchNoteAgreement: Bool?, - eventAgreement: Bool? - ) { - self.nickname = nickname - self.jobId = jobId - self.jobName = jobName - self.level = level - self.profileUrl = profileUrl - self.platform = platform - self.noticeAgreement = noticeAgreement ?? false - self.patchNoteAgreement = patchNoteAgreement ?? false - self.eventAgreement = eventAgreement ?? false - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift index abc34978..ee9acb40 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + public enum AuthError: Error { case unknown(message: String) case userNotFound(credential: Credential) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift index 090ccb67..ff82d8ed 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift @@ -1,5 +1,7 @@ import Foundation +import MLSAuthFeatureInterface + import RxSwift public protocol SocialAuthenticatableProvider { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift index 9197a0ea..1dc3cc29 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift @@ -1,4 +1,4 @@ -import Foundation +import MLSAuthFeatureInterface import RxSwift @@ -9,13 +9,8 @@ public protocol AuthAPIRepository { func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable func withdraw() -> Completable func fetchJobList() -> Observable - func fetchJob(jobId: String) -> Observable func updateUserInfo(level: Int, selectedJobID: Int) -> Completable func reissueToken(refreshToken: String) -> Observable func fcmToken(fcmToken: String?) -> Completable - func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable - func updateNickName(nickName: String) -> Observable - func updateProfileImage(url: String) -> Completable - func fetchProfile() -> Observable } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift index 42cea260..9fdd6081 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public protocol UserDefaultsRepository { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift deleted file mode 100644 index fc8d8347..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol CheckLoginUseCase { - func execute() -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift deleted file mode 100644 index dd749cf5..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckLoginUseCaseImpl.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - - -import RxSwift - -public final class CheckLoginUseCaseImpl: CheckLoginUseCase { - private let authRepository: AuthAPIRepository - private let tokenRepository: TokenRepository - - public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository) { - self.authRepository = authRepository - self.tokenRepository = tokenRepository - } - - public func execute() -> Observable { - switch tokenRepository.fetchToken(type: .refreshToken) { - case .success(let token): - guard !token.isEmpty else { return .just(false) } - - return authRepository.reissueToken(refreshToken: token) - .map { [weak self] response in - guard let self else { return false } - - let accessResult = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - let refreshResult = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - - switch (accessResult, refreshResult) { - case (.success, .success): - return true - case (.failure(let error), _), - (_, .failure(let error)): - print("Token 저장 실패:", error.localizedDescription) - return false - } - } - .catch { error in - print("reissueToken 실패:", error.localizedDescription) - return .just(false) - } - - case .failure: - return .just(false) - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift deleted file mode 100644 index 04ae921c..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol CheckNotificationPermissionUseCase { - func execute() -> Single -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift deleted file mode 100644 index dca095b1..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/CheckNotificationPermissionUseCaseImpl.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import UserNotifications - - -import RxSwift - -public final class CheckNotificationPermissionUseCaseImpl: CheckNotificationPermissionUseCase { - public init() {} - - public func execute() -> Single { - return Single.create { observer in - UNUserNotificationCenter.current().getNotificationSettings { settings in - switch settings.authorizationStatus { - case .authorized, .provisional, .ephemeral: - observer(.success(true)) - default: - observer(.success(false)) - } - } - return Disposables.create() - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift deleted file mode 100644 index 12ab91bb..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol FetchJobListUseCase { - func execute() -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift deleted file mode 100644 index d98dfa56..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchJobListUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class FetchJobListUseCaseImpl: FetchJobListUseCase { - private let repository: AuthAPIRepository - - public init(repository: AuthAPIRepository) { - self.repository = repository - } - - public func execute() -> Observable { - return repository.fetchJobList() - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift deleted file mode 100644 index ce1113f9..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol FetchPlatformUseCase { - func execute() -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift deleted file mode 100644 index d4b6bf4f..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/FetchPlatformUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class FetchPlatformUseCaseImpl: FetchPlatformUseCase { - private let repository: UserDefaultsRepository - - public init(repository: UserDefaultsRepository) { - self.repository = repository - } - - public func execute() -> Observable { - return repository.fetchPlatform() - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift index d938224b..575ae504 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public protocol LoginWithAppleUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift index 8f60e6a4..65415609 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift @@ -1,5 +1,6 @@ import Foundation +import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift index 8f856400..91e6a3f0 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public protocol LoginWithKakaoUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift index 76d9507a..09d0dc0b 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift @@ -1,5 +1,6 @@ import Foundation +import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift deleted file mode 100644 index 10cf0abc..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol LogoutUseCase { - func execute() -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift deleted file mode 100644 index af53b181..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LogoutUseCaseImpl.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - - -import RxSwift - -public class LogoutUseCaseImpl: LogoutUseCase { - private let repository: TokenRepository - - public init(repository: TokenRepository) { - self.repository = repository - } - - public func execute() -> Completable { - return Completable.create { [weak self] completable in - guard let self else { - completable(.completed) - return Disposables.create() - } - - let deleteAccess = self.repository.deleteToken(type: .accessToken) - let deleteRefresh = self.repository.deleteToken(type: .refreshToken) - - guard case .success = deleteAccess, case .success = deleteRefresh else { - completable(.error(NSError(domain: "LogoutError", code: -1, userInfo: nil))) - return Disposables.create() - } - - if case .success = self.repository.fetchToken(type: .fcmToken) { - _ = self.repository.deleteToken(type: .fcmToken) - } - - completable(.completed) - return Disposables.create() - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift deleted file mode 100644 index b2f485ca..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCase.swift +++ /dev/null @@ -1,3 +0,0 @@ -public protocol OpenNotificationSettingUseCase { - func execute() -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift deleted file mode 100644 index 3496e877..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/OpenNotificationSettingUseCaseImpl.swift +++ /dev/null @@ -1,12 +0,0 @@ -import UIKit - - - -public class OpenNotificationSettingUseCaseImpl: OpenNotificationSettingUseCase { - public init() {} - - public func execute() { - guard let url = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(url) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift deleted file mode 100644 index 283f676c..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol PutFCMTokenUseCase { - func execute(fcmToken: String?) -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift deleted file mode 100644 index 42fba104..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/PutFCMTokenUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class PutFCMTokenUseCaseImpl: PutFCMTokenUseCase { - private let repository: AuthAPIRepository - - public init(repository: AuthAPIRepository) { - self.repository = repository - } - - public func execute(fcmToken: String?) -> Completable { - return repository.fcmToken(fcmToken: fcmToken) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift deleted file mode 100644 index 4ab43bb7..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol ReissueUseCase { - func execute(refreshToken: String) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift deleted file mode 100644 index 7ddf0ade..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/ReissueUseCaseImpl.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation - - -import RxSwift - -public final class ReissueUseCaseImpl: ReissueUseCase { - private let repository: AuthAPIRepository - private let tokenRepository: TokenRepository - - public init(repository: AuthAPIRepository, tokenRepository: TokenRepository) { - self.repository = repository - self.tokenRepository = tokenRepository - } - - public func execute(refreshToken: String) -> Observable { - return repository.reissueToken(refreshToken: refreshToken) - .flatMap { [weak self] response -> Observable in - guard let self else { return .empty() } - - let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - - switch (saveAccess, saveRefresh) { - case (.success, .success): - return .just(response) - default: - return .error(TokenRepositoryError.dataConversionError(message: "Failed to save new tokens")) - } - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift index 77c0033c..5faade72 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public protocol SignUpWithAppleUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift index 1db0815f..cbd1e2e1 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift @@ -1,5 +1,6 @@ import Foundation +import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift index 480bc0d9..403e2cb3 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public protocol SignUpWithKakaoUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift index 73d976bd..32ba8501 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift @@ -1,5 +1,6 @@ import Foundation +import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift deleted file mode 100644 index 5311731f..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol UpdateMarketingAgreementUseCase { - func execute(credential: String, isMarketingAgreement: Bool) -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift deleted file mode 100644 index 4578bee0..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateMarketingAgreementUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class UpdateMarketingAgreementUseCaseImpl: UpdateMarketingAgreementUseCase { - private let repository: AuthAPIRepository - - public init(repository: AuthAPIRepository) { - self.repository = repository - } - - public func execute(credential: String, isMarketingAgreement: Bool) -> Completable { - return repository.updateMarketingAgreement(credential: credential, isMarketingAgreement: isMarketingAgreement) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift deleted file mode 100644 index f7d052be..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol UpdateNotificationAgreementUseCase { - func execute(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift deleted file mode 100644 index cb01bd9f..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateNotificationAgreementUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class UpdateNotificationAgreementUseCaseImpl: UpdateNotificationAgreementUseCase { - private let repository: AuthAPIRepository - - public init(repository: AuthAPIRepository) { - self.repository = repository - } - - public func execute(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { - return repository.updateNotificationAgreement(noticeAgreement: noticeAgreement, patchNoteAgreement: patchNoteAgreement, eventAgreement: eventAgreement) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift deleted file mode 100644 index 8e4e8cb9..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol UpdateUserInfoUseCase { - func execute(level: Int, selectedJobID: Int) -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift deleted file mode 100644 index fb3ec223..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/UpdateUserInfoUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class UpdateUserInfoUseCaseImpl: UpdateUserInfoUseCase { - private let repository: AuthAPIRepository - - public init(repository: AuthAPIRepository) { - self.repository = repository - } - - public func execute(level: Int, selectedJobID: Int) -> Completable { - return repository.updateUserInfo(level: level, selectedJobID: selectedJobID) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift deleted file mode 100644 index 0f8869db..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol WithdrawUseCase { - func execute() -> Completable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift deleted file mode 100644 index 9196ad0e..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/WithdrawUseCaseImpl.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - - -import RxSwift - -public class WithdrawUseCaseImpl: WithdrawUseCase { - private let authRepository: AuthAPIRepository - private let tokenRepository: TokenRepository - - public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository) { - self.authRepository = authRepository - self.tokenRepository = tokenRepository - } - - public func execute() -> Completable { - return authRepository.withdraw() - .andThen(Completable.create { [weak self] completable in - guard let self else { - completable(.completed) - return Disposables.create() - } - _ = self.tokenRepository.deleteToken(type: .accessToken) - _ = self.tokenRepository.deleteToken(type: .refreshToken) - _ = self.tokenRepository.deleteToken(type: .fcmToken) - completable(.completed) - return Disposables.create() - }) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift deleted file mode 100644 index bc2578cf..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public protocol FetchTokenFromLocalUseCase { - func execute(type: TokenType) -> Result -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift deleted file mode 100644 index a5cc67c5..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/LocalToken/FetchTokenFromLocalUseCaseImpl.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - - - -public class FetchTokenFromLocalUseCaseImpl: FetchTokenFromLocalUseCase { - private let repository: TokenRepository - - public init(repository: TokenRepository) { - self.repository = repository - } - - public func execute(type: TokenType) -> Result { - return repository.fetchToken(type: type) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift index 41e15acf..834ca30e 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift @@ -1,4 +1,3 @@ - import RxSwift public class CheckEmptyLevelAndRoleUseCaseImpl: CheckEmptyLevelAndRoleUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift deleted file mode 100644 index 05f6eff2..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol CheckNickNameUseCase { - func execute(nickName: String) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift deleted file mode 100644 index 070feb34..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckNickNameUseCaseImpl.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - - -import RxSwift - -public class CheckNickNameUseCaseImpl: CheckNickNameUseCase { - public init() {} - - public func execute(nickName: String) -> Observable { - let pattern = "^[가-힣ㄱ-ㅎㅏ-ㅣ]{2,15}$" - let trimmed = nickName.trimmingCharacters(in: .whitespacesAndNewlines) - let isValid = NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: trimmed) - return .just(isValid) - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift index 293b3946..3d528505 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift @@ -1,4 +1,3 @@ - import RxSwift public class CheckValidLevelUseCaseImpl: CheckValidLevelUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift deleted file mode 100644 index c883ebd9..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/FetchSocialCredentialUseCase.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -import RxSwift - -public protocol FetchSocialCredentialUseCase { - func execute() -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift deleted file mode 100644 index 2d8cdcd8..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/Shared/SocialLoginUseCaseImpl.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - - -import RxSwift - -public class SocialLoginUseCaseImpl: FetchSocialCredentialUseCase { - public var provider: any SocialAuthenticatableProvider - - public init(provider: any SocialAuthenticatableProvider) { - self.provider = provider - } - - public func execute() -> Observable { - return provider.getCredential() - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift index 741eecd0..7f5c8dfd 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift @@ -1,37 +1,36 @@ import MLSAuthFeatureInterface import MLSCore - import RxSwift public struct LoginFactoryImpl: LoginFactory { private let termsAgreementsFactory: TermsAgreementFactory - private let appleLoginUseCase: FetchSocialCredentialUseCase - private let kakaoLoginUseCase: FetchSocialCredentialUseCase + private let appleProvider: SocialAuthenticatableProvider + private let kakaoProvider: SocialAuthenticatableProvider private let loginWithAppleUseCase: LoginWithAppleUseCase private let loginWithKakaoUseCase: LoginWithKakaoUseCase - private let fetchTokenUseCase: FetchTokenFromLocalUseCase - private let putFCMTokenUseCase: PutFCMTokenUseCase - private let fetchPlatformUseCase: FetchPlatformUseCase + private let tokenRepository: TokenRepository + private let authRepository: AuthAPIRepository + private let userDefaultsRepository: UserDefaultsRepository public init( termsAgreementsFactory: TermsAgreementFactory, - appleLoginUseCase: FetchSocialCredentialUseCase, - kakaoLoginUseCase: FetchSocialCredentialUseCase, + appleProvider: SocialAuthenticatableProvider, + kakaoProvider: SocialAuthenticatableProvider, loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, - fetchTokenUseCase: FetchTokenFromLocalUseCase, - putFCMTokenUseCase: PutFCMTokenUseCase, - fetchPlatformUseCase: FetchPlatformUseCase + tokenRepository: TokenRepository, + authRepository: AuthAPIRepository, + userDefaultsRepository: UserDefaultsRepository ) { self.termsAgreementsFactory = termsAgreementsFactory - self.appleLoginUseCase = appleLoginUseCase - self.kakaoLoginUseCase = kakaoLoginUseCase + self.appleProvider = appleProvider + self.kakaoProvider = kakaoProvider self.loginWithAppleUseCase = loginWithAppleUseCase self.loginWithKakaoUseCase = loginWithKakaoUseCase - self.fetchTokenUseCase = fetchTokenUseCase - self.putFCMTokenUseCase = putFCMTokenUseCase - self.fetchPlatformUseCase = fetchPlatformUseCase + self.tokenRepository = tokenRepository + self.authRepository = authRepository + self.userDefaultsRepository = userDefaultsRepository } public func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController { @@ -39,13 +38,13 @@ public struct LoginFactoryImpl: LoginFactory { viewController.isBottomTabbarHidden = true viewController.reactor = LoginReactor( - fetchAppleCredentialUseCase: appleLoginUseCase, - fetchKakaoCredentialUseCase: kakaoLoginUseCase, + appleProvider: appleProvider, + kakaoProvider: kakaoProvider, loginWithAppleUseCase: loginWithAppleUseCase, loginWithKakaoUseCase: loginWithKakaoUseCase, - fetchTokenUseCase: fetchTokenUseCase, - putFCMTokenUseCase: putFCMTokenUseCase, - fetchPlatformUseCase: fetchPlatformUseCase + tokenRepository: tokenRepository, + authRepository: authRepository, + userDefaultsRepository: userDefaultsRepository ) viewController.routeToHome diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 2f1a1605..6ab4d3e1 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -1,8 +1,8 @@ - -import NotificationCenter +import UserNotifications import MLSAuthFeatureInterface import ReactorKit + import RxSwift public final class LoginReactor: Reactor { @@ -36,31 +36,31 @@ public final class LoginReactor: Reactor { // MARK: - properties public var initialState: State var disposeBag = DisposeBag() - private let fetchAppleCredentialUseCase: FetchSocialCredentialUseCase - private let fetchKakaoCredentialUseCase: FetchSocialCredentialUseCase + private let appleProvider: SocialAuthenticatableProvider + private let kakaoProvider: SocialAuthenticatableProvider private let loginWithAppleUseCase: LoginWithAppleUseCase private let loginWithKakaoUseCase: LoginWithKakaoUseCase - private let fetchTokenUseCase: FetchTokenFromLocalUseCase - private let putFCMTokenUseCase: PutFCMTokenUseCase - private let fetchPlatformUseCase: FetchPlatformUseCase + private let tokenRepository: TokenRepository + private let authRepository: AuthAPIRepository + private let userDefaultsRepository: UserDefaultsRepository // MARK: - init public init( - fetchAppleCredentialUseCase: FetchSocialCredentialUseCase, - fetchKakaoCredentialUseCase: FetchSocialCredentialUseCase, + appleProvider: SocialAuthenticatableProvider, + kakaoProvider: SocialAuthenticatableProvider, loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, - fetchTokenUseCase: FetchTokenFromLocalUseCase, - putFCMTokenUseCase: PutFCMTokenUseCase, - fetchPlatformUseCase: FetchPlatformUseCase + tokenRepository: TokenRepository, + authRepository: AuthAPIRepository, + userDefaultsRepository: UserDefaultsRepository ) { - self.fetchAppleCredentialUseCase = fetchAppleCredentialUseCase - self.fetchKakaoCredentialUseCase = fetchKakaoCredentialUseCase + self.appleProvider = appleProvider + self.kakaoProvider = kakaoProvider self.loginWithAppleUseCase = loginWithAppleUseCase self.loginWithKakaoUseCase = loginWithKakaoUseCase - self.fetchTokenUseCase = fetchTokenUseCase - self.putFCMTokenUseCase = putFCMTokenUseCase - self.fetchPlatformUseCase = fetchPlatformUseCase + self.tokenRepository = tokenRepository + self.authRepository = authRepository + self.userDefaultsRepository = userDefaultsRepository self.initialState = State() } @@ -68,7 +68,7 @@ public final class LoginReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { case .viewWillAppear: - return fetchPlatformUseCase.execute() + return userDefaultsRepository.fetchPlatform() .map { Mutation.setRelogin($0) } case .kakaoLoginButtonTapped: return handleKakaoLogin() @@ -100,14 +100,14 @@ private extension LoginReactor { .withUnretained(self) .flatMap { owner, fcmToken in // 1. 카카오 로그인 자격 증명 가져오기 - owner.fetchKakaoCredentialUseCase.execute() + owner.kakaoProvider.getCredential() .flatMap { credential in // 2. 자격 증명을 바탕으로 로그인 요청 owner.loginWithKakaoUseCase.execute(credential: credential) .flatMap { response -> Observable in if response.isRegister { // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 - return owner.putFCMTokenUseCase.execute(fcmToken: fcmToken) + return owner.authRepository.fcmToken(fcmToken: fcmToken) .andThen(.just(.navigateTo(route: .home))) } else { // 4. 미가입 유저면 약관 동의 화면으로 이동 @@ -137,14 +137,14 @@ private extension LoginReactor { .withUnretained(self) .flatMap { owner, fcmToken in // 1. 애플 로그인 자격 증명 가져오기 - owner.fetchAppleCredentialUseCase.execute() + owner.appleProvider.getCredential() .flatMap { credential in // 2. 자격 증명을 바탕으로 로그인 요청 owner.loginWithAppleUseCase.execute(credential: credential) .flatMap { response -> Observable in if response.isRegister { // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 - return owner.putFCMTokenUseCase.execute(fcmToken: fcmToken) + return owner.authRepository.fcmToken(fcmToken: fcmToken) .andThen(.just(.navigateTo(route: .home))) } else { // 4. 미가입 유저면 약관 동의 화면으로 이동 @@ -180,7 +180,7 @@ private extension LoginReactor { UNUserNotificationCenter.current().getNotificationSettings { settings in switch settings.authorizationStatus { case .authorized, .provisional, .ephemeral: - let result = self.fetchTokenUseCase.execute(type: .fcmToken) + let result = self.tokenRepository.fetchToken(type: .fcmToken) switch result { case .success(let token): observer.onNext(token) case .failure: observer.onNext(nil) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift index ef8395c6..a9d9ce83 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginView.swift @@ -1,8 +1,8 @@ import UIKit +import MLSAuthFeatureInterface import MLSDesignSystem - import SnapKit final class LoginView: UIView { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift index 70b8cc0c..027544fd 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -4,7 +4,6 @@ import UserNotifications import MLSAuthFeatureInterface import MLSCore - import ReactorKit import RxCocoa import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift index 96e9ba6a..e1e04104 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputFactoryImpl.swift @@ -1,28 +1,23 @@ import MLSAuthFeatureInterface import MLSCore - - public struct OnBoardingInputFactoryImpl: OnBoardingInputFactory { private let checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase private let checkValidLevelUseCase: CheckValidLevelUseCase - private let fetchJobListUseCase: FetchJobListUseCase - private let updateUserInfoUseCase: UpdateUserInfoUseCase + private let authRepository: AuthAPIRepository private let onBoardingNotificationFactory: OnBoardingNotificationFactory private let appCoordinator: () -> AppCoordinatorProtocol public init( checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase, checkValidLevelUseCase: CheckValidLevelUseCase, - fetchJobListUseCase: FetchJobListUseCase, - updateUserInfoUseCase: UpdateUserInfoUseCase, + authRepository: AuthAPIRepository, onBoardingNotificationFactory: OnBoardingNotificationFactory, appCoordinator: @escaping () -> AppCoordinatorProtocol ) { self.checkEmptyUseCase = checkEmptyUseCase self.checkValidLevelUseCase = checkValidLevelUseCase - self.fetchJobListUseCase = fetchJobListUseCase - self.updateUserInfoUseCase = updateUserInfoUseCase + self.authRepository = authRepository self.onBoardingNotificationFactory = onBoardingNotificationFactory self.appCoordinator = appCoordinator } @@ -33,7 +28,7 @@ public struct OnBoardingInputFactoryImpl: OnBoardingInputFactory { viewController.reactor = OnBoardingInputReactor( checkEmptyUseCase: checkEmptyUseCase, checkValidLevelUseCase: checkValidLevelUseCase, - fetchJobListUseCase: fetchJobListUseCase + authRepository: authRepository ) return viewController } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift index d2164c97..71b164f6 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift @@ -1,5 +1,3 @@ - - import ReactorKit import RxSwift @@ -47,17 +45,17 @@ public final class OnBoardingInputReactor: Reactor { private let checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase private let checkValidLevelUseCase: CheckValidLevelUseCase - private let fetchJobListUseCase: FetchJobListUseCase + private let authRepository: AuthAPIRepository // MARK: - init public init( checkEmptyUseCase: CheckEmptyLevelAndRoleUseCase, checkValidLevelUseCase: CheckValidLevelUseCase, - fetchJobListUseCase: FetchJobListUseCase + authRepository: AuthAPIRepository ) { self.checkEmptyUseCase = checkEmptyUseCase self.checkValidLevelUseCase = checkValidLevelUseCase - self.fetchJobListUseCase = fetchJobListUseCase + self.authRepository = authRepository self.initialState = State() } @@ -65,7 +63,7 @@ public final class OnBoardingInputReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { case .viewWillAppear: - return fetchJobListUseCase.execute() + return authRepository.fetchJobList() .map { response in .setJobList(jobList: response.jobList) } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift index 0ff945af..0a2feebb 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputViewController.swift @@ -4,7 +4,6 @@ import MLSAuthFeatureInterface import MLSCore import MLSDesignSystem - import ReactorKit import RxCocoa import RxKeyboard diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift index d1fc161c..886ebcc4 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift @@ -2,26 +2,15 @@ import MLSAuthFeatureInterface import MLSCore import MLSDesignSystem - - public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationSheetFactory { - private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase - private let openNotificationSettingUseCase: OpenNotificationSettingUseCase - private let updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase - private let updateUserInfoUseCase: UpdateUserInfoUseCase + private let authRepository: AuthAPIRepository private let appCoordinator: () -> AppCoordinatorProtocol public init( - checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, - openNotificationSettingUseCase: OpenNotificationSettingUseCase, - updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, - updateUserInfoUseCase: UpdateUserInfoUseCase, + authRepository: AuthAPIRepository, appCoordinator: @escaping () -> AppCoordinatorProtocol ) { - self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase - self.openNotificationSettingUseCase = openNotificationSettingUseCase - self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase - self.updateUserInfoUseCase = updateUserInfoUseCase + self.authRepository = authRepository self.appCoordinator = appCoordinator } @@ -31,10 +20,7 @@ public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationShee viewController.reactor = OnBoardingNotificationSheetReactor( selectedLevel: selectedLevel, selectedJobID: selectedJobID, - checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, - openNotificationSettingUseCase: openNotificationSettingUseCase, - updateNotificationAgreementUseCase: updateNotificationAgreementUseCase, - updateUserInfoUseCase: updateUserInfoUseCase + authRepository: authRepository ) return viewController } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift index 07e7aebf..192d0b47 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift @@ -1,4 +1,5 @@ - +import UIKit +import UserNotifications import ReactorKit import RxCocoa @@ -43,42 +44,42 @@ public final class OnBoardingNotificationSheetReactor: Reactor { public var initialState: State var disposeBag = DisposeBag() - private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase - private let openNotificationSettingUseCase: OpenNotificationSettingUseCase - private let updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase - private let updateUserInfoUseCase: UpdateUserInfoUseCase + private let authRepository: AuthAPIRepository // MARK: - init public init( selectedLevel: Int, selectedJobID: Int, - checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, - openNotificationSettingUseCase: OpenNotificationSettingUseCase, - updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, - updateUserInfoUseCase: UpdateUserInfoUseCase + authRepository: AuthAPIRepository ) { self.initialState = State(selectedLevel: selectedLevel, selectedJobID: selectedJobID) - self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase - self.openNotificationSettingUseCase = openNotificationSettingUseCase - self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase - self.updateUserInfoUseCase = updateUserInfoUseCase + self.authRepository = authRepository } // MARK: - Reactor Methods public func mutate(action: Action) -> Observable { switch action { case .viewWillAppear, .appWillEnterForeground: - return checkNotificationPermissionUseCase.execute() - .asObservable() - .map { .setLocalNotification($0) } + return Single.create { single in + UNUserNotificationCenter.current().getNotificationSettings { settings in + let isAuthorized = settings.authorizationStatus == .authorized + || settings.authorizationStatus == .provisional + single(.success(isAuthorized)) + } + return Disposables.create() + } + .asObservable() + .map { .setLocalNotification($0) } case .toggleSwitchButton(let isAgree): return .just(.setRemoteNotification(isAgree)) case .setButtonTapped: - openNotificationSettingUseCase.execute() + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } return .just(.navigateTo(route: .setting)) case .applyButtonTapped: - return updateUserInfoUseCase.execute(level: currentState.selectedLevel, selectedJobID: currentState.selectedJobID) - .andThen(updateNotificationAgreementUseCase.execute( + return authRepository.updateUserInfo(level: currentState.selectedLevel, selectedJobID: currentState.selectedJobID) + .andThen(authRepository.updateNotificationAgreement( noticeAgreement: true, patchNoteAgreement: true, eventAgreement: true diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift index e6d735b3..f6a4e5eb 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift @@ -1,27 +1,23 @@ import MLSAuthFeatureInterface import MLSCore - public struct TermsAgreementFactoryImpl: TermsAgreementFactory { private let onBoardingQuestionFactory: OnBoardingQuestionFactory private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase private let signUpWithAppleUseCase: SignUpWithAppleUseCase - private let fetchTokenUseCase: FetchTokenFromLocalUseCase - private let updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + private let tokenRepository: TokenRepository public init( onBoardingQuestionFactory: OnBoardingQuestionFactory, signUpWithKakaoUseCase: SignUpWithKakaoUseCase, signUpWithAppleUseCase: SignUpWithAppleUseCase, - fetchTokenUseCase: FetchTokenFromLocalUseCase, - updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + tokenRepository: TokenRepository ) { self.onBoardingQuestionFactory = onBoardingQuestionFactory self.signUpWithKakaoUseCase = signUpWithKakaoUseCase self.signUpWithAppleUseCase = signUpWithAppleUseCase - self.fetchTokenUseCase = fetchTokenUseCase - self.updateMarketingAgreementUseCase = updateMarketingAgreementUseCase + self.tokenRepository = tokenRepository } public func make(credential: Credential, platform: LoginPlatform) -> BaseViewController { @@ -32,7 +28,7 @@ public struct TermsAgreementFactoryImpl: TermsAgreementFactory { socialPlatform: platform, signUpWithKakaoUseCase: signUpWithKakaoUseCase, signUpWithAppleUseCase: signUpWithAppleUseCase, - fetchTokenUseCase: fetchTokenUseCase, updateMarketingAgreementUseCase: updateMarketingAgreementUseCase + tokenRepository: tokenRepository ) return viewController } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift index 21d7d116..512195fc 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift @@ -1,7 +1,5 @@ - -import NotificationCenter - import MLSAuthFeatureInterface + import ReactorKit import RxSwift @@ -53,8 +51,7 @@ public final class TermsAgreementReactor: Reactor { private let socialPlatform: LoginPlatform private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase private let signUpWithAppleUseCase: SignUpWithAppleUseCase - private let fetchTokenUseCase: FetchTokenFromLocalUseCase - private let updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + private let tokenRepository: TokenRepository // MARK: - init public init( @@ -62,15 +59,13 @@ public final class TermsAgreementReactor: Reactor { socialPlatform: LoginPlatform, signUpWithKakaoUseCase: SignUpWithKakaoUseCase, signUpWithAppleUseCase: SignUpWithAppleUseCase, - fetchTokenUseCase: FetchTokenFromLocalUseCase, - updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCase + tokenRepository: TokenRepository ) { self.credential = credential self.socialPlatform = socialPlatform self.signUpWithKakaoUseCase = signUpWithKakaoUseCase self.signUpWithAppleUseCase = signUpWithAppleUseCase - self.fetchTokenUseCase = fetchTokenUseCase - self.updateMarketingAgreementUseCase = updateMarketingAgreementUseCase + self.tokenRepository = tokenRepository self.initialState = State() } @@ -96,7 +91,7 @@ public final class TermsAgreementReactor: Reactor { return .just(.setAgreeState(type: type, isOn: isOn)) case .bottomButtonTapped: let fcmToken: String? = { - if case .success(let token) = fetchTokenUseCase.execute(type: .fcmToken) { + if case .success(let token) = tokenRepository.fetchToken(type: .fcmToken) { return token } else { return nil diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift index 114aa95e..b8912c9b 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift @@ -1,4 +1,3 @@ - import MLSCore public protocol TermsAgreementFactory { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift index 9f4d9379..f8d65242 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift @@ -1,4 +1,6 @@ import MLSAuthFeature +import MLSAuthFeatureInterface + import RxSwift public final class MockAuthAPIRepository: AuthAPIRepository { @@ -33,10 +35,6 @@ public final class MockAuthAPIRepository: AuthAPIRepository { ])) } - public func fetchJob(jobId: String) -> Observable { - return .just(Job(name: "전사", id: 1)) - } - public func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { return .empty() } public func reissueToken(refreshToken: String) -> Observable { @@ -45,15 +43,5 @@ public final class MockAuthAPIRepository: AuthAPIRepository { public func fcmToken(fcmToken: String?) -> Completable { return .empty() } - public func updateMarketingAgreement(credential: String, isMarketingAgreement: Bool) -> Completable { return .empty() } - public func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { return .empty() } - - public func updateNickName(nickName: String) -> Observable { - return .just(MyPageResponse(nickname: nickName, jobId: nil, jobName: "", level: nil, profileUrl: "", platform: .kakao, noticeAgreement: nil, patchNoteAgreement: nil, eventAgreement: nil)) - } - - public func updateProfileImage(url: String) -> Completable { return .empty() } - - public func fetchProfile() -> Observable { return .just(nil) } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift index f7a3c5e3..416f80ac 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift @@ -1,5 +1,5 @@ -import UIKit import MLSAuthFeature +import MLSAuthFeatureInterface import MLSCore public final class MockTermsAgreementFactory: TermsAgreementFactory { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift index e1d5d1f9..ae98ca4c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift @@ -1,4 +1,5 @@ import MLSAuthFeature +import MLSAuthFeatureInterface public final class MockTokenRepository: TokenRepository { private var storage: [String: String] = [:] diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift index 9e66586d..1b1bbc7f 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift @@ -1,4 +1,6 @@ import MLSAuthFeature +import MLSAuthFeatureInterface + import RxSwift public final class MockUserDefaultsRepository: UserDefaultsRepository { diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index a4dab287..dbf0bd70 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -1,5 +1,7 @@ import UIKit + import MLSAuthFeature +import MLSAuthFeatureInterface import MLSAuthFeatureTesting class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol { @@ -49,8 +51,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol tokenRepository: tokenRepository, userDefaultsRepository: userDefaultsRepository ), - appleLoginUseCase: SocialLoginUseCaseImpl(provider: appleProvider), - kakaoLoginUseCase: SocialLoginUseCaseImpl(provider: kakaoProvider), + appleProvider: appleProvider, + kakaoProvider: kakaoProvider, loginWithAppleUseCase: LoginWithAppleUseCaseImpl( authRepository: authRepository, tokenRepository: tokenRepository, @@ -61,9 +63,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol tokenRepository: tokenRepository, userDefaultsRepository: userDefaultsRepository ), - fetchTokenUseCase: FetchTokenFromLocalUseCaseImpl(repository: tokenRepository), - putFCMTokenUseCase: PutFCMTokenUseCaseImpl(repository: authRepository), - fetchPlatformUseCase: FetchPlatformUseCaseImpl(repository: userDefaultsRepository) + tokenRepository: tokenRepository, + authRepository: authRepository, + userDefaultsRepository: userDefaultsRepository ) return factory.make(exitRoute: .home, onLoginCompleted: { [weak self] in @@ -79,11 +81,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol let onBoardingInputFactory = OnBoardingInputFactoryImpl( checkEmptyUseCase: CheckEmptyLevelAndRoleUseCaseImpl(), checkValidLevelUseCase: CheckValidLevelUseCaseImpl(), - fetchJobListUseCase: FetchJobListUseCaseImpl(repository: authRepository), - updateUserInfoUseCase: UpdateUserInfoUseCaseImpl(repository: authRepository), + authRepository: authRepository, onBoardingNotificationFactory: makeOnBoardingNotificationFactory( - authRepository: authRepository, - tokenRepository: tokenRepository + authRepository: authRepository ), appCoordinator: { [weak self] in self! } ) @@ -104,20 +104,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol tokenRepository: tokenRepository, userDefaultsRepository: userDefaultsRepository ), - fetchTokenUseCase: FetchTokenFromLocalUseCaseImpl(repository: tokenRepository), - updateMarketingAgreementUseCase: UpdateMarketingAgreementUseCaseImpl(repository: authRepository) + tokenRepository: tokenRepository ) } private func makeOnBoardingNotificationFactory( - authRepository: AuthAPIRepository, - tokenRepository: TokenRepository + authRepository: AuthAPIRepository ) -> OnBoardingNotificationFactory { let sheetFactory = OnBoardingNotificationSheetFactoryImpl( - checkNotificationPermissionUseCase: CheckNotificationPermissionUseCaseImpl(), - openNotificationSettingUseCase: OpenNotificationSettingUseCaseImpl(), - updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCaseImpl(repository: authRepository), - updateUserInfoUseCase: UpdateUserInfoUseCaseImpl(repository: authRepository), + authRepository: authRepository, appCoordinator: { [weak self] in self! } ) From 634fae286683310c71b37fdc995e920067a32f26 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 23:10:40 +0900 Subject: [PATCH 07/16] =?UTF-8?q?rename/#316:=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/{Network/DTO => DTOs}/AuthResponseDTO.swift | 0 .../MLSAuthFeature/Data/{Network/DTO => DTOs}/JobsDTO.swift | 0 .../MLSAuthFeature/Data/{Network/DTO => DTOs}/MemberDTO.swift | 0 .../Data/{Network/Endpoint => Endpoints}/AuthEndPoint.swift | 0 .../Data/Providers/{Apple => }/AppleLoginProviderImpl.swift | 2 +- .../Data/Providers/{Kakao => }/KakaoLoginProviderImpl.swift | 0 .../{Repository => Repositories}/AuthAPIRepositoryImpl.swift | 0 .../Providers/Apple => Domain/Entities}/AppleCredential.swift | 0 .../Domain/{Entity => Entities}/JobListResponse.swift | 2 -- .../Providers/Kakao => Domain/Entities}/KakaoCredential.swift | 0 .../Domain/{Entity => Entities}/LoginResponse.swift | 2 -- .../Domain/{Entity => Entities}/SignUpResponse.swift | 2 -- .../MLSAuthFeature/Domain/{Error => Errors}/AuthError.swift | 0 .../Domain/{Error => Errors}/TokenRepositoryError.swift | 0 .../{Provider => Providers}/SocialAuthenticatableProvider.swift | 2 -- .../Domain/{Repository => Repositories}/AuthAPIRepository.swift | 0 .../Domain/{Repository => Repositories}/TokenRepository.swift | 2 -- .../{Repository => Repositories}/UserDefaultsRepository.swift | 0 .../{UseCase => UseCases}/AuthAPI/LoginWithAppleUseCase.swift | 0 .../AuthAPI/LoginWithAppleUseCaseImpl.swift | 2 -- .../{UseCase => UseCases}/AuthAPI/LoginWithKakaoUseCase.swift | 0 .../AuthAPI/LoginWithKakaoUseCaseImpl.swift | 2 -- .../{UseCase => UseCases}/AuthAPI/SignUpWithAppleUseCase.swift | 0 .../AuthAPI/SignUpWithAppleUseCaseImpl.swift | 2 -- .../{UseCase => UseCases}/AuthAPI/SignUpWithKakaoUseCase.swift | 0 .../AuthAPI/SignUpWithKakaoUseCaseImpl.swift | 2 -- .../OnBoarding/CheckEmptyLevelAndRoleUseCase.swift | 0 .../OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift | 0 .../OnBoarding/CheckValidLevelUseCase.swift | 0 .../OnBoarding/CheckValidLevelUseCaseImpl.swift | 0 .../MLSAuthFeature/Presentation/Login/LoginReactor.swift | 2 +- .../OnBoardingNotificationFactoryImpl.swift | 0 .../OnBoardingNotificationReactor.swift | 0 .../OnBoardingNotificationView.swift | 0 .../OnBoardingNotificationViewController.swift | 0 ...icationFactory.swift => OnBoardingNotificationFactory.swift} | 0 36 files changed, 2 insertions(+), 20 deletions(-) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/{Network/DTO => DTOs}/AuthResponseDTO.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/{Network/DTO => DTOs}/JobsDTO.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/{Network/DTO => DTOs}/MemberDTO.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/{Network/Endpoint => Endpoints}/AuthEndPoint.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/{Apple => }/AppleLoginProviderImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/{Kakao => }/KakaoLoginProviderImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/{Repository => Repositories}/AuthAPIRepositoryImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Data/Providers/Apple => Domain/Entities}/AppleCredential.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Entity => Entities}/JobListResponse.swift (94%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Data/Providers/Kakao => Domain/Entities}/KakaoCredential.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Entity => Entities}/LoginResponse.swift (94%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Entity => Entities}/SignUpResponse.swift (92%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Error => Errors}/AuthError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Error => Errors}/TokenRepositoryError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Provider => Providers}/SocialAuthenticatableProvider.swift (88%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Repository => Repositories}/AuthAPIRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Repository => Repositories}/TokenRepository.swift (92%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{Repository => Repositories}/UserDefaultsRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/LoginWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/LoginWithAppleUseCaseImpl.swift (99%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/LoginWithKakaoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/LoginWithKakaoUseCaseImpl.swift (99%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/SignUpWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/SignUpWithAppleUseCaseImpl.swift (98%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/SignUpWithKakaoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/AuthAPI/SignUpWithKakaoUseCaseImpl.swift (98%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/OnBoarding/CheckValidLevelUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/{UseCase => UseCases}/OnBoarding/CheckValidLevelUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/{OnBoadingNotification => OnBoardingNotification}/OnBoardingNotificationFactoryImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/{OnBoadingNotification => OnBoardingNotification}/OnBoardingNotificationReactor.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/{OnBoadingNotification => OnBoardingNotification}/OnBoardingNotificationView.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/{OnBoadingNotification => OnBoardingNotification}/OnBoardingNotificationViewController.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{OnBoadingNotificationFactory.swift => OnBoardingNotificationFactory.swift} (100%) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/AuthResponseDTO.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/JobsDTO.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/MemberDTO.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/DTO/MemberDTO.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/MemberDTO.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Network/Endpoint/AuthEndPoint.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift index 788d62ad..3dd3fef9 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift @@ -1,7 +1,7 @@ +import AuthenticationServices import UIKit import MLSAuthFeatureInterface -import AuthenticationServices import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoLoginProviderImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoLoginProviderImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoLoginProviderImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repositories/AuthAPIRepositoryImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repository/AuthAPIRepositoryImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Repositories/AuthAPIRepositoryImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/AppleCredential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Apple/AppleCredential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/AppleCredential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/JobListResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/JobListResponse.swift similarity index 94% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/JobListResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/JobListResponse.swift index 44e49789..9ffb71b6 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/JobListResponse.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/JobListResponse.swift @@ -1,5 +1,3 @@ -import Foundation - public struct JobListResponse { public var jobList: [Job] diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/KakaoCredential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/Kakao/KakaoCredential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/KakaoCredential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/LoginResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/LoginResponse.swift similarity index 94% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/LoginResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/LoginResponse.swift index 2d2e0105..62338dd7 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/LoginResponse.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/LoginResponse.swift @@ -1,5 +1,3 @@ -import Foundation - public struct LoginResponse { public var isRegister: Bool public var accessToken: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/SignUpResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/SignUpResponse.swift similarity index 92% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/SignUpResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/SignUpResponse.swift index 1f4091e4..91a79260 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entity/SignUpResponse.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/SignUpResponse.swift @@ -1,5 +1,3 @@ -import Foundation - public struct SignUpResponse { public var accessToken: String public var refreshToken: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/AuthError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/AuthError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/AuthError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/TokenRepositoryError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/TokenRepositoryError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Error/TokenRepositoryError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/TokenRepositoryError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift similarity index 88% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift index ff82d8ed..35379d3a 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Provider/SocialAuthenticatableProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift @@ -1,5 +1,3 @@ -import Foundation - import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/AuthAPIRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/AuthAPIRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/AuthAPIRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/TokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/TokenRepository.swift similarity index 92% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/TokenRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/TokenRepository.swift index 695342f8..a24a3e51 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/TokenRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/TokenRepository.swift @@ -1,5 +1,3 @@ -import Foundation - public protocol TokenRepository { func fetchToken(type: TokenType) -> Result func saveToken(type: TokenType, value: String) -> Result diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/UserDefaultsRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repository/UserDefaultsRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/UserDefaultsRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCaseImpl.swift similarity index 99% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCaseImpl.swift index 65415609..8ddc4b4a 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCaseImpl.swift @@ -1,5 +1,3 @@ -import Foundation - import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCaseImpl.swift similarity index 99% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCaseImpl.swift index 09d0dc0b..7f590a73 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/LoginWithKakaoUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCaseImpl.swift @@ -1,5 +1,3 @@ -import Foundation - import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCaseImpl.swift similarity index 98% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCaseImpl.swift index cbd1e2e1..247482a3 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCaseImpl.swift @@ -1,5 +1,3 @@ -import Foundation - import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCaseImpl.swift similarity index 98% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCaseImpl.swift index 32ba8501..30f43544 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/AuthAPI/SignUpWithKakaoUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCaseImpl.swift @@ -1,5 +1,3 @@ -import Foundation - import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCase/OnBoarding/CheckValidLevelUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 6ab4d3e1..2a44e59c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -1,8 +1,8 @@ import UserNotifications import MLSAuthFeatureInterface -import ReactorKit +import ReactorKit import RxSwift public final class LoginReactor: Reactor { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationFactoryImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationFactoryImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationFactoryImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationReactor.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationReactor.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationReactor.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationView.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationView.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationView.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationViewController.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoadingNotification/OnBoardingNotificationViewController.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotification/OnBoardingNotificationViewController.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoadingNotificationFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationFactory.swift From 59228bd5c9a70d51e101a362faf6207492cfb19d Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 23:18:35 +0900 Subject: [PATCH 08/16] =?UTF-8?q?rename/#316:=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=94=84=EB=A1=9C=EB=B0=94?= =?UTF-8?q?=EC=9D=B4=EB=8D=94=20=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...inProviderImpl.swift => AppleCredentialProvider.swift} | 4 ++-- ...inProviderImpl.swift => KakaoCredentialProvider.swift} | 2 +- ...tableProvider.swift => SocialCredentialProvider.swift} | 2 +- .../Presentation/Login/LoginFactoryImpl.swift | 8 ++++---- .../MLSAuthFeature/Presentation/Login/LoginReactor.swift | 8 ++++---- MLS/MLSAuthFeatureExample/SceneDelegate.swift | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/{AppleLoginProviderImpl.swift => AppleCredentialProvider.swift} (91%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/{KakaoLoginProviderImpl.swift => KakaoCredentialProvider.swift} (94%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/{SocialAuthenticatableProvider.swift => SocialCredentialProvider.swift} (67%) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift similarity index 91% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift index 3dd3fef9..8f8beb44 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift @@ -5,7 +5,7 @@ import MLSAuthFeatureInterface import RxSwift -public final class AppleLoginProviderImpl: NSObject, SocialAuthenticatableProvider { +public final class AppleCredentialProvider: NSObject, SocialCredentialProvider { override public init() {} private var authServiceResponse = PublishSubject() @@ -27,7 +27,7 @@ public final class AppleLoginProviderImpl: NSObject, SocialAuthenticatableProvid } } -extension AppleLoginProviderImpl: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { +extension AppleCredentialProvider: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoLoginProviderImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift similarity index 94% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoLoginProviderImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift index d30d61e9..b8b3ef13 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoLoginProviderImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift @@ -6,7 +6,7 @@ import KakaoSDKAuth import KakaoSDKUser import RxSwift -public final class KakaoLoginProviderImpl: SocialAuthenticatableProvider, @unchecked Sendable { +public final class KakaoCredentialProvider: SocialCredentialProvider, @unchecked Sendable { public init() {} public func getCredential() -> Observable { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialCredentialProvider.swift similarity index 67% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialCredentialProvider.swift index 35379d3a..fb6ef6e2 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialAuthenticatableProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialCredentialProvider.swift @@ -2,6 +2,6 @@ import MLSAuthFeatureInterface import RxSwift -public protocol SocialAuthenticatableProvider { +public protocol SocialCredentialProvider { func getCredential() -> Observable } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift index 7f5c8dfd..c83348b7 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift @@ -5,8 +5,8 @@ import RxSwift public struct LoginFactoryImpl: LoginFactory { private let termsAgreementsFactory: TermsAgreementFactory - private let appleProvider: SocialAuthenticatableProvider - private let kakaoProvider: SocialAuthenticatableProvider + private let appleProvider: SocialCredentialProvider + private let kakaoProvider: SocialCredentialProvider private let loginWithAppleUseCase: LoginWithAppleUseCase private let loginWithKakaoUseCase: LoginWithKakaoUseCase private let tokenRepository: TokenRepository @@ -15,8 +15,8 @@ public struct LoginFactoryImpl: LoginFactory { public init( termsAgreementsFactory: TermsAgreementFactory, - appleProvider: SocialAuthenticatableProvider, - kakaoProvider: SocialAuthenticatableProvider, + appleProvider: SocialCredentialProvider, + kakaoProvider: SocialCredentialProvider, loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, tokenRepository: TokenRepository, diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 2a44e59c..61c4badd 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -36,8 +36,8 @@ public final class LoginReactor: Reactor { // MARK: - properties public var initialState: State var disposeBag = DisposeBag() - private let appleProvider: SocialAuthenticatableProvider - private let kakaoProvider: SocialAuthenticatableProvider + private let appleProvider: SocialCredentialProvider + private let kakaoProvider: SocialCredentialProvider private let loginWithAppleUseCase: LoginWithAppleUseCase private let loginWithKakaoUseCase: LoginWithKakaoUseCase private let tokenRepository: TokenRepository @@ -46,8 +46,8 @@ public final class LoginReactor: Reactor { // MARK: - init public init( - appleProvider: SocialAuthenticatableProvider, - kakaoProvider: SocialAuthenticatableProvider, + appleProvider: SocialCredentialProvider, + kakaoProvider: SocialCredentialProvider, loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, tokenRepository: TokenRepository, diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index dbf0bd70..6324c780 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -42,8 +42,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol let userDefaultsRepository = MockUserDefaultsRepository() let authRepository = MockAuthAPIRepository() - let appleProvider = AppleLoginProviderImpl() - let kakaoProvider = KakaoLoginProviderImpl() + let appleProvider = AppleCredentialProvider() + let kakaoProvider = KakaoCredentialProvider() let factory = LoginFactoryImpl( termsAgreementsFactory: makeTermsAgreementFactory( From 0f231eb24a33a6ce25015edae55ae91eb9100519 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 23:25:30 +0900 Subject: [PATCH 09/16] =?UTF-8?q?rename/#316:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{OnBoarding => }/CheckEmptyLevelAndRoleUseCaseImpl.swift | 0 .../UseCases/{OnBoarding => }/CheckValidLevelUseCaseImpl.swift | 0 .../Domain/UseCases/{AuthAPI => }/LoginWithAppleUseCaseImpl.swift | 0 .../Domain/UseCases/{AuthAPI => }/LoginWithKakaoUseCaseImpl.swift | 0 .../UseCases/{AuthAPI => }/SignUpWithAppleUseCaseImpl.swift | 0 .../UseCases/{AuthAPI => }/SignUpWithKakaoUseCaseImpl.swift | 0 .../{Domain => DomainInterface}/Entities/AppleCredential.swift | 0 .../{Domain => DomainInterface}/Entities/JobListResponse.swift | 0 .../{Domain => DomainInterface}/Entities/KakaoCredential.swift | 0 .../{Domain => DomainInterface}/Entities/LoginResponse.swift | 0 .../{Domain => DomainInterface}/Entities/SignUpResponse.swift | 0 .../{Domain => DomainInterface}/Errors/AuthError.swift | 0 .../{Domain => DomainInterface}/Errors/TokenRepositoryError.swift | 0 .../Providers/SocialCredentialProvider.swift | 0 .../Repositories/AuthAPIRepository.swift | 0 .../Repositories/TokenRepository.swift | 0 .../Repositories/UserDefaultsRepository.swift | 0 .../UseCases}/CheckEmptyLevelAndRoleUseCase.swift | 0 .../UseCases}/CheckValidLevelUseCase.swift | 0 .../UseCases}/LoginWithAppleUseCase.swift | 0 .../UseCases}/LoginWithKakaoUseCase.swift | 0 .../UseCases}/SignUpWithAppleUseCase.swift | 0 .../UseCases}/SignUpWithKakaoUseCase.swift | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{OnBoarding => }/CheckEmptyLevelAndRoleUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{OnBoarding => }/CheckValidLevelUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{AuthAPI => }/LoginWithAppleUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{AuthAPI => }/LoginWithKakaoUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{AuthAPI => }/SignUpWithAppleUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{AuthAPI => }/SignUpWithKakaoUseCaseImpl.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Entities/AppleCredential.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Entities/JobListResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Entities/KakaoCredential.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Entities/LoginResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Entities/SignUpResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Errors/AuthError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Errors/TokenRepositoryError.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Providers/SocialCredentialProvider.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Repositories/AuthAPIRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Repositories/TokenRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain => DomainInterface}/Repositories/UserDefaultsRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/OnBoarding => DomainInterface/UseCases}/CheckEmptyLevelAndRoleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/OnBoarding => DomainInterface/UseCases}/CheckValidLevelUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/AuthAPI => DomainInterface/UseCases}/LoginWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/AuthAPI => DomainInterface/UseCases}/LoginWithKakaoUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/AuthAPI => DomainInterface/UseCases}/SignUpWithAppleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/{Domain/UseCases/AuthAPI => DomainInterface/UseCases}/SignUpWithKakaoUseCase.swift (100%) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithAppleUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithAppleUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithAppleUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithAppleUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/AppleCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/AppleCredential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/JobListResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/JobListResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/JobListResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/JobListResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/KakaoCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/KakaoCredential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/LoginResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/LoginResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/LoginResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/LoginResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/SignUpResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/SignUpResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Entities/SignUpResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/SignUpResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/AuthError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/AuthError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/AuthError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/TokenRepositoryError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/TokenRepositoryError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Errors/TokenRepositoryError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/TokenRepositoryError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialCredentialProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Providers/SocialCredentialProvider.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Providers/SocialCredentialProvider.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Providers/SocialCredentialProvider.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/AuthAPIRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/AuthAPIRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/AuthAPIRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/TokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/TokenRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/TokenRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/TokenRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/UserDefaultsRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/Repositories/UserDefaultsRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/UserDefaultsRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckEmptyLevelAndRoleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckValidLevelUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/OnBoarding/CheckValidLevelUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckValidLevelUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/LoginWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithAppleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/AuthAPI/SignUpWithKakaoUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift From 9374b0ac127272f195b5711eefb73bf4bd311cdf Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 23:28:52 +0900 Subject: [PATCH 10/16] =?UTF-8?q?rename/#316:=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => Factories}/LoginFactory.swift | 5 ----- .../{ => Factories}/OnBoardingInputFactory.swift | 0 .../{ => Factories}/OnBoardingModalFactory.swift | 0 .../{ => Factories}/OnBoardingNotificationFactory.swift | 0 .../{ => Factories}/OnBoardingNotificationSheetFactory.swift | 0 .../{ => Factories}/OnBoardingQuestionFactory.swift | 0 .../{ => Factories}/TermsAgreementFactory.swift | 0 .../MLSAuthFeatureInterface/{ => Models}/Credential.swift | 0 .../MLSAuthFeatureInterface/Models/LoginExitRoute.swift | 4 ++++ .../MLSAuthFeatureInterface/{ => Models}/LoginPlatform.swift | 2 -- 10 files changed, 4 insertions(+), 7 deletions(-) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/LoginFactory.swift (83%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/OnBoardingInputFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/OnBoardingModalFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/OnBoardingNotificationFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/OnBoardingNotificationSheetFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/OnBoardingQuestionFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Factories}/TermsAgreementFactory.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Models}/Credential.swift (100%) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{ => Models}/LoginPlatform.swift (78%) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/LoginFactory.swift similarity index 83% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/LoginFactory.swift index 7b697eaf..a7a7cd36 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginFactory.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/LoginFactory.swift @@ -1,10 +1,5 @@ import MLSCore -public enum LoginExitRoute { - case home - case pop -} - public protocol LoginFactory { func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingInputFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingInputFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingInputFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingModalFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingModalFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingModalFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingNotificationFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingNotificationFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingNotificationSheetFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingNotificationSheetFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingNotificationSheetFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingQuestionFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/OnBoardingQuestionFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/OnBoardingQuestionFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/TermsAgreementFactory.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/TermsAgreementFactory.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Factories/TermsAgreementFactory.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Credential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Credential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift new file mode 100644 index 00000000..597ae3e7 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift @@ -0,0 +1,4 @@ +public enum LoginExitRoute { + case home + case pop +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginPlatform.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginPlatform.swift similarity index 78% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginPlatform.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginPlatform.swift index 35c4f7ff..5bc534e1 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/LoginPlatform.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginPlatform.swift @@ -1,5 +1,3 @@ -import Foundation - public enum LoginPlatform: String { case kakao case apple From 92c0f2bad839707f81b1c43c250317655c3ed4a4 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sat, 11 Apr 2026 23:50:50 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat/#316:=20=EC=9C=A0=EC=A6=88=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCases/LoginWithKakaoUseCaseImpl.swift | 51 ------- .../UseCases/SignUpWithKakaoUseCaseImpl.swift | 38 ----- ...mpl.swift => SocialLoginUseCaseImpl.swift} | 16 ++- ...pl.swift => SocialSignUpUseCaseImpl.swift} | 16 ++- .../UseCases/LoginWithAppleUseCase.swift | 7 - .../UseCases/LoginWithKakaoUseCase.swift | 7 - .../UseCases/SignUpWithAppleUseCase.swift | 7 - .../UseCases/SignUpWithKakaoUseCase.swift | 7 - .../UseCases/SocialLoginUseCase.swift | 7 + .../UseCases/SocialSignUpUseCase.swift | 7 + .../Presentation/Login/LoginFactoryImpl.swift | 20 +-- .../Presentation/Login/LoginReactor.swift | 132 +++--------------- .../TermsAgreementFactoryImpl.swift | 13 +- .../TermsAgreementReactor.swift | 26 +--- .../Models/Credential.swift | 2 +- MLS/MLSAuthFeatureExample/SceneDelegate.swift | 16 +-- 16 files changed, 77 insertions(+), 295 deletions(-) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{LoginWithAppleUseCaseImpl.swift => SocialLoginUseCaseImpl.swift} (77%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/{SignUpWithAppleUseCaseImpl.swift => SocialSignUpUseCaseImpl.swift} (64%) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift deleted file mode 100644 index 7f590a73..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithKakaoUseCaseImpl.swift +++ /dev/null @@ -1,51 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public class LoginWithKakaoUseCaseImpl: LoginWithKakaoUseCase { - private let authRepository: AuthAPIRepository - private let tokenRepository: TokenRepository - private let userDefaultsRepository: UserDefaultsRepository - - public init( - authRepository: AuthAPIRepository, - tokenRepository: TokenRepository, - userDefaultsRepository: UserDefaultsRepository - ) { - self.authRepository = authRepository - self.tokenRepository = tokenRepository - self.userDefaultsRepository = userDefaultsRepository - } - - public func execute(credential: Credential) -> Observable { - return authRepository.loginWithKakao(credential: credential) - .flatMap { response -> Observable in - let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) - - guard case (.success, .success) = (saveAccess, saveRefresh) else { - return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) - } - - var fcmToken: String? - if case .success(let token) = self.tokenRepository.fetchToken(type: .fcmToken) { - fcmToken = token - } - - let fcmUpdate = if let fcmToken { - self.authRepository.fcmToken(fcmToken: fcmToken) - .catch { error in - print("FCM token update failed: \(error)") - return .empty() - } - } else { - Completable.empty() - } - return fcmUpdate.andThen(savePlatform).andThen(Observable.just(response)) - } - .catch { error in - Observable.error(error) - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift deleted file mode 100644 index 30f43544..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithKakaoUseCaseImpl.swift +++ /dev/null @@ -1,38 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public final class SignUpWithKakaoUseCaseImpl: SignUpWithKakaoUseCase { - private let authRepository: AuthAPIRepository - private let tokenRepository: TokenRepository - private let userDefaultsRepository: UserDefaultsRepository - - public init( - authRepository: AuthAPIRepository, - tokenRepository: TokenRepository, - userDefaultsRepository: UserDefaultsRepository - ) { - self.authRepository = authRepository - self.tokenRepository = tokenRepository - self.userDefaultsRepository = userDefaultsRepository - } - - public func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { - return authRepository.signUpWithKakao(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) - .flatMap { response -> Observable in - let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) - - switch (saveAccess, saveRefresh) { - case (.success, .success): - return savePlatform.andThen(Observable.just(response)) - default: - return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) - } - } - .catch { error in - Observable.error(error) - } - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialLoginUseCaseImpl.swift similarity index 77% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialLoginUseCaseImpl.swift index 8ddc4b4a..a618f57c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/LoginWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialLoginUseCaseImpl.swift @@ -2,7 +2,7 @@ import MLSAuthFeatureInterface import RxSwift -public class LoginWithAppleUseCaseImpl: LoginWithAppleUseCase { +public final class SocialLoginUseCaseImpl: SocialLoginUseCase { private let authRepository: AuthAPIRepository private let tokenRepository: TokenRepository private let userDefaultsRepository: UserDefaultsRepository @@ -17,12 +17,20 @@ public class LoginWithAppleUseCaseImpl: LoginWithAppleUseCase { self.userDefaultsRepository = userDefaultsRepository } - public func execute(credential: Credential) -> Observable { - return authRepository.loginWithApple(credential: credential) + public func execute(credential: Credential, platform: LoginPlatform) -> Observable { + let loginObservable: Observable + switch platform { + case .apple: + loginObservable = authRepository.loginWithApple(credential: credential) + case .kakao: + loginObservable = authRepository.loginWithKakao(credential: credential) + } + + return loginObservable .flatMap { response -> Observable in let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: platform) guard case (.success, .success) = (saveAccess, saveRefresh) else { return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithAppleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialSignUpUseCaseImpl.swift similarity index 64% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithAppleUseCaseImpl.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialSignUpUseCaseImpl.swift index 247482a3..78eadfaa 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SignUpWithAppleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/SocialSignUpUseCaseImpl.swift @@ -2,7 +2,7 @@ import MLSAuthFeatureInterface import RxSwift -public final class SignUpWithAppleUseCaseImpl: SignUpWithAppleUseCase { +public final class SocialSignUpUseCaseImpl: SocialSignUpUseCase { private let authRepository: AuthAPIRepository private let tokenRepository: TokenRepository private let userDefaultsRepository: UserDefaultsRepository @@ -17,12 +17,20 @@ public final class SignUpWithAppleUseCaseImpl: SignUpWithAppleUseCase { self.userDefaultsRepository = userDefaultsRepository } - public func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { - return authRepository.signUpWithApple(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) + public func execute(credential: Credential, platform: LoginPlatform, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + let signUpObservable: Observable + switch platform { + case .apple: + signUpObservable = authRepository.signUpWithApple(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) + case .kakao: + signUpObservable = authRepository.signUpWithKakao(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) + } + + return signUpObservable .flatMap { response -> Observable in let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: platform) switch (saveAccess, saveRefresh) { case (.success, .success): diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift deleted file mode 100644 index 575ae504..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithAppleUseCase.swift +++ /dev/null @@ -1,7 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public protocol LoginWithAppleUseCase { - func execute(credential: Credential) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift deleted file mode 100644 index 91e6a3f0..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/LoginWithKakaoUseCase.swift +++ /dev/null @@ -1,7 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public protocol LoginWithKakaoUseCase { - func execute(credential: Credential) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift deleted file mode 100644 index 5faade72..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithAppleUseCase.swift +++ /dev/null @@ -1,7 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public protocol SignUpWithAppleUseCase { - func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift deleted file mode 100644 index 403e2cb3..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SignUpWithKakaoUseCase.swift +++ /dev/null @@ -1,7 +0,0 @@ -import MLSAuthFeatureInterface - -import RxSwift - -public protocol SignUpWithKakaoUseCase { - func execute(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift new file mode 100644 index 00000000..d8f556bb --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift @@ -0,0 +1,7 @@ +import MLSAuthFeatureInterface + +import RxSwift + +public protocol SocialLoginUseCase { + func execute(credential: Credential, platform: LoginPlatform) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift new file mode 100644 index 00000000..4323b06b --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift @@ -0,0 +1,7 @@ +import MLSAuthFeatureInterface + +import RxSwift + +public protocol SocialSignUpUseCase { + func execute(credential: Credential, platform: LoginPlatform, isMarketingAgreement: Bool, fcmToken: String?) -> Observable +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift index c83348b7..29d87721 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginFactoryImpl.swift @@ -7,29 +7,20 @@ public struct LoginFactoryImpl: LoginFactory { private let termsAgreementsFactory: TermsAgreementFactory private let appleProvider: SocialCredentialProvider private let kakaoProvider: SocialCredentialProvider - private let loginWithAppleUseCase: LoginWithAppleUseCase - private let loginWithKakaoUseCase: LoginWithKakaoUseCase - private let tokenRepository: TokenRepository - private let authRepository: AuthAPIRepository + private let socialLoginUseCase: SocialLoginUseCase private let userDefaultsRepository: UserDefaultsRepository public init( termsAgreementsFactory: TermsAgreementFactory, appleProvider: SocialCredentialProvider, kakaoProvider: SocialCredentialProvider, - loginWithAppleUseCase: LoginWithAppleUseCase, - loginWithKakaoUseCase: LoginWithKakaoUseCase, - tokenRepository: TokenRepository, - authRepository: AuthAPIRepository, + socialLoginUseCase: SocialLoginUseCase, userDefaultsRepository: UserDefaultsRepository ) { self.termsAgreementsFactory = termsAgreementsFactory self.appleProvider = appleProvider self.kakaoProvider = kakaoProvider - self.loginWithAppleUseCase = loginWithAppleUseCase - self.loginWithKakaoUseCase = loginWithKakaoUseCase - self.tokenRepository = tokenRepository - self.authRepository = authRepository + self.socialLoginUseCase = socialLoginUseCase self.userDefaultsRepository = userDefaultsRepository } @@ -40,10 +31,7 @@ public struct LoginFactoryImpl: LoginFactory { viewController.reactor = LoginReactor( appleProvider: appleProvider, kakaoProvider: kakaoProvider, - loginWithAppleUseCase: loginWithAppleUseCase, - loginWithKakaoUseCase: loginWithKakaoUseCase, - tokenRepository: tokenRepository, - authRepository: authRepository, + socialLoginUseCase: socialLoginUseCase, userDefaultsRepository: userDefaultsRepository ) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift index 61c4badd..3da6bf59 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/Login/LoginReactor.swift @@ -1,5 +1,3 @@ -import UserNotifications - import MLSAuthFeatureInterface import ReactorKit @@ -38,28 +36,19 @@ public final class LoginReactor: Reactor { var disposeBag = DisposeBag() private let appleProvider: SocialCredentialProvider private let kakaoProvider: SocialCredentialProvider - private let loginWithAppleUseCase: LoginWithAppleUseCase - private let loginWithKakaoUseCase: LoginWithKakaoUseCase - private let tokenRepository: TokenRepository - private let authRepository: AuthAPIRepository + private let socialLoginUseCase: SocialLoginUseCase private let userDefaultsRepository: UserDefaultsRepository // MARK: - init public init( appleProvider: SocialCredentialProvider, kakaoProvider: SocialCredentialProvider, - loginWithAppleUseCase: LoginWithAppleUseCase, - loginWithKakaoUseCase: LoginWithKakaoUseCase, - tokenRepository: TokenRepository, - authRepository: AuthAPIRepository, + socialLoginUseCase: SocialLoginUseCase, userDefaultsRepository: UserDefaultsRepository ) { self.appleProvider = appleProvider self.kakaoProvider = kakaoProvider - self.loginWithAppleUseCase = loginWithAppleUseCase - self.loginWithKakaoUseCase = loginWithKakaoUseCase - self.tokenRepository = tokenRepository - self.authRepository = authRepository + self.socialLoginUseCase = socialLoginUseCase self.userDefaultsRepository = userDefaultsRepository self.initialState = State() } @@ -71,9 +60,9 @@ public final class LoginReactor: Reactor { return userDefaultsRepository.fetchPlatform() .map { Mutation.setRelogin($0) } case .kakaoLoginButtonTapped: - return handleKakaoLogin() + return handleLogin(provider: kakaoProvider, platform: .kakao) case .appleLoginButtonTapped: - return handleAppleLogin() + return handleLogin(provider: appleProvider, platform: .apple) case .guestLoginButtonTapped: return .just(.navigateTo(route: .home)) case .backButtonTapped: @@ -95,103 +84,24 @@ public final class LoginReactor: Reactor { // MARK: - Methods private extension LoginReactor { - func handleKakaoLogin() -> Observable { - return checkNotificationPermissionAndFetchFCMToken() - .withUnretained(self) - .flatMap { owner, fcmToken in - // 1. 카카오 로그인 자격 증명 가져오기 - owner.kakaoProvider.getCredential() - .flatMap { credential in - // 2. 자격 증명을 바탕으로 로그인 요청 - owner.loginWithKakaoUseCase.execute(credential: credential) - .flatMap { response -> Observable in - if response.isRegister { - // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 - return owner.authRepository.fcmToken(fcmToken: fcmToken) - .andThen(.just(.navigateTo(route: .home))) - } else { - // 4. 미가입 유저면 약관 동의 화면으로 이동 - return .just(.navigateTo(route: .termsAgreements( - credential: credential, - platform: .kakao - ))) - } - } - .catch { error in - // 5. 로그인 실패 예외 처리 - if case AuthError.userNotFound = error { - return .just(.navigateTo(route: .termsAgreements( - credential: credential, - platform: .kakao - ))) - } else { - return .just(.navigateTo(route: .error)) - } - } - } - } - } - - func handleAppleLogin() -> Observable { - return checkNotificationPermissionAndFetchFCMToken() - .withUnretained(self) - .flatMap { owner, fcmToken in - // 1. 애플 로그인 자격 증명 가져오기 - owner.appleProvider.getCredential() - .flatMap { credential in - // 2. 자격 증명을 바탕으로 로그인 요청 - owner.loginWithAppleUseCase.execute(credential: credential) - .flatMap { response -> Observable in - if response.isRegister { - // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 - return owner.authRepository.fcmToken(fcmToken: fcmToken) - .andThen(.just(.navigateTo(route: .home))) - } else { - // 4. 미가입 유저면 약관 동의 화면으로 이동 - return .just(.navigateTo(route: .termsAgreements( - credential: credential, - platform: .apple - ))) - } - } - .catch { error in - // 5. 로그인 실패 예외 처리 - if case AuthError.userNotFound = error { - return .just(.navigateTo(route: .termsAgreements( - credential: credential, - platform: .apple - ))) - } else { - return .just(.navigateTo(route: .error)) - } - } + func handleLogin(provider: SocialCredentialProvider, platform: LoginPlatform) -> Observable { + return provider.getCredential() + .flatMap { [weak self] credential -> Observable in + guard let self else { return .empty() } + return self.socialLoginUseCase.execute(credential: credential, platform: platform) + .map { response -> Mutation in + if response.isRegister { + return .navigateTo(route: .home) + } else { + return .navigateTo(route: .termsAgreements(credential: credential, platform: platform)) + } } - } - } - - func checkNotificationPermissionAndFetchFCMToken() -> Observable { - return Observable.create { [weak self] observer in - guard let self else { - observer.onNext(nil) - observer.onCompleted() - return Disposables.create() - } - - UNUserNotificationCenter.current().getNotificationSettings { settings in - switch settings.authorizationStatus { - case .authorized, .provisional, .ephemeral: - let result = self.tokenRepository.fetchToken(type: .fcmToken) - switch result { - case .success(let token): observer.onNext(token) - case .failure: observer.onNext(nil) + .catch { error in + if case AuthError.userNotFound = error { + return .just(.navigateTo(route: .termsAgreements(credential: credential, platform: platform))) + } + return .just(.navigateTo(route: .error)) } - default: - observer.onNext(nil) - } - observer.onCompleted() } - - return Disposables.create() - } } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift index f6a4e5eb..2fa1374c 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementFactoryImpl.swift @@ -3,20 +3,16 @@ import MLSCore public struct TermsAgreementFactoryImpl: TermsAgreementFactory { private let onBoardingQuestionFactory: OnBoardingQuestionFactory - - private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase - private let signUpWithAppleUseCase: SignUpWithAppleUseCase + private let socialSignUpUseCase: SocialSignUpUseCase private let tokenRepository: TokenRepository public init( onBoardingQuestionFactory: OnBoardingQuestionFactory, - signUpWithKakaoUseCase: SignUpWithKakaoUseCase, - signUpWithAppleUseCase: SignUpWithAppleUseCase, + socialSignUpUseCase: SocialSignUpUseCase, tokenRepository: TokenRepository ) { self.onBoardingQuestionFactory = onBoardingQuestionFactory - self.signUpWithKakaoUseCase = signUpWithKakaoUseCase - self.signUpWithAppleUseCase = signUpWithAppleUseCase + self.socialSignUpUseCase = socialSignUpUseCase self.tokenRepository = tokenRepository } @@ -26,8 +22,7 @@ public struct TermsAgreementFactoryImpl: TermsAgreementFactory { viewController.reactor = TermsAgreementReactor( credential: credential, socialPlatform: platform, - signUpWithKakaoUseCase: signUpWithKakaoUseCase, - signUpWithAppleUseCase: signUpWithAppleUseCase, + socialSignUpUseCase: socialSignUpUseCase, tokenRepository: tokenRepository ) return viewController diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift index 512195fc..5017067f 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/TermsAgreement/TermsAgreementReactor.swift @@ -49,22 +49,19 @@ public final class TermsAgreementReactor: Reactor { var disposeBag = DisposeBag() private let credential: Credential private let socialPlatform: LoginPlatform - private let signUpWithKakaoUseCase: SignUpWithKakaoUseCase - private let signUpWithAppleUseCase: SignUpWithAppleUseCase + private let socialSignUpUseCase: SocialSignUpUseCase private let tokenRepository: TokenRepository // MARK: - init public init( credential: Credential, socialPlatform: LoginPlatform, - signUpWithKakaoUseCase: SignUpWithKakaoUseCase, - signUpWithAppleUseCase: SignUpWithAppleUseCase, + socialSignUpUseCase: SocialSignUpUseCase, tokenRepository: TokenRepository ) { self.credential = credential self.socialPlatform = socialPlatform - self.signUpWithKakaoUseCase = signUpWithKakaoUseCase - self.signUpWithAppleUseCase = signUpWithAppleUseCase + self.socialSignUpUseCase = socialSignUpUseCase self.tokenRepository = tokenRepository self.initialState = State() } @@ -98,19 +95,10 @@ public final class TermsAgreementReactor: Reactor { } }() - switch socialPlatform { - case .kakao: - return signUpWithKakaoUseCase - .execute(credential: credential, isMarketingAgreement: currentState.isMarketingAgree, fcmToken: fcmToken) - .map { _ in .navigateTo(route: .onBoarding) } - .catchAndReturn(.navigateTo(route: .error)) - - case .apple: - return signUpWithAppleUseCase - .execute(credential: credential, isMarketingAgreement: currentState.isMarketingAgree, fcmToken: fcmToken) - .map { _ in .navigateTo(route: .onBoarding) } - .catchAndReturn(.navigateTo(route: .error)) - } + return socialSignUpUseCase + .execute(credential: credential, platform: socialPlatform, isMarketingAgreement: currentState.isMarketingAgree, fcmToken: fcmToken) + .map { _ in .navigateTo(route: .onBoarding) } + .catchAndReturn(.navigateTo(route: .error)) case .navigateTo(let route): return .just(.navigateTo(route: route)) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift index 7abf603a..12e80b02 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift @@ -1,4 +1,4 @@ -public protocol Credential: Encodable { +public protocol Credential { var token: String { get } var providerID: String { get } } diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index 6324c780..792fc9d3 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -53,18 +53,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol ), appleProvider: appleProvider, kakaoProvider: kakaoProvider, - loginWithAppleUseCase: LoginWithAppleUseCaseImpl( + socialLoginUseCase: SocialLoginUseCaseImpl( authRepository: authRepository, tokenRepository: tokenRepository, userDefaultsRepository: userDefaultsRepository ), - loginWithKakaoUseCase: LoginWithKakaoUseCaseImpl( - authRepository: authRepository, - tokenRepository: tokenRepository, - userDefaultsRepository: userDefaultsRepository - ), - tokenRepository: tokenRepository, - authRepository: authRepository, userDefaultsRepository: userDefaultsRepository ) @@ -94,12 +87,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol return TermsAgreementFactoryImpl( onBoardingQuestionFactory: onBoardingQuestionFactory, - signUpWithKakaoUseCase: SignUpWithKakaoUseCaseImpl( - authRepository: authRepository, - tokenRepository: tokenRepository, - userDefaultsRepository: userDefaultsRepository - ), - signUpWithAppleUseCase: SignUpWithAppleUseCaseImpl( + socialSignUpUseCase: SocialSignUpUseCaseImpl( authRepository: authRepository, tokenRepository: tokenRepository, userDefaultsRepository: userDefaultsRepository From 56ad416907a7ee89a01a6d81425f62bbce090805 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sun, 12 Apr 2026 00:15:57 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat/#316:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=ED=9B=84=20=ED=94=BC=EC=B2=98=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLSAuthFeature/Package.swift | 2 +- .../Data/DTOs/AuthResponseDTO.swift | 2 ++ .../MLSAuthFeature/Data/DTOs/JobsDTO.swift | 2 ++ .../Providers/AppleCredentialProvider.swift | 2 +- .../Providers/KakaoCredentialProvider.swift | 2 +- .../CheckEmptyLevelAndRoleUseCaseImpl.swift | 2 ++ .../UseCases/CheckValidLevelUseCaseImpl.swift | 2 ++ .../Entities/KakaoCredential.swift | 11 ----------- .../OnBoardingInputReactor.swift | 2 ++ .../OnBoardingNotificationSheetReactor.swift | 2 ++ .../Entities/Credential.swift} | 4 +--- .../Entities/JobListResponse.swift | 0 .../{Models => Entities}/LoginExitRoute.swift | 0 .../{Models => Entities}/LoginPlatform.swift | 0 .../Entities/LoginResponse.swift | 0 .../Entities/SignUpResponse.swift | 0 .../Errors/AuthError.swift | 2 -- .../Errors/TokenRepositoryError.swift | 0 .../Models/Credential.swift | 4 ---- .../Providers/SocialCredentialProvider.swift | 2 -- .../Repositories/AuthAPIRepository.swift | 2 -- .../Repositories/TokenRepository.swift | 0 .../Repositories/UserDefaultsRepository.swift | 2 -- .../CheckEmptyLevelAndRoleUseCase.swift | 0 .../UseCases/CheckValidLevelUseCase.swift | 0 .../UseCases/SocialLoginUseCase.swift | 2 -- .../UseCases/SocialSignUpUseCase.swift | 2 -- .../Mock/MockAuthAPIRepository.swift | 3 +-- .../Mock/MockSocialCredentialProviders.swift | 19 +++++++++++++++++++ .../Mock/MockTermsAgreementFactory.swift | 1 - .../Mock/MockTokenRepository.swift | 1 - .../Mock/MockUserDefaultsRepository.swift | 1 - MLS/MLSAuthFeatureExample/SceneDelegate.swift | 4 ++-- 33 files changed, 38 insertions(+), 40 deletions(-) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift => MLSAuthFeatureInterface/Entities/Credential.swift} (71%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Entities/JobListResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{Models => Entities}/LoginExitRoute.swift (100%) rename MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/{Models => Entities}/LoginPlatform.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Entities/LoginResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Entities/SignUpResponse.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Errors/AuthError.swift (80%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Errors/TokenRepositoryError.swift (100%) delete mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Providers/SocialCredentialProvider.swift (77%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Repositories/AuthAPIRepository.swift (96%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Repositories/TokenRepository.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/Repositories/UserDefaultsRepository.swift (84%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/UseCases/CheckEmptyLevelAndRoleUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/UseCases/CheckValidLevelUseCase.swift (100%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/UseCases/SocialLoginUseCase.swift (82%) rename MLS/MLSAuthFeature/Sources/{MLSAuthFeature/DomainInterface => MLSAuthFeatureInterface}/UseCases/SocialSignUpUseCase.swift (86%) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialCredentialProviders.swift diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index f2d7b076..18cef2ed 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MLSAuthFeatureTesting", dependencies: [ - "MLSAuthFeature", + "MLSAuthFeatureInterface", .product(name: "RxSwift", package: "RxSwift"), ], swiftSettings: [.swiftLanguageMode(.v5)] diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift index 4257d695..5079deba 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/AuthResponseDTO.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + public struct AuthResponseDTO: Decodable { public let accessToken: String public let refreshToken: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift index 7d457828..42847b79 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/DTOs/JobsDTO.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + public struct JobsDTO: Decodable { public let jobId: Int public let jobName: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift index 8f8beb44..9ea26fdf 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/AppleCredentialProvider.swift @@ -49,7 +49,7 @@ extension AppleCredentialProvider: ASAuthorizationControllerPresentationContextP return } - let credential = AppleCredential(token: idToken, providerID: authCode) + let credential = Credential(token: idToken, providerID: authCode) authServiceResponse.onNext(credential) authServiceResponse.onCompleted() } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift index b8b3ef13..b877ce4d 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Providers/KakaoCredentialProvider.swift @@ -48,7 +48,7 @@ public final class KakaoCredentialProvider: SocialCredentialProvider, @unchecked } let id = user?.id ?? 0 - let credential = KakaoCredential(token: accessToken, providerID: String(id)) + let credential = Credential(token: accessToken, providerID: String(id)) observer.onNext(credential) observer.onCompleted() } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift index 834ca30e..612eea35 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public class CheckEmptyLevelAndRoleUseCaseImpl: CheckEmptyLevelAndRoleUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift index 3d528505..d2f250eb 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import RxSwift public class CheckValidLevelUseCaseImpl: CheckValidLevelUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift deleted file mode 100644 index 4044f742..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/KakaoCredential.swift +++ /dev/null @@ -1,11 +0,0 @@ -import MLSAuthFeatureInterface - -public struct KakaoCredential: Credential { - public let token: String - public let providerID: String - - public init(token: String, providerID: String) { - self.token = token - self.providerID = providerID - } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift index 71b164f6..d8b069fe 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift @@ -1,3 +1,5 @@ +import MLSAuthFeatureInterface + import ReactorKit import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift index 192d0b47..0220359d 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift @@ -1,6 +1,8 @@ import UIKit import UserNotifications +import MLSAuthFeatureInterface + import ReactorKit import RxCocoa import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/Credential.swift similarity index 71% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/Credential.swift index 027b7d06..b5963583 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/AppleCredential.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/Credential.swift @@ -1,6 +1,4 @@ -import MLSAuthFeatureInterface - -public struct AppleCredential: Credential { +public struct Credential { public let token: String public let providerID: String diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/JobListResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/JobListResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/JobListResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/JobListResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginExitRoute.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginExitRoute.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginExitRoute.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginPlatform.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginPlatform.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/LoginPlatform.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginPlatform.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/LoginResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/LoginResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/LoginResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/SignUpResponse.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/SignUpResponse.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Entities/SignUpResponse.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Entities/SignUpResponse.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/AuthError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Errors/AuthError.swift similarity index 80% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/AuthError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Errors/AuthError.swift index ee9acb40..abc34978 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/AuthError.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Errors/AuthError.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - public enum AuthError: Error { case unknown(message: String) case userNotFound(credential: Credential) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/TokenRepositoryError.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Errors/TokenRepositoryError.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Errors/TokenRepositoryError.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Errors/TokenRepositoryError.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift deleted file mode 100644 index 12e80b02..00000000 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Models/Credential.swift +++ /dev/null @@ -1,4 +0,0 @@ -public protocol Credential { - var token: String { get } - var providerID: String { get } -} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Providers/SocialCredentialProvider.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Providers/SocialCredentialProvider.swift similarity index 77% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Providers/SocialCredentialProvider.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Providers/SocialCredentialProvider.swift index fb6ef6e2..699ce075 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Providers/SocialCredentialProvider.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Providers/SocialCredentialProvider.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - import RxSwift public protocol SocialCredentialProvider { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/AuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/AuthAPIRepository.swift similarity index 96% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/AuthAPIRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/AuthAPIRepository.swift index 1dc3cc29..0be26ed2 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/AuthAPIRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/AuthAPIRepository.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - import RxSwift public protocol AuthAPIRepository { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/TokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/TokenRepository.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/TokenRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/TokenRepository.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/UserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/UserDefaultsRepository.swift similarity index 84% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/UserDefaultsRepository.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/UserDefaultsRepository.swift index 9fdd6081..42cea260 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/Repositories/UserDefaultsRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/Repositories/UserDefaultsRepository.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - import RxSwift public protocol UserDefaultsRepository { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift similarity index 100% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/CheckValidLevelUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialLoginUseCase.swift similarity index 82% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialLoginUseCase.swift index d8f556bb..64ba6b85 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialLoginUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialLoginUseCase.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - import RxSwift public protocol SocialLoginUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialSignUpUseCase.swift similarity index 86% rename from MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift rename to MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialSignUpUseCase.swift index 4323b06b..0f0932d9 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/DomainInterface/UseCases/SocialSignUpUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/SocialSignUpUseCase.swift @@ -1,5 +1,3 @@ -import MLSAuthFeatureInterface - import RxSwift public protocol SocialSignUpUseCase { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift index f8d65242..58035624 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift @@ -1,4 +1,3 @@ -import MLSAuthFeature import MLSAuthFeatureInterface import RxSwift @@ -8,7 +7,7 @@ public final class MockAuthAPIRepository: AuthAPIRepository { public init() {} public func loginWithKakao(credential: Credential) -> Observable { - return .just(LoginResponse(isRegister: true, accessToken: "mock_access", refreshToken: "mock_refresh")) + return .just(LoginResponse(isRegister: false, accessToken: "mock_access", refreshToken: "mock_refresh")) } public func loginWithApple(credential: Credential) -> Observable { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialCredentialProviders.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialCredentialProviders.swift new file mode 100644 index 00000000..9cd1e2dd --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialCredentialProviders.swift @@ -0,0 +1,19 @@ +import MLSAuthFeatureInterface + +import RxSwift + +public final class MockKakaoCredentialProvider: SocialCredentialProvider { + public init() {} + + public func getCredential() -> Observable { + return .just(Credential(token: "mock_kakao_token", providerID: "mock_kakao_provider_id")) + } +} + +public final class MockAppleCredentialProvider: SocialCredentialProvider { + public init() {} + + public func getCredential() -> Observable { + return .just(Credential(token: "mock_apple_token", providerID: "mock_apple_provider_id")) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift index 416f80ac..f4cb9335 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTermsAgreementFactory.swift @@ -1,4 +1,3 @@ -import MLSAuthFeature import MLSAuthFeatureInterface import MLSCore diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift index ae98ca4c..2b529333 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockTokenRepository.swift @@ -1,4 +1,3 @@ -import MLSAuthFeature import MLSAuthFeatureInterface public final class MockTokenRepository: TokenRepository { diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift index 1b1bbc7f..6b8c1896 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockUserDefaultsRepository.swift @@ -1,4 +1,3 @@ -import MLSAuthFeature import MLSAuthFeatureInterface import RxSwift diff --git a/MLS/MLSAuthFeatureExample/SceneDelegate.swift b/MLS/MLSAuthFeatureExample/SceneDelegate.swift index 792fc9d3..0e253ecd 100644 --- a/MLS/MLSAuthFeatureExample/SceneDelegate.swift +++ b/MLS/MLSAuthFeatureExample/SceneDelegate.swift @@ -42,8 +42,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AppCoordinatorProtocol let userDefaultsRepository = MockUserDefaultsRepository() let authRepository = MockAuthAPIRepository() - let appleProvider = AppleCredentialProvider() - let kakaoProvider = KakaoCredentialProvider() + let appleProvider = MockAppleCredentialProvider() + let kakaoProvider = MockKakaoCredentialProvider() let factory = LoginFactoryImpl( termsAgreementsFactory: makeTermsAgreementFactory( From 519008c48eb51090164e889c1afca6a55ca24ddc Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sun, 12 Apr 2026 01:08:04 +0900 Subject: [PATCH 13/16] =?UTF-8?q?test/#316:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9C=A0=EC=A6=88=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=98=B5=EC=A0=80=EB=B2=84?= =?UTF-8?q?=EB=B8=94=20=EB=9E=A9=ED=95=91=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLSAuthFeature/Package.swift | 4 +- .../CheckEmptyLevelAndRoleUseCaseImpl.swift | 6 +- .../UseCases/CheckValidLevelUseCaseImpl.swift | 8 +- .../OnBoardingInputReactor.swift | 20 ++- .../CheckEmptyLevelAndRoleUseCase.swift | 4 +- .../UseCases/CheckValidLevelUseCase.swift | 4 +- .../Mock/Credential+Mock.swift | 5 + .../FCMFailingMockAuthAPIRepository.swift | 23 ++++ .../Mock/FailingMockTokenRepository.swift | 17 +++ .../Mock/LoginResponse+Mock.swift | 6 + .../Mock/MockSocialLoginUseCase.swift | 18 +++ .../Mock/MockSocialSignUpUseCase.swift | 18 +++ .../CheckUseCaseTests.swift | 53 ++++++++ .../LoginReactorTests.swift | 120 ++++++++++++++++++ .../MLSAuthFeatureTests.swift | 6 - .../SocialLoginUseCaseTests.swift | 78 ++++++++++++ .../TermsAgreementReactorTests.swift | 103 +++++++++++++++ 17 files changed, 460 insertions(+), 33 deletions(-) create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/Credential+Mock.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FCMFailingMockAuthAPIRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FailingMockTokenRepository.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/LoginResponse+Mock.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialLoginUseCase.swift create mode 100644 MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialSignUpUseCase.swift create mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/CheckUseCaseTests.swift create mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift delete mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift create mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift create mode 100644 MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index 18cef2ed..97582d43 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -75,7 +75,9 @@ let package = Package( "MLSAuthFeature", "MLSAuthFeatureInterface", "MLSAuthFeatureTesting", - ] + .product(name: "RxBlocking", package: "RxSwift"), + ], + swiftSettings: [.swiftLanguageMode(.v5)] ), ] ) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift index 612eea35..2cfc7b7b 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckEmptyLevelAndRoleUseCaseImpl.swift @@ -1,13 +1,11 @@ import MLSAuthFeatureInterface -import RxSwift - public class CheckEmptyLevelAndRoleUseCaseImpl: CheckEmptyLevelAndRoleUseCase { public init() {} - public func execute(level: Int?, job: String?) -> Observable { + public func execute(level: Int?, job: String?) -> Bool { let isValidLevel = level.map { (1 ... 200).contains($0) } ?? false let isValidRole = job != nil && job != "" - return .just(isValidLevel && isValidRole) + return isValidLevel && isValidRole } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift index d2f250eb..0a8c7471 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Domain/UseCases/CheckValidLevelUseCaseImpl.swift @@ -1,12 +1,10 @@ import MLSAuthFeatureInterface -import RxSwift - public class CheckValidLevelUseCaseImpl: CheckValidLevelUseCase { public init() {} - public func execute(level: Int?) -> Observable { - guard let level else { return .just(nil) } - return .just((1 ... 200).contains(level)) + public func execute(level: Int?) -> Bool? { + guard let level else { return nil } + return (1 ... 200).contains(level) } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift index d8b069fe..547afdd3 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingInput/OnBoardingInputReactor.swift @@ -77,18 +77,16 @@ public final class OnBoardingInputReactor: Reactor { case .nextButtonTapped: return Observable.just(.navigateTo(route: .notification)) case .inputLevel(let level): - let changeLevel = Observable.just(Mutation.setLevel(level)) - let validateJob = checkEmptyUseCase.execute(level: level, job: currentState.job?.name) - .map(Mutation.setButtonEnabled) - let validateLevel = checkValidLevelUseCase.execute(level: level) - .map(Mutation.setLevelValid) - return .merge(changeLevel, validateJob, validateLevel) + let isButtonEnabled = checkEmptyUseCase.execute(level: level, job: currentState.job?.name) + let isLevelValid = checkValidLevelUseCase.execute(level: level) + return .of( + .setLevel(level), + .setButtonEnabled(isButtonEnabled), + .setLevelValid(isLevelValid) + ) case .inputRole(let job): - return checkEmptyUseCase.execute(level: currentState.level, job: job?.name) - .map { isValid in - [.setRole(job), .setButtonEnabled(isValid)] - } - .flatMap { Observable.from($0) } + let isButtonEnabled = checkEmptyUseCase.execute(level: currentState.level, job: job?.name) + return .of(.setRole(job), .setButtonEnabled(isButtonEnabled)) } } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift index 83646065..8bb00642 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckEmptyLevelAndRoleUseCase.swift @@ -1,5 +1,3 @@ -import RxSwift - public protocol CheckEmptyLevelAndRoleUseCase { - func execute(level: Int?, job: String?) -> Observable + func execute(level: Int?, job: String?) -> Bool } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift index d7c90322..b02d0866 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureInterface/UseCases/CheckValidLevelUseCase.swift @@ -1,5 +1,3 @@ -import RxSwift - public protocol CheckValidLevelUseCase { - func execute(level: Int?) -> Observable + func execute(level: Int?) -> Bool? } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/Credential+Mock.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/Credential+Mock.swift new file mode 100644 index 00000000..bf29cf5f --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/Credential+Mock.swift @@ -0,0 +1,5 @@ +import MLSAuthFeatureInterface + +public extension Credential { + static let mock = Credential(token: "test_token", providerID: "test_provider_id") +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FCMFailingMockAuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FCMFailingMockAuthAPIRepository.swift new file mode 100644 index 00000000..3feca629 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FCMFailingMockAuthAPIRepository.swift @@ -0,0 +1,23 @@ +import MLSAuthFeatureInterface + +import RxSwift + +/// FCM 토큰 등록만 실패하는 Mock. 나머지는 MockAuthAPIRepository에 위임. +public final class FCMFailingMockAuthAPIRepository: AuthAPIRepository { + private let base = MockAuthAPIRepository() + + public init() {} + + public func loginWithKakao(credential: Credential) -> Observable { base.loginWithKakao(credential: credential) } + public func loginWithApple(credential: Credential) -> Observable { base.loginWithApple(credential: credential) } + public func signUpWithKakao(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { base.signUpWithKakao(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) } + public func signUpWithApple(credential: Credential, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { base.signUpWithApple(credential: credential, isMarketingAgreement: isMarketingAgreement, fcmToken: fcmToken) } + public func withdraw() -> Completable { base.withdraw() } + public func fetchJobList() -> Observable { base.fetchJobList() } + public func updateUserInfo(level: Int, selectedJobID: Int) -> Completable { base.updateUserInfo(level: level, selectedJobID: selectedJobID) } + public func reissueToken(refreshToken: String) -> Observable { base.reissueToken(refreshToken: refreshToken) } + public func fcmToken(fcmToken: String?) -> Completable { .error(FCMError.failed) } + public func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable { base.updateNotificationAgreement(noticeAgreement: noticeAgreement, patchNoteAgreement: patchNoteAgreement, eventAgreement: eventAgreement) } + + private enum FCMError: Error { case failed } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FailingMockTokenRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FailingMockTokenRepository.swift new file mode 100644 index 00000000..477b2626 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/FailingMockTokenRepository.swift @@ -0,0 +1,17 @@ +import MLSAuthFeatureInterface + +public final class FailingMockTokenRepository: TokenRepository { + public init() {} + + public func fetchToken(type: TokenType) -> Result { + .failure(TokenRepositoryError.noValueFound(message: "")) + } + + public func saveToken(type: TokenType, value: String) -> Result { + .failure(TokenRepositoryError.dataConversionError(message: "forced failure")) + } + + public func deleteToken(type: TokenType) -> Result { + .failure(TokenRepositoryError.noValueFound(message: "")) + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/LoginResponse+Mock.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/LoginResponse+Mock.swift new file mode 100644 index 00000000..7b25f1ab --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/LoginResponse+Mock.swift @@ -0,0 +1,6 @@ +import MLSAuthFeatureInterface + +public extension LoginResponse { + static let registered = LoginResponse(isRegister: true, accessToken: "", refreshToken: "") + static let unregistered = LoginResponse(isRegister: false, accessToken: "", refreshToken: "") +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialLoginUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialLoginUseCase.swift new file mode 100644 index 00000000..339ea8a2 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialLoginUseCase.swift @@ -0,0 +1,18 @@ +import MLSAuthFeatureInterface + +import RxSwift + +public final class MockSocialLoginUseCase: SocialLoginUseCase { + private let result: Result + + public init(result: Result) { + self.result = result + } + + public func execute(credential: Credential, platform: LoginPlatform) -> Observable { + switch result { + case .success(let response): return .just(response) + case .failure(let error): return .error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialSignUpUseCase.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialSignUpUseCase.swift new file mode 100644 index 00000000..23335fc3 --- /dev/null +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockSocialSignUpUseCase.swift @@ -0,0 +1,18 @@ +import MLSAuthFeatureInterface + +import RxSwift + +public final class MockSocialSignUpUseCase: SocialSignUpUseCase { + private let result: Result + + public init(result: Result = .success(SignUpResponse(accessToken: "mock", refreshToken: "mock"))) { + self.result = result + } + + public func execute(credential: Credential, platform: LoginPlatform, isMarketingAgreement: Bool, fcmToken: String?) -> Observable { + switch result { + case .success(let response): return .just(response) + case .failure(let error): return .error(error) + } + } +} diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/CheckUseCaseTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/CheckUseCaseTests.swift new file mode 100644 index 00000000..39663fe2 --- /dev/null +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/CheckUseCaseTests.swift @@ -0,0 +1,53 @@ +import Testing + +@testable import MLSAuthFeature + +@Suite("CheckValidLevelUseCase") +struct CheckValidLevelUseCaseTests { + private let sut = CheckValidLevelUseCaseImpl() + + @Test("유효 레벨(1·100·200): true 반환", arguments: [1, 100, 200]) + func validLevel_returnsTrue(level: Int) { + #expect(sut.execute(level: level) == true) + } + + @Test("범위 외 레벨(0·201·-1): false 반환", arguments: [0, 201, -1]) + func outOfRangeLevel_returnsFalse(level: Int) { + #expect(sut.execute(level: level) == false) + } + + @Test("nil 레벨: nil 반환") + func nilLevel_returnsNil() { + #expect(sut.execute(level: nil) == nil) + } +} + +@Suite("CheckEmptyLevelAndRoleUseCase") +struct CheckEmptyLevelAndRoleUseCaseTests { + private let sut = CheckEmptyLevelAndRoleUseCaseImpl() + + @Test("레벨·직업 모두 유효: true 반환") + func bothValid_returnsTrue() { + #expect(sut.execute(level: 50, job: "전사") == true) + } + + @Test("레벨 nil: false 반환") + func nilLevel_returnsFalse() { + #expect(sut.execute(level: nil, job: "전사") == false) + } + + @Test("레벨 범위 초과(0): false 반환") + func outOfRangeLevel_returnsFalse() { + #expect(sut.execute(level: 0, job: "전사") == false) + } + + @Test("직업 nil: false 반환") + func nilJob_returnsFalse() { + #expect(sut.execute(level: 50, job: nil) == false) + } + + @Test("직업 빈 문자열: false 반환") + func emptyJob_returnsFalse() { + #expect(sut.execute(level: 50, job: "") == false) + } +} diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift new file mode 100644 index 00000000..78502581 --- /dev/null +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift @@ -0,0 +1,120 @@ +import Testing + +@testable import MLSAuthFeature +import MLSAuthFeatureInterface +import MLSAuthFeatureTesting + +import RxBlocking +import RxSwift + +@Suite("LoginReactor - 로그인 라우팅") +struct LoginReactorTests { + + // MARK: - Apple 로그인 + + @Test("Apple 로그인 + isRegister true: 홈으로 이동") + func appleLogin_registeredUser_navigatesToHome() throws { + let reactor = makeSUT(response: .registered) + let mutation = try reactor.mutate(action: .appleLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .home = state.route {} else { + Issue.record("Expected .home, got \(state.route)") + } + } + + @Test("Apple 로그인 + isRegister false: 약관 동의 화면으로 이동") + func appleLogin_unregisteredUser_navigatesToTerms() throws { + let reactor = makeSUT(response: .unregistered) + let mutation = try reactor.mutate(action: .appleLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .termsAgreements = state.route {} else { + Issue.record("Expected .termsAgreements, got \(state.route)") + } + } + + // MARK: - Kakao 로그인 + + @Test("Kakao 로그인 + isRegister false: 약관 동의 화면으로 이동") + func kakaoLogin_unregisteredUser_navigatesToTerms() throws { + let reactor = makeSUT(response: .unregistered) + let mutation = try reactor.mutate(action: .kakaoLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .termsAgreements = state.route {} else { + Issue.record("Expected .termsAgreements, got \(state.route)") + } + } + + // MARK: - 에러 라우팅 + + @Test("userNotFound 에러: 약관 동의 화면으로 이동") + func userNotFound_navigatesToTerms() throws { + let reactor = makeSUT(error: AuthError.userNotFound(credential: Credential(token: "", providerID: ""))) + let mutation = try reactor.mutate(action: .appleLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .termsAgreements = state.route {} else { + Issue.record("Expected .termsAgreements on userNotFound, got \(state.route)") + } + } + + @Test("그 외 에러: 에러 화면으로 이동") + func otherError_navigatesToError() throws { + let reactor = makeSUT(error: AuthError.tokenExpired) + let mutation = try reactor.mutate(action: .appleLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .error = state.route {} else { + Issue.record("Expected .error, got \(state.route)") + } + } + + // MARK: - 기타 액션 + + @Test("게스트 로그인: 홈으로 이동") + func guestLogin_navigatesToHome() throws { + let reactor = makeSUT() + let mutation = try reactor.mutate(action: .guestLoginButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .home = state.route {} else { + Issue.record("Expected .home, got \(state.route)") + } + } + + @Test("뒤로가기: dismiss") + func backButton_navigatesToDismiss() throws { + let reactor = makeSUT() + let mutation = try reactor.mutate(action: .backButtonTapped).toBlocking().first()! + let state = reactor.reduce(state: reactor.initialState, mutation: mutation) + + if case .dismiss = state.route {} else { + Issue.record("Expected .dismiss, got \(state.route)") + } + } +} + +// MARK: - Helpers + +private func makeSUT( + response: LoginResponse = .registered +) -> LoginReactor { + LoginReactor( + appleProvider: MockAppleCredentialProvider(), + kakaoProvider: MockKakaoCredentialProvider(), + socialLoginUseCase: MockSocialLoginUseCase(result: .success(response)), + userDefaultsRepository: MockUserDefaultsRepository() + ) +} + +private func makeSUT(error: Error) -> LoginReactor { + LoginReactor( + appleProvider: MockAppleCredentialProvider(), + kakaoProvider: MockKakaoCredentialProvider(), + socialLoginUseCase: MockSocialLoginUseCase(result: .failure(error)), + userDefaultsRepository: MockUserDefaultsRepository() + ) +} + diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift deleted file mode 100644 index ab6a9846..00000000 --- a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/MLSAuthFeatureTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Testing -@testable import MLSAuthFeature - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift new file mode 100644 index 00000000..efd86a1b --- /dev/null +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift @@ -0,0 +1,78 @@ +import Testing + +@testable import MLSAuthFeature +import MLSAuthFeatureInterface +import MLSAuthFeatureTesting + +import RxBlocking +import RxSwift + +@Suite("SocialLoginUseCase") +struct SocialLoginUseCaseTests { + + // MARK: - 플랫폼 저장 + + @Test("Apple 로그인 성공: UserDefaults에 .apple 저장") + func appleLogin_savesApplePlatform() throws { + let userDefaultsRepo = MockUserDefaultsRepository() + let sut = makeSUT(userDefaultsRepository: userDefaultsRepo) + + _ = try sut.execute(credential: .mock, platform: .apple).toBlocking().first() + + let saved = try userDefaultsRepo.fetchPlatform().toBlocking().first() + #expect(saved == .apple) + } + + @Test("Kakao 로그인 성공: UserDefaults에 .kakao 저장") + func kakaoLogin_savesKakaoPlatform() throws { + let userDefaultsRepo = MockUserDefaultsRepository() + let sut = makeSUT(userDefaultsRepository: userDefaultsRepo) + + _ = try sut.execute(credential: .mock, platform: .kakao).toBlocking().first() + + let saved = try userDefaultsRepo.fetchPlatform().toBlocking().first() + #expect(saved == .kakao) + } + + // MARK: - 토큰 저장 실패 + + @Test("토큰 저장 실패: 에러 전파") + func tokenSaveFailure_propagatesError() { + let sut = makeSUT(tokenRepository: FailingMockTokenRepository()) + + #expect(throws: (any Error).self) { + _ = try sut.execute(credential: .mock, platform: .apple).toBlocking().first() + } + } + + // MARK: - FCM 등록 실패 + + @Test("FCM 등록 실패: 로그인 결과는 정상 반환") + func fcmFailure_doesNotBlockLoginResult() throws { + let tokenRepo = MockTokenRepository() + _ = tokenRepo.saveToken(type: .fcmToken, value: "fcm_token") + + let sut = makeSUT( + authRepository: FCMFailingMockAuthAPIRepository(), + tokenRepository: tokenRepo + ) + + let result = try sut.execute(credential: .mock, platform: .apple).toBlocking().first() + #expect(result != nil) + } +} + +// MARK: - Helpers + +private func makeSUT( + authRepository: AuthAPIRepository = MockAuthAPIRepository(), + tokenRepository: TokenRepository = MockTokenRepository(), + userDefaultsRepository: UserDefaultsRepository = MockUserDefaultsRepository() +) -> SocialLoginUseCaseImpl { + SocialLoginUseCaseImpl( + authRepository: authRepository, + tokenRepository: tokenRepository, + userDefaultsRepository: userDefaultsRepository + ) +} + diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift new file mode 100644 index 00000000..bb54bc9d --- /dev/null +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift @@ -0,0 +1,103 @@ +import Testing + +@testable import MLSAuthFeature +import MLSAuthFeatureInterface +import MLSAuthFeatureTesting + +import RxSwift + +@Suite("TermsAgreementReactor - 동의 상태 관리") +struct TermsAgreementReactorTests { + + // MARK: - 전체 동의 토글 + + @Test("전체동의 ON: 모든 항목 true") + func toggleTotalOn_setsAllAgreementsTrue() { + let reactor = makeSUT() + let state = reactor.reduce(state: reactor.initialState, mutation: .setAgreeState(type: .total, isOn: true)) + + #expect(state.isTotalAgree == true) + #expect(state.isAgeAgree == true) + #expect(state.isServiceTermsAgree == true) + #expect(state.isPersonalInformationAgree == true) + #expect(state.isMarketingAgree == true) + } + + @Test("전체동의 OFF: 모든 항목 false") + func toggleTotalOff_setsAllAgreementsFalse() { + let reactor = makeSUT() + var state = reactor.reduce(state: reactor.initialState, mutation: .setAgreeState(type: .total, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .total, isOn: false)) + + #expect(state.isTotalAgree == false) + #expect(state.isAgeAgree == false) + #expect(state.isServiceTermsAgree == false) + #expect(state.isPersonalInformationAgree == false) + #expect(state.isMarketingAgree == false) + } + + // MARK: - bottomButton 활성화 + + @Test("필수 3개(나이·서비스·개인정보) 동의: bottomButton 활성화") + func requiredFieldsAgreed_enablesBottomButton() { + let reactor = makeSUT() + var state = reactor.initialState + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .age, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .serviceTerms, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .personalInfo, isOn: true)) + + #expect(state.bottomButtonIsEnabled == true) + } + + @Test("마케팅만 동의: bottomButton 비활성화") + func onlyMarketingAgreed_disablesBottomButton() { + let reactor = makeSUT() + let state = reactor.reduce(state: reactor.initialState, mutation: .setAgreeState(type: .marketing, isOn: true)) + + #expect(state.bottomButtonIsEnabled == false) + } + + @Test("필수 항목 하나 누락: bottomButton 비활성화") + func missingOneRequired_disablesBottomButton() { + let reactor = makeSUT() + var state = reactor.initialState + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .age, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .serviceTerms, isOn: true)) + // personalInfo 미동의 + + #expect(state.bottomButtonIsEnabled == false) + } + + // MARK: - isTotalAgree 조건 + + @Test("필수 3개만 동의: isTotalAgree false") + func requiredOnlyAgreed_isTotalAgreeFalse() { + let reactor = makeSUT() + var state = reactor.initialState + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .age, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .serviceTerms, isOn: true)) + state = reactor.reduce(state: state, mutation: .setAgreeState(type: .personalInfo, isOn: true)) + + #expect(state.isTotalAgree == false) + } + + @Test("필수 3개 + 마케팅 동의: isTotalAgree true") + func allIncludingMarketing_isTotalAgreeTrue() { + let reactor = makeSUT() + let state = reactor.reduce(state: reactor.initialState, mutation: .setAgreeState(type: .total, isOn: true)) + + #expect(state.isTotalAgree == true) + } +} + +// MARK: - Helpers + +private func makeSUT() -> TermsAgreementReactor { + TermsAgreementReactor( + credential: Credential(token: "", providerID: ""), + socialPlatform: .kakao, + socialSignUpUseCase: MockSocialSignUpUseCase(), + tokenRepository: MockTokenRepository() + ) +} + From 5e8467567078d309f29f6a97192e50e4c14241c3 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 13 Apr 2026 21:45:04 +0900 Subject: [PATCH 14/16] =?UTF-8?q?refactor/#316:=20=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B7=B0=EC=BB=A8=20=EB=A1=9C=EA=B9=85=20=EB=A1=9C?= =?UTF-8?q?=EA=B1=B0=EB=B8=94=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MLSCore/BaseController/BaseViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift b/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift index b39b28bc..d8f66210 100644 --- a/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift +++ b/MLS/MLSCore/Sources/MLSCore/BaseController/BaseViewController.swift @@ -4,14 +4,14 @@ import UIKit import RxKeyboard import RxSwift -open class BaseViewController: UIViewController { +open class BaseViewController: UIViewController, Loggable { private let disposeBag = DisposeBag() public var isBottomTabbarHidden: Bool = false public init() { super.init(nibName: nil, bundle: nil) - os_log("➕init: \(String(describing: self))") + logDebug("init \(String(describing: self))") } @available(*, unavailable) @@ -20,7 +20,7 @@ open class BaseViewController: UIViewController { } deinit { - os_log("➖deinit: \(String(describing: self))") + logDebug("deinit: \(String(describing: self))") } } From c4ade97906c5ac58fa6aca989d10985396c95ed0 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 13 Apr 2026 21:45:24 +0900 Subject: [PATCH 15/16] =?UTF-8?q?feat/#316:=20=EC=98=88=EC=8B=9C=EC=95=B1?= =?UTF-8?q?=20=ED=8F=B0=ED=8A=B8=20=EB=93=B1=EB=A1=9D=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLSAuthFeatureExample/AppDelegate.swift | 23 +++------------------ 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/MLS/MLSAuthFeatureExample/AppDelegate.swift b/MLS/MLSAuthFeatureExample/AppDelegate.swift index 25efac65..9d61447b 100644 --- a/MLS/MLSAuthFeatureExample/AppDelegate.swift +++ b/MLS/MLSAuthFeatureExample/AppDelegate.swift @@ -1,36 +1,19 @@ -// -// AppDelegate.swift -// MLSAuthFeatureExample -// -// Created by SeoJunYoung on 4/8/26. -// - import UIKit +import MLSDesignSystem + @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + FontManager.registerFonts() return true } - // MARK: UISceneSession Lifecycle - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - } From 8a954ca01eb2f91c7ed5166f0db993116e962d64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Apr 2026 13:01:12 +0000 Subject: [PATCH 16/16] style/#316: Apply SwiftLint autocorrect --- MLS/MLSAuthFeature/Package.swift | 14 +++++++------- .../Data/Endpoints/AuthEndPoint.swift | 2 +- ...OnBoardingNotificationSheetViewController.swift | 1 - .../Mock/MockAuthAPIRepository.swift | 2 +- .../MLSAuthFeatureTests/LoginReactorTests.swift | 1 - .../SocialLoginUseCaseTests.swift | 1 - .../TermsAgreementReactorTests.swift | 1 - MLS/MLSAuthFeatureExample/AppDelegate.swift | 1 - MLS/MLSCore/Package.swift | 4 ++-- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/MLS/MLSAuthFeature/Package.swift b/MLS/MLSAuthFeature/Package.swift index 97582d43..1e1db184 100644 --- a/MLS/MLSAuthFeature/Package.swift +++ b/MLS/MLSAuthFeature/Package.swift @@ -19,7 +19,7 @@ let package = Package( .library( name: "MLSAuthFeatureTesting", targets: ["MLSAuthFeatureTesting"] - ), + ) ], dependencies: [ .package(path: "../MLSCore"), @@ -28,7 +28,7 @@ let package = Package( .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.22.0"), .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"), .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0"), - .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"), + .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1") ], targets: [ // Interface 모듈 (Presentation 팩토리 프로토콜) @@ -37,7 +37,7 @@ let package = Package( dependencies: [ .product(name: "MLSCore", package: "MLSCore"), .product(name: "MLSDesignSystem", package: "MLSDesignSystem"), - .product(name: "RxSwift", package: "RxSwift"), + .product(name: "RxSwift", package: "RxSwift") ], swiftSettings: [.swiftLanguageMode(.v5)] ), @@ -55,7 +55,7 @@ let package = Package( .product(name: "RxKeyboard", package: "RxKeyboard"), .product(name: "KakaoSDKAuth", package: "kakao-ios-sdk"), .product(name: "KakaoSDKUser", package: "kakao-ios-sdk"), - .product(name: "SnapKit", package: "SnapKit"), + .product(name: "SnapKit", package: "SnapKit") ], swiftSettings: [.swiftLanguageMode(.v5)] ), @@ -64,7 +64,7 @@ let package = Package( name: "MLSAuthFeatureTesting", dependencies: [ "MLSAuthFeatureInterface", - .product(name: "RxSwift", package: "RxSwift"), + .product(name: "RxSwift", package: "RxSwift") ], swiftSettings: [.swiftLanguageMode(.v5)] ), @@ -75,9 +75,9 @@ let package = Package( "MLSAuthFeature", "MLSAuthFeatureInterface", "MLSAuthFeatureTesting", - .product(name: "RxBlocking", package: "RxSwift"), + .product(name: "RxBlocking", package: "RxSwift") ], swiftSettings: [.swiftLanguageMode(.v5)] - ), + ) ] ) diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift index c3fd7bc9..1fb0051f 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Data/Endpoints/AuthEndPoint.swift @@ -57,7 +57,7 @@ public enum AuthEndPoint { method: .POST, headers: [ "accept": "*/*", - "refresh-token": refreshToken, + "refresh-token": refreshToken ] ) } diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift index fda492ff..971d5098 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeature/Presentation/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -4,7 +4,6 @@ import MLSAuthFeatureInterface import MLSCore import MLSDesignSystem - import ReactorKit import RxCocoa import RxSwift diff --git a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift index 58035624..911e99e4 100644 --- a/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift +++ b/MLS/MLSAuthFeature/Sources/MLSAuthFeatureTesting/Mock/MockAuthAPIRepository.swift @@ -30,7 +30,7 @@ public final class MockAuthAPIRepository: AuthAPIRepository { Job(name: "마법사", id: 2), Job(name: "궁수", id: 3), Job(name: "도적", id: 4), - Job(name: "해적", id: 5), + Job(name: "해적", id: 5) ])) } diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift index 78502581..2151f5ea 100644 --- a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/LoginReactorTests.swift @@ -117,4 +117,3 @@ private func makeSUT(error: Error) -> LoginReactor { userDefaultsRepository: MockUserDefaultsRepository() ) } - diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift index efd86a1b..20b58508 100644 --- a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/SocialLoginUseCaseTests.swift @@ -75,4 +75,3 @@ private func makeSUT( userDefaultsRepository: userDefaultsRepository ) } - diff --git a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift index bb54bc9d..4819524e 100644 --- a/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift +++ b/MLS/MLSAuthFeature/Tests/MLSAuthFeatureTests/TermsAgreementReactorTests.swift @@ -100,4 +100,3 @@ private func makeSUT() -> TermsAgreementReactor { tokenRepository: MockTokenRepository() ) } - diff --git a/MLS/MLSAuthFeatureExample/AppDelegate.swift b/MLS/MLSAuthFeatureExample/AppDelegate.swift index 9d61447b..55e88a8e 100644 --- a/MLS/MLSAuthFeatureExample/AppDelegate.swift +++ b/MLS/MLSAuthFeatureExample/AppDelegate.swift @@ -16,4 +16,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { } } - diff --git a/MLS/MLSCore/Package.swift b/MLS/MLSCore/Package.swift index d016ff9b..57a153fb 100644 --- a/MLS/MLSCore/Package.swift +++ b/MLS/MLSCore/Package.swift @@ -15,7 +15,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"), - .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0"), + .package(url: "https://github.com/RxSwiftCommunity/RxKeyboard.git", from: "2.0.0") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -26,7 +26,7 @@ let package = Package( .product(name: "RxSwift", package: "RxSwift"), .product(name: "RxCocoa", package: "RxSwift"), .product(name: "RxRelay", package: "RxSwift"), - .product(name: "RxKeyboard", package: "RxKeyboard"), + .product(name: "RxKeyboard", package: "RxKeyboard") ], swiftSettings: [.swiftLanguageMode(.v5)] ),