From 372c5acb9ac0a955819f60790cdbe02ecc78be36 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Fri, 14 Jun 2024 12:45:46 +0200 Subject: [PATCH 01/13] [build] add new SPM to the project, from STRV public repo --- App/Course App.xcodeproj/project.pbxproj | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 0281dfd..8946fcc 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ F130890C2C19B69E00FA83EC /* HomeViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890B2C19B69E00FA83EC /* HomeViewEvent.swift */; }; F130890E2C19B78700FA83EC /* SwipingViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */; }; F13089102C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */; }; + F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */ = {isa = PBXBuildFile; productRef = F13089122C1C55AF00FA83EC /* DependencyInjection */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -234,6 +235,7 @@ F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */, A8830C4F2BEF8565005CAFEA /* FirebaseAuth in Frameworks */, F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */, + F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */, A8830C512BEF8565005CAFEA /* FirebaseFirestore in Frameworks */, F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */, ); @@ -744,6 +746,7 @@ F18F1A782C107591005364B7 /* KeychainAccess */, F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */, F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */, + F13089122C1C55AF00FA83EC /* DependencyInjection */, ); productName = "Course App"; productReference = A81F21F02BDC1DB700A8A301 /* App Course Dev.app */; @@ -821,6 +824,7 @@ A8830C422BEF7B17005CAFEA /* XCRemoteSwiftPackageReference "SwiftLint" */, A8830C4D2BEF8565005CAFEA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, F18F1A772C107591005364B7 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + F13089112C1C55AF00FA83EC /* XCRemoteSwiftPackageReference "ios-dependency-injection" */, ); productRefGroup = A81F21F12BDC1DB700A8A301 /* Products */; projectDirPath = ""; @@ -1606,6 +1610,14 @@ minimumVersion = 10.25.0; }; }; + F13089112C1C55AF00FA83EC /* XCRemoteSwiftPackageReference "ios-dependency-injection" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/strvcom/ios-dependency-injection.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.3; + }; + }; F18F1A772C107591005364B7 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; @@ -1632,6 +1644,11 @@ package = A8830C4D2BEF8565005CAFEA /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseFirestore; }; + F13089122C1C55AF00FA83EC /* DependencyInjection */ = { + isa = XCSwiftPackageProductDependency; + package = F13089112C1C55AF00FA83EC /* XCRemoteSwiftPackageReference "ios-dependency-injection" */; + productName = DependencyInjection; + }; F18F1A782C107591005364B7 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = F18F1A772C107591005364B7 /* XCRemoteSwiftPackageReference "KeychainAccess" */; From fd42c724bc6994ce969382209e13745581f8ccb7 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Fri, 14 Jun 2024 12:57:51 +0200 Subject: [PATCH 02/13] [feat] add container to protocol Coordinator and update all files --- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++++++- App/Course App/App/AppCoordinator.swift | 6 ++++-- App/Course App/Common/Navigation/Coordinator.swift | 2 ++ .../Home/Navigation/HomeNavigationCoordinator.swift | 7 +++++++ .../Scenes/Navigation/MainTabBarCoordinator.swift | 12 +++++++++--- .../Navigation/OnboardingCoordinatorPresenting.swift | 2 +- .../Navigation/OnboardingNavigationCoordinator.swift | 5 ++++- .../Navigation/ProfileNavigationCoordinator.swift | 6 ++++++ .../Navigation/SignInNavigationCoordinator.swift | 7 +++++++ .../SwipingViewNavigationCoordinator.swift | 6 ++++++ 10 files changed, 56 insertions(+), 8 deletions(-) diff --git a/App/Course App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/App/Course App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ec1b168..976f403 100644 --- a/App/Course App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/App/Course App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f3035187119b3d85d325ceeb9993feda912fa8e00b088f184770c017c27e8e0f", + "originHash" : "c7aa07fe9b249c130e0e17bc0e9951f12a164ec5a5ed9f0de0c47c2a7a93e8d4", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -100,6 +100,15 @@ "version" : "100.0.0" } }, + { + "identity" : "ios-dependency-injection", + "kind" : "remoteSourceControl", + "location" : "https://github.com/strvcom/ios-dependency-injection.git", + "state" : { + "revision" : "8dbf23b56f11ecc34580745d16141e4d2a4c5129", + "version" : "1.0.3" + } + }, { "identity" : "keychainaccess", "kind" : "remoteSourceControl", diff --git a/App/Course App/App/AppCoordinator.swift b/App/Course App/App/AppCoordinator.swift index 01a6542..32526f8 100644 --- a/App/Course App/App/AppCoordinator.swift +++ b/App/Course App/App/AppCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import UIKit final class AppCoordinator: ObservableObject, ViewControllerCoordinator, CancellablesContaining { @@ -21,6 +22,7 @@ final class AppCoordinator: ObservableObject, ViewControllerCoordinator, Cancell // MARK: Public properties var cancellables = Set() var childCoordinators = [Coordinator]() + var container = Container() @Published var isSignedIn = false } @@ -34,7 +36,7 @@ extension AppCoordinator { // MARK: - Factory Methods private extension AppCoordinator { func makeTabBarFlow() -> ViewControllerCoordinator { - let mainTabBarCoordinator = MainTabBarCoordinator() + let mainTabBarCoordinator = MainTabBarCoordinator(container: container) startChildCoordinator(mainTabBarCoordinator) mainTabBarCoordinator.eventPublisher.sink { [weak self] event in self?.handle(event) @@ -44,7 +46,7 @@ private extension AppCoordinator { } func makeSignInFlow() -> ViewControllerCoordinator { - let signInCoordinator = SignInNavigationCoordinator() + let signInCoordinator = SignInNavigationCoordinator(container: container) startChildCoordinator(signInCoordinator) signInCoordinator.eventPublisher.sink { [weak self] event in self?.handle(event) diff --git a/App/Course App/Common/Navigation/Coordinator.swift b/App/Course App/Common/Navigation/Coordinator.swift index c37f7fe..2498223 100644 --- a/App/Course App/Common/Navigation/Coordinator.swift +++ b/App/Course App/Common/Navigation/Coordinator.swift @@ -5,10 +5,12 @@ // Created by Tomáš Duchoslav on 30.05.2024. // +import DependencyInjection import Foundation protocol Coordinator: AnyObject, DeeplinkHandling { var childCoordinators: [Coordinator] { get set } + var container: Container { get } func start() } diff --git a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift index 86eca4c..1ea651f 100644 --- a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift +++ b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import os import UIKit @@ -18,10 +19,16 @@ final class HomeNavigationCoordinator: NavigationControllerCoordinator, Cancella // MARK: Public properties var childCoordinators = [Coordinator]() var cancellables = Set() + var container: Container + // MARK: Lifecycle deinit { logger.info("Deinit HomeNavigationCoordinator") } + + init(container: Container) { + self.container = container + } } // MARK: - EventEmitting diff --git a/App/Course App/Scenes/Navigation/MainTabBarCoordinator.swift b/App/Course App/Scenes/Navigation/MainTabBarCoordinator.swift index d88a8e6..b49ff84 100644 --- a/App/Course App/Scenes/Navigation/MainTabBarCoordinator.swift +++ b/App/Course App/Scenes/Navigation/MainTabBarCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import os import SwiftUI import UIKit @@ -19,11 +20,16 @@ final class MainTabBarCoordinator: NSObject, TabBarControllerCoordinator, Cancel // MARK: Public Properties var cancellables = Set() var childCoordinators = [Coordinator]() + var container: Container // MARK: Lifecycle deinit { logger.info("Deinit MainTabBarCoordinator") } + + init(container: Container) { + self.container = container + } } // MARK: - Start the coordinator @@ -68,7 +74,7 @@ private extension MainTabBarCoordinator { } func makeHomeFlow() -> ViewControllerCoordinator { - let homeViewCoordinator = HomeNavigationCoordinator() + let homeViewCoordinator = HomeNavigationCoordinator(container: container) startChildCoordinator(homeViewCoordinator) homeViewCoordinator.rootViewController.tabBarItem = UITabBarItem( title: "Categories", @@ -79,14 +85,14 @@ private extension MainTabBarCoordinator { } func makeSwipingFlow() -> ViewControllerCoordinator { - let swipingNavigationCoordinator = SwipingViewNavigationCoordinator() + let swipingNavigationCoordinator = SwipingViewNavigationCoordinator(container: container) startChildCoordinator(swipingNavigationCoordinator) swipingNavigationCoordinator.rootViewController.tabBarItem = UITabBarItem(title: "Random", image: UIImage(systemName: "switch.2"), tag: 1) return swipingNavigationCoordinator } func makeProfileFlow() -> ViewControllerCoordinator { - let profileNavigationCoordinator = ProfileNavigationCoordinator() + let profileNavigationCoordinator = ProfileNavigationCoordinator(container: container) startChildCoordinator(profileNavigationCoordinator) profileNavigationCoordinator.eventPublisher.sink { [weak self] event in self?.handle(event) diff --git a/App/Course App/Scenes/Onboarding/Navigation/OnboardingCoordinatorPresenting.swift b/App/Course App/Scenes/Onboarding/Navigation/OnboardingCoordinatorPresenting.swift index 26ceaad..6a5ed61 100644 --- a/App/Course App/Scenes/Onboarding/Navigation/OnboardingCoordinatorPresenting.swift +++ b/App/Course App/Scenes/Onboarding/Navigation/OnboardingCoordinatorPresenting.swift @@ -14,7 +14,7 @@ protocol OnboardingCoordinatorPresenting { extension OnboardingCoordinatorPresenting where Self: Coordinator, Self: CancellablesContaining, Self: UINavigationControllerDelegate { func makeOnboardingFlow(navigationController: UINavigationController? = nil) -> ViewControllerCoordinator { - let coordinator = OnboardingNavigationCoordinator(navigationController: navigationController) + let coordinator = OnboardingNavigationCoordinator(container: container, navigationController: navigationController) startChildCoordinator(coordinator) if navigationController != nil { diff --git a/App/Course App/Scenes/Onboarding/Navigation/OnboardingNavigationCoordinator.swift b/App/Course App/Scenes/Onboarding/Navigation/OnboardingNavigationCoordinator.swift index 6b1f23d..c01d768 100644 --- a/App/Course App/Scenes/Onboarding/Navigation/OnboardingNavigationCoordinator.swift +++ b/App/Course App/Scenes/Onboarding/Navigation/OnboardingNavigationCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import os import SwiftUI import UIKit @@ -20,12 +21,14 @@ final class OnboardingNavigationCoordinator: NavigationControllerCoordinator, Ca // MARK: Public properties var cancellables = Set() var childCoordinators = [Coordinator]() + var container: Container deinit { logger.info("Deinit OnboardingNavigationCoordinator") } - init(navigationController: UINavigationController? = nil) { + init(container: Container, navigationController: UINavigationController? = nil) { + self.container = container if let navigationController { isPushNavigation = true self.navigationController = navigationController diff --git a/App/Course App/Scenes/Profile/Navigation/ProfileNavigationCoordinator.swift b/App/Course App/Scenes/Profile/Navigation/ProfileNavigationCoordinator.swift index ab272b6..3314037 100644 --- a/App/Course App/Scenes/Profile/Navigation/ProfileNavigationCoordinator.swift +++ b/App/Course App/Scenes/Profile/Navigation/ProfileNavigationCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import os import SwiftUI import UIKit @@ -19,11 +20,16 @@ final class ProfileNavigationCoordinator: NSObject, NavigationControllerCoordina // MARK: Public properties var cancellables = Set() var childCoordinators = [Coordinator]() + var container: Container // MARK: Lifecycle deinit { logger.info("Deinit ProfileNavigationCoordinator") } + + init(container: Container) { + self.container = container + } } // MARK: - Start coordinator diff --git a/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift b/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift index 7a24549..00b3182 100644 --- a/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift +++ b/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import DependencyInjection import os import SwiftUI import UIKit @@ -19,10 +20,16 @@ final class SignInNavigationCoordinator: NavigationControllerCoordinator, Cancel // MARK: Public properties var childCoordinators = [Coordinator]() var cancellables = Set() + var container: Container + // MARK: Lifecycle deinit { logger.info("Deinit SignInVIew") } + + init(container: Container) { + self.container = container + } } // MARK: - Start coordinator diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift index e174923..34ead89 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift @@ -5,6 +5,7 @@ // Created by Tomáš Duchoslav on 30.05.2024. // +import DependencyInjection import os import SwiftUI import UIKit @@ -16,11 +17,16 @@ final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator { // MARK: Public Properties var childCoordinators = [Coordinator]() + var container: Container // MARK: Lifecycle deinit { logger.info("Deinit SwipingViewNavigationCoordinator") } + + init(container: Container) { + self.container = container + } } // MARK: - Start coordinator From fdcf8f5d3072a164a727eb4f8f67e776e6a1e849 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Fri, 14 Jun 2024 13:22:23 +0200 Subject: [PATCH 03/13] [feat] create Manager and Service registration --- App/Course App.xcodeproj/project.pbxproj | 16 +++++++++ App/Course App/App/AppCoordinator.swift | 9 +++++ .../DIRegistration/ManagerRegistration.swift | 33 +++++++++++++++++++ .../DIRegistration/ServiceRegistration.swift | 23 +++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 App/Course App/App/DIRegistration/ManagerRegistration.swift create mode 100644 App/Course App/App/DIRegistration/ServiceRegistration.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 8946fcc..f24ebd7 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ F130890E2C19B78700FA83EC /* SwipingViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */; }; F13089102C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */; }; F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */ = {isa = PBXBuildFile; productRef = F13089122C1C55AF00FA83EC /* DependencyInjection */; }; + F13089162C1C5A1300FA83EC /* ManagerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */; }; + F13089182C1C5E0300FA83EC /* ServiceRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -206,6 +208,8 @@ F130890B2C19B69E00FA83EC /* HomeViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewEvent.swift; sourceTree = ""; }; F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewFactory.swift; sourceTree = ""; }; F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationCoordinatorEvent.swift; sourceTree = ""; }; + F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagerRegistration.swift; sourceTree = ""; }; + F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceRegistration.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -426,6 +430,7 @@ F1076BBE2C09052900653A6E /* AppCoordinator.swift */, A8830C3F2BED0EE0005CAFEA /* BuildConfiguration.swift */, A81F21F32BDC1DB700A8A301 /* Course_AppApp.swift */, + F13089142C1C59EB00FA83EC /* DIRegistration */, A8830C4B2BEF84BC005CAFEA /* GoogleService-Info.plist */, A845D9402BF364410039AA1D /* Resources */, ); @@ -636,6 +641,15 @@ path = Storage; sourceTree = ""; }; + F13089142C1C59EB00FA83EC /* DIRegistration */ = { + isa = PBXGroup; + children = ( + F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */, + F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */, + ); + path = DIRegistration; + sourceTree = ""; + }; F18F1A7E2C107639005364B7 /* Managers */ = { isa = PBXGroup; children = ( @@ -921,6 +935,7 @@ F190AB292BFDDBA5001AF32D /* SelectableButtonStyle.swift in Sources */, F1076BDF2C0DA48A00653A6E /* OnboardingNavigationCoordinatorEvent.swift in Sources */, F1076BF72C0DDA9400653A6E /* ProfileViewEvent.swift in Sources */, + F13089182C1C5E0300FA83EC /* ServiceRegistration.swift in Sources */, F13089042C1714AF00FA83EC /* StorageManager.swift in Sources */, F1076BBD2C09027100653A6E /* CoordinatorView.swift in Sources */, F1076B612C00FF7500653A6E /* Font+AppFonts.swift in Sources */, @@ -931,6 +946,7 @@ F1076BFB2C0DDCBD00653A6E /* MainTabBarCoordinatorEvent.swift in Sources */, F1076BB62C08FCFE00653A6E /* NavigationControllerCoordinator.swift in Sources */, F13088FD2C1708AB00FA83EC /* FirebaseAuthManaging.swift in Sources */, + F13089162C1C5A1300FA83EC /* ManagerRegistration.swift in Sources */, F130890C2C19B69E00FA83EC /* HomeViewEvent.swift in Sources */, F13088E92C11F58900FA83EC /* JokesRouter.swift in Sources */, F13088F82C16EC0F00FA83EC /* Action.swift in Sources */, diff --git a/App/Course App/App/AppCoordinator.swift b/App/Course App/App/AppCoordinator.swift index 32526f8..bff9b22 100644 --- a/App/Course App/App/AppCoordinator.swift +++ b/App/Course App/App/AppCoordinator.swift @@ -30,6 +30,15 @@ final class AppCoordinator: ObservableObject, ViewControllerCoordinator, Cancell extension AppCoordinator { func start() { setupAppUI() + assembleDependencyInjectionRegistration() + } +} + +// MARK: - Dependency injection registration +private extension AppCoordinator { + func assembleDependencyInjectionRegistration() { + ManagerRegistration.registerDependencies(to: container) + ServiceRegistration.registerDependecies(to: container) } } diff --git a/App/Course App/App/DIRegistration/ManagerRegistration.swift b/App/Course App/App/DIRegistration/ManagerRegistration.swift new file mode 100644 index 0000000..67278f0 --- /dev/null +++ b/App/Course App/App/DIRegistration/ManagerRegistration.swift @@ -0,0 +1,33 @@ +// +// ManagerRegistration.swift +// Course App +// +// Created by Tomáš Duchoslav on 14.06.2024. +// + +import DependencyInjection +import Foundation + +enum ManagerRegistration { + static func registerDependencies(to container: Container) { + container.autoregister( + type: StorageManaging.self, + in: .shared, + initializer: StorageManager.init) + + container.autoregister( + type: KeychainManaging.self, + in: .shared, + initializer: KeychainManager.init) + + container.autoregister( + type: APIManaging.self, + in: .shared, + initializer: APIManager.init) + + container.autoregister( + type: FirebaseAuthManaging.self, + in: .shared, + initializer: FirebaseAuthManager.init) + } +} diff --git a/App/Course App/App/DIRegistration/ServiceRegistration.swift b/App/Course App/App/DIRegistration/ServiceRegistration.swift new file mode 100644 index 0000000..c75d730 --- /dev/null +++ b/App/Course App/App/DIRegistration/ServiceRegistration.swift @@ -0,0 +1,23 @@ +// +// ServiceRegistration.swift +// Course App +// +// Created by Tomáš Duchoslav on 14.06.2024. +// + +import DependencyInjection +import Foundation + +enum ServiceRegistration { + static func registerDependecies(to container: Container) { + container.autoregister( + type: KeychainServicing.self, + in: .shared, + initializer: KeychainService.init) + + container.autoregister( + type: JokeServicing.self, + in: .shared, + initializer: JokeService.init) + } +} From a008fbadb486ee475c8cde50e580e7e7047eb7be Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Fri, 14 Jun 2024 13:47:24 +0200 Subject: [PATCH 04/13] [feat] create protocol Store --- App/Course App.xcodeproj/project.pbxproj | 4 ++++ App/Course App/Common/Protocols/Store.swift | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 App/Course App/Common/Protocols/Store.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index f24ebd7..ddf9029 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */ = {isa = PBXBuildFile; productRef = F13089122C1C55AF00FA83EC /* DependencyInjection */; }; F13089162C1C5A1300FA83EC /* ManagerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */; }; F13089182C1C5E0300FA83EC /* ServiceRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */; }; + F130891A2C1C64FF00FA83EC /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089192C1C64FF00FA83EC /* Store.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -210,6 +211,7 @@ F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationCoordinatorEvent.swift; sourceTree = ""; }; F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagerRegistration.swift; sourceTree = ""; }; F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceRegistration.swift; sourceTree = ""; }; + F13089192C1C64FF00FA83EC /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -475,6 +477,7 @@ A845D9522BF377250039AA1D /* ReusableIdentifier.swift */, F1076BDA2C0DA2EA00653A6E /* EventEmitting.swift */, F18F1A8E2C107C70005364B7 /* CancellablesContaining.swift */, + F13089192C1C64FF00FA83EC /* Store.swift */, ); path = Protocols; sourceTree = ""; @@ -924,6 +927,7 @@ F18F1A872C1076EA005364B7 /* KeychainManager.swift in Sources */, F1076BBF2C09052900653A6E /* AppCoordinator.swift in Sources */, F18F1A9E2C11B502005364B7 /* ImagesRouter.swift in Sources */, + F130891A2C1C64FF00FA83EC /* Store.swift in Sources */, F13088FF2C1708FC00FA83EC /* FirebaseAuthManager.swift in Sources */, F1076BCA2C0B474200653A6E /* OnboardingView.swift in Sources */, A8830C402BED0EE0005CAFEA /* BuildConfiguration.swift in Sources */, diff --git a/App/Course App/Common/Protocols/Store.swift b/App/Course App/Common/Protocols/Store.swift new file mode 100644 index 0000000..e24e9e8 --- /dev/null +++ b/App/Course App/Common/Protocols/Store.swift @@ -0,0 +1,17 @@ +// +// Store.swift +// Course App +// +// Created by Tomáš Duchoslav on 14.06.2024. +// + +import Foundation + +protocol Store { + associatedtype State + associatedtype Action + + @MainActor var state: State { get } + + @MainActor func send(action: Action) +} From d6c966aef7f7caeab86a2e79c280f8e8964d4f94 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Sat, 15 Jun 2024 12:46:58 +0200 Subject: [PATCH 05/13] [feat] create ViewModel as Store with state and action --- App/Course App.xcodeproj/project.pbxproj | 20 +++ App/Course App/Common/Protocols/Store.swift | 4 +- .../Navigation/SwipingViewFactory.swift | 2 +- .../SwipingViewNavigationCoordinator.swift | 2 +- .../SwipingView/Store/SwipingViewAction.swift | 15 ++ .../SwipingView/Store/SwipingViewState.swift | 21 +++ .../SwipingView/Store/SwipingViewStore.swift | 98 +++++++++++++ .../Scenes/SwipingView/SwipingView.swift | 130 +++++------------- 8 files changed, 194 insertions(+), 98 deletions(-) create mode 100644 App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift create mode 100644 App/Course App/Scenes/SwipingView/Store/SwipingViewState.swift create mode 100644 App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index ddf9029..7d5bdfe 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -87,6 +87,9 @@ F13089162C1C5A1300FA83EC /* ManagerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */; }; F13089182C1C5E0300FA83EC /* ServiceRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */; }; F130891A2C1C64FF00FA83EC /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089192C1C64FF00FA83EC /* Store.swift */; }; + F130891D2C1C659100FA83EC /* SwipingViewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130891C2C1C659100FA83EC /* SwipingViewStore.swift */; }; + F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */; }; + F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089202C1DA85900FA83EC /* SwipingViewAction.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -212,6 +215,9 @@ F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagerRegistration.swift; sourceTree = ""; }; F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceRegistration.swift; sourceTree = ""; }; F13089192C1C64FF00FA83EC /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; + F130891C2C1C659100FA83EC /* SwipingViewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewStore.swift; sourceTree = ""; }; + F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewState.swift; sourceTree = ""; }; + F13089202C1DA85900FA83EC /* SwipingViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewAction.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -466,6 +472,7 @@ isa = PBXGroup; children = ( F1076BC02C09088900653A6E /* Navigation */, + F130891B2C1C657800FA83EC /* Store */, F190AB332BFDE2D0001AF32D /* SwipingView.swift */, ); path = SwipingView; @@ -653,6 +660,16 @@ path = DIRegistration; sourceTree = ""; }; + F130891B2C1C657800FA83EC /* Store */ = { + isa = PBXGroup; + children = ( + F13089202C1DA85900FA83EC /* SwipingViewAction.swift */, + F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */, + F130891C2C1C659100FA83EC /* SwipingViewStore.swift */, + ); + path = Store; + sourceTree = ""; + }; F18F1A7E2C107639005364B7 /* Managers */ = { isa = PBXGroup; children = ( @@ -897,6 +914,7 @@ F1076BC22C0908A400653A6E /* SwipingViewNavigationCoordinator.swift in Sources */, F1076BED2C0DBF9000653A6E /* SignInNavigationCoordinator.swift in Sources */, F190AB362BFDE300001AF32D /* SwipingCard.swift in Sources */, + F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */, F1076BDD2C0DA40000653A6E /* OnboardingViewEvent.swift in Sources */, F1076BF92C0DDB2800653A6E /* ProfileNavigationCoordinatorEvent.swift in Sources */, F13088F22C14A79600FA83EC /* JokeService.swift in Sources */, @@ -960,11 +978,13 @@ F1076BD72C0D9F2200653A6E /* DeeplinkHandling.swift in Sources */, F190AB2E2BFDDDA1001AF32D /* BorderedModifier.swift in Sources */, F18F1A812C107656005364B7 /* KeychainManaging.swift in Sources */, + F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */, F1076B5A2C00FF2F00653A6E /* FontType.swift in Sources */, F130890E2C19B78700FA83EC /* SwipingViewFactory.swift in Sources */, F18F1A852C1076A8005364B7 /* KeychainManagerError.swift in Sources */, F18F1A952C11B3DB005364B7 /* NetworkingError.swift in Sources */, F190AB382BFDE372001AF32D /* ScratchView.swift in Sources */, + F130891D2C1C659100FA83EC /* SwipingViewStore.swift in Sources */, F18F1A972C11B3EB005364B7 /* HTTPHeader.swift in Sources */, F1076BE92C0DB91700653A6E /* ProfileView.swift in Sources */, F1076BB92C08FDA500653A6E /* MainTabBarCoordinator.swift in Sources */, diff --git a/App/Course App/Common/Protocols/Store.swift b/App/Course App/Common/Protocols/Store.swift index e24e9e8..8f28626 100644 --- a/App/Course App/Common/Protocols/Store.swift +++ b/App/Course App/Common/Protocols/Store.swift @@ -11,7 +11,7 @@ protocol Store { associatedtype State associatedtype Action - @MainActor var state: State { get } + var viewState: State { get } - @MainActor func send(action: Action) + func send(action: Action) } diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift index c8395c9..c41ee29 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift @@ -14,6 +14,6 @@ protocol SwipingViewFactory { extension SwipingViewFactory { func makeSwipingView(with joke: Joke? = nil) -> UIViewController { - UIHostingController(rootView: SwipingView(joke: joke)) + UIHostingController(rootView: SwipingView(store: SwipingViewStore())) } } diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift index 34ead89..19ab53b 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift @@ -39,6 +39,6 @@ extension SwipingViewNavigationCoordinator { // MARK: - Factory methods private extension SwipingViewNavigationCoordinator { func makeSwipingView() -> UIViewController { - UIHostingController(rootView: SwipingView()) + UIHostingController(rootView: SwipingView(store: SwipingViewStore())) } } diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift new file mode 100644 index 0000000..d28e6b6 --- /dev/null +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift @@ -0,0 +1,15 @@ +// +// SwipingViewAction.swift +// Course App +// +// Created by Tomáš Duchoslav on 15.06.2024. +// + +import Foundation + +enum SwipingViewAction { + case viewDidLoad + case dataLoaded([Joke]) + case didLike(String, Bool) + case swipeHalfPack +} diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewState.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewState.swift new file mode 100644 index 0000000..85425f2 --- /dev/null +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewState.swift @@ -0,0 +1,21 @@ +// +// SwipingViewState.swift +// Course App +// +// Created by Tomáš Duchoslav on 15.06.2024. +// + +import Foundation + +struct SwipingViewState { + enum Status { + case initial + case loading + case ready + } + + var jokes: [Joke] = [] + var status: Status = .initial + + static let initial = SwipingViewState() +} diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift new file mode 100644 index 0000000..c0803c0 --- /dev/null +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -0,0 +1,98 @@ +// +// SwipingViewStore.swift +// Course App +// +// Created by Tomáš Duchoslav on 14.06.2024. +// + +import os +import Foundation + +final class SwipingViewStore: Store, ObservableObject { + // MARK: Public properties + @Published var viewState: SwipingViewState = .initial + + // MARK: Private properties + private let jokeService = JokeService(apiManager: APIManager()) + private let storage = StorageManager() + private let logger = Logger() + private let category: String? + + init(joke: Joke? = nil) { + self.category = joke?.categories.first + if let joke { + viewState.jokes.append(joke) + } + } +} + + +// MARK: - Action sender +extension SwipingViewStore { + @MainActor + func send(action: SwipingViewAction) { + + } +} + +// MARK: - Functions +extension SwipingViewStore { + // MARK: Loading random jokes + func loadRandomJokes() { + Task { + try await withThrowingTaskGroup(of: JokeResponse.self) { [weak self] group in + guard let self else { + return + } + + // swiftlint:disable:next no_magic_numbers + for _ in 1...10 { + group.addTask { + if let category = self.category { + try await self.jokeService.loadJokeForCategory(category) + } else { + try await self.jokeService.loadRandomJoke() + } + } + } + + var jokes: [Joke] = [] + for try await jokeResponse in group { + let isLiked = try await storage.liked(jokeId: jokeResponse.id) + jokes.append(Joke(jokeResponse: jokeResponse, isLiked: isLiked)) + } + self.viewState.jokes.append(contentsOf: jokes) + logger.info("INFO: Count cards is \(self.viewState.jokes.count).") + } + } + } + + // MARK: Storing joke like + func storeJokeLike(jokeId: String, liked: Bool) { + Task { + try await storage.storeLike(jokeId: jokeId, liked: liked) + } + } + + // MARK: Check for jokes count + func checkCardStack() { + let fiveCards = 5 + if viewState.jokes.count <= fiveCards { + loadRandomJokes() + } + } + + // MARK: Remove joke + func removeCard(of joke: Joke) { + if let index = viewState.jokes.firstIndex(of: joke) { + loggerInfo(message: "INFO: Card number \(index) removed from Jokes array") + viewState.jokes.remove(at: index) + loggerInfo(message: "INFO: Cards count is \(viewState.jokes.count)") + } + } + + // MARK: Logger + func loggerInfo(message: String) { + logger.info("\(message)") + } +} diff --git a/App/Course App/Scenes/SwipingView/SwipingView.swift b/App/Course App/Scenes/SwipingView/SwipingView.swift index 1b07258..3c9a281 100644 --- a/App/Course App/Scenes/SwipingView/SwipingView.swift +++ b/App/Course App/Scenes/SwipingView/SwipingView.swift @@ -14,121 +14,63 @@ struct SwipingView: View { static let paddingDivider: CGFloat = 20 static let sizeDivider = 1.2 static let sizeWidthMultiplicator = 1.5 - static let fiveCard = 5 } // MARK: Private properties - private let logger = Logger() - private let jokeService = JokeService(apiManager: APIManager()) - private let category: String? - @State private var jokes: [Joke] = [] - private let storage = StorageManager() + @StateObject private var store: SwipingViewStore - init(joke: Joke? = nil) { - self.category = joke?.categories.first - if let joke { - self.jokes.append(joke) - } + init(store: SwipingViewStore) { + _store = .init(wrappedValue: store) } // MARK: View var body: some View { GeometryReader { geometry in - HStack { - Spacer() - - VStack { - ZStack { - ForEach(jokes, id: \.self) { joke in - SwipingCard( - configuration: SwipingCard.Configuration( - title: joke.categories.first ?? "", - description: joke.text, - isLiked: joke.isLiked ?? false - ), - swipeStateAction: { action in - switch action { - case let .finished(direction): - storeJokeLike(jokeId: joke.jokeID, liked: direction == .left) - removeCard(of: joke) - checkCardStack() - default: - break + if store.viewState.status == .loading { + ProgressView() + .progressViewStyle(.circular) + } else { + HStack { + Spacer() + + VStack { + ZStack { + ForEach(store.viewState.jokes, id: \.self) { joke in + SwipingCard( + configuration: SwipingCard.Configuration( + title: joke.categories.first ?? "", + description: joke.text, + isLiked: joke.isLiked ?? false + ), + swipeStateAction: { action in + switch action { + case let .finished(direction): + store.storeJokeLike(jokeId: joke.jokeID, liked: direction == .left) + store.removeCard(of: joke) + store.checkCardStack() + default: + break + } } - } - ) + ) + } } + .padding(.top, geometry.size.height / Constants.paddingDivider) + .frame(width: geometry.size.width / Constants.sizeDivider, height: (geometry.size.width / Constants.sizeDivider) * Constants.sizeWidthMultiplicator) + + Spacer() } - .padding(.top, geometry.size.height / Constants.paddingDivider) - .frame(width: geometry.size.width / Constants.sizeDivider, height: (geometry.size.width / Constants.sizeDivider) * Constants.sizeWidthMultiplicator) - Spacer() } - Spacer() } } .navigationTitle("Random") .onFirstAppear { - loadRandomJokes() + store.loadRandomJokes() } } } #Preview { - SwipingView() -} - -// MARK: - Functions -extension SwipingView { - // MARK: Loading jokes - func loadRandomJokes() { - Task { - try await withThrowingTaskGroup(of: JokeResponse.self) { group in - // swiftlint:disable:next no_magic_numbers - for _ in 1...10 { - group.addTask { - if let category { - try await jokeService.loadJokeForCategory(category) - } else { - try await jokeService.loadRandomJoke() - } - } - } - - for try await jokeResponse in group { - let isLiked = try await storage.liked(jokeId: jokeResponse.id) - jokes.append(Joke(jokeResponse: jokeResponse, isLiked: isLiked)) - } - logger.info("INFO: Count cards is \(jokes.count).") - } - } - } - - // MARK: Storing joke like - func storeJokeLike(jokeId: String, liked: Bool) { - Task { - try await storage.storeLike(jokeId: jokeId, liked: liked) - } - } - - // MARK: Check for jokes count - func checkCardStack() { - if jokes.count <= Constants.fiveCard { - loadRandomJokes() - } - } - - // MARK: Remove joke - func removeCard(of joke: Joke) { - if let index = jokes.firstIndex(of: joke) { - loggerInfo(message: "INFO: Card number \(index) removed from Jokes array") - jokes.remove(at: index) - loggerInfo(message: "INFO: Cards count is \(jokes.count)") - } - } - - // MARK: Logger - func loggerInfo(message: String) { - logger.info("\(message)") - } + SwipingView(store: SwipingViewStore()) } From d1513cfdb8947e5fdb44a91d8ab15a5328db6f46 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Sat, 15 Jun 2024 16:02:25 +0200 Subject: [PATCH 06/13] [feat] add send Actions, handle everythings --- App/Course App.xcodeproj/project.pbxproj | 4 ++ .../HomeNavigationCoordinator.swift | 16 +++++- .../Navigation/SwipingViewFactory.swift | 8 +-- .../SwipingViewNavigationCoordinator.swift | 11 ++-- .../SwipingView/Store/SwipingViewAction.swift | 4 +- .../SwipingView/Store/SwipingViewStore.swift | 55 +++++++++++++------ .../Scenes/SwipingView/SwipingView.swift | 6 +- .../Scenes/SwipingView/SwipingViewEvent.swift | 12 ++++ 8 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 App/Course App/Scenes/SwipingView/SwipingViewEvent.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 7d5bdfe..cd33307 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ F130891D2C1C659100FA83EC /* SwipingViewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130891C2C1C659100FA83EC /* SwipingViewStore.swift */; }; F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */; }; F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089202C1DA85900FA83EC /* SwipingViewAction.swift */; }; + F13089232C1DCB0C00FA83EC /* SwipingViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -218,6 +219,7 @@ F130891C2C1C659100FA83EC /* SwipingViewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewStore.swift; sourceTree = ""; }; F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewState.swift; sourceTree = ""; }; F13089202C1DA85900FA83EC /* SwipingViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewAction.swift; sourceTree = ""; }; + F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewEvent.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -474,6 +476,7 @@ F1076BC02C09088900653A6E /* Navigation */, F130891B2C1C657800FA83EC /* Store */, F190AB332BFDE2D0001AF32D /* SwipingView.swift */, + F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */, ); path = SwipingView; sourceTree = ""; @@ -926,6 +929,7 @@ F13088E42C11F08000FA83EC /* APIManager.swift in Sources */, F18F1A992C11B400005364B7 /* HTTPMethod.swift in Sources */, A845D9532BF377250039AA1D /* ReusableIdentifier.swift in Sources */, + F13089232C1DCB0C00FA83EC /* SwipingViewEvent.swift in Sources */, F18F1A892C107725005364B7 /* KeychainServicing.swift in Sources */, F1076BCF2C0B552B00653A6E /* NavigationButtonStyle.swift in Sources */, F1076BD52C0D9A7800653A6E /* OpacityVisibility.swift in Sources */, diff --git a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift index 1ea651f..3724d6f 100644 --- a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift +++ b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift @@ -8,9 +8,10 @@ import Combine import DependencyInjection import os +import SwiftUI import UIKit -final class HomeNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining, SwipingViewFactory { +final class HomeNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining { // MARK: Private properties private(set) lazy var navigationController: UINavigationController = CustomNavigationController() private let logger = Logger() @@ -55,15 +56,24 @@ private extension HomeNavigationCoordinator { .store(in: &cancellables) return homeView } + + func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { + let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) + store.eventPublisher.sink { [weak self] _ in + self?.navigationController.popToRootViewController(animated: true) + } + .store(in: &cancellables) + return UIHostingController(rootView: SwipingView(store: store)) + } } // MARK: - Handling events -private extension HomeNavigationCoordinator { +extension HomeNavigationCoordinator { func handle(_ event: HomeViewEvent) { switch event { case let .itemTapped(joke): logger.info("Joke on home screen was tapped \(joke.text)") - navigationController.pushViewController(makeSwipingView(with: joke), animated: true) + navigationController.pushViewController(makeSwipingView(with: joke, isChildCoordinator: true), animated: true) } } } diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift index c41ee29..0c4b684 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift @@ -9,11 +9,5 @@ import SwiftUI import UIKit protocol SwipingViewFactory { - func makeSwipingView(with joke: Joke?) -> UIViewController -} - -extension SwipingViewFactory { - func makeSwipingView(with joke: Joke? = nil) -> UIViewController { - UIHostingController(rootView: SwipingView(store: SwipingViewStore())) - } + func makeSwipingView(with joke: Joke?, isChildCoordinator: Bool) -> UIViewController } diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift index 19ab53b..fc9d17a 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift @@ -5,12 +5,13 @@ // Created by Tomáš Duchoslav on 30.05.2024. // +import Combine import DependencyInjection import os import SwiftUI import UIKit -final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator { +final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining { // MARK: Private properties private(set) lazy var navigationController: UINavigationController = CustomNavigationController() private let logger = Logger() @@ -18,6 +19,7 @@ final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator { // MARK: Public Properties var childCoordinators = [Coordinator]() var container: Container + var cancellables = Set() // MARK: Lifecycle deinit { @@ -37,8 +39,9 @@ extension SwipingViewNavigationCoordinator { } // MARK: - Factory methods -private extension SwipingViewNavigationCoordinator { - func makeSwipingView() -> UIViewController { - UIHostingController(rootView: SwipingView(store: SwipingViewStore())) +extension SwipingViewNavigationCoordinator: SwipingViewFactory { + func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { + let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) + return UIHostingController(rootView: SwipingView(store: store)) } } diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift index d28e6b6..80f7e4a 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewAction.swift @@ -10,6 +10,6 @@ import Foundation enum SwipingViewAction { case viewDidLoad case dataLoaded([Joke]) - case didLike(String, Bool) - case swipeHalfPack + case didLike(Joke, Bool) + case noMoreJokes } diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift index c0803c0..8dcb95d 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -5,6 +5,7 @@ // Created by Tomáš Duchoslav on 14.06.2024. // +import Combine import os import Foundation @@ -17,8 +18,11 @@ final class SwipingViewStore: Store, ObservableObject { private let storage = StorageManager() private let logger = Logger() private let category: String? + private let eventSubject = PassthroughSubject() + private var isChildCoordinator: Bool = false - init(joke: Joke? = nil) { + init(joke: Joke? = nil, isChildCoordinator: Bool = false) { + self.isChildCoordinator = isChildCoordinator self.category = joke?.categories.first if let joke { viewState.jokes.append(joke) @@ -26,17 +30,44 @@ final class SwipingViewStore: Store, ObservableObject { } } +// MARK: - Event emitter +extension SwipingViewStore: EventEmitting { + var eventPublisher: AnyPublisher { + eventSubject.eraseToAnyPublisher() + } +} + // MARK: - Action sender extension SwipingViewStore { @MainActor func send(action: SwipingViewAction) { - + switch action { + case .viewDidLoad: + viewState.status = .loading + loadRandomJokes() + case let .dataLoaded(jokes): + viewState.jokes.append(contentsOf: jokes) + viewState.status = .ready + case let .didLike(joke, liked): + storeJokeLike(joke: joke, liked: liked) + removeCard(of: joke) + if viewState.jokes.isEmpty { + send(action: .noMoreJokes) + } + case .noMoreJokes: + if isChildCoordinator { + eventSubject.send(.dismiss) + } else { + viewState.status = .loading + loadRandomJokes() + } + } } } // MARK: - Functions -extension SwipingViewStore { +private extension SwipingViewStore { // MARK: Loading random jokes func loadRandomJokes() { Task { @@ -46,7 +77,7 @@ extension SwipingViewStore { } // swiftlint:disable:next no_magic_numbers - for _ in 1...10 { + for _ in 1...5 { group.addTask { if let category = self.category { try await self.jokeService.loadJokeForCategory(category) @@ -61,24 +92,16 @@ extension SwipingViewStore { let isLiked = try await storage.liked(jokeId: jokeResponse.id) jokes.append(Joke(jokeResponse: jokeResponse, isLiked: isLiked)) } - self.viewState.jokes.append(contentsOf: jokes) - logger.info("INFO: Count cards is \(self.viewState.jokes.count).") + + await send(action: .dataLoaded(jokes)) } } } // MARK: Storing joke like - func storeJokeLike(jokeId: String, liked: Bool) { + func storeJokeLike(joke: Joke, liked: Bool) { Task { - try await storage.storeLike(jokeId: jokeId, liked: liked) - } - } - - // MARK: Check for jokes count - func checkCardStack() { - let fiveCards = 5 - if viewState.jokes.count <= fiveCards { - loadRandomJokes() + try await storage.storeLike(jokeId: joke.jokeID, liked: liked) } } diff --git a/App/Course App/Scenes/SwipingView/SwipingView.swift b/App/Course App/Scenes/SwipingView/SwipingView.swift index 3c9a281..0db8128 100644 --- a/App/Course App/Scenes/SwipingView/SwipingView.swift +++ b/App/Course App/Scenes/SwipingView/SwipingView.swift @@ -45,9 +45,7 @@ struct SwipingView: View { swipeStateAction: { action in switch action { case let .finished(direction): - store.storeJokeLike(jokeId: joke.jokeID, liked: direction == .left) - store.removeCard(of: joke) - store.checkCardStack() + store.send(action: .didLike(joke, direction == .left)) default: break } @@ -66,7 +64,7 @@ struct SwipingView: View { } .navigationTitle("Random") .onFirstAppear { - store.loadRandomJokes() + store.send(action: .viewDidLoad) } } } diff --git a/App/Course App/Scenes/SwipingView/SwipingViewEvent.swift b/App/Course App/Scenes/SwipingView/SwipingViewEvent.swift new file mode 100644 index 0000000..446ce01 --- /dev/null +++ b/App/Course App/Scenes/SwipingView/SwipingViewEvent.swift @@ -0,0 +1,12 @@ +// +// SwipingViewEvent.swift +// Course App +// +// Created by Tomáš Duchoslav on 15.06.2024. +// + +import Foundation + +enum SwipingViewEvent { + case dismiss +} From b18ee4aa600c6757f5e22416e029a5259f886382 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Sat, 15 Jun 2024 16:26:51 +0200 Subject: [PATCH 07/13] [feat] create SwipingViewCoordinating to be able handle more Coordinators --- App/Course App.xcodeproj/project.pbxproj | 8 +++--- .../HomeNavigationCoordinator.swift | 11 +------- .../Navigation/SwipingViewCoordinating.swift | 25 +++++++++++++++++++ .../Navigation/SwipingViewFactory.swift | 13 ---------- .../SwipingViewNavigationCoordinator.swift | 10 +------- .../SwipingView/Store/SwipingViewStore.swift | 4 +-- 6 files changed, 33 insertions(+), 38 deletions(-) create mode 100644 App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift delete mode 100644 App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index cd33307..fe08db4 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -81,7 +81,7 @@ F13089022C17146600FA83EC /* StorageManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089012C17146600FA83EC /* StorageManaging.swift */; }; F13089042C1714AF00FA83EC /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089032C1714AF00FA83EC /* StorageManager.swift */; }; F130890C2C19B69E00FA83EC /* HomeViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890B2C19B69E00FA83EC /* HomeViewEvent.swift */; }; - F130890E2C19B78700FA83EC /* SwipingViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */; }; + F130890E2C19B78700FA83EC /* SwipingViewCoordinating.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890D2C19B78700FA83EC /* SwipingViewCoordinating.swift */; }; F13089102C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */; }; F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */ = {isa = PBXBuildFile; productRef = F13089122C1C55AF00FA83EC /* DependencyInjection */; }; F13089162C1C5A1300FA83EC /* ManagerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */; }; @@ -211,7 +211,7 @@ F13089012C17146600FA83EC /* StorageManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManaging.swift; sourceTree = ""; }; F13089032C1714AF00FA83EC /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; F130890B2C19B69E00FA83EC /* HomeViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewEvent.swift; sourceTree = ""; }; - F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewFactory.swift; sourceTree = ""; }; + F130890D2C19B78700FA83EC /* SwipingViewCoordinating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewCoordinating.swift; sourceTree = ""; }; F130890F2C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationCoordinatorEvent.swift; sourceTree = ""; }; F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagerRegistration.swift; sourceTree = ""; }; F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceRegistration.swift; sourceTree = ""; }; @@ -536,7 +536,7 @@ isa = PBXGroup; children = ( F1076BC12C0908A400653A6E /* SwipingViewNavigationCoordinator.swift */, - F130890D2C19B78700FA83EC /* SwipingViewFactory.swift */, + F130890D2C19B78700FA83EC /* SwipingViewCoordinating.swift */, ); path = Navigation; sourceTree = ""; @@ -984,7 +984,7 @@ F18F1A812C107656005364B7 /* KeychainManaging.swift in Sources */, F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */, F1076B5A2C00FF2F00653A6E /* FontType.swift in Sources */, - F130890E2C19B78700FA83EC /* SwipingViewFactory.swift in Sources */, + F130890E2C19B78700FA83EC /* SwipingViewCoordinating.swift in Sources */, F18F1A852C1076A8005364B7 /* KeychainManagerError.swift in Sources */, F18F1A952C11B3DB005364B7 /* NetworkingError.swift in Sources */, F190AB382BFDE372001AF32D /* ScratchView.swift in Sources */, diff --git a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift index 3724d6f..a4e0ce7 100644 --- a/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift +++ b/App/Course App/Scenes/Home/Navigation/HomeNavigationCoordinator.swift @@ -11,7 +11,7 @@ import os import SwiftUI import UIKit -final class HomeNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining { +final class HomeNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining, SwipingViewCoordinating { // MARK: Private properties private(set) lazy var navigationController: UINavigationController = CustomNavigationController() private let logger = Logger() @@ -56,15 +56,6 @@ private extension HomeNavigationCoordinator { .store(in: &cancellables) return homeView } - - func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { - let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) - store.eventPublisher.sink { [weak self] _ in - self?.navigationController.popToRootViewController(animated: true) - } - .store(in: &cancellables) - return UIHostingController(rootView: SwipingView(store: store)) - } } // MARK: - Handling events diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift new file mode 100644 index 0000000..6892061 --- /dev/null +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift @@ -0,0 +1,25 @@ +// +// SwipingViewCoordinating.swift +// Course App +// +// Created by Tomáš Duchoslav on 12.06.2024. +// + +import SwiftUI +import UIKit + +protocol SwipingViewCoordinating: ViewControllerCoordinator { + func makeSwipingView(with joke: Joke?, isChildCoordinator: Bool) -> UIViewController +} + +extension SwipingViewCoordinating where Self: CancellablesContaining, Self: NavigationControllerCoordinator { + func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { + let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) + store.eventPublisher.sink { [weak self] _ in + self?.navigationController.popToRootViewController(animated: true) + } + .store(in: &cancellables) + + return UIHostingController(rootView: SwipingView(store: store)) + } +} diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift deleted file mode 100644 index 0c4b684..0000000 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewFactory.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// SwipingViewFactory.swift -// Course App -// -// Created by Tomáš Duchoslav on 12.06.2024. -// - -import SwiftUI -import UIKit - -protocol SwipingViewFactory { - func makeSwipingView(with joke: Joke?, isChildCoordinator: Bool) -> UIViewController -} diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift index fc9d17a..50cc1c7 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewNavigationCoordinator.swift @@ -11,7 +11,7 @@ import os import SwiftUI import UIKit -final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining { +final class SwipingViewNavigationCoordinator: NavigationControllerCoordinator, CancellablesContaining, SwipingViewCoordinating { // MARK: Private properties private(set) lazy var navigationController: UINavigationController = CustomNavigationController() private let logger = Logger() @@ -37,11 +37,3 @@ extension SwipingViewNavigationCoordinator { navigationController.setViewControllers([makeSwipingView()], animated: false) } } - -// MARK: - Factory methods -extension SwipingViewNavigationCoordinator: SwipingViewFactory { - func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { - let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) - return UIHostingController(rootView: SwipingView(store: store)) - } -} diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift index 8dcb95d..ea1c3fe 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -6,8 +6,8 @@ // import Combine -import os import Foundation +import os final class SwipingViewStore: Store, ObservableObject { // MARK: Public properties @@ -19,7 +19,7 @@ final class SwipingViewStore: Store, ObservableObject { private let logger = Logger() private let category: String? private let eventSubject = PassthroughSubject() - private var isChildCoordinator: Bool = false + private var isChildCoordinator = false init(joke: Joke? = nil, isChildCoordinator: Bool = false) { self.isChildCoordinator = isChildCoordinator From ba6d48c5a28b4717cf42a85aa34921c295825f63 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Sat, 15 Jun 2024 18:12:08 +0200 Subject: [PATCH 08/13] [feat] add container resolve to swipingViewStore --- App/Course App.xcodeproj/project.pbxproj | 4 +++ App/Course App/App/AppCoordinator.swift | 1 + .../DIRegistration/StoreRegistration.swift | 18 +++++++++++++ .../Networking/Storage/StorageManager.swift | 2 +- .../Networking/Storage/StorageManaging.swift | 1 + .../Scenes/Home/HomeViewController.swift | 2 +- .../Navigation/SwipingViewCoordinating.swift | 2 +- .../SwipingView/Store/SwipingViewStore.swift | 26 +++++++++++-------- .../Scenes/SwipingView/SwipingView.swift | 6 ++--- 9 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 App/Course App/App/DIRegistration/StoreRegistration.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index fe08db4..436ba3a 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */; }; F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089202C1DA85900FA83EC /* SwipingViewAction.swift */; }; F13089232C1DCB0C00FA83EC /* SwipingViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */; }; + F13089252C1DE6E000FA83EC /* StoreRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089242C1DE6E000FA83EC /* StoreRegistration.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -220,6 +221,7 @@ F130891E2C1DA84A00FA83EC /* SwipingViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewState.swift; sourceTree = ""; }; F13089202C1DA85900FA83EC /* SwipingViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewAction.swift; sourceTree = ""; }; F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewEvent.swift; sourceTree = ""; }; + F13089242C1DE6E000FA83EC /* StoreRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreRegistration.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -659,6 +661,7 @@ children = ( F13089152C1C5A1300FA83EC /* ManagerRegistration.swift */, F13089172C1C5E0300FA83EC /* ServiceRegistration.swift */, + F13089242C1DE6E000FA83EC /* StoreRegistration.swift */, ); path = DIRegistration; sourceTree = ""; @@ -956,6 +959,7 @@ A845D9552BF377A70039AA1D /* UICollectionView+ReusableIdentifier.swift in Sources */, F18F1A8F2C107C70005364B7 /* CancellablesContaining.swift in Sources */, F1076BE52C0DB83300653A6E /* ProfileNavigationCoordinator.swift in Sources */, + F13089252C1DE6E000FA83EC /* StoreRegistration.swift in Sources */, F13089102C19B81F00FA83EC /* HomeNavigationCoordinatorEvent.swift in Sources */, F13088E72C11F32900FA83EC /* JokeResponse.swift in Sources */, F190AB292BFDDBA5001AF32D /* SelectableButtonStyle.swift in Sources */, diff --git a/App/Course App/App/AppCoordinator.swift b/App/Course App/App/AppCoordinator.swift index bff9b22..593dd3e 100644 --- a/App/Course App/App/AppCoordinator.swift +++ b/App/Course App/App/AppCoordinator.swift @@ -39,6 +39,7 @@ private extension AppCoordinator { func assembleDependencyInjectionRegistration() { ManagerRegistration.registerDependencies(to: container) ServiceRegistration.registerDependecies(to: container) + StoreRegistration.registerDependencies(to: container) } } diff --git a/App/Course App/App/DIRegistration/StoreRegistration.swift b/App/Course App/App/DIRegistration/StoreRegistration.swift new file mode 100644 index 0000000..bca62e1 --- /dev/null +++ b/App/Course App/App/DIRegistration/StoreRegistration.swift @@ -0,0 +1,18 @@ +// +// StoreRegistration.swift +// Course App +// +// Created by Tomáš Duchoslav on 15.06.2024. +// + +import DependencyInjection +import Foundation + +enum StoreRegistration { + static func registerDependencies(to container: Container) { + container.autoregister( + type: SwipingViewStore.self, + in: .new, + initializer: SwipingViewStore.init) + } +} diff --git a/App/Course App/Networking/Storage/StorageManager.swift b/App/Course App/Networking/Storage/StorageManager.swift index 9776e79..5aac7a1 100644 --- a/App/Course App/Networking/Storage/StorageManager.swift +++ b/App/Course App/Networking/Storage/StorageManager.swift @@ -27,7 +27,7 @@ final class StorageManager: StorageManaging { } } - func liked(jokeId: String) async throws -> Bool { + func loadLiked(jokeId: String) async throws -> Bool { let docRef = database.collection("jokesLikes").document(jokeId) do { let document = try await docRef.getDocument() diff --git a/App/Course App/Networking/Storage/StorageManaging.swift b/App/Course App/Networking/Storage/StorageManaging.swift index 9245522..7f46a74 100644 --- a/App/Course App/Networking/Storage/StorageManaging.swift +++ b/App/Course App/Networking/Storage/StorageManaging.swift @@ -10,4 +10,5 @@ import Foundation protocol StorageManaging { func storeLike(jokeId: String, liked: Bool) async throws + func loadLiked(jokeId: String) async throws -> Bool } diff --git a/App/Course App/Scenes/Home/HomeViewController.swift b/App/Course App/Scenes/Home/HomeViewController.swift index 3a60092..2d7bae6 100644 --- a/App/Course App/Scenes/Home/HomeViewController.swift +++ b/App/Course App/Scenes/Home/HomeViewController.swift @@ -109,7 +109,7 @@ extension HomeViewController { for sectionIndex in dataProvider.data.indices { for jokeIndex in dataProvider.data[sectionIndex].jokes.indices { - let isLiked = try await storage.liked(jokeId: dataProvider.data[sectionIndex].jokes[jokeIndex].jokeID) + let isLiked = try await storage.loadLiked(jokeId: dataProvider.data[sectionIndex].jokes[jokeIndex].jokeID) dataProvider.data[sectionIndex].jokes[jokeIndex].isLiked = isLiked } } diff --git a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift index 6892061..5a70e48 100644 --- a/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift +++ b/App/Course App/Scenes/SwipingView/Navigation/SwipingViewCoordinating.swift @@ -14,7 +14,7 @@ protocol SwipingViewCoordinating: ViewControllerCoordinator { extension SwipingViewCoordinating where Self: CancellablesContaining, Self: NavigationControllerCoordinator { func makeSwipingView(with joke: Joke? = nil, isChildCoordinator: Bool = false) -> UIViewController { - let store = SwipingViewStore(isChildCoordinator: isChildCoordinator) + let store = container.resolve(type: SwipingViewStore.self) store.eventPublisher.sink { [weak self] _ in self?.navigationController.popToRootViewController(animated: true) } diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift index ea1c3fe..9b29735 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -14,19 +14,23 @@ final class SwipingViewStore: Store, ObservableObject { @Published var viewState: SwipingViewState = .initial // MARK: Private properties - private let jokeService = JokeService(apiManager: APIManager()) - private let storage = StorageManager() + private let jokeService: JokeServicing + private let storage: StorageManaging + private let keychainService: KeychainServicing private let logger = Logger() - private let category: String? + private var category: String? = nil private let eventSubject = PassthroughSubject() - private var isChildCoordinator = false + private var isChildCoordinator: Bool = false - init(joke: Joke? = nil, isChildCoordinator: Bool = false) { - self.isChildCoordinator = isChildCoordinator - self.category = joke?.categories.first - if let joke { - viewState.jokes.append(joke) - } + init(/*joke: Joke? = nil, isChildCoordinator: Bool = false,*/ storage: StorageManaging, keychainSevice: KeychainServicing, jokeService: JokeServicing) { + self.storage = storage + self.keychainService = keychainSevice + self.jokeService = jokeService +// self.isChildCoordinator = isChildCoordinator +// self.category = joke?.categories.first +// if let joke { +// viewState.jokes.append(joke) +// } } } @@ -89,7 +93,7 @@ private extension SwipingViewStore { var jokes: [Joke] = [] for try await jokeResponse in group { - let isLiked = try await storage.liked(jokeId: jokeResponse.id) + let isLiked = try await storage.loadLiked(jokeId: jokeResponse.id) jokes.append(Joke(jokeResponse: jokeResponse, isLiked: isLiked)) } diff --git a/App/Course App/Scenes/SwipingView/SwipingView.swift b/App/Course App/Scenes/SwipingView/SwipingView.swift index 0db8128..8cf6d2d 100644 --- a/App/Course App/Scenes/SwipingView/SwipingView.swift +++ b/App/Course App/Scenes/SwipingView/SwipingView.swift @@ -69,6 +69,6 @@ struct SwipingView: View { } } -#Preview { - SwipingView(store: SwipingViewStore()) -} +//#Preview { +// SwipingView(store: SwipingViewStore()) +//} From 45f01e71ce8ec10afd8dc3c0ed70010bb1efe58f Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Mon, 17 Jun 2024 11:54:08 +0200 Subject: [PATCH 09/13] [feat] create Store for SignInView --- App/Course App.xcodeproj/project.pbxproj | 20 +++++++ .../DIRegistration/StoreRegistration.swift | 9 ++- App/Course App/Common/Protocols/Store.swift | 4 +- .../SignInNavigationCoordinator.swift | 6 +- App/Course App/Scenes/SignIn/SignInView.swift | 44 +++----------- .../SignIn/Store/SignInViewAction.swift | 13 ++++ .../Scenes/SignIn/Store/SignInViewState.swift | 23 ++++++++ .../Scenes/SignIn/Store/SignInViewStore.swift | 59 +++++++++++++++++++ .../SwipingView/Store/SwipingViewStore.swift | 1 + 9 files changed, 138 insertions(+), 41 deletions(-) create mode 100644 App/Course App/Scenes/SignIn/Store/SignInViewAction.swift create mode 100644 App/Course App/Scenes/SignIn/Store/SignInViewState.swift create mode 100644 App/Course App/Scenes/SignIn/Store/SignInViewStore.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 436ba3a..59437bb 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -92,6 +92,9 @@ F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089202C1DA85900FA83EC /* SwipingViewAction.swift */; }; F13089232C1DCB0C00FA83EC /* SwipingViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */; }; F13089252C1DE6E000FA83EC /* StoreRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089242C1DE6E000FA83EC /* StoreRegistration.swift */; }; + F13089282C202D2B00FA83EC /* SignInViewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089272C202D2B00FA83EC /* SignInViewStore.swift */; }; + F130892B2C202D3E00FA83EC /* SignInViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130892A2C202D3E00FA83EC /* SignInViewAction.swift */; }; + F130892D2C202D4500FA83EC /* SignInViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130892C2C202D4500FA83EC /* SignInViewState.swift */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -222,6 +225,9 @@ F13089202C1DA85900FA83EC /* SwipingViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewAction.swift; sourceTree = ""; }; F13089222C1DCB0C00FA83EC /* SwipingViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingViewEvent.swift; sourceTree = ""; }; F13089242C1DE6E000FA83EC /* StoreRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreRegistration.swift; sourceTree = ""; }; + F13089272C202D2B00FA83EC /* SignInViewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewStore.swift; sourceTree = ""; }; + F130892A2C202D3E00FA83EC /* SignInViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewAction.swift; sourceTree = ""; }; + F130892C2C202D4500FA83EC /* SignInViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewState.swift; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -598,6 +604,7 @@ F1076BEB2C0DBF8600653A6E /* Navigation */, F1076BEE2C0DBF9A00653A6E /* SignInView.swift */, F1076BF02C0DC1DA00653A6E /* SignInViewEvent.swift */, + F13089262C202D1800FA83EC /* Store */, ); path = SignIn; sourceTree = ""; @@ -676,6 +683,16 @@ path = Store; sourceTree = ""; }; + F13089262C202D1800FA83EC /* Store */ = { + isa = PBXGroup; + children = ( + F130892A2C202D3E00FA83EC /* SignInViewAction.swift */, + F130892C2C202D4500FA83EC /* SignInViewState.swift */, + F13089272C202D2B00FA83EC /* SignInViewStore.swift */, + ); + path = Store; + sourceTree = ""; + }; F18F1A7E2C107639005364B7 /* Managers */ = { isa = PBXGroup; children = ( @@ -921,6 +938,7 @@ F1076BED2C0DBF9000653A6E /* SignInNavigationCoordinator.swift in Sources */, F190AB362BFDE300001AF32D /* SwipingCard.swift in Sources */, F13089212C1DA85900FA83EC /* SwipingViewAction.swift in Sources */, + F13089282C202D2B00FA83EC /* SignInViewStore.swift in Sources */, F1076BDD2C0DA40000653A6E /* OnboardingViewEvent.swift in Sources */, F1076BF92C0DDB2800653A6E /* ProfileNavigationCoordinatorEvent.swift in Sources */, F13088F22C14A79600FA83EC /* JokeService.swift in Sources */, @@ -982,6 +1000,7 @@ F13088F82C16EC0F00FA83EC /* Action.swift in Sources */, F1076BC52C090C0600653A6E /* HomeNavigationCoordinator.swift in Sources */, F1076B672C0101A700653A6E /* CustomNavigationController.swift in Sources */, + F130892D2C202D4500FA83EC /* SignInViewState.swift in Sources */, F1076BFD2C0F0B8500653A6E /* CustomError.swift in Sources */, F1076BD72C0D9F2200653A6E /* DeeplinkHandling.swift in Sources */, F190AB2E2BFDDDA1001AF32D /* BorderedModifier.swift in Sources */, @@ -991,6 +1010,7 @@ F130890E2C19B78700FA83EC /* SwipingViewCoordinating.swift in Sources */, F18F1A852C1076A8005364B7 /* KeychainManagerError.swift in Sources */, F18F1A952C11B3DB005364B7 /* NetworkingError.swift in Sources */, + F130892B2C202D3E00FA83EC /* SignInViewAction.swift in Sources */, F190AB382BFDE372001AF32D /* ScratchView.swift in Sources */, F130891D2C1C659100FA83EC /* SwipingViewStore.swift in Sources */, F18F1A972C11B3EB005364B7 /* HTTPHeader.swift in Sources */, diff --git a/App/Course App/App/DIRegistration/StoreRegistration.swift b/App/Course App/App/DIRegistration/StoreRegistration.swift index bca62e1..e3df20f 100644 --- a/App/Course App/App/DIRegistration/StoreRegistration.swift +++ b/App/Course App/App/DIRegistration/StoreRegistration.swift @@ -13,6 +13,13 @@ enum StoreRegistration { container.autoregister( type: SwipingViewStore.self, in: .new, - initializer: SwipingViewStore.init) + initializer: SwipingViewStore.init + ) + + container.autoregister( + type: SignInViewStore.self, + in: .new, + initializer: SignInViewStore.init + ) } } diff --git a/App/Course App/Common/Protocols/Store.swift b/App/Course App/Common/Protocols/Store.swift index 8f28626..36c0081 100644 --- a/App/Course App/Common/Protocols/Store.swift +++ b/App/Course App/Common/Protocols/Store.swift @@ -11,7 +11,7 @@ protocol Store { associatedtype State associatedtype Action - var viewState: State { get } + @MainActor var viewState: State { get } - func send(action: Action) + @MainActor func send(action: Action) } diff --git a/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift b/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift index 00b3182..6d74dfb 100644 --- a/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift +++ b/App/Course App/Scenes/SignIn/Navigation/SignInNavigationCoordinator.swift @@ -53,12 +53,12 @@ private extension SignInNavigationCoordinator { } func makeSignInView() -> UIViewController { - let signInView = SignInView() - signInView.eventPublisher.sink { [weak self] event in + let store = container.resolve(type: SignInViewStore.self) + store.eventPublisher.sink { [weak self] event in self?.handle(event) } .store(in: &cancellables) - return UIHostingController(rootView: signInView) + return UIHostingController(rootView: SignInView(store: store)) } } diff --git a/App/Course App/Scenes/SignIn/SignInView.swift b/App/Course App/Scenes/SignIn/SignInView.swift index fe4ff1d..83f563a 100644 --- a/App/Course App/Scenes/SignIn/SignInView.swift +++ b/App/Course App/Scenes/SignIn/SignInView.swift @@ -10,53 +10,27 @@ import os import SwiftUI struct SignInView: View { - // MARK: Private properties - @State private var emailField: String - @State private var passwordField: String - private let eventSubject = PassthroughSubject() - private let authManager = FirebaseAuthManager() - private let logger = Logger() + @StateObject private var store: SignInViewStore - // MARK: Lifecycle - init(emailField: String = "Test@test.test", passwordField: String = "test123") { - self.emailField = emailField - self.passwordField = passwordField + init(store: SignInViewStore) { + _store = .init(wrappedValue: store) } var body: some View { Form { - TextField("Email", text: $emailField) - SecureField("password", text: $passwordField) + TextField("Email", text: $store.viewState.emailField) + SecureField("password", text: $store.viewState.passwordField) Button("SignIn") { - signIn() + store.send(action: .signInButtonTapped) } } .navigationTitle("SignIn") - } -} - -// MARK: - Event emitter -extension SignInView: EventEmitting { - var eventPublisher: AnyPublisher { - eventSubject.eraseToAnyPublisher() - } -} - -// MARK: - Functions -private extension SignInView { - @MainActor - func signIn() { - Task { - do { - try await authManager.signIn(Credentials(email: emailField, password: passwordField)) - eventSubject.send(.signedIn) - } catch { - logger.info("ERROR: \(error)") - } + .onFirstAppear { + store.send(action: .viewDidLoad) } } } #Preview { - SignInView() + SignInView(store: SignInViewStore(authManager: FirebaseAuthManager())) } diff --git a/App/Course App/Scenes/SignIn/Store/SignInViewAction.swift b/App/Course App/Scenes/SignIn/Store/SignInViewAction.swift new file mode 100644 index 0000000..76dd884 --- /dev/null +++ b/App/Course App/Scenes/SignIn/Store/SignInViewAction.swift @@ -0,0 +1,13 @@ +// +// SignInViewAction.swift +// Course App +// +// Created by Tomáš Duchoslav on 17.06.2024. +// + +import Foundation + +enum SignInViewAction { + case signInButtonTapped + case viewDidLoad +} diff --git a/App/Course App/Scenes/SignIn/Store/SignInViewState.swift b/App/Course App/Scenes/SignIn/Store/SignInViewState.swift new file mode 100644 index 0000000..1429a86 --- /dev/null +++ b/App/Course App/Scenes/SignIn/Store/SignInViewState.swift @@ -0,0 +1,23 @@ +// +// SignInViewState.swift +// Course App +// +// Created by Tomáš Duchoslav on 17.06.2024. +// + +import Foundation + +struct SignInViewState { + enum Status { + case initial + case signingIn + case ready + case loading + } + + var emailField: String = "" + var passwordField: String = "" + var status: Status = .initial + + static let initial = SignInViewState() +} diff --git a/App/Course App/Scenes/SignIn/Store/SignInViewStore.swift b/App/Course App/Scenes/SignIn/Store/SignInViewStore.swift new file mode 100644 index 0000000..560aaad --- /dev/null +++ b/App/Course App/Scenes/SignIn/Store/SignInViewStore.swift @@ -0,0 +1,59 @@ +// +// SignInViewStore.swift +// Course App +// +// Created by Tomáš Duchoslav on 17.06.2024. +// + +import Combine +import Foundation +import os + +final class SignInViewStore: Store, ObservableObject { + // MARK: Private properties + private let eventSubject = PassthroughSubject() + private let authManager: FirebaseAuthManaging + private let logger = Logger() + + @Published var viewState: SignInViewState = .initial + + init(authManager: FirebaseAuthManaging) { + self.authManager = authManager + } +} + +// MARK: - Event emitter +extension SignInViewStore: EventEmitting { + var eventPublisher: AnyPublisher { + eventSubject.eraseToAnyPublisher() + } +} + +// MARK: - Sending actions +extension SignInViewStore { + @MainActor + func send(action: SignInViewAction) { + switch action { + case .signInButtonTapped: + signIn() + case .viewDidLoad: + viewState.emailField = "Test@test.test" + viewState.passwordField = "test123" + } + } +} + +// MARK: - Functions +private extension SignInViewStore { + @MainActor + func signIn() { + Task { + do { + try await authManager.signIn(Credentials(email: viewState.emailField, password: viewState.passwordField)) + eventSubject.send(.signedIn) + } catch { + logger.info("ERROR: \(error)") + } + } + } +} diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift index 9b29735..a6f37a0 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -110,6 +110,7 @@ private extension SwipingViewStore { } // MARK: Remove joke + @MainActor func removeCard(of joke: Joke) { if let index = viewState.jokes.firstIndex(of: joke) { loggerInfo(message: "INFO: Card number \(index) removed from Jokes array") From 42c0e5cb23bf26a2a6b947e72ef1a4fce53e828b Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Mon, 17 Jun 2024 13:05:12 +0200 Subject: [PATCH 10/13] [feat] create Library DesignSystem --- App/Course App.xcodeproj/project.pbxproj | 2 ++ App/DesignSystem/.gitignore | 8 +++++++ App/DesignSystem/Package.swift | 24 +++++++++++++++++++ .../Sources/DesignSystem/DesignSystem.swift | 2 ++ .../DesignSystemTests/DesignSystemTests.swift | 12 ++++++++++ 5 files changed, 48 insertions(+) create mode 100644 App/DesignSystem/.gitignore create mode 100644 App/DesignSystem/Package.swift create mode 100644 App/DesignSystem/Sources/DesignSystem/DesignSystem.swift create mode 100644 App/DesignSystem/Tests/DesignSystemTests/DesignSystemTests.swift diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 59437bb..1d1d641 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ F13089272C202D2B00FA83EC /* SignInViewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewStore.swift; sourceTree = ""; }; F130892A2C202D3E00FA83EC /* SignInViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewAction.swift; sourceTree = ""; }; F130892C2C202D4500FA83EC /* SignInViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewState.swift; sourceTree = ""; }; + F13089312C204FB900FA83EC /* DesignSystem */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = DesignSystem; sourceTree = ""; }; F18F1A802C107656005364B7 /* KeychainManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManaging.swift; sourceTree = ""; }; F18F1A842C1076A8005364B7 /* KeychainManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManagerError.swift; sourceTree = ""; }; F18F1A862C1076EA005364B7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -283,6 +284,7 @@ A81F21E72BDC1DB700A8A301 = { isa = PBXGroup; children = ( + F13089312C204FB900FA83EC /* DesignSystem */, A8830C342BED05FC005CAFEA /* Configurations */, A81F21F22BDC1DB700A8A301 /* Course App */, A8830C1C2BED0315005CAFEA /* Course App UnitTests */, diff --git a/App/DesignSystem/.gitignore b/App/DesignSystem/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/App/DesignSystem/.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/App/DesignSystem/Package.swift b/App/DesignSystem/Package.swift new file mode 100644 index 0000000..75225e3 --- /dev/null +++ b/App/DesignSystem/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 5.10 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "DesignSystem", + platforms: [.iOS(.v16)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "DesignSystem", + targets: ["DesignSystem"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "DesignSystem"), + .testTarget( + name: "DesignSystemTests", + dependencies: ["DesignSystem"]), + ] +) diff --git a/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift b/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift new file mode 100644 index 0000000..08b22b8 --- /dev/null +++ b/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/App/DesignSystem/Tests/DesignSystemTests/DesignSystemTests.swift b/App/DesignSystem/Tests/DesignSystemTests/DesignSystemTests.swift new file mode 100644 index 0000000..ae32aef --- /dev/null +++ b/App/DesignSystem/Tests/DesignSystemTests/DesignSystemTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import DesignSystem + +final class DesignSystemTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} From 7111340542f4d01d5eb338e646897e9caebc1d38 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Mon, 17 Jun 2024 14:00:16 +0200 Subject: [PATCH 11/13] [refactor] move files of design System to library and import it --- App/Course App.xcodeproj/project.pbxproj | 55 +++++-------------- App/Course App/App/AppCoordinator.swift | 2 + .../Scenes/Home/HomeViewController.swift | 1 + .../Scenes/Onboarding/OnboardingView.swift | 1 + .../UINavigationController+NavBarUI.swift | 1 + .../Styles/NavigationButtonStyle.swift | 1 + .../SharedUI/ViewModifiers/TextStyle.swift | 1 + .../SharedUI/Views/SwiftUI/ScratchView.swift | 1 + .../SharedUI/Views/SwiftUI/SwipingCard.swift | 1 + .../DesignSystem/CornerRadiusSize.swift | 2 +- .../Sources/DesignSystem/DesignSystem.swift | 2 - .../Sources/DesignSystem}/Font+AppFonts.swift | 2 +- .../Sources}/DesignSystem/FontSize.swift | 2 +- .../Sources}/DesignSystem/FontType.swift | 2 +- .../DesignSystem/OpacityVisibility.swift | 2 +- .../Sources}/DesignSystem/PaddingSize.swift | 2 +- .../Sources}/DesignSystem/TextType.swift | 6 +- .../DesignSystem}/UIFont+AppFonts.swift | 2 +- 18 files changed, 34 insertions(+), 52 deletions(-) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/CornerRadiusSize.swift (80%) delete mode 100644 App/DesignSystem/Sources/DesignSystem/DesignSystem.swift rename App/{Course App/SharedUI/Extensions => DesignSystem/Sources/DesignSystem}/Font+AppFonts.swift (95%) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/FontSize.swift (86%) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/FontType.swift (88%) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/OpacityVisibility.swift (80%) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/PaddingSize.swift (82%) rename App/{Course App/SharedUI => DesignSystem/Sources}/DesignSystem/TextType.swift (92%) rename App/{Course App/SharedUI/Extensions => DesignSystem/Sources/DesignSystem}/UIFont+AppFonts.swift (95%) diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 1d1d641..df9ec1b 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -26,13 +26,6 @@ A8830C4F2BEF8565005CAFEA /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A8830C4E2BEF8565005CAFEA /* FirebaseAuth */; }; A8830C512BEF8565005CAFEA /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = A8830C502BEF8565005CAFEA /* FirebaseFirestore */; }; F1076B532C00FE1000653A6E /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B522C00FE1000653A6E /* TextStyle.swift */; }; - F1076B562C00FE4A00653A6E /* TextType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B552C00FE4A00653A6E /* TextType.swift */; }; - F1076B582C00FF2400653A6E /* FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B572C00FF2400653A6E /* FontSize.swift */; }; - F1076B5A2C00FF2F00653A6E /* FontType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B592C00FF2F00653A6E /* FontType.swift */; }; - F1076B5C2C00FF4100653A6E /* PaddingSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B5B2C00FF4100653A6E /* PaddingSize.swift */; }; - F1076B5E2C00FF5200653A6E /* CornerRadiusSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B5D2C00FF5200653A6E /* CornerRadiusSize.swift */; }; - F1076B612C00FF7500653A6E /* Font+AppFonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B602C00FF7500653A6E /* Font+AppFonts.swift */; }; - F1076B632C00FF8500653A6E /* UIFont+AppFonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B622C00FF8500653A6E /* UIFont+AppFonts.swift */; }; F1076B652C01016B00653A6E /* UINavigationController+NavBarUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B642C01016B00653A6E /* UINavigationController+NavBarUI.swift */; }; F1076B672C0101A700653A6E /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076B662C0101A700653A6E /* CustomNavigationController.swift */; }; F1076BB02C08FB5500653A6E /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BAF2C08FB5500653A6E /* Coordinator.swift */; }; @@ -49,7 +42,6 @@ F1076BCF2C0B552B00653A6E /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BCE2C0B552B00653A6E /* NavigationButtonStyle.swift */; }; F1076BD12C0B560E00653A6E /* ButtonStyle+CustomButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BD02C0B560E00653A6E /* ButtonStyle+CustomButtonStyle.swift */; }; F1076BD32C0B5CAA00653A6E /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BD22C0B5CAA00653A6E /* OnboardingPage.swift */; }; - F1076BD52C0D9A7800653A6E /* OpacityVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BD42C0D9A7800653A6E /* OpacityVisibility.swift */; }; F1076BD72C0D9F2200653A6E /* DeeplinkHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BD62C0D9F2200653A6E /* DeeplinkHandling.swift */; }; F1076BD92C0D9F5400653A6E /* Deeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BD82C0D9F5400653A6E /* Deeplink.swift */; }; F1076BDB2C0DA2EA00653A6E /* EventEmitting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1076BDA2C0DA2EA00653A6E /* EventEmitting.swift */; }; @@ -95,6 +87,7 @@ F13089282C202D2B00FA83EC /* SignInViewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13089272C202D2B00FA83EC /* SignInViewStore.swift */; }; F130892B2C202D3E00FA83EC /* SignInViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130892A2C202D3E00FA83EC /* SignInViewAction.swift */; }; F130892D2C202D4500FA83EC /* SignInViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130892C2C202D4500FA83EC /* SignInViewState.swift */; }; + F13089342C205C3300FA83EC /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = F13089332C205C3300FA83EC /* DesignSystem */; }; F18F1A792C107591005364B7 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A782C107591005364B7 /* KeychainAccess */; }; F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */; }; F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */; }; @@ -160,13 +153,6 @@ A8830C472BEF80FA005CAFEA /* Poppins-MediumItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-MediumItalic.ttf"; sourceTree = ""; }; A8830C4B2BEF84BC005CAFEA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; F1076B522C00FE1000653A6E /* TextStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStyle.swift; sourceTree = ""; }; - F1076B552C00FE4A00653A6E /* TextType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextType.swift; sourceTree = ""; }; - F1076B572C00FF2400653A6E /* FontSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSize.swift; sourceTree = ""; }; - F1076B592C00FF2F00653A6E /* FontType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontType.swift; sourceTree = ""; }; - F1076B5B2C00FF4100653A6E /* PaddingSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingSize.swift; sourceTree = ""; }; - F1076B5D2C00FF5200653A6E /* CornerRadiusSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusSize.swift; sourceTree = ""; }; - F1076B602C00FF7500653A6E /* Font+AppFonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+AppFonts.swift"; sourceTree = ""; }; - F1076B622C00FF8500653A6E /* UIFont+AppFonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+AppFonts.swift"; sourceTree = ""; }; F1076B642C01016B00653A6E /* UINavigationController+NavBarUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+NavBarUI.swift"; sourceTree = ""; }; F1076B662C0101A700653A6E /* CustomNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = ""; }; F1076BAF2C08FB5500653A6E /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; @@ -183,7 +169,6 @@ F1076BCE2C0B552B00653A6E /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = ""; }; F1076BD02C0B560E00653A6E /* ButtonStyle+CustomButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ButtonStyle+CustomButtonStyle.swift"; sourceTree = ""; }; F1076BD22C0B5CAA00653A6E /* OnboardingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = ""; }; - F1076BD42C0D9A7800653A6E /* OpacityVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpacityVisibility.swift; sourceTree = ""; }; F1076BD62C0D9F2200653A6E /* DeeplinkHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkHandling.swift; sourceTree = ""; }; F1076BD82C0D9F5400653A6E /* Deeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deeplink.swift; sourceTree = ""; }; F1076BDA2C0DA2EA00653A6E /* EventEmitting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitting.swift; sourceTree = ""; }; @@ -257,6 +242,7 @@ files = ( F18F1A7D2C1075C7005364B7 /* FirebaseFirestoreSwift in Frameworks */, A8830C4F2BEF8565005CAFEA /* FirebaseAuth in Frameworks */, + F13089342C205C3300FA83EC /* DesignSystem in Frameworks */, F18F1A7B2C1075C7005364B7 /* FirebaseFirestoreCombine-Community in Frameworks */, F13089132C1C55AF00FA83EC /* DependencyInjection in Frameworks */, A8830C512BEF8565005CAFEA /* FirebaseFirestore in Frameworks */, @@ -290,6 +276,7 @@ A8830C1C2BED0315005CAFEA /* Course App UnitTests */, A8830C292BED03BB005CAFEA /* Course App UITests */, A81F21F12BDC1DB700A8A301 /* Products */, + F13089322C205C3300FA83EC /* Frameworks */, ); sourceTree = ""; }; @@ -420,24 +407,9 @@ path = Fonts; sourceTree = ""; }; - F1076B542C00FE4000653A6E /* DesignSystem */ = { - isa = PBXGroup; - children = ( - F1076B5D2C00FF5200653A6E /* CornerRadiusSize.swift */, - F1076B572C00FF2400653A6E /* FontSize.swift */, - F1076B592C00FF2F00653A6E /* FontType.swift */, - F1076BD42C0D9A7800653A6E /* OpacityVisibility.swift */, - F1076B5B2C00FF4100653A6E /* PaddingSize.swift */, - F1076B552C00FE4A00653A6E /* TextType.swift */, - ); - path = DesignSystem; - sourceTree = ""; - }; F1076B5F2C00FF6600653A6E /* Extensions */ = { isa = PBXGroup; children = ( - F1076B602C00FF7500653A6E /* Font+AppFonts.swift */, - F1076B622C00FF8500653A6E /* UIFont+AppFonts.swift */, F1076B642C01016B00653A6E /* UINavigationController+NavBarUI.swift */, F1076BD02C0B560E00653A6E /* ButtonStyle+CustomButtonStyle.swift */, ); @@ -695,6 +667,13 @@ path = Store; sourceTree = ""; }; + F13089322C205C3300FA83EC /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; F18F1A7E2C107639005364B7 /* Managers */ = { isa = PBXGroup; children = ( @@ -753,7 +732,6 @@ F190AB262BFDDB68001AF32D /* SharedUI */ = { isa = PBXGroup; children = ( - F1076B542C00FE4000653A6E /* DesignSystem */, F1076B5F2C00FF6600653A6E /* Extensions */, F190AB272BFDDB89001AF32D /* Styles */, F190AB2C2BFDDD87001AF32D /* ViewModifiers */, @@ -806,6 +784,7 @@ F18F1A7A2C1075C7005364B7 /* FirebaseFirestoreCombine-Community */, F18F1A7C2C1075C7005364B7 /* FirebaseFirestoreSwift */, F13089122C1C55AF00FA83EC /* DependencyInjection */, + F13089332C205C3300FA83EC /* DesignSystem */, ); productName = "Course App"; productReference = A81F21F02BDC1DB700A8A301 /* App Course Dev.app */; @@ -944,10 +923,8 @@ F1076BDD2C0DA40000653A6E /* OnboardingViewEvent.swift in Sources */, F1076BF92C0DDB2800653A6E /* ProfileNavigationCoordinatorEvent.swift in Sources */, F13088F22C14A79600FA83EC /* JokeService.swift in Sources */, - F1076B582C00FF2400653A6E /* FontSize.swift in Sources */, F1076B532C00FE1000653A6E /* TextStyle.swift in Sources */, F13088EB2C130A1500FA83EC /* HTTPResponseStatusCode.swift in Sources */, - F1076B632C00FF8500653A6E /* UIFont+AppFonts.swift in Sources */, F18F1A922C108CCB005364B7 /* Endpoint.swift in Sources */, F13088E42C11F08000FA83EC /* APIManager.swift in Sources */, F18F1A992C11B400005364B7 /* HTTPMethod.swift in Sources */, @@ -955,17 +932,13 @@ F13089232C1DCB0C00FA83EC /* SwipingViewEvent.swift in Sources */, F18F1A892C107725005364B7 /* KeychainServicing.swift in Sources */, F1076BCF2C0B552B00653A6E /* NavigationButtonStyle.swift in Sources */, - F1076BD52C0D9A7800653A6E /* OpacityVisibility.swift in Sources */, F1076BB22C08FC4700653A6E /* ViewControllerCoordinator.swift in Sources */, - F1076B562C00FE4A00653A6E /* TextType.swift in Sources */, - F1076B5C2C00FF4100653A6E /* PaddingSize.swift in Sources */, F1076BE12C0DAB1C00653A6E /* CustomNavigationControllerEvent.swift in Sources */, F13089022C17146600FA83EC /* StorageManaging.swift in Sources */, F1076BD32C0B5CAA00653A6E /* OnboardingPage.swift in Sources */, F1076BB02C08FB5500653A6E /* Coordinator.swift in Sources */, F1076BF32C0DC55200653A6E /* SignInNavigationCoordinatorEvent.swift in Sources */, F13088E22C11F05300FA83EC /* APIManaging.swift in Sources */, - F1076B5E2C00FF5200653A6E /* CornerRadiusSize.swift in Sources */, F18F1A9B2C11B413005364B7 /* Endpoint+Convenience.swift in Sources */, F1076BCD2C0B475A00653A6E /* OnboardingNavigationCoordinator.swift in Sources */, F18F1A8D2C107AFE005364B7 /* OnboardingCoordinatorPresenting.swift in Sources */, @@ -988,7 +961,6 @@ F13089182C1C5E0300FA83EC /* ServiceRegistration.swift in Sources */, F13089042C1714AF00FA83EC /* StorageManager.swift in Sources */, F1076BBD2C09027100653A6E /* CoordinatorView.swift in Sources */, - F1076B612C00FF7500653A6E /* Font+AppFonts.swift in Sources */, A845D94C2BF3711D0039AA1D /* DataProvider.swift in Sources */, F190AB342BFDE2D0001AF32D /* SwipingView.swift in Sources */, F18F1A8B2C10774C005364B7 /* KeychainService.swift in Sources */, @@ -1008,7 +980,6 @@ F190AB2E2BFDDDA1001AF32D /* BorderedModifier.swift in Sources */, F18F1A812C107656005364B7 /* KeychainManaging.swift in Sources */, F130891F2C1DA84A00FA83EC /* SwipingViewState.swift in Sources */, - F1076B5A2C00FF2F00653A6E /* FontType.swift in Sources */, F130890E2C19B78700FA83EC /* SwipingViewCoordinating.swift in Sources */, F18F1A852C1076A8005364B7 /* KeychainManagerError.swift in Sources */, F18F1A952C11B3DB005364B7 /* NetworkingError.swift in Sources */, @@ -1719,6 +1690,10 @@ package = F13089112C1C55AF00FA83EC /* XCRemoteSwiftPackageReference "ios-dependency-injection" */; productName = DependencyInjection; }; + F13089332C205C3300FA83EC /* DesignSystem */ = { + isa = XCSwiftPackageProductDependency; + productName = DesignSystem; + }; F18F1A782C107591005364B7 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = F18F1A772C107591005364B7 /* XCRemoteSwiftPackageReference "KeychainAccess" */; diff --git a/App/Course App/App/AppCoordinator.swift b/App/Course App/App/AppCoordinator.swift index 593dd3e..5f4f1f7 100644 --- a/App/Course App/App/AppCoordinator.swift +++ b/App/Course App/App/AppCoordinator.swift @@ -7,6 +7,7 @@ import Combine import DependencyInjection +import DesignSystem import UIKit final class AppCoordinator: ObservableObject, ViewControllerCoordinator, CancellablesContaining { @@ -19,6 +20,7 @@ final class AppCoordinator: ObservableObject, ViewControllerCoordinator, Cancell } }() + // MARK: Public properties var cancellables = Set() var childCoordinators = [Coordinator]() diff --git a/App/Course App/Scenes/Home/HomeViewController.swift b/App/Course App/Scenes/Home/HomeViewController.swift index 2d7bae6..ac1fb20 100644 --- a/App/Course App/Scenes/Home/HomeViewController.swift +++ b/App/Course App/Scenes/Home/HomeViewController.swift @@ -8,6 +8,7 @@ import Combine import os import SwiftUI +import DesignSystem import UIKit final class HomeViewController: UIViewController { diff --git a/App/Course App/Scenes/Onboarding/OnboardingView.swift b/App/Course App/Scenes/Onboarding/OnboardingView.swift index 7ac61d2..f8cef9a 100644 --- a/App/Course App/Scenes/Onboarding/OnboardingView.swift +++ b/App/Course App/Scenes/Onboarding/OnboardingView.swift @@ -6,6 +6,7 @@ // import Combine +import DesignSystem import SwiftUI struct OnboardingView: View { diff --git a/App/Course App/SharedUI/Extensions/UINavigationController+NavBarUI.swift b/App/Course App/SharedUI/Extensions/UINavigationController+NavBarUI.swift index 41312dd..18e570e 100644 --- a/App/Course App/SharedUI/Extensions/UINavigationController+NavBarUI.swift +++ b/App/Course App/SharedUI/Extensions/UINavigationController+NavBarUI.swift @@ -5,6 +5,7 @@ // Created by Tomáš Duchoslav on 24.05.2024. // +import DesignSystem import UIKit extension UINavigationController { diff --git a/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift b/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift index c8f7628..8bc7793 100644 --- a/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift +++ b/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift @@ -6,6 +6,7 @@ // import SwiftUI +import DesignSystem struct NavigationButtonStyle: ButtonStyle { private enum StyleConstant { diff --git a/App/Course App/SharedUI/ViewModifiers/TextStyle.swift b/App/Course App/SharedUI/ViewModifiers/TextStyle.swift index 33ded6e..ab73cc4 100644 --- a/App/Course App/SharedUI/ViewModifiers/TextStyle.swift +++ b/App/Course App/SharedUI/ViewModifiers/TextStyle.swift @@ -6,6 +6,7 @@ // import SwiftUI +import DesignSystem private struct TextStyle: ViewModifier { let textType: TextType diff --git a/App/Course App/SharedUI/Views/SwiftUI/ScratchView.swift b/App/Course App/SharedUI/Views/SwiftUI/ScratchView.swift index 99af11b..1fe6d82 100644 --- a/App/Course App/SharedUI/Views/SwiftUI/ScratchView.swift +++ b/App/Course App/SharedUI/Views/SwiftUI/ScratchView.swift @@ -5,6 +5,7 @@ // Created by Tomáš Duchoslav on 22.05.2024. // +import DesignSystem import SwiftUI struct Line { diff --git a/App/Course App/SharedUI/Views/SwiftUI/SwipingCard.swift b/App/Course App/SharedUI/Views/SwiftUI/SwipingCard.swift index 43fb9d6..d3067ba 100644 --- a/App/Course App/SharedUI/Views/SwiftUI/SwipingCard.swift +++ b/App/Course App/SharedUI/Views/SwiftUI/SwipingCard.swift @@ -5,6 +5,7 @@ // Created by Tomáš Duchoslav on 22.05.2024. // +import DesignSystem import SwiftUI struct SwipingCard: View { diff --git a/App/Course App/SharedUI/DesignSystem/CornerRadiusSize.swift b/App/DesignSystem/Sources/DesignSystem/CornerRadiusSize.swift similarity index 80% rename from App/Course App/SharedUI/DesignSystem/CornerRadiusSize.swift rename to App/DesignSystem/Sources/DesignSystem/CornerRadiusSize.swift index 7b6aa34..1ee3235 100644 --- a/App/Course App/SharedUI/DesignSystem/CornerRadiusSize.swift +++ b/App/DesignSystem/Sources/DesignSystem/CornerRadiusSize.swift @@ -7,7 +7,7 @@ import Foundation -enum CornerRadiusSize: CGFloat { +public enum CornerRadiusSize: CGFloat { case `default` = 10 case extra = 15 } diff --git a/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift b/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift deleted file mode 100644 index 08b22b8..0000000 --- a/App/DesignSystem/Sources/DesignSystem/DesignSystem.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/App/Course App/SharedUI/Extensions/Font+AppFonts.swift b/App/DesignSystem/Sources/DesignSystem/Font+AppFonts.swift similarity index 95% rename from App/Course App/SharedUI/Extensions/Font+AppFonts.swift rename to App/DesignSystem/Sources/DesignSystem/Font+AppFonts.swift index 172445d..4f46647 100644 --- a/App/Course App/SharedUI/Extensions/Font+AppFonts.swift +++ b/App/DesignSystem/Sources/DesignSystem/Font+AppFonts.swift @@ -7,7 +7,7 @@ import SwiftUI -extension Font { +public extension Font { static func regular(with size: FontSize) -> Font { Font.custom(FontType.regular.rawValue, size: size.rawValue) } diff --git a/App/Course App/SharedUI/DesignSystem/FontSize.swift b/App/DesignSystem/Sources/DesignSystem/FontSize.swift similarity index 86% rename from App/Course App/SharedUI/DesignSystem/FontSize.swift rename to App/DesignSystem/Sources/DesignSystem/FontSize.swift index 303bc57..d9e9d26 100644 --- a/App/Course App/SharedUI/DesignSystem/FontSize.swift +++ b/App/DesignSystem/Sources/DesignSystem/FontSize.swift @@ -7,7 +7,7 @@ import Foundation -enum FontSize: CGFloat { +public enum FontSize: CGFloat { case size28 = 28 case size22 = 22 case size18 = 18 diff --git a/App/Course App/SharedUI/DesignSystem/FontType.swift b/App/DesignSystem/Sources/DesignSystem/FontType.swift similarity index 88% rename from App/Course App/SharedUI/DesignSystem/FontType.swift rename to App/DesignSystem/Sources/DesignSystem/FontType.swift index 6a07b26..8adaceb 100644 --- a/App/Course App/SharedUI/DesignSystem/FontType.swift +++ b/App/DesignSystem/Sources/DesignSystem/FontType.swift @@ -7,7 +7,7 @@ import Foundation -enum FontType: String { +public enum FontType: String { case regular = "Poppins-Regular" case bold = "Poppins-Bold" case mediumItalic = "Poppins-MediumItalic" diff --git a/App/Course App/SharedUI/DesignSystem/OpacityVisibility.swift b/App/DesignSystem/Sources/DesignSystem/OpacityVisibility.swift similarity index 80% rename from App/Course App/SharedUI/DesignSystem/OpacityVisibility.swift rename to App/DesignSystem/Sources/DesignSystem/OpacityVisibility.swift index f20222c..99dac4f 100644 --- a/App/Course App/SharedUI/DesignSystem/OpacityVisibility.swift +++ b/App/DesignSystem/Sources/DesignSystem/OpacityVisibility.swift @@ -7,7 +7,7 @@ import Foundation -enum OpacityVisibility: CGFloat { +public enum OpacityVisibility: CGFloat { case `default` = 1 case half = 0.5 } diff --git a/App/Course App/SharedUI/DesignSystem/PaddingSize.swift b/App/DesignSystem/Sources/DesignSystem/PaddingSize.swift similarity index 82% rename from App/Course App/SharedUI/DesignSystem/PaddingSize.swift rename to App/DesignSystem/Sources/DesignSystem/PaddingSize.swift index 013a4a6..40fe0f8 100644 --- a/App/Course App/SharedUI/DesignSystem/PaddingSize.swift +++ b/App/DesignSystem/Sources/DesignSystem/PaddingSize.swift @@ -7,7 +7,7 @@ import Foundation -enum PaddingSize: CGFloat { +public enum PaddingSize: CGFloat { case `default` = 10 case extra = 15 } diff --git a/App/Course App/SharedUI/DesignSystem/TextType.swift b/App/DesignSystem/Sources/DesignSystem/TextType.swift similarity index 92% rename from App/Course App/SharedUI/DesignSystem/TextType.swift rename to App/DesignSystem/Sources/DesignSystem/TextType.swift index f50e5a8..0a73fbe 100644 --- a/App/Course App/SharedUI/DesignSystem/TextType.swift +++ b/App/DesignSystem/Sources/DesignSystem/TextType.swift @@ -8,7 +8,7 @@ import SwiftUI import UIKit -enum TextType { +public enum TextType { case navigationTitle case sectionTitle case baseText @@ -16,7 +16,7 @@ enum TextType { } // MARK: - TextType attributes SwiftUI -extension TextType { +public extension TextType { var font: Font { switch self { case .navigationTitle: @@ -36,7 +36,7 @@ extension TextType { } // MARK: - TextType attributes UIKit -extension TextType { +public extension TextType { var uiFont: UIFont { switch self { case .navigationTitle: diff --git a/App/Course App/SharedUI/Extensions/UIFont+AppFonts.swift b/App/DesignSystem/Sources/DesignSystem/UIFont+AppFonts.swift similarity index 95% rename from App/Course App/SharedUI/Extensions/UIFont+AppFonts.swift rename to App/DesignSystem/Sources/DesignSystem/UIFont+AppFonts.swift index 9cfa32b..c11fa3a 100644 --- a/App/Course App/SharedUI/Extensions/UIFont+AppFonts.swift +++ b/App/DesignSystem/Sources/DesignSystem/UIFont+AppFonts.swift @@ -7,7 +7,7 @@ import UIKit // swiftlint:disable force_unwrapping -extension UIFont { +public extension UIFont { static func regular(with size: FontSize) -> UIFont { UIFont(name: FontType.regular.rawValue, size: size.rawValue)! } From b1f41ca735b691f2541ad318376c86e438b5ce35 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Mon, 17 Jun 2024 14:01:58 +0200 Subject: [PATCH 12/13] [style] remove swiftlint warnings --- .../App/DIRegistration/ManagerRegistration.swift | 12 ++++++++---- .../App/DIRegistration/ServiceRegistration.swift | 6 ++++-- App/Course App/Scenes/Home/HomeViewController.swift | 2 +- .../Scenes/SwipingView/Store/SwipingViewStore.swift | 2 +- .../SharedUI/Styles/NavigationButtonStyle.swift | 2 +- .../SharedUI/ViewModifiers/TextStyle.swift | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/App/Course App/App/DIRegistration/ManagerRegistration.swift b/App/Course App/App/DIRegistration/ManagerRegistration.swift index 67278f0..f6ddaa3 100644 --- a/App/Course App/App/DIRegistration/ManagerRegistration.swift +++ b/App/Course App/App/DIRegistration/ManagerRegistration.swift @@ -13,21 +13,25 @@ enum ManagerRegistration { container.autoregister( type: StorageManaging.self, in: .shared, - initializer: StorageManager.init) + initializer: StorageManager.init + ) container.autoregister( type: KeychainManaging.self, in: .shared, - initializer: KeychainManager.init) + initializer: KeychainManager.init + ) container.autoregister( type: APIManaging.self, in: .shared, - initializer: APIManager.init) + initializer: APIManager.init + ) container.autoregister( type: FirebaseAuthManaging.self, in: .shared, - initializer: FirebaseAuthManager.init) + initializer: FirebaseAuthManager.init + ) } } diff --git a/App/Course App/App/DIRegistration/ServiceRegistration.swift b/App/Course App/App/DIRegistration/ServiceRegistration.swift index c75d730..7cad3e6 100644 --- a/App/Course App/App/DIRegistration/ServiceRegistration.swift +++ b/App/Course App/App/DIRegistration/ServiceRegistration.swift @@ -13,11 +13,13 @@ enum ServiceRegistration { container.autoregister( type: KeychainServicing.self, in: .shared, - initializer: KeychainService.init) + initializer: KeychainService.init + ) container.autoregister( type: JokeServicing.self, in: .shared, - initializer: JokeService.init) + initializer: JokeService.init + ) } } diff --git a/App/Course App/Scenes/Home/HomeViewController.swift b/App/Course App/Scenes/Home/HomeViewController.swift index ac1fb20..6462fbe 100644 --- a/App/Course App/Scenes/Home/HomeViewController.swift +++ b/App/Course App/Scenes/Home/HomeViewController.swift @@ -6,9 +6,9 @@ // import Combine +import DesignSystem import os import SwiftUI -import DesignSystem import UIKit final class HomeViewController: UIViewController { diff --git a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift index a6f37a0..725ff13 100644 --- a/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift +++ b/App/Course App/Scenes/SwipingView/Store/SwipingViewStore.swift @@ -20,7 +20,7 @@ final class SwipingViewStore: Store, ObservableObject { private let logger = Logger() private var category: String? = nil private let eventSubject = PassthroughSubject() - private var isChildCoordinator: Bool = false + private var isChildCoordinator = false init(/*joke: Joke? = nil, isChildCoordinator: Bool = false,*/ storage: StorageManaging, keychainSevice: KeychainServicing, jokeService: JokeServicing) { self.storage = storage diff --git a/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift b/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift index 8bc7793..26a64d1 100644 --- a/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift +++ b/App/Course App/SharedUI/Styles/NavigationButtonStyle.swift @@ -5,8 +5,8 @@ // Created by Tomáš Duchoslav on 01.06.2024. // -import SwiftUI import DesignSystem +import SwiftUI struct NavigationButtonStyle: ButtonStyle { private enum StyleConstant { diff --git a/App/Course App/SharedUI/ViewModifiers/TextStyle.swift b/App/Course App/SharedUI/ViewModifiers/TextStyle.swift index ab73cc4..3876159 100644 --- a/App/Course App/SharedUI/ViewModifiers/TextStyle.swift +++ b/App/Course App/SharedUI/ViewModifiers/TextStyle.swift @@ -5,8 +5,8 @@ // Created by Tomáš Duchoslav on 24.05.2024. // -import SwiftUI import DesignSystem +import SwiftUI private struct TextStyle: ViewModifier { let textType: TextType From 7830ee1a1b436dad904770b8112106f0285843e4 Mon Sep 17 00:00:00 2001 From: GosutoDev Date: Wed, 19 Jun 2024 13:56:28 +0200 Subject: [PATCH 13/13] [style] move folder DIRegistration from app folder --- App/Course App.xcodeproj/project.pbxproj | 2 +- .../{App => }/DIRegistration/ManagerRegistration.swift | 0 .../{App => }/DIRegistration/ServiceRegistration.swift | 0 App/Course App/{App => }/DIRegistration/StoreRegistration.swift | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename App/Course App/{App => }/DIRegistration/ManagerRegistration.swift (100%) rename App/Course App/{App => }/DIRegistration/ServiceRegistration.swift (100%) rename App/Course App/{App => }/DIRegistration/StoreRegistration.swift (100%) diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index df9ec1b..07889f3 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ isa = PBXGroup; children = ( F1076B682C0101C300653A6E /* App */, + F13089142C1C59EB00FA83EC /* DIRegistration */, A845D9502BF376860039AA1D /* Common */, A8830C3E2BED0E80005CAFEA /* Course-App-Info.plist */, A845D9482BF36F070039AA1D /* DataAPI */, @@ -422,7 +423,6 @@ F1076BBE2C09052900653A6E /* AppCoordinator.swift */, A8830C3F2BED0EE0005CAFEA /* BuildConfiguration.swift */, A81F21F32BDC1DB700A8A301 /* Course_AppApp.swift */, - F13089142C1C59EB00FA83EC /* DIRegistration */, A8830C4B2BEF84BC005CAFEA /* GoogleService-Info.plist */, A845D9402BF364410039AA1D /* Resources */, ); diff --git a/App/Course App/App/DIRegistration/ManagerRegistration.swift b/App/Course App/DIRegistration/ManagerRegistration.swift similarity index 100% rename from App/Course App/App/DIRegistration/ManagerRegistration.swift rename to App/Course App/DIRegistration/ManagerRegistration.swift diff --git a/App/Course App/App/DIRegistration/ServiceRegistration.swift b/App/Course App/DIRegistration/ServiceRegistration.swift similarity index 100% rename from App/Course App/App/DIRegistration/ServiceRegistration.swift rename to App/Course App/DIRegistration/ServiceRegistration.swift diff --git a/App/Course App/App/DIRegistration/StoreRegistration.swift b/App/Course App/DIRegistration/StoreRegistration.swift similarity index 100% rename from App/Course App/App/DIRegistration/StoreRegistration.swift rename to App/Course App/DIRegistration/StoreRegistration.swift