diff --git a/.tuist-version b/.tuist-version index 3f67e25c..c5b45eb7 100644 --- a/.tuist-version +++ b/.tuist-version @@ -1 +1 @@ -3.17.0 +3.18.0 diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift index f4082e8d..fc7e1c10 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift @@ -5,6 +5,8 @@ public extension TargetDependency { } public extension TargetDependency.SPM { + static let Needle = TargetDependency.external(name: "NeedleFoundation") + static let GAuthSignin = TargetDependency.external(name: "GAuthSignin") static let Nimble = TargetDependency.external(name: "Nimble") static let Quick = TargetDependency.external(name: "Quick") static let Emdpoint = TargetDependency.external(name: "Emdpoint") diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index b376cae8..6296e351 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift @@ -9,6 +9,14 @@ public extension TargetDependency { } public extension TargetDependency.Feature { + static let SigninFeatureInterface = TargetDependency.project( + target: ModulePaths.Feature.SigninFeature.targetName(type: .interface), + path: .relativeToFeature(ModulePaths.Feature.SigninFeature.rawValue) + ) + static let SigninFeature = TargetDependency.project( + target: ModulePaths.Feature.SigninFeature.targetName(type: .sources), + path: .relativeToFeature(ModulePaths.Feature.SigninFeature.rawValue) + ) static let BaseFeature = TargetDependency.project( target: ModulePaths.Feature.BaseFeature.targetName(type: .sources), path: .relativeToFeature(ModulePaths.Feature.BaseFeature.rawValue) diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift index 875a2bbb..e434cd48 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift @@ -10,6 +10,7 @@ public enum ModulePaths { public extension ModulePaths { enum Feature: String { + case SigninFeature case BaseFeature func targetName(type: MicroTargetType) -> String { diff --git a/Projects/App/.swiftlint.yml b/Projects/App/.swiftlint.yml new file mode 100644 index 00000000..8b86bfd3 --- /dev/null +++ b/Projects/App/.swiftlint.yml @@ -0,0 +1,3 @@ +excluded: + - "**/*/NeedleGenerated.swift" + - "Tuist" diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index 4364d88b..b06d76eb 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -23,7 +23,7 @@ let settings: Settings = configurations: configurations, defaultSettings: .recommended) -let scripts: [TargetScript] = isCI ? [] : [.swiftLint] +let scripts: [TargetScript] = isCI ? [] : [.swiftLint, .needle] let targets: [Target] = [ .init( @@ -36,7 +36,12 @@ let targets: [Target] = [ sources: ["Sources/**"], resources: ["Resources/**"], scripts: scripts, - dependencies: [], + dependencies: [ + .Feature.SigninFeature, + .Domain.AuthDomain, + .Core.JwtStore, + .Shared.KeychainModule + ], settings: .settings(base: env.baseSetting) ) ] diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift deleted file mode 100644 index 8bf8d764..00000000 --- a/Projects/App/Sources/Application/AppDelegate.swift +++ /dev/null @@ -1,30 +0,0 @@ -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. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } -} diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift new file mode 100644 index 00000000..d432ab15 --- /dev/null +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -0,0 +1,33 @@ +import NeedleFoundation +import SwiftUI +import AuthDomainInterface +import AuthDomain +import SigninFeature +import SigninFeatureInterface +import BaseDomain +import JwtStore +import JwtStoreInterface +import KeychainModule +import KeychainModuleInterface + +final class AppComponent: BootstrapComponent { + func makeRootView() -> some View { + signinBuildable.makeView().eraseToAnyView() + } + + var signinBuildable: any SigninBuildable { + SigninComponent(parent: self) + } + + var authDomainBuildable: any AuthDomainBuildable { + AuthDomainComponent(parent: self) + } + + var jwtStoreBuildable: any JwtStoreBuildable { + JwtStoreComponent(parent: self) + } + + var keychainBuildable: any KeychainBuildable { + KeychainComponent(parent: self) + } +} diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift new file mode 100644 index 00000000..b9a628b6 --- /dev/null +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -0,0 +1,124 @@ + + +import AuthDomain +import AuthDomainInterface +import BaseDomain +import BaseFeature +import JwtStore +import JwtStoreInterface +import KeychainModule +import KeychainModuleInterface +import NeedleFoundation +import SigninFeature +import SigninFeatureInterface +import SwiftUI + +// swiftlint:disable unused_declaration +private let needleDependenciesHash : String? = nil + +// MARK: - Traversal Helpers + +private func parent1(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { + return component.parent +} + +// MARK: - Providers + +#if !NEEDLE_DYNAMIC + +private class JwtStoreDependency5613ee3d4fea5093f6faProvider: JwtStoreDependency { + var keychainBuildable: any KeychainBuildable { + return appComponent.keychainBuildable + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->JwtStoreComponent +private func factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return JwtStoreDependency5613ee3d4fea5093f6faProvider(appComponent: parent1(component) as! AppComponent) +} +private class SigninDependencyde06a9d0b22764487733Provider: SigninDependency { + var authDomainBuildable: any AuthDomainBuildable { + return appComponent.authDomainBuildable + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->SigninComponent +private func factory2882a056d84a613debccf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return SigninDependencyde06a9d0b22764487733Provider(appComponent: parent1(component) as! AppComponent) +} +private class AuthDomainDependency4518b8977185a5c9ff71Provider: AuthDomainDependency { + var jwtStoreBuildable: any JwtStoreBuildable { + return appComponent.jwtStoreBuildable + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->AuthDomainComponent +private func factoryc9b20c320bb79402d4c1f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return AuthDomainDependency4518b8977185a5c9ff71Provider(appComponent: parent1(component) as! AppComponent) +} + +#else +extension JwtStoreComponent: Registration { + public func registerItems() { + keyPathToName[\JwtStoreDependency.keychainBuildable] = "keychainBuildable-any KeychainBuildable" + } +} +extension AppComponent: Registration { + public func registerItems() { + + + } +} +extension KeychainComponent: Registration { + public func registerItems() { + + } +} +extension SigninComponent: Registration { + public func registerItems() { + keyPathToName[\SigninDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" + } +} +extension AuthDomainComponent: Registration { + public func registerItems() { + keyPathToName[\AuthDomainDependency.jwtStoreBuildable] = "jwtStoreBuildable-any JwtStoreBuildable" + } +} + + +#endif + +private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { + return EmptyDependencyProvider(component: component) +} + +// MARK: - Registration +private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { + __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) +} + +#if !NEEDLE_DYNAMIC + +private func register1() { + registerProviderFactory("^->AppComponent->JwtStoreComponent", factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) + registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) + registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->AuthDomainComponent", factoryc9b20c320bb79402d4c1f47b58f8f304c97af4d5) +} +#endif + +public func registerProviderFactories() { +#if !NEEDLE_DYNAMIC + register1() +#endif +} diff --git a/Projects/App/Sources/Application/SMSApp.swift b/Projects/App/Sources/Application/SMSApp.swift new file mode 100644 index 00000000..b31b7c38 --- /dev/null +++ b/Projects/App/Sources/Application/SMSApp.swift @@ -0,0 +1,16 @@ +import BaseFeature +import DesignSystem +import SwiftUI + +@main +struct SMSApp: App { + init() { + registerProviderFactories() + } + + var body: some Scene { + WindowGroup { + AppComponent().makeRootView() + } + } +} diff --git a/Projects/App/Sources/Application/SceneDelegate.swift b/Projects/App/Sources/Application/SceneDelegate.swift deleted file mode 100644 index 61bb8456..00000000 --- a/Projects/App/Sources/Application/SceneDelegate.swift +++ /dev/null @@ -1,24 +0,0 @@ -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions - ) { - guard let scene = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) {} - - func sceneDidBecomeActive(_ scene: UIScene) {} - - func sceneWillResignActive(_ scene: UIScene) {} - - func sceneWillEnterForeground(_ scene: UIScene) {} - - func sceneDidEnterBackground(_ scene: UIScene) {} -} diff --git a/Projects/App/Support/Info.plist b/Projects/App/Support/Info.plist index 2688b32b..7112340e 100644 --- a/Projects/App/Support/Info.plist +++ b/Projects/App/Support/Info.plist @@ -2,6 +2,10 @@ + CLIENT_ID + $(CLIENT_ID) + REDIREDCT_URI + $(REDIREDCT_URI) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -31,8 +35,6 @@ UISceneConfigurationName Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate diff --git a/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 1.png b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 1.png new file mode 100644 index 00000000..551143f8 Binary files /dev/null and b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 1.png differ diff --git a/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 2.png b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 2.png new file mode 100644 index 00000000..551143f8 Binary files /dev/null and b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background 2.png differ diff --git a/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background.png b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background.png new file mode 100644 index 00000000..551143f8 Binary files /dev/null and b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Background.png differ diff --git a/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Contents.json new file mode 100644 index 00000000..079b735d --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Background.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Background 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Background 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Contents.json b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Image/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DesignSystem/Sources/Image/SMSImage.swift b/Projects/Core/DesignSystem/Sources/Image/SMSImage.swift new file mode 100644 index 00000000..f207670c --- /dev/null +++ b/Projects/Core/DesignSystem/Sources/Image/SMSImage.swift @@ -0,0 +1,31 @@ +import SwiftUI + +public struct SMSImage: View { + public enum Image { + case background + } + + private var image: Image + private var renderingMode: SwiftUI.Image.TemplateRenderingMode + + public init( + _ image: Image, + renderingMode: SwiftUI.Image.TemplateRenderingMode = .original + ) { + self.image = image + self.renderingMode = renderingMode + } + + public var body: some View { + smsImageToImage() + .resizable() + .renderingMode(renderingMode) + } + + private func smsImageToImage() -> SwiftUI.Image { + switch image { + case .background: + return DesignSystemAsset.Images.background.swiftUIImage + } + } +} diff --git a/Projects/Core/JwtStore/Interface/DI/JwtStoreBuildable.swift b/Projects/Core/JwtStore/Interface/DI/JwtStoreBuildable.swift new file mode 100644 index 00000000..490cfb7f --- /dev/null +++ b/Projects/Core/JwtStore/Interface/DI/JwtStoreBuildable.swift @@ -0,0 +1,5 @@ +import NeedleFoundation + +public protocol JwtStoreBuildable { + var jwtStore: any JwtStore { get } +} diff --git a/Projects/Core/JwtStore/Sources/DI/JwtStoreComponent.swift b/Projects/Core/JwtStore/Sources/DI/JwtStoreComponent.swift new file mode 100644 index 00000000..2a3e98b1 --- /dev/null +++ b/Projects/Core/JwtStore/Sources/DI/JwtStoreComponent.swift @@ -0,0 +1,13 @@ +import JwtStoreInterface +import KeychainModuleInterface +import NeedleFoundation + +public protocol JwtStoreDependency: Dependency { + var keychainBuildable: any KeychainBuildable { get } +} + +public final class JwtStoreComponent: Component, JwtStoreBuildable { + public var jwtStore: any JwtStore { + KeychainJwtStore(keychain: dependency.keychainBuildable.keychain) + } +} diff --git a/Projects/Domain/AuthDomain/Interface/DI/AuthDomainBuildable.swift b/Projects/Domain/AuthDomain/Interface/DI/AuthDomainBuildable.swift new file mode 100644 index 00000000..59a48bef --- /dev/null +++ b/Projects/Domain/AuthDomain/Interface/DI/AuthDomainBuildable.swift @@ -0,0 +1,4 @@ +public protocol AuthDomainBuildable { + var loginUseCase: any LoginUseCase { get } + var authRepository: any AuthRepository { get } +} diff --git a/Projects/Domain/AuthDomain/Sources/DI/AuthDomainComponent.swift b/Projects/Domain/AuthDomain/Sources/DI/AuthDomainComponent.swift new file mode 100644 index 00000000..0a267d59 --- /dev/null +++ b/Projects/Domain/AuthDomain/Sources/DI/AuthDomainComponent.swift @@ -0,0 +1,19 @@ +import NeedleFoundation +import AuthDomainInterface +import JwtStoreInterface + +public protocol AuthDomainDependency: Dependency { + var jwtStoreBuildable: any JwtStoreBuildable { get } +} + +public final class AuthDomainComponent: Component, AuthDomainBuildable { + public var loginUseCase: any LoginUseCase { + LoginUseCaseImpl(authRepository: authRepository) + } + public var authRepository: any AuthRepository { + AuthRepositoryImpl(remoteAuthDataSource: remoteAuthDataSource) + } + var remoteAuthDataSource: any RemoteAuthDataSource { + RemoteAuthDataSourceImpl(jwtStore: dependency.jwtStoreBuildable.jwtStore) + } +} diff --git a/Projects/Feature/BaseFeature/Project.swift b/Projects/Feature/BaseFeature/Project.swift index ee7a7a8a..944748be 100644 --- a/Projects/Feature/BaseFeature/Project.swift +++ b/Projects/Feature/BaseFeature/Project.swift @@ -6,6 +6,9 @@ let project = Project.makeModule( name: ModulePaths.Feature.BaseFeature.rawValue, product: .framework, targets: [.unitTest], + externalDependencies: [ + .SPM.Needle + ], internalDependencies: [ .Core.DesignSystem, .Shared.DateUtil, diff --git a/Projects/Feature/BaseFeature/Sources/MVIContainer.swift b/Projects/Feature/BaseFeature/Sources/MVIContainer.swift index 6a186dd5..90fe834d 100644 --- a/Projects/Feature/BaseFeature/Sources/MVIContainer.swift +++ b/Projects/Feature/BaseFeature/Sources/MVIContainer.swift @@ -2,8 +2,8 @@ import Combine import SwiftUI public final class MVIContainer: ObservableObject { - private let intent: Intent - @Published private var model: Model + public let intent: Intent + @Published public var model: Model private var bag = Set() diff --git a/Projects/Feature/SigninFeature/Interface/SigninBuildable.swift b/Projects/Feature/SigninFeature/Interface/SigninBuildable.swift new file mode 100644 index 00000000..ab30731a --- /dev/null +++ b/Projects/Feature/SigninFeature/Interface/SigninBuildable.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol SigninBuildable { + associatedtype ViewType: View + func makeView() -> ViewType +} diff --git a/Projects/Feature/SigninFeature/Project.swift b/Projects/Feature/SigninFeature/Project.swift new file mode 100644 index 00000000..0eca4dd4 --- /dev/null +++ b/Projects/Feature/SigninFeature/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: ModulePaths.Feature.SigninFeature.rawValue, + product: .staticLibrary, + targets: [.interface, .unitTest], + externalDependencies: [ + .SPM.GAuthSignin + ], + internalDependencies: [ + .Domain.AuthDomainInterface, + .Feature.BaseFeature + ] +) diff --git a/Projects/Feature/SigninFeature/Sources/DI/SigninComponent.swift b/Projects/Feature/SigninFeature/Sources/DI/SigninComponent.swift new file mode 100644 index 00000000..ea5de7e6 --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/DI/SigninComponent.swift @@ -0,0 +1,25 @@ +import AuthDomainInterface +import SwiftUI +import SigninFeatureInterface +import BaseFeature +import NeedleFoundation + +public protocol SigninDependency: Dependency { + var authDomainBuildable: any AuthDomainBuildable { get } +} + +public final class SigninComponent: Component, SigninBuildable { + public func makeView() -> some View { + let model = SigninModel() + let intent = SigninIntent( + loginUseCase: dependency.authDomainBuildable.loginUseCase, + model: model + ) + let container = MVIContainer( + intent: intent as SigninIntentProtocol, + model: model as SigninStateProtocol, + modelChangePublisher: model.objectWillChange + ) + return SigninView(container: container) + } +} diff --git a/Projects/Feature/SigninFeature/Sources/Intent/SigninIntent.swift b/Projects/Feature/SigninFeature/Sources/Intent/SigninIntent.swift new file mode 100644 index 00000000..6f82bfea --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Intent/SigninIntent.swift @@ -0,0 +1,26 @@ +import Combine +import AuthDomainInterface + +final class SigninIntent: SigninIntentProtocol { + private let loginUseCase: any LoginUseCase + private weak var model: (any SigninActionProtocol)? + + init( + loginUseCase: any LoginUseCase, + model: any SigninActionProtocol + ) { + self.loginUseCase = loginUseCase + self.model = model + } + + func signin(code: String) { + Task { + do { + try await loginUseCase.execute(code: code) + model?.updateIsSuccess(isSuccess: true) + } catch { + model?.updateIsError(isError: true) + } + } + } +} diff --git a/Projects/Feature/SigninFeature/Sources/Intent/SigninIntentProtocol.swift b/Projects/Feature/SigninFeature/Sources/Intent/SigninIntentProtocol.swift new file mode 100644 index 00000000..6b3ec479 --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Intent/SigninIntentProtocol.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol SigninIntentProtocol { + func signin(code: String) +} diff --git a/Projects/Feature/SigninFeature/Sources/Model/SigninModel.swift b/Projects/Feature/SigninFeature/Sources/Model/SigninModel.swift new file mode 100644 index 00000000..34a7923d --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Model/SigninModel.swift @@ -0,0 +1,16 @@ +import Foundation + +final class SigninModel: ObservableObject, SigninStateProtocol { + @Published var isError: Bool = false + @Published var isSuccess: Bool = false +} + +extension SigninModel: SigninActionProtocol { + func updateIsError(isError: Bool) { + self.isError = isError + } + + func updateIsSuccess(isSuccess: Bool) { + self.isSuccess = isSuccess + } +} diff --git a/Projects/Feature/SigninFeature/Sources/Model/SigninModelProtocol.swift b/Projects/Feature/SigninFeature/Sources/Model/SigninModelProtocol.swift new file mode 100644 index 00000000..af80cf80 --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Model/SigninModelProtocol.swift @@ -0,0 +1,9 @@ +protocol SigninStateProtocol { + var isError: Bool { get } + var isSuccess: Bool { get } +} + +protocol SigninActionProtocol: AnyObject { + func updateIsError(isError: Bool) + func updateIsSuccess(isSuccess: Bool) +} diff --git a/Projects/Feature/SigninFeature/Sources/Scene/GAuthButtonView.swift b/Projects/Feature/SigninFeature/Sources/Scene/GAuthButtonView.swift new file mode 100644 index 00000000..88a3977d --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Scene/GAuthButtonView.swift @@ -0,0 +1,33 @@ +import UIKit +import SwiftUI +import GAuthSignin +import BaseFeature + +struct GAuthButtonView: UIViewRepresentable { + private let completion: (String) -> Void + + func updateUIView(_ uiView: UIViewType, context: Context) { } + + func makeUIView(context: Context) -> some UIView { + let gauthButton = GAuthButton(rounded: .rounded) + guard let topViewController = (UIApplication.shared.connectedScenes.first as? UIWindowScene)? + .windows + .first? + .rootViewController + else { return gauthButton } + gauthButton.prepare( + clientID: Bundle.main.object(forInfoDictionaryKey: "CLIENT_ID") as? String ?? "", + redirectURI: Bundle.main.object(forInfoDictionaryKey: "REDIREDCT_URI") as? String ?? "", + presenting: topViewController + ) { code in + completion(code) + } + return gauthButton + } + + init( + completion: @escaping (String) -> Void + ) { + self.completion = completion + } +} diff --git a/Projects/Feature/SigninFeature/Sources/Scene/SigninView.swift b/Projects/Feature/SigninFeature/Sources/Scene/SigninView.swift new file mode 100644 index 00000000..df81495e --- /dev/null +++ b/Projects/Feature/SigninFeature/Sources/Scene/SigninView.swift @@ -0,0 +1,46 @@ +import SwiftUI +import BaseFeature +import DesignSystem +import GlobalThirdPartyLibrary +import ViewUtil +import UtilityModule + +struct SigninView: View { + @StateObject var container: MVIContainer + var intent: any SigninIntentProtocol { container.intent } + var state: any SigninStateProtocol { container.model } + + var body: some View { + VStack(spacing: 0) { + Text("STUDENT\nMANAGEMENT\nSERVICE") + .multilineTextAlignment(.center) + .padding(.top, 124) + .font(.sms(.headline1)) + .foregroundColor(.sms(.system(.white))) + + Text("학생 정보 통합관리 서비스") + .font(.sms(.title2)) + .foregroundColor(.sms(.system(.white))) + .padding(.top, 16) + + Spacer() + + GAuthButtonView { code in + container.intent.signin(code: code) + } + .padding(.horizontal, 20) + .frame(height: 50) + .padding(.bottom, 54) + } + .background { + SMSImage(.background) + .ignoresSafeArea() + } + .alert( + "알수없는 에러가 발생했습니다. 잠시 후 다시 시도해 주세요.", + isPresented: Binding(get: { state.isError }, set: { _ in }) + ) { + Button("확인", role: .cancel) { } + } + } +} diff --git a/Projects/Feature/SigninFeature/Support/Info.plist b/Projects/Feature/SigninFeature/Support/Info.plist new file mode 100644 index 00000000..323e5ecf --- /dev/null +++ b/Projects/Feature/SigninFeature/Support/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Projects/Feature/SigninFeature/Tests/SigninFeatureTest.swift b/Projects/Feature/SigninFeature/Tests/SigninFeatureTest.swift new file mode 100644 index 00000000..6223252d --- /dev/null +++ b/Projects/Feature/SigninFeature/Tests/SigninFeatureTest.swift @@ -0,0 +1,11 @@ +import XCTest + +final class SigninFeatureTests: XCTestCase { + override func setUpWithError() throws {} + + override func tearDownWithError() throws {} + + func testExample() { + XCTAssertEqual(1, 1) + } +} diff --git a/Projects/Shared/KeychainModule/Interface/DI/KeychainBuildable.swift b/Projects/Shared/KeychainModule/Interface/DI/KeychainBuildable.swift new file mode 100644 index 00000000..38ca9cea --- /dev/null +++ b/Projects/Shared/KeychainModule/Interface/DI/KeychainBuildable.swift @@ -0,0 +1,3 @@ +public protocol KeychainBuildable { + var keychain: any Keychain { get } +} diff --git a/Projects/Shared/KeychainModule/Sources/DI/KeychainComponent.swift b/Projects/Shared/KeychainModule/Sources/DI/KeychainComponent.swift new file mode 100644 index 00000000..09f7d76a --- /dev/null +++ b/Projects/Shared/KeychainModule/Sources/DI/KeychainComponent.swift @@ -0,0 +1,8 @@ +import KeychainModuleInterface +import NeedleFoundation + +public final class KeychainComponent: Component, KeychainBuildable { + public var keychain: any Keychain { + DefaultKeychain() + } +} diff --git a/Scripts/NeedleRunScript.sh b/Scripts/NeedleRunScript.sh new file mode 100755 index 00000000..c15ff419 --- /dev/null +++ b/Scripts/NeedleRunScript.sh @@ -0,0 +1,11 @@ +if test -d "/opt/homebrew/bin/"; then + PATH="/opt/homebrew/bin/:${PATH}" +fi + +export PATH + +if which needle > /dev/null; then + needle generate Sources/Application/NeedleGenerated.swift ../ +else + echo "warning: Needle not installed, plz run 'brew install needle'" +fi diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index b3fbd21f..5c12263a 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -5,6 +5,8 @@ let dependencies = Dependencies( carthage: nil, swiftPackageManager: SwiftPackageManagerDependencies( [ + .remote(url: "https://github.com/uber/needle", requirement: .exact("0.22.0")), + .remote(url: "https://github.com/GSM-MSG/GAuthSignin-Swift", requirement: .exact("0.0.3")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .exact("11.2.2")), .remote(url: "https://github.com/Quick/Quick.git", requirement: .exact("6.1.0")), .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.4")), diff --git a/Tuist/ProjectDescriptionHelpers/Action+Template.swift b/Tuist/ProjectDescriptionHelpers/Action+Template.swift index 50385bf9..d0f5d0be 100644 --- a/Tuist/ProjectDescriptionHelpers/Action+Template.swift +++ b/Tuist/ProjectDescriptionHelpers/Action+Template.swift @@ -5,4 +5,9 @@ public extension TargetScript { path: Path.relativeToRoot("Scripts/SwiftLintRunScript.sh"), name: "SwiftLint" ) + static let needle = TargetScript.pre( + path: .relativeToRoot("Scripts/NeedleRunScript.sh"), + name: "Needle", + basedOnDependencyAnalysis: false + ) }