diff --git a/HMH_Tuist_iOS/Projects/App/Sources/AppView.swift b/HMH_Tuist_iOS/Projects/App/Sources/AppView.swift new file mode 100644 index 00000000..2bce6222 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/AppView.swift @@ -0,0 +1,32 @@ +// +// AppView.swift +// HMH-iOS +// +// Created by 이지희 on 11/15/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +import KakaoSDKAuth + +import DSKit +import Core + +struct AppView: View { + @EnvironmentObject var appDIContainer: AppDIContainer + @StateObject var coordinator = AppCoordinator(navigationPath: .init()) + + var body: some View { + ZStack { + Color(DSKitAsset.blackground.swiftUIColor) + .ignoresSafeArea(.all) + coordinator.start() + } + .onOpenURL { url in + if AuthApi.isKakaoTalkLoginUrl(url) { + _ = AuthController.handleOpenUrl(url: url) + } + } + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/ContentView.swift b/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/ContentView.swift deleted file mode 100644 index 5f2d4e95..00000000 --- a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/ContentView.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// ContentView.swift -// HMH_iOS -// -// Created by 지희의 MAC on 2/15/24. -// - -import SwiftUI - -import KakaoSDKAuth - -import LoginFeature -import OnboardingFeature -import MyPageFeature -import HomeFeature - -import DSKit -import Core - -struct ContentView: View { - @StateObject var loginViewModel = LoginViewModel() - @StateObject var userManager = UserManager.shared - @StateObject var appStateViewModel = AppStateViewModel.shared - - @State private var showWelcomeAlert = false - - var body: some View { - ZStack { - Color(DSKitAsset.blackground.swiftUIColor) - .ignoresSafeArea() - if loginViewModel.isLoading { - SplashView(viewModel: loginViewModel) - } else { - switch userManager.appState { - case .onboarding: - OnboardingContentView() - case .onboardingComplete: - OnboardingCompleteView() - case .servicePrepare: - ServicePrepareView() - case .home: - TabBarView() - .onAppear { - appStateViewModel.onAppear() - if userManager.isFirstLogin { - showWelcomeAlert = true - userManager.isFirstLogin = false - } - } - .overlay( - CustomAlertView( - alertType: appStateViewModel.currentAlertType, - confirmBtn: CustomAlertButtonView( - buttonType: .Confirm, - alertType: appStateViewModel.currentAlertType, - isPresented: $appStateViewModel.showCustomAlert, - action: { - appStateViewModel.cancelAlert() - } - ), - cancelBtn: CustomAlertButtonView( - buttonType: .Cancel, - alertType: appStateViewModel.currentAlertType, - isPresented: $appStateViewModel.showCustomAlert, - action: { - appStateViewModel.nextAlert() - } - ), currentPoint: appStateViewModel.currentPoint, usagePoint: appStateViewModel.usagePoint - ) - .opacity(appStateViewModel.showCustomAlert ? 1 : 0) - ) - .overlay( - GuideView(isPresented: $showWelcomeAlert) - .opacity(showWelcomeAlert ? 1 : 0) - ) - case .login: - LoginView(viewModel: loginViewModel) - .onOpenURL { url in - if AuthApi.isKakaoTalkLoginUrl(url) { - _ = AuthController.handleOpenUrl(url: url) - } - } - } - } - } - } -} - -#Preview { - ContentView() -} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift b/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift index f2ae4aaa..37afaaf4 100644 --- a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift +++ b/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift @@ -7,13 +7,13 @@ import SwiftUI -import Lottie - -import LoginFeature import DSKit +import Core + +import Lottie struct SplashView: View { - @ObservedObject var viewModel: LoginViewModel + @ObservedObject var coordinator: AppCoordinator var body: some View { ZStack { @@ -29,9 +29,9 @@ struct SplashView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .background(DSKitAsset.blackground.swiftUIColor, ignoresSafeAreaEdges: .all) .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 3.5, execute: { -// viewModel.handleSplashScreen() - }) + DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) { + coordinator.appState = .login // Splash에서 홈뷰 통신하면서 상태 판단 필요 + } } } } diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AppCoordinator.swift b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AppCoordinator.swift new file mode 100644 index 00000000..52cbf5b2 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AppCoordinator.swift @@ -0,0 +1,66 @@ +// +// AppCoordinator.swift +// Coordinator +// +// Created by 이지희 on 11/7/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +import Core +import LoginFeature +import OnboardingFeature +import MyPageFeature + + +final class AppCoordinator: ObservableObject, CoordinatorType { + @Published var currentView: AnyView = AnyView(EmptyView()) + @Published var appState: AppState = .login + + + var navigationPath: NavigationPath = NavigationPath() + private let diContainer: AppDIContainer + + init(diContainer: AppDIContainer) { + self.diContainer = diContainer + } + + func start() -> AnyView { + // 초기 상태에 따라 적절한 뷰를 설정합니다. + showSplashScreen() + return currentView + } + + private func showSplashScreen() { + currentView = AnyView(SplashView(coordinator: self)) // 코디네이터 주입 + + /// 스플래쉬에서 토큰 검사 과정 (혹은 홈뷰 API 호출) 로 로그인 필요 여부 확인 + } + + func transitionToNextView() { + switch UserManager.shared.appState { + case .onboarding: + currentView = AnyView(OnboardingContentView()) + case .onboardingComplete: + currentView = AnyView(OnboardingCompleteView()) + case .servicePrepare: + currentView = AnyView(ServicePrepareView()) + case .home: + startTabBar() + case .login: + startLogin() + } + } + + func startLogin() { + let authDIContainer = diContainer.injectAuthDIContainer() + let authCoordinator = AuthCoordinator(navigationPath: navigationPath, diContainer: authDIContainer) + currentView = authCoordinator.start() + } + + func startTabBar() { + let tabBarCoordinator = TabBarCoordinator(parentCoordinator: self, navigationPath: self.navigationPath) + currentView = tabBarCoordinator.start() + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AuthCoordinator.swift b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AuthCoordinator.swift new file mode 100644 index 00000000..933126c9 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/AuthCoordinator.swift @@ -0,0 +1,30 @@ +// +// LoginCoordinator.swift +// Coordinator +// +// Created by 이지희 on 11/15/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +import LoginFeature + +final class AuthCoordinator: ObservableObject, CoordinatorType { + var navigationPath: NavigationPath + private let diContainer: AuthDIContainer + + init( + navigationPath: NavigationPath, + diContainer: AuthDIContainer + ) { + self.navigationPath = navigationPath + self.diContainer = diContainer + } + + func start() -> AnyView { + let viewModel = diContainer.injectLoginViewModel() + let view = LoginView(viewModel: viewModel) + return AnyView(view) + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/BaseCoordinator.swift b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/BaseCoordinator.swift new file mode 100644 index 00000000..08b6a2fe --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/BaseCoordinator.swift @@ -0,0 +1,40 @@ +// +// BaseCoordinator.swift +// Coordinator +// +// Created by 이지희 on 11/7/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// +import SwiftUI +import Combine + +import Core + +// MARK: - BaseCoordinator + +/// NavigationStack을 사용할 경우 이전에 대한 정보를 모두 갖고 있기 때문에 부모 - 자식 코디네이터 불필요 +public protocol CoordinatorType: AnyObject { + associatedtype View: SwiftUI.View + + var navigationPath: NavigationPath { get set } + + func start() -> View + + func push(to view: any Hashable) + func pop() + func popToRoot() +} + +extension CoordinatorType { + func push(to view: any Hashable) { + navigationPath.append(view) + } + + func pop() { + navigationPath.removeLast() + } + + func popToRoot() { + navigationPath.removeLast(navigationPath.count) + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/OnboardingCoordinator.swift b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/OnboardingCoordinator.swift new file mode 100644 index 00000000..9270f8c3 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/OnboardingCoordinator.swift @@ -0,0 +1,29 @@ +// +// OnboardingCoordinator.swift +// Coordinator +// +// Created by 이지희 on 11/15/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +import OnboardingFeature + +final class OnboardingCoordinator: ObservableObject, CoordinatorType { + var parentCoordinator: (any CoordinatorType)? + + var navigationPath: NavigationPath + + init( + parentCoordinator: (any CoordinatorType)? = nil, + navigationPath: NavigationPath + ) { + self.parentCoordinator = parentCoordinator + self.navigationPath = navigationPath + } + + func start() -> AnyView { + return AnyView(OnboardingContentView()) + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/TabBarCoordinator.swift b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/TabBarCoordinator.swift new file mode 100644 index 00000000..bb88f97f --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/Coordinator/TabBarCoordinator.swift @@ -0,0 +1,35 @@ +// +// TabBarCoordinator.swift +// Coordinator +// +// Created by 이지희 on 11/15/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +class TabBarCoordinator: ObservableObject, CoordinatorType { + var navigationPath: NavigationPath + + var parentCoordinator: (any CoordinatorType)? + + @Published var selectedTab: Tab = .home + + init( + parentCoordinator: CoordinatorType, + navigationPath: NavigationPath + ) { + self.parentCoordinator = parentCoordinator + self.navigationPath = navigationPath + } + + func start() -> AnyView { + AnyView(TabBarView()) + } + + enum Tab { + case home + case challenge + case myPage + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AppDIContainer.swift b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AppDIContainer.swift new file mode 100644 index 00000000..684681cf --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AppDIContainer.swift @@ -0,0 +1,31 @@ +// +// AppDIConatiner.swift +// HMH-iOS +// +// Created by 이지희 on 11/27/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import SwiftUI + +import Networks +import Data +import Domain + +final class AppDIContainer: ObservableObject { + + // Auth DI + func injectAuthDIContainer() -> AuthDIContainer { + let service = injectAuthService() + let serviceFactory = injectOAuthFactory() + return AuthDIContainer(services: service, oAuthServiceFactory: serviceFactory) + } + + private func injectAuthService() -> AuthService { + return AuthService() + } + + private func injectOAuthFactory() -> OAuthServiceFactory { + return OAuthServiceFactory() + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AuthDIContainer.swift b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AuthDIContainer.swift new file mode 100644 index 00000000..9796e963 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/AuthDIContainer.swift @@ -0,0 +1,46 @@ +// +// AuthDIContainer.swift +// HMH-iOS +// +// Created by 이지희 on 11/28/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import Foundation + +import Networks +import Core +import Data +import Domain + +// TODO: UseCase가 Feature안에 존재해서 import 수정 후 import 제거 +import LoginFeature + +final class AuthDIContainer: ObservableObject { + + private let services: AuthService + private let oAuthServiceFactory: OAuthServiceFactory + + init(services: AuthService, oAuthServiceFactory: OAuthServiceFactory) { + self.services = services + self.oAuthServiceFactory = oAuthServiceFactory + } + + func injectLoginViewModel() -> LoginViewModel { + let useCase = injectLoginUseCase() + return LoginViewModel(loginUseCase: useCase) + } + + // MARK: Usecase + + private func injectLoginUseCase() -> LoginUseCase { + let repository = injectAuthRepository() + return LoginUseCase(repository: repository) + } + + // MARK: Repository + + private func injectAuthRepository() -> AuthRepository { + return AuthRepository(authService: services, oauthServiceFactory: oAuthServiceFactory) + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/ChallengeDIContainer.swift b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/ChallengeDIContainer.swift new file mode 100644 index 00000000..62dcd935 --- /dev/null +++ b/HMH_Tuist_iOS/Projects/App/Sources/DIContainer/ChallengeDIContainer.swift @@ -0,0 +1,32 @@ +// +// ChallengeDIContainer.swift +// HMH-iOS +// +// Created by 이지희 on 11/27/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import Networks + +import Core +import Data +import Domain + +final class ChallengeDIContainer { + + private let service: ChallengeService = ChallengeService() + + // MARK: Usecase + + private func injectChallengeUseCase() -> ChallengeUseCase { + return ChallengeUseCase( + repository: injectChallengeRepository() + ) + } + + // MARK: Repository + + private func injectChallengeRepository() -> ChallengeRepository { + return ChallengeRepository(service: service) + } +} diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Example.swift b/HMH_Tuist_iOS/Projects/App/Sources/Example.swift deleted file mode 100644 index e69de29b..00000000 diff --git a/HMH_Tuist_iOS/Projects/App/Sources/HMH_iOSApp.swift b/HMH_Tuist_iOS/Projects/App/Sources/HMH_iOSApp.swift index dba4075a..0047f423 100644 --- a/HMH_Tuist_iOS/Projects/App/Sources/HMH_iOSApp.swift +++ b/HMH_Tuist_iOS/Projects/App/Sources/HMH_iOSApp.swift @@ -14,7 +14,7 @@ import KakaoSDKAuth struct HMH_iOSApp: App { let kakaoAPIKey = Bundle.main.infoDictionary?["KAKAO_API_KEY"] as! String @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - @StateObject private var scheduler = MidnightTaskScheduler() + @StateObject private var appDIContainer = AppDIContainer() @Environment(\.scenePhase) private var scenePhase init() { @@ -23,16 +23,8 @@ struct HMH_iOSApp: App { var body: some Scene { WindowGroup { - ContentView() - } - .onChange(of: scenePhase) { newPhase in - switch newPhase { - case .background: - print("App moved to background.") - scheduler.scheduleMidnightTask() - @unknown default: - break - } + AppView() + .environmentObject(appDIContainer) } } } diff --git "a/HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/.gitkeep" b/HMH_Tuist_iOS/Projects/App/Sources/Manager/.gitkeep similarity index 100% rename from "HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/.gitkeep" rename to HMH_Tuist_iOS/Projects/App/Sources/Manager/.gitkeep diff --git "a/HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/NotificationManager.swift" b/HMH_Tuist_iOS/Projects/App/Sources/Manager/NotificationManager.swift similarity index 100% rename from "HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/NotificationManager.swift" rename to HMH_Tuist_iOS/Projects/App/Sources/Manager/NotificationManager.swift diff --git "a/HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/UserDefaultsManager.swift" b/HMH_Tuist_iOS/Projects/App/Sources/Manager/UserDefaultsManager.swift similarity index 100% rename from "HMH_Tuist_iOS/Projects/App/Sources/Manager \354\230\244\355\233\204 9.25.10 \354\230\244\355\233\204 9.35.22/UserDefaultsManager.swift" rename to HMH_Tuist_iOS/Projects/App/Sources/Manager/UserDefaultsManager.swift diff --git a/HMH_Tuist_iOS/Projects/Core/Sources/AppState.swift b/HMH_Tuist_iOS/Projects/Core/Sources/AppState.swift deleted file mode 100644 index 1410a996..00000000 --- a/HMH_Tuist_iOS/Projects/Core/Sources/AppState.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public enum AppState: String { - case login - case onboarding - case onboardingComplete - case home - case servicePrepare -} diff --git a/HMH_Tuist_iOS/Projects/Core/Sources/Manager/UserManager.swift b/HMH_Tuist_iOS/Projects/Core/Sources/Manager/UserManager.swift index 7a87de2d..7f2ba4e5 100644 --- a/HMH_Tuist_iOS/Projects/Core/Sources/Manager/UserManager.swift +++ b/HMH_Tuist_iOS/Projects/Core/Sources/Manager/UserManager.swift @@ -7,6 +7,14 @@ import SwiftUI +public enum AppState: String { + case login + case onboarding + case onboardingComplete + case home + case servicePrepare +} + public class UserManager: ObservableObject { @KeychainStorage("accessToken") public var accessToken @KeychainStorage("refreshToken") public var refreshToken diff --git a/HMH_Tuist_iOS/Projects/Data/Sources/Mapper/Challenge/ChallengeDetailMapper.swift b/HMH_Tuist_iOS/Projects/Data/Sources/Mapper/Challenge/ChallengeDetailMapper.swift index 6ee75462..b95b36f1 100644 --- a/HMH_Tuist_iOS/Projects/Data/Sources/Mapper/Challenge/ChallengeDetailMapper.swift +++ b/HMH_Tuist_iOS/Projects/Data/Sources/Mapper/Challenge/ChallengeDetailMapper.swift @@ -14,7 +14,7 @@ import Networks extension GetChallengeResult { public func toEntity() -> ChallengeDetail { .init( - statuses: statuses, + statuses: statuses.map{ PointStatusEnum(rawValue: $0) ?? .none}, todayIndex: todayIndex, startDate: startDate, challengeInfo: .init( diff --git a/HMH_Tuist_iOS/Projects/Domain/Project.swift b/HMH_Tuist_iOS/Projects/Domain/Project.swift index 433e3d79..02c989c9 100644 --- a/HMH_Tuist_iOS/Projects/Domain/Project.swift +++ b/HMH_Tuist_iOS/Projects/Domain/Project.swift @@ -15,6 +15,7 @@ let project = Project.makeModule( name: "Domain", targets: [.dynamicFramework], internalDependencies: [ - .core + .core, + .Modules.dsKit ] ) diff --git a/HMH_Tuist_iOS/Projects/Modules/Networks/Sources/Service/Challenge/ChallengeService.swift b/HMH_Tuist_iOS/Projects/Modules/Networks/Sources/Service/Challenge/ChallengeService.swift index 5c1a33f6..d8544ffa 100644 --- a/HMH_Tuist_iOS/Projects/Modules/Networks/Sources/Service/Challenge/ChallengeService.swift +++ b/HMH_Tuist_iOS/Projects/Modules/Networks/Sources/Service/Challenge/ChallengeService.swift @@ -23,11 +23,12 @@ public protocol ChallengeServiceType { } extension ChallengeService: ChallengeServiceType { + public func getDailyChallenge() -> AnyPublisher { return requestWithResult(.getdailyChallenge) } - public func getSuccesChallenge() -> AnyPublisher { + public func postSuccesChallenge(request: ChallengeSuccessRequest) -> AnyPublisher { return requestWithResult(.postSuccesChallenge) } @@ -65,7 +66,7 @@ public struct StubChallengeService: ChallengeServiceType { .eraseToAnyPublisher() } - public func getSuccesChallenge() -> AnyPublisher { + public func postSuccesChallenge(request: ChallengeSuccessRequest) -> AnyPublisher { return Just(.stub) .setFailureType(to: HMHNetworkError.self) .eraseToAnyPublisher() diff --git a/HMH_Tuist_iOS/Tuist/ResourceSynthesizers/Lottie.stencil b/HMH_Tuist_iOS/Tuist/ResourceSynthesizers/Lottie.stencil new file mode 100644 index 00000000..b420b4aa --- /dev/null +++ b/HMH_Tuist_iOS/Tuist/ResourceSynthesizers/Lottie.stencil @@ -0,0 +1,58 @@ +// swiftformat:disable all +// swiftlint:disable all +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation +#if canImport(Lottie) +import Lottie +// MARK: - Animations Assets +{{accessModifier}} extension AnimationAsset { + {% for file in files %} + static let {{file.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Self(named: "{{file.name}}") + {% endfor %} +} +// MARK: - Animation Helpers +{{accessModifier}} extension AnimationAsset { + /// All the available animation. Can be used to preload them + static let allAnimations: [Self] = [ + {% for file in files %} + Self.{{file.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% endfor %} + ] +} +// MARK: - Structures +{{accessModifier}} struct AnimationAsset: Hashable { + {{accessModifier}} fileprivate(set) var name: String + {{accessModifier}} let animation: LottieAnimation? + {{accessModifier}} init(named name: String) { + self.name = name + if let url = Bundle.module.url(forResource: name, withExtension: "json") { + self.animation = LottieAnimation.filepath(url.path) + } else { + self.animation = nil + } + } + // MARK: Hashable Conformance + {{accessModifier}} static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.name == rhs.name + } + {{accessModifier}} func hash(into hasher: inout Hasher) { + hasher.combine(self.name) + } +} +// MARK: - Preload Helpers +{{accessModifier}} extension AnimationAsset { + /// Preloads all the Lottie Animations to avoid performance issues when loading them + static func preload() -> Void { + for animationAsset in Self.allAnimations { + _ = animationAsset.animation + } + } +} +// swiftformat:enable all +// swiftlint:enable all +#endif +{% else %} +// No files found +{% endif %}