diff --git a/Package.swift b/Package.swift index 9f27f5c4c29eb483e7136b6a798f568d8a2854f3..531a2a484d66914bc8be79ec1849b335ea29dcee 100644 --- a/Package.swift +++ b/Package.swift @@ -10,14 +10,12 @@ let package = Package( products: [ .library(name: "App", targets: ["App"]), .library(name: "Shared", targets: ["Shared"]), - .library(name: "Models", targets: ["Models"]), .library(name: "XXLogger", targets: ["XXLogger"]), .library(name: "Defaults", targets: ["Defaults"]), .library(name: "Keychain", targets: ["Keychain"]), .library(name: "Voxophone", targets: ["Voxophone"]), .library(name: "Countries", targets: ["Countries"]), .library(name: "InputField", targets: ["InputField"]), - .library(name: "TestHelpers", targets: ["TestHelpers"]), .library(name: "ScanFeature", targets: ["ScanFeature"]), .library(name: "Permissions", targets: ["Permissions"]), .library(name: "MenuFeature", targets: ["MenuFeature"]), @@ -25,6 +23,7 @@ let package = Package( .library(name: "PushFeature", targets: ["PushFeature"]), .library(name: "CrashService", targets: ["CrashService"]), .library(name: "TermsFeature", targets: ["TermsFeature"]), + .library(name: "XXNavigation", targets: ["XXNavigation"]), .library(name: "Presentation", targets: ["Presentation"]), .library(name: "BackupFeature", targets: ["BackupFeature"]), .library(name: "LaunchFeature", targets: ["LaunchFeature"]), @@ -153,6 +152,7 @@ let package = Package( .target(name: "ReportingFeature"), .target(name: "OnboardingFeature"), .target(name: "ContactListFeature"), + .target(name: "XXNavigation"), .product(name: "Navigation", package: "Navigation"), ] ), @@ -196,10 +196,17 @@ let package = Package( .target(name: "DependencyInjection"), ] ), + .target( + name: "XXNavigation", + dependencies: [ + .target(name: "DependencyInjection"), + .product(name: "Navigation", package: "Navigation"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), .target( name: "PushFeature", dependencies: [ - .target(name: "Models"), .target(name: "Defaults"), .target(name: "ReportingFeature"), .target(name: "DependencyInjection"), @@ -208,13 +215,6 @@ let package = Package( .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), ] ), - .target( - name: "TestHelpers", - dependencies: [ - .target(name: "Models"), - .target(name: "Presentation"), - ] - ), .target( name: "Keychain", dependencies: [ @@ -227,13 +227,6 @@ let package = Package( .target(name: "Shared"), ] ), - .target( - name: "Models", - dependencies: [ - .product(name: "DifferenceKit", package: "DifferenceKit"), - .product(name: "SwiftProtobuf", package: "swift-protobuf"), - ] - ), .target( name: "Defaults", dependencies: [ @@ -277,6 +270,7 @@ let package = Package( .product(name: "SnapKit", package: "SnapKit"), .product(name: "ChatLayout", package: "ChatLayout"), .product(name: "DifferenceKit", package: "DifferenceKit"), + .product(name: "SwiftProtobuf", package: "swift-protobuf"), ], exclude: [ "swiftgen.yml", @@ -335,7 +329,6 @@ let package = Package( .testTarget( name: "ContactFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "ContactFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -348,7 +341,6 @@ let package = Package( .target(name: "Defaults"), .target(name: "Keychain"), .target(name: "Voxophone"), - .target(name: "Models"), .target(name: "Permissions"), .target(name: "Presentation"), .target(name: "DrawerFeature"), @@ -366,7 +358,6 @@ let package = Package( name: "ChatFeatureTests", dependencies: [ .target(name: "ChatFeature"), - .target(name: "TestHelpers"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), ] @@ -388,7 +379,6 @@ let package = Package( .testTarget( name: "SearchFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "SearchFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -433,7 +423,6 @@ let package = Package( .testTarget( name: "RequestsFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "RequestsFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -462,7 +451,6 @@ let package = Package( .testTarget( name: "ProfileFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "ProfileFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -485,7 +473,6 @@ let package = Package( .testTarget( name: "ChatListFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "ChatListFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -512,7 +499,6 @@ let package = Package( .testTarget( name: "OnboardingFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "OnboardingFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -534,7 +520,6 @@ let package = Package( name: "BackupFeature", dependencies: [ .target(name: "Shared"), - .target(name: "Models"), .target(name: "InputField"), .target(name: "Presentation"), .target(name: "DrawerFeature"), @@ -565,7 +550,6 @@ let package = Package( name: "ScanFeatureTests", dependencies: [ .target(name: "ScanFeature"), - .target(name: "TestHelpers"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), ] @@ -583,7 +567,6 @@ let package = Package( .testTarget( name: "ContactListFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "ContactListFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), @@ -610,7 +593,6 @@ let package = Package( .testTarget( name: "SettingsFeatureTests", dependencies: [ - .target(name: "TestHelpers"), .target(name: "SettingsFeature"), .product(name: "Quick", package: "Quick"), .product(name: "Nimble", package: "Nimble"), diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index 6ff2efd3a3207403d7bc1be03792a276cee9235e..a10b8e67138fc8563e7b20f43891d21ad241ce7a 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -45,9 +45,13 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { setupCrashReporting() setupLogging() + let navController = UINavigationController(rootViewController: LaunchController()) + let rootViewController = RootViewController(navController) window = UIWindow(frame: UIScreen.main.bounds) - window?.rootViewController = RootViewController(UINavigationController(rootViewController: LaunchController())) + window?.rootViewController = rootViewController window?.makeKeyAndVisible() + + DependencyRegistrator.registerNavigators(navController) return true } diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift index 5f3d0418f49bf1eb249ca680a54b8fd21c043224..211cc7c4d9a65ba097a7e4e0f445d160555d126a 100644 --- a/Sources/App/DependencyRegistrator.swift +++ b/Sources/App/DependencyRegistrator.swift @@ -40,10 +40,12 @@ import RequestsFeature import OnboardingFeature import ContactListFeature +import Shared import XXClient import Navigation +import XXNavigation import KeychainAccess -import Shared +import XXMessengerClient struct DependencyRegistrator { static private let container = DependencyInjection.Container.shared @@ -99,7 +101,83 @@ struct DependencyRegistrator { // MARK: COMMON + static public func registerNavigators(_ navController: UINavigationController) { + container.register(CombinedNavigator( + PresentModalNavigator(), + DismissModalNavigator(), + PushNavigator(), + PopToRootNavigator(), + PopToNavigator(), + SetStackNavigator(), + + PresentOnboardingStartNavigator( + screen: OnboardingStartController.init, + navigationController: { navController } + ), + PresentChatListNavigator( + screen: ChatListController.init, + navigationController: { navController } + ), + PresentTermsAndConditionsNavigator( + screen: TermsConditionsController.init, + navigationController: { navController } + ), + PresentSearchNavigator( + screen: SearchContainerController.init(_:), + navigationController: { navController } + ), + PresentRequestsNavigator( + screen: RequestsContainerController.init, + navigationController: { navController } + ), + PresentChatNavigator( + screen: SingleChatController.init(_:), + navigationController: { navController } + ), + PresentGroupChatNavigator( + screen: GroupChatController.init(_:), + navigationController: { navController } + ), + PresentOnboardingWelcomeNavigator( + screen: OnboardingWelcomeController.init, + navigationController: { navController } + ), + PresentOnboardingUsernameNavigator( + screen: OnboardingUsernameController.init, + navigationController: { navController } + ), + PresentRestoreListNavigator( + screen: RestoreListController.init, + navigationController: { navController } + ), + PresentOnboardingEmailNavigator( + screen: OnboardingEmailController.init, + navigationController: { navController } + ), + PresentOnboardingPhoneNavigator( + screen: OnboardingPhoneController.init, + navigationController: { navController } + ), + PresentOnboardingCodeNavigator( + screen: OnboardingCodeController.init(_:_:_:), + navigationController: { navController } + ) + // searchFactory: SearchContainerController.init, + // restoreListFactory: RestoreListController.init, + // countriesFactory: CountryListController.init(_:), + ) as Navigator) + } + static private func registerCommonDependencies() { + var environment: MessengerEnvironment = .live() + environment.ndfEnvironment = .mainnet + environment.udEnvironment = .init( + address: AlternativeUDConstants.address, + cert: AlternativeUDConstants.cert.data(using: .utf8)!, + contact: AlternativeUDConstants.contact.data(using: .utf8)! + ) + container.register(Messenger.live(environment)) + container.register(Voxophone()) container.register(BackupService()) container.register(MakeAppScreenshot.live) @@ -113,8 +191,6 @@ struct DependencyRegistrator { container.register(ToastController()) container.register(StatusBarStylist()) - // MARK: Coordinators - container.register( TermsCoordinator.live( usernameFactory: OnboardingUsernameController.init, @@ -122,17 +198,6 @@ struct DependencyRegistrator { ) ) - container.register( - LaunchCoordinator( - termsFactory: TermsConditionsController.init, - searchFactory: SearchContainerController.init, - requestsFactory: RequestsContainerController.init, - chatListFactory: ChatListController.init, - onboardingFactory: OnboardingStartController.init, - singleChatFactory: SingleChatController.init(_:), - groupChatFactory: GroupChatController.init(_:) - ) as LaunchCoordinating) - container.register( BackupCoordinator( sftpFactory: BackupSFTPController.init(_:), @@ -164,8 +229,8 @@ struct DependencyRegistrator { imagePickerFactory: UIImagePickerController.init, permissionFactory: RequestPermissionController.init, sideMenuFactory: MenuController.init(_:_:), - countriesFactory: CountryListController.init(_:), - codeFactory: ProfileCodeController.init(_:_:) + countriesFactory: CountryListController.init(_:) + //codeFactory: ProfileCodeController.init(_:_:) ) as ProfileCoordinating) container.register( @@ -188,7 +253,7 @@ struct DependencyRegistrator { container.register( ChatCoordinator( retryFactory: RetrySheetController.init, - webFactory: WebScreen.init(url:), + webFactory: WebScreen.init(_:), previewFactory: QLPreviewController.init, contactFactory: ContactController.init(_:), imagePickerFactory: UIImagePickerController.init, @@ -213,22 +278,6 @@ struct DependencyRegistrator { nicknameFactory: NicknameController.init(_:_:) ) as RequestsCoordinating) - container.register( - OnboardingCoordinator( - emailFactory: OnboardingEmailController.init, - phoneFactory: OnboardingPhoneController.init, - searchFactory: SearchContainerController.init, - welcomeFactory: OnboardingWelcomeController.init, - chatListFactory: ChatListController.init, - termsFactory: TermsConditionsController.init, - usernameFactory: OnboardingUsernameController.init, - restoreListFactory: RestoreListController.init, - successFactory: OnboardingSuccessController.init(_:), - countriesFactory: CountryListController.init(_:), - phoneConfirmationFactory: OnboardingPhoneConfirmationController.init(_:_:), - emailConfirmationFactory: OnboardingEmailConfirmationController.init(_:_:) - ) as OnboardingCoordinating) - container.register( ContactListCoordinator( scanFactory: ScanContainerController.init, @@ -279,3 +328,33 @@ extension PasswordStorage { ) }() } + +private enum AlternativeUDConstants { + static let address = "46.101.98.49:18001" + static let cert = """ + -----BEGIN CERTIFICATE----- + MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV + BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx + GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp + cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT + MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV + BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw + DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh + Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs + WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE + tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA + m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9 + bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA + AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA + neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf + U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2 + qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4 + cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R + tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5 + 6m52PyzMNV+2N21IPppKwA== + -----END CERTIFICATE----- +""" + static let contact = """ +<xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc> +""" +} diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift index f17bdbc85f9df2b65441645f456730d7ac642469..18848479c0193b3f62b2a00e5fc10dead562c015 100644 --- a/Sources/BackupFeature/Controllers/BackupConfigController.swift +++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import CloudFiles @@ -9,7 +8,7 @@ import DependencyInjection final class BackupConfigController: UIViewController { @Dependency private var coordinator: BackupCoordinating - lazy private var screenView = BackupConfigView() + private lazy var screenView = BackupConfigView() private let viewModel: BackupConfigViewModel private var cancellables = Set<AnyCancellable>() diff --git a/Sources/BackupFeature/Controllers/BackupController.swift b/Sources/BackupFeature/Controllers/BackupController.swift index 780efb8f5a18c448210e76943a1172085bac77c6..a996d324f30869f1bd8f2f478a3e7970c35cf450 100644 --- a/Sources/BackupFeature/Controllers/BackupController.swift +++ b/Sources/BackupFeature/Controllers/BackupController.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import DependencyInjection diff --git a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift index 5021b438717d18a72e144d2a13e8372bd21073a8..9ec594c8680d4bc8162cfbc47c077bbbea26b127 100644 --- a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift +++ b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift @@ -4,7 +4,7 @@ import Combine import InputField public final class BackupPassphraseController: UIViewController { - lazy private var screenView = BackupPassphraseView() + private lazy var screenView = BackupPassphraseView() private var passphrase = "" { didSet { diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift index 3fa7f9c396b5412f9465e43e99c6e3a2953a5004..7b6af4f5897855d740b6e7918071090f04765729 100644 --- a/Sources/BackupFeature/Controllers/BackupSFTPController.swift +++ b/Sources/BackupFeature/Controllers/BackupSFTPController.swift @@ -3,8 +3,8 @@ import Combine import ScrollViewController public final class BackupSFTPController: UIViewController { - lazy private var screenView = BackupSFTPView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = BackupSFTPView() + private lazy var scrollViewController = ScrollViewController() private let completion: (String, String, String) -> Void private let viewModel = BackupSFTPViewModel() diff --git a/Sources/BackupFeature/Controllers/BackupSetupController.swift b/Sources/BackupFeature/Controllers/BackupSetupController.swift index e22de517680d7ab5fc348b56cbb396ca80c4e091..a9600531d4e311b9f3942338ebe2cfca6cb9ee62 100644 --- a/Sources/BackupFeature/Controllers/BackupSetupController.swift +++ b/Sources/BackupFeature/Controllers/BackupSetupController.swift @@ -1,10 +1,9 @@ import UIKit -import Models import Combine import DependencyInjection final class BackupSetupController: UIViewController { - lazy private var screenView = BackupSetupView() + private lazy var screenView = BackupSetupView() private let viewModel: BackupSetupViewModel private var cancellables = Set<AnyCancellable>() diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift index 8b1b10f7b1779bb7cfb66de02c48c414b56b450b..4c888b8824c3b8fe3b5919332c29eac2e11bf796 100644 --- a/Sources/BackupFeature/Service/BackupService.swift +++ b/Sources/BackupFeature/Service/BackupService.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Combine import XXClient import Defaults diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift index 1057d966d5dd2e584db74cb730127a3b6a6831ac..38adaf920874de8e0cd1cfad00049a9396a03e8e 100644 --- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift +++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXClient diff --git a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift index 1dc8e61209c4dde61cc375c74de2181abddc5217..0d44658be087717608df2d2e252f7473c7422593 100644 --- a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift +++ b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import CloudFiles diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift index 9f8b464a831ee4b05a8df465e520f51031307a8c..31a65eaffd081f19e397da2ace17604ec8c3a9a2 100644 --- a/Sources/ChatFeature/Controllers/GroupChatController.swift +++ b/Sources/ChatFeature/Controllers/GroupChatController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels @@ -28,7 +27,7 @@ public final class GroupChatController: UIViewController { private let members: MembersController private var collectionView: UICollectionView! - lazy private var header = GroupHeaderView() + private lazy var header = GroupHeaderView() private let inputComponent: ChatInputView private var animator: ManualAnimator? diff --git a/Sources/ChatFeature/Controllers/MembersController.swift b/Sources/ChatFeature/Controllers/MembersController.swift index e7bf2c3ece345ace155ed7babbf5c9ec05bf1caf..435ec4dfb63beb7869794dc51e15566942b6a16b 100644 --- a/Sources/ChatFeature/Controllers/MembersController.swift +++ b/Sources/ChatFeature/Controllers/MembersController.swift @@ -1,10 +1,9 @@ import UIKit -import Models import Shared import XXModels final class MembersController: UIViewController { - lazy private var stackView = UIStackView() + private lazy var stackView = UIStackView() private let members: [Contact] diff --git a/Sources/ChatFeature/Controllers/RetrySheetController.swift b/Sources/ChatFeature/Controllers/RetrySheetController.swift index 48424f434d189d76a29a29c278e3ba34fe659ca5..4605017e1368171f2b705c45412831c89f8d229d 100644 --- a/Sources/ChatFeature/Controllers/RetrySheetController.swift +++ b/Sources/ChatFeature/Controllers/RetrySheetController.swift @@ -10,7 +10,7 @@ public final class RetrySheetController: UIViewController { // MARK: UI - lazy private var screenView = RetrySheetView() + private lazy var screenView = RetrySheetView() // MARK: Properties diff --git a/Sources/ChatFeature/Controllers/SheetController.swift b/Sources/ChatFeature/Controllers/SheetController.swift index 974f086d5df232b982c0ab101b0f04f48f1bc578..b0f9ed2937c7b4a57ebfc3458d4aa028f73d4737 100644 --- a/Sources/ChatFeature/Controllers/SheetController.swift +++ b/Sources/ChatFeature/Controllers/SheetController.swift @@ -8,7 +8,7 @@ final class SheetController: UIViewController { case report } - lazy private var screenView = SheetView() + private lazy var screenView = SheetView() var actionPublisher: AnyPublisher<Action, Never> { actionRelay.eraseToAnyPublisher() diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift index a356dfa257152b0a8339f801255e6f6353a5e2c2..708b9d5043d22ad2df980e19a346c42ba17a0ac2 100644 --- a/Sources/ChatFeature/Controllers/SingleChatController.swift +++ b/Sources/ChatFeature/Controllers/SingleChatController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXLogger @@ -31,13 +30,13 @@ public final class SingleChatController: UIViewController { @Dependency var makeReportDrawer: MakeReportDrawer @Dependency var makeAppScreenshot: MakeAppScreenshot - lazy private var infoView = UIControl() - lazy private var nameLabel = UILabel() - lazy private var avatarView = AvatarView() + private lazy var infoView = UIControl() + private lazy var nameLabel = UILabel() + private lazy var avatarView = AvatarView() - lazy private var moreButton = UIButton() - lazy private var screenView = ChatView() - lazy private var sheet = SheetController() + private lazy var moreButton = UIButton() + private lazy var screenView = ChatView() + private lazy var sheet = SheetController() private let inputComponent: ChatInputView private var collectionView: UICollectionView! diff --git a/Sources/ChatFeature/Controllers/WebController.swift b/Sources/ChatFeature/Controllers/WebController.swift index b093af5ef2232a2b23c9499a16e68b2ed21e2874..f3099b7a5bcb8cea0b867f5cbdeeb9e5327f8b46 100644 --- a/Sources/ChatFeature/Controllers/WebController.swift +++ b/Sources/ChatFeature/Controllers/WebController.swift @@ -2,68 +2,61 @@ import UIKit import WebKit public final class WebScreen: UIViewController { - lazy private(set) var webView = WebView() - - private var url: String! - - public init(url: String) { - self.url = url - super.init(nibName: nil, bundle: nil) - } - - public required init?(coder: NSCoder) { nil } - - public override func viewDidLoad() { - super.viewDidLoad() - setupScreen() - } - - private func setupScreen() { - view.addSubview(webView) - webView.snp.makeConstraints { $0.edges.equalToSuperview() } - - webView.webView.load(URLRequest(url: URL(string: url)!)) - webView.closeButton = UIBarButtonItem(title: "Close", style: .done, target: self, - action: #selector(didTappedClose)) - } - - @objc private func didTappedClose() { - dismiss(animated: true) - } + private let url: URL + private lazy var screenView = WebView() + + public init(_ urlString: String) { + self.url = .init(string: urlString)! + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + public override func loadView() { + view = screenView + } + + public override func viewDidLoad() { + super.viewDidLoad() + screenView.webView.load(URLRequest(url: url)) + screenView.closeButton = UIBarButtonItem( + title: "Close", + style: .done, + target: self, + action: #selector(didTappedClose)) + } + + @objc private func didTappedClose() { + dismiss(animated: true) + } } final class WebView: UIView { - - let webView = WKWebView() - let navBar = UINavigationBar() - var closeButton: UIBarButtonItem! { - didSet { navBar.topItem?.leftBarButtonItem = closeButton } + let webView = WKWebView() + let navBar = UINavigationBar() + var closeButton: UIBarButtonItem! { + didSet { navBar.topItem?.leftBarButtonItem = closeButton } + } + + init() { + super.init(frame: .zero) + backgroundColor = .white + navBar.items = [UINavigationItem(title: "")] + addSubview(webView) + addSubview(navBar) + + navBar.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide) + $0.left.equalToSuperview() + $0.right.equalToSuperview() } - - init() { - super.init(frame: .zero) - setupLayout() + webView.snp.makeConstraints { + $0.bottom.equalToSuperview() + $0.left.equalToSuperview() + $0.right.equalToSuperview() + $0.top.equalTo(navBar.snp.bottom) } + } - required init?(coder: NSCoder) { nil } - - private func setupLayout() { - backgroundColor = .white - navBar.items = [UINavigationItem(title: "")] - addSubview(webView) - addSubview(navBar) - - navBar.snp.makeConstraints { make -> Void in - make.top.equalTo(safeAreaLayoutGuide) - make.left.equalToSuperview() - make.right.equalToSuperview() - } - - webView.snp.makeConstraints { make -> Void in - make.bottom.equalToSuperview() - make.left.equalToSuperview() - make.right.equalToSuperview() - make.top.equalTo(navBar.snp.bottom) - } - } + required init?(coder: NSCoder) { nil } } diff --git a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift b/Sources/ChatFeature/Coordinator/ChatCoordinator.swift index 64a8e46de2eada72bba4b371bed2b1d441a2b846..5c9045dded7d2202c7b23dd12364d6af83c3ccdb 100644 --- a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift +++ b/Sources/ChatFeature/Coordinator/ChatCoordinator.swift @@ -1,10 +1,9 @@ import UIKit -import Models import Shared +import XXModels import QuickLook import Permissions import Presentation -import XXModels public protocol ChatCoordinating { func toCamera(from: UIViewController) diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift index 4a7c0e9ed8fca41b15155f9b2773d4a57867c2ad..324d01dc16684a481127d12f3b214ef2b9ae5846 100644 --- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift index cd1a6f4b65431b2c51f8e47901bc950fff5c29e8..4b95f84bd3e3d68a85b5a75c49e813a2f4730e0e 100644 --- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXLogger @@ -12,7 +11,6 @@ import DifferenceKit import ReportingFeature import DependencyInjection import XXMessengerClient -import XXClient import struct XXModels.Message import struct XXModels.FileTransfer @@ -138,33 +136,33 @@ final class SingleChatViewModel: NSObject { } func didSendAudio(url: URL) { - do { - let _ = try transferManager.send( - params: .init( - payload: .init( - name: "", - type: "", - preview: Data(), - contents: Data() - ), - recipientId: contact.id, - retry: 1, - period: 1_000 - ), - callback: .init(handle: { - switch $0 { - case .success(let progressCallback): - print(progressCallback.progress.total) - case .failure(let error): - print(error.localizedDescription) - } - }) - ) - - // transferId - } catch { - - } +// do { +// let _ = try transferManager.send( +// params: .init( +// payload: .init( +// name: "", +// type: "", +// preview: Data(), +// contents: Data() +// ), +// recipientId: contact.id, +// retry: 1, +// period: 1_000 +// ), +// callback: .init(handle: { +// switch $0 { +// case .success(let progressCallback): +// print(progressCallback.progress.total) +// case .failure(let error): +// print(error.localizedDescription) +// } +// }) +// ) +// +// // transferId +// } catch { +// +// } } func didSend(image: UIImage) { @@ -174,62 +172,62 @@ final class SingleChatViewModel: NSObject { let transferName = UUID().uuidString do { - let tid = try transferManager.send( - params: .init( - payload: .init( - name: transferName, - type: "jpeg", - preview: Data(), - contents: imageData - ), - recipientId: contact.id, - retry: 10, - period: 1_000 - ), - callback: .init(handle: { - switch $0 { - case .success(let progressCallback): - - if progressCallback.progress.completed { - print(">>> Outgoing transfer finished successfully") - } else { - print(">>> Outgoing transfer. (\(progressCallback.progress.transmitted)/\(progressCallback.progress.total))") - } - - /// THIS IS TOO COMPLEX, NEEDS HELP FROM DARIUSZ - - case .failure(let error): - print(">>> Transfer.error: \(error.localizedDescription)") - } - }) - ) - - let transferModel = FileTransfer( - id: tid, - contactId: contact.id, - name: transferName, - type: "jpeg", - data: imageData, - progress: 0.0, - isIncoming: false, - createdAt: Date() - ) - - let transferMessage = Message( - senderId: myId, - recipientId: contact.id, - groupId: nil, - date: Date(), - status: .sending, - isUnread: false, - text: "", - replyMessageId: nil, - roundURL: nil, - fileTransferId: tid - ) - - try database.saveFileTransfer(transferModel) - try database.saveMessage(transferMessage) +// let tid = try transferManager.send( +// params: .init( +// payload: .init( +// name: transferName, +// type: "jpeg", +// preview: Data(), +// contents: imageData +// ), +// recipientId: contact.id, +// retry: 10, +// period: 1_000 +// ), +// callback: .init(handle: { +// switch $0 { +// case .success(let progressCallback): +// +// if progressCallback.progress.completed { +// print(">>> Outgoing transfer finished successfully") +// } else { +// print(">>> Outgoing transfer. (\(progressCallback.progress.transmitted)/\(progressCallback.progress.total))") +// } +// +// /// THIS IS TOO COMPLEX, NEEDS HELP FROM DARIUSZ +// +// case .failure(let error): +// print(">>> Transfer.error: \(error.localizedDescription)") +// } +// }) +//// ) +// +// let transferModel = FileTransfer( +// id: tid, +// contactId: contact.id, +// name: transferName, +// type: "jpeg", +// data: imageData, +// progress: 0.0, +// isIncoming: false, +// createdAt: Date() +// ) +// +// let transferMessage = Message( +// senderId: myId, +// recipientId: contact.id, +// groupId: nil, +// date: Date(), +// status: .sending, +// isUnread: false, +// text: "", +// replyMessageId: nil, +// roundURL: nil, +// fileTransferId: tid +// ) +// +// try database.saveFileTransfer(transferModel) +// try database.saveMessage(transferMessage) hudController.dismiss() } catch { diff --git a/Sources/ChatListFeature/Controller/ChatListController.swift b/Sources/ChatListFeature/Controller/ChatListController.swift index be41db769a23a3ef9e8215d2e240ffee932a7c33..34c6fac7f659f1435d76ab904073de80ce971bb9 100644 --- a/Sources/ChatListFeature/Controller/ChatListController.swift +++ b/Sources/ChatListFeature/Controller/ChatListController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels @@ -10,11 +9,11 @@ public final class ChatListController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ChatListCoordinating - lazy private var screenView = ChatListView() - lazy private var topLeftView = ChatListTopLeftNavView() - lazy private var topRightView = ChatListTopRightNavView() - lazy private var tableController = ChatListTableController(viewModel) - lazy private var searchTableController = ChatSearchTableController(viewModel) + private lazy var screenView = ChatListView() + private lazy var topLeftView = ChatListTopLeftNavView() + private lazy var topRightView = ChatListTopRightNavView() + private lazy var tableController = ChatListTableController(viewModel) + private lazy var searchTableController = ChatSearchTableController(viewModel) private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>! private let viewModel = ChatListViewModel() diff --git a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift index 46e9c20bed9b21b96d883f0ffc07db81caad0de9..c1651f2668c2a0dee962cc43511e88d58e47cc77 100644 --- a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift +++ b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import DependencyInjection diff --git a/Sources/ChatListFeature/Controller/ChatListSheetController.swift b/Sources/ChatListFeature/Controller/ChatListSheetController.swift index cb52ce6a9313f14e4351bcae2c5fdd82d0bee952..79ad33eb198d177dee3618d27f4f455d6f1d94fc 100644 --- a/Sources/ChatListFeature/Controller/ChatListSheetController.swift +++ b/Sources/ChatListFeature/Controller/ChatListSheetController.swift @@ -7,7 +7,7 @@ public final class ChatListSheetController: UIViewController { case deleteAll } - lazy private var screenView = ChatListMenuView() + private lazy var screenView = ChatListMenuView() var didChooseAction: (Action) -> Void private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ChatListFeature/Controller/ChatListTableController.swift b/Sources/ChatListFeature/Controller/ChatListTableController.swift index 8527195eee326146909a865a6768e553c10a5e3d..b67c29da977c59eeb16428f251481ef8c0b2674b 100644 --- a/Sources/ChatListFeature/Controller/ChatListTableController.swift +++ b/Sources/ChatListFeature/Controller/ChatListTableController.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels import DifferenceKit diff --git a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift b/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift index acd4fbcaecf645611fd30d058a0172f785b03fab..57f94bb3ad0e3d07ebf4781549b6762c14d17c35 100644 --- a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift +++ b/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import XXModels import MenuFeature import ChatFeature diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index f61aef1e7e33b55fd857cfae60899b3ad71dd6bf..3792e0c14aebb66f4229c6d246d9cfe22cc9f379 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels import Defaults diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift index 1fa130ce1a3bc80b88a280fd575ae4f4f73e75da..0fbb22e4b0620b4f99c4ecd656d1e2fc09d10977 100644 --- a/Sources/ContactFeature/Controllers/ContactController.swift +++ b/Sources/ContactFeature/Controllers/ContactController.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels import DrawerFeature @@ -11,8 +10,8 @@ public final class ContactController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ContactCoordinating - lazy private var screenView = ContactView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = ContactView() + private lazy var scrollViewController = ScrollViewController() private let viewModel: ContactViewModel private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ContactFeature/Controllers/NicknameController.swift b/Sources/ContactFeature/Controllers/NicknameController.swift index 709a66998b46c59b402adbbaedb8098be57c8ae3..2492083e2f4ed93fd910acf8a8f80c024117399b 100644 --- a/Sources/ContactFeature/Controllers/NicknameController.swift +++ b/Sources/ContactFeature/Controllers/NicknameController.swift @@ -5,7 +5,7 @@ import InputField import ScrollViewController public final class NicknameController: UIViewController { - lazy private var screenView = NicknameView() + private lazy var screenView = NicknameView() private let prefilled: String private let completion: StringClosure diff --git a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift b/Sources/ContactFeature/Coordinator/ContactCoordinator.swift index eea7f1ca94bbcace7a637abc5a1d094d53fff74b..2b366da6b634495937099e42482b3a1cb49337ee 100644 --- a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift +++ b/Sources/ContactFeature/Coordinator/ContactCoordinator.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import XXModels import ChatFeature diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift index ee0e2676bcc7fd665c32f05a5fb45a6aae4b387b..3876c0b98eaaa5280e3129956a7a38075df36c53 100644 --- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift +++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels import Defaults diff --git a/Sources/ContactFeature/Views/ContactInProgressView.swift b/Sources/ContactFeature/Views/ContactInProgressView.swift index 165600f9deb908c47de78f7e4b71e9213d3d847e..533b5fe893b6f2ab5637dacaab9c2b38cf20881b 100644 --- a/Sources/ContactFeature/Views/ContactInProgressView.swift +++ b/Sources/ContactFeature/Views/ContactInProgressView.swift @@ -1,70 +1,53 @@ import UIKit import Shared -import Models import XXModels final class ContactAlmostView: UIView { - // MARK: UI + let stack = UIStackView() + let feedback = BottomFeedbackComponent() - let stack = UIStackView() - let feedback = BottomFeedbackComponent() + init() { + super.init(frame: .zero) + stack.axis = .vertical + stack.spacing = 25 - // MARK: Lifecycle + addSubview(stack) + addSubview(feedback) - init() { - super.init(frame: .zero) - setup() + stack.snp.makeConstraints { + $0.top.equalToSuperview().offset(24) + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) } - required init?(coder: NSCoder) { nil } - - // MARK: Public - - func set(status: Contact.AuthStatus) { - switch status { - case .requestFailed, .confirmationFailed: - feedback.set( - icon: Asset.contactRequestExclamation.image, - title: Localized.Contact.Inprogress.failed, - style: .danger, - actionTitle: Localized.Contact.Inprogress.resend - ) - - case .confirming, .requested, .requesting: - feedback.set( - icon: Asset.contactRequestExclamation.image, - title: Localized.Contact.Inprogress.pending, - style: .chill - ) - default: - break - } - } - - // MARK: Properties - - private func setup() { - stack.axis = .vertical - stack.spacing = 25 - - addSubview(stack) - addSubview(feedback) - - setupConstraints() + feedback.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(stack.snp.bottom).offset(24) + $0.left.equalToSuperview() + $0.right.equalToSuperview() + $0.bottom.equalToSuperview() } - - private func setupConstraints() { - stack.snp.makeConstraints { make in - make.top.equalToSuperview().offset(24) - make.left.equalToSuperview().offset(24) - make.right.equalToSuperview().offset(-24) - } - - feedback.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(stack.snp.bottom).offset(24) - make.left.equalToSuperview() - make.right.equalToSuperview() - make.bottom.equalToSuperview() - } + } + + required init?(coder: NSCoder) { nil } + + func set(status: Contact.AuthStatus) { + switch status { + case .requestFailed, .confirmationFailed: + feedback.set( + icon: Asset.contactRequestExclamation.image, + title: Localized.Contact.Inprogress.failed, + style: .danger, + actionTitle: Localized.Contact.Inprogress.resend + ) + + case .confirming, .requested, .requesting: + feedback.set( + icon: Asset.contactRequestExclamation.image, + title: Localized.Contact.Inprogress.pending, + style: .chill + ) + default: + break } + } } diff --git a/Sources/ContactFeature/Views/ContactView.swift b/Sources/ContactFeature/Views/ContactView.swift index e5fb03927caf3de9018a382a004b7c89c8a69e99..08b4a4c6d445f66af68e5ba2637fe26e3c46efe6 100644 --- a/Sources/ContactFeature/Views/ContactView.swift +++ b/Sources/ContactFeature/Views/ContactView.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import XXModels final class ContactView: UIView { diff --git a/Sources/ContactListFeature/Controllers/ContactListController.swift b/Sources/ContactListFeature/Controllers/ContactListController.swift index d7f9094b833da623928d8c197ddd468239cbe091..0b276e5f97881413d5e8ad561b181674196809b3 100644 --- a/Sources/ContactListFeature/Controllers/ContactListController.swift +++ b/Sources/ContactListFeature/Controllers/ContactListController.swift @@ -7,8 +7,8 @@ public final class ContactListController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ContactListCoordinating - lazy private var screenView = ContactListView() - lazy private var tableController = ContactListTableController(viewModel) + private lazy var screenView = ContactListView() + private lazy var tableController = ContactListTableController(viewModel) private let viewModel = ContactListViewModel() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ContactListFeature/Controllers/ContactListTableController.swift b/Sources/ContactListFeature/Controllers/ContactListTableController.swift index ac7a06628045f199393f1839e2a6f700a7cb194e..0e456781aadc10fe5ce5c8e98b2444720a540546 100644 --- a/Sources/ContactListFeature/Controllers/ContactListTableController.swift +++ b/Sources/ContactListFeature/Controllers/ContactListTableController.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels diff --git a/Sources/ContactListFeature/Controllers/CreateDrawerController.swift b/Sources/ContactListFeature/Controllers/CreateDrawerController.swift index 543a9722869b0bf6cbc1b66579f235cfa88daf26..4ba7213d8a117cd7650fa992f40ccc8e43ab2924 100644 --- a/Sources/ContactListFeature/Controllers/CreateDrawerController.swift +++ b/Sources/ContactListFeature/Controllers/CreateDrawerController.swift @@ -3,7 +3,7 @@ import Shared import Combine public final class CreateDrawerController: UIViewController { - lazy private var screenView = CreateDrawerView() + private lazy var screenView = CreateDrawerView() private let selectedCount: Int private let viewModel = CreateDrawerViewModel() diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift index d6df303a0771dfc7e01b4efc83c9ef099ece1002..8f2b2c93626f0390b2713d4f50adf7eb6e145bd2 100644 --- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift +++ b/Sources/ContactListFeature/Controllers/CreateGroupController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels @@ -8,9 +7,9 @@ import DependencyInjection public final class CreateGroupController: UIViewController { @Dependency private var coordinator: ContactListCoordinating - lazy private var titleLabel = UILabel() - lazy private var createButton = UIButton() - lazy private var screenView = CreateGroupView() + private lazy var titleLabel = UILabel() + private lazy var createButton = UIButton() + private lazy var screenView = CreateGroupView() private var selectedElements = [Contact]() { didSet { screenView.tableView.reloadData() } diff --git a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift index eb967cf04231cb85c203714bb66dd9546bac3209..650f031d8739feeb64b38ff2befd1f3993937421 100644 --- a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift +++ b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import XXModels import MenuFeature import ChatFeature diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift index 2f1364c5306724b14a08ed3592d4af6b094efd86..93de50cdd963d8c60d57118ec8692d1746ed56fb 100644 --- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift @@ -1,4 +1,3 @@ -import Models import Combine import XXModels import Defaults diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift index 2277d915c4a50b1673c30879ed32b17d712fcec4..724aa19f108c22ff8164bdfae69a38ddbd92c8e1 100644 --- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import XXModels import Defaults diff --git a/Sources/Countries/CountryListController.swift b/Sources/Countries/CountryListController.swift index 633633f1a921be0183cd00effe2f3e73080105b6..5956946f6ea8f5edb51740f0204c34716a300519 100644 --- a/Sources/Countries/CountryListController.swift +++ b/Sources/Countries/CountryListController.swift @@ -7,7 +7,7 @@ import DependencyInjection public final class CountryListController: UIViewController { @Dependency var barStylist: StatusBarStylist - lazy private var screenView = CountryListView() + private lazy var screenView = CountryListView() private var didChoose: ((Country) -> Void)! private let viewModel = CountryListViewModel() diff --git a/Sources/DrawerFeature/DrawerController.swift b/Sources/DrawerFeature/DrawerController.swift index d907c26ba78956a919bcf58034b6e50720c0456a..de613530cd8fc92b150c1d56af1a110ad767c0eb 100644 --- a/Sources/DrawerFeature/DrawerController.swift +++ b/Sources/DrawerFeature/DrawerController.swift @@ -2,7 +2,7 @@ import UIKit import Combine public final class DrawerController: UIViewController { - lazy private var screenView = DrawerView() + private lazy var screenView = DrawerView() private let content: [DrawerItem] public var cancellables = Set<AnyCancellable>() diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift index 4c533ee1f9fa7c2357300e75191af99aa9463fec..fbcd6044ff3888c8d3ceb1109f6b3f13ab3aa756 100644 --- a/Sources/LaunchFeature/LaunchController.swift +++ b/Sources/LaunchFeature/LaunchController.swift @@ -1,19 +1,18 @@ import UIKit import Shared import Combine -import Defaults +import Navigation import PushFeature import DependencyInjection public final class LaunchController: UIViewController { - @Dependency var coordinator: LaunchCoordinating + @Dependency var navigator: Navigator - @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool - - lazy private var screenView = LaunchView() + // TO REMOVE: + public var pendingPushRoute: PushRouter.Route? private let viewModel = LaunchViewModel() - public var pendingPushRoute: PushRouter.Route? + private lazy var screenView = LaunchView() private var cancellables = Set<AnyCancellable>() public override func viewDidAppear(_ animated: Bool) { @@ -27,64 +26,23 @@ public final class LaunchController: UIViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController? - .navigationBar - .customize(translucent: true) + navigationController?.navigationBar.customize(translucent: true) } public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - screenView.setupGradient() - } - - public override func viewDidLoad() { - super.viewDidLoad() - - viewModel.routePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - switch $0 { - case .chats: - guard didAcceptTerms == true else { - coordinator.toTerms(from: self) - return - } - - if let pushRoute = pendingPushRoute { - switch pushRoute { - case .requests: - coordinator.toRequests(from: self) - - case .search(username: let username): - coordinator.toSearch(searching: username, from: self) - - case .groupChat(id: let groupId): - if let groupInfo = viewModel.getGroupInfoWith(groupId: groupId) { - coordinator.toGroupChat(with: groupInfo, from: self) - return - } - coordinator.toChats(from: self) - - case .contactChat(id: let userId): - if let contact = viewModel.getContactWith(userId: userId) { - coordinator.toSingleChat(with: contact, from: self) - return - } - coordinator.toChats(from: self) - } - - return - } - - coordinator.toChats(from: self) - - case .onboarding: - coordinator.toOnboarding(from: self) - - case .update(let model): - offerUpdate(model: model) - } - }.store(in: &cancellables) + let gradient = CAGradientLayer() + gradient.colors = [ + UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor, + UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor, + UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor, + UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor + ] + + gradient.frame = screenView.bounds + gradient.startPoint = CGPoint(x: 1, y: 0) + gradient.endPoint = CGPoint(x: 0, y: 1) + screenView.layer.insertSublayer(gradient, at: 0) } private func offerUpdate(model: Update) { @@ -129,26 +87,26 @@ public final class LaunchController: UIViewController { title: model.positiveActionTitle ) -// if let negativeTitle = model.negativeActionTitle { -// let negativeButton = CapsuleButton() -// negativeButton.set(style: .simplestColoredRed, title: negativeTitle) -// -// negativeButton.publisher(for: .touchUpInside) -// .sink { [unowned self] in -// blocker.hideWindow() -// viewModel.continueWithInitialization() -// }.store(in: &cancellables) -// -// vStack.addArrangedSubview(negativeButton) -// } -// -// blocker.window?.addSubview(drawerView) -// drawerView.snp.makeConstraints { -// $0.left.equalToSuperview().offset(18) -// $0.center.equalToSuperview() -// $0.right.equalToSuperview().offset(-18) -// } -// -// blocker.showWindow() + // if let negativeTitle = model.negativeActionTitle { + // let negativeButton = CapsuleButton() + // negativeButton.set(style: .simplestColoredRed, title: negativeTitle) + // + // negativeButton.publisher(for: .touchUpInside) + // .sink { [unowned self] in + // blocker.hideWindow() + // viewModel.continueWithInitialization() + // }.store(in: &cancellables) + // + // vStack.addArrangedSubview(negativeButton) + // } + // + // blocker.window?.addSubview(drawerView) + // drawerView.snp.makeConstraints { + // $0.left.equalToSuperview().offset(18) + // $0.center.equalToSuperview() + // $0.right.equalToSuperview().offset(-18) + // } + // + // blocker.showWindow() } } diff --git a/Sources/LaunchFeature/LaunchCoordinator.swift b/Sources/LaunchFeature/LaunchCoordinator.swift deleted file mode 100644 index b07a99c759cf5a4748a66c5d0a51db08c6117848..0000000000000000000000000000000000000000 --- a/Sources/LaunchFeature/LaunchCoordinator.swift +++ /dev/null @@ -1,84 +0,0 @@ -import UIKit -import Models -import XXModels -import Presentation - -public protocol LaunchCoordinating { - func toChats(from: UIViewController) - func toTerms(from: UIViewController) - func toRequests(from: UIViewController) - func toSearch(searching: String, from: UIViewController) - func toOnboarding(from: UIViewController) - func toSingleChat(with: Contact, from: UIViewController) - func toGroupChat(with: GroupInfo, from: UIViewController) -} - -public struct LaunchCoordinator: LaunchCoordinating { - var replacePresenter: Presenting = ReplacePresenter() - - var termsFactory: () -> UIViewController - var searchFactory: (String) -> UIViewController - var requestsFactory: () -> UIViewController - var chatListFactory: () -> UIViewController - var onboardingFactory: () -> UIViewController - var singleChatFactory: (Contact) -> UIViewController - var groupChatFactory: (GroupInfo) -> UIViewController - - public init( - termsFactory: @escaping () -> UIViewController, - searchFactory: @escaping (String) -> UIViewController, - requestsFactory: @escaping () -> UIViewController, - chatListFactory: @escaping () -> UIViewController, - onboardingFactory: @escaping () -> UIViewController, - singleChatFactory: @escaping (Contact) -> UIViewController, - groupChatFactory: @escaping (GroupInfo) -> UIViewController - ) { - self.termsFactory = termsFactory - self.searchFactory = searchFactory - self.requestsFactory = requestsFactory - self.chatListFactory = chatListFactory - self.groupChatFactory = groupChatFactory - self.onboardingFactory = onboardingFactory - self.singleChatFactory = singleChatFactory - } -} - -public extension LaunchCoordinator { - func toSearch(searching: String, from parent: UIViewController) { - let screen = searchFactory(searching) - let chatListScreen = chatListFactory() - replacePresenter.present(chatListScreen, screen, from: parent) - } - - func toTerms(from parent: UIViewController) { - let screen = termsFactory() - replacePresenter.present(screen, from: parent) - } - - func toChats(from parent: UIViewController) { - let screen = chatListFactory() - replacePresenter.present(screen, from: parent) - } - - func toRequests(from parent: UIViewController) { - let screen = requestsFactory() - replacePresenter.present(screen, from: parent) - } - - func toOnboarding(from parent: UIViewController) { - let screen = onboardingFactory() - replacePresenter.present(screen, from: parent) - } - - func toSingleChat(with contact: Contact, from parent: UIViewController) { - let chatListScreen = chatListFactory() - let singleChatScreen = singleChatFactory(contact) - replacePresenter.present(chatListScreen, singleChatScreen, from: parent) - } - - func toGroupChat(with group: GroupInfo, from parent: UIViewController) { - let chatListScreen = chatListFactory() - let groupChatScreen = groupChatFactory(group) - replacePresenter.present(chatListScreen, groupChatScreen, from: parent) - } -} diff --git a/Sources/LaunchFeature/LaunchView.swift b/Sources/LaunchFeature/LaunchView.swift index 995c9f8c6b66501798069a736594c3e4a87a5537..8a9fc7a43cfdf3b3b498b365c57763b9c2d7d0d0 100644 --- a/Sources/LaunchFeature/LaunchView.swift +++ b/Sources/LaunchFeature/LaunchView.swift @@ -2,36 +2,19 @@ import UIKit import Shared final class LaunchView: UIView { - private var imageView = UIImageView() + let imageView = UIImageView() - init() { - super.init(frame: .zero) - imageView.image = Asset.splash.image - imageView.contentMode = .scaleAspectFit - backgroundColor = Asset.neutralWhite.color - - addSubview(imageView) - - imageView.snp.makeConstraints { - $0.center.equalToSuperview() - $0.left.equalToSuperview().offset(100) - } + init() { + super.init(frame: .zero) + imageView.image = Asset.splash.image + imageView.contentMode = .scaleAspectFit + backgroundColor = Asset.neutralWhite.color + addSubview(imageView) + imageView.snp.makeConstraints { + $0.center.equalToSuperview() + $0.left.equalToSuperview().offset(100) } + } - required init?(coder: NSCoder) { nil } - - func setupGradient() { - let gradient = CAGradientLayer() - gradient.colors = [ - UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor, - UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor, - UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor, - UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor - ] - - gradient.frame = bounds - gradient.startPoint = CGPoint(x: 1, y: 0) - gradient.endPoint = CGPoint(x: 0, y: 1) - layer.insertSublayer(gradient, at: 0) - } + required init?(coder: NSCoder) { nil } } diff --git a/Sources/LaunchFeature/LaunchViewModel+Banned.swift b/Sources/LaunchFeature/LaunchViewModel+Banned.swift new file mode 100644 index 0000000000000000000000000000000000000000..d62c2c3d70fc34379fe00cca21dbf84e11cc79d4 --- /dev/null +++ b/Sources/LaunchFeature/LaunchViewModel+Banned.swift @@ -0,0 +1,61 @@ +import Shared +import XXModels +import Foundation + +extension LaunchViewModel { + func updateBannedList(completion: @escaping () -> Void) { + fetchBannedList { result in + switch result { + case .failure(_): + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + self.updateBannedList(completion: completion) + } + case .success(let data): + self.processBannedList(data, completion: completion) + } + } + } + + func processBannedList(_ data: Data, completion: @escaping () -> Void) { + processBannedList( + data: data, + forEach: { result in + switch result { + case .success(let userId): + let query = Contact.Query(id: [userId]) + if var contact = try! database.fetchContacts(query).first { + if contact.isBanned == false { + contact.isBanned = true + try! database.saveContact(contact) + self.enqueueBanWarning(contact: contact) + } + } else { + try! database.saveContact(.init(id: userId, isBanned: true)) + } + + case .failure(_): + break + } + }, + completion: { result in + switch result { + case .failure(_): + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + self.updateBannedList(completion: completion) + } + + case .success(_): + completion() + } + } + ) + } + + func enqueueBanWarning(contact: XXModels.Contact) { + let name = (contact.nickname ?? contact.username) ?? "One of your contacts" + toastController.enqueueToast(model: .init( + title: "\(name) has been banned for offensive content.", + leftImage: Asset.requestSentToaster.image + )) + } +} diff --git a/Sources/LaunchFeature/LaunchViewModel+Database.swift b/Sources/LaunchFeature/LaunchViewModel+Database.swift new file mode 100644 index 0000000000000000000000000000000000000000..62fa45aafb2c7d73cc7efa2a0fb834938e889f8d --- /dev/null +++ b/Sources/LaunchFeature/LaunchViewModel+Database.swift @@ -0,0 +1,77 @@ +import XXModels +import Foundation +import XXDatabase +import DependencyInjection +import XXLegacyDatabaseMigrator + +extension LaunchViewModel { + func setupDatabase() throws { + let legacyOldPath = NSSearchPathForDirectoriesInDomains( + .documentDirectory, .userDomainMask, true + )[0].appending("/xxmessenger.sqlite") + + let legacyPath = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! + .appendingPathComponent("database") + .appendingPathExtension("sqlite").path + + let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath) + let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath) + + if dbExistsInLegacyOldPath && !dbExistsInLegacyPath { + try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath) + } + + let dbPath = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! + .appendingPathComponent("xxm_database") + .appendingPathExtension("sqlite").path + + let database = try Database.onDisk(path: dbPath) + + if dbExistsInLegacyPath { + try Migrator.live()( + try .init(path: legacyPath), + to: database, + myContactId: Data(), //client.bindings.myId, + meMarshaled: Data() //client.bindings.meMarshalled + ) + + try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup")) + } + + DependencyInjection.Container.shared.register(database) + + _ = try? database.bulkUpdateContacts(.init(authStatus: [.requesting]), .init(authStatus: .requestFailed)) + _ = try? database.bulkUpdateContacts(.init(authStatus: [.confirming]), .init(authStatus: .confirmationFailed)) + _ = try? database.bulkUpdateContacts(.init(authStatus: [.verificationInProgress]), .init(authStatus: .verificationFailed)) + } + + + // func getContactWith(userId: Data) -> XXModels.Contact? { + // let query = Contact.Query( + // id: [userId], + // isBlocked: reportingStatus.isEnabled() ? false : nil, + // isBanned: reportingStatus.isEnabled() ? false : nil + // ) + // + // guard let database: Database = try? DependencyInjection.Container.shared.resolve(), + // let contact = try? database.fetchContacts(query).first else { + // return nil + // } + // + // return contact + // } + + + func getGroupInfoWith(groupId: Data) -> GroupInfo? { + let query = GroupInfo.Query(groupId: groupId) + + guard let database: Database = try? DependencyInjection.Container.shared.resolve(), + let info = try? database.fetchGroupInfos(query).first else { + return nil + } + + return info + } +} diff --git a/Sources/LaunchFeature/LaunchViewModel+Messenger.swift b/Sources/LaunchFeature/LaunchViewModel+Messenger.swift new file mode 100644 index 0000000000000000000000000000000000000000..f5ed8ee1af24b5fe4793f2b337fe979da126debe --- /dev/null +++ b/Sources/LaunchFeature/LaunchViewModel+Messenger.swift @@ -0,0 +1,396 @@ +import XXClient +import XXModels +import XXLogger +import Foundation +import XXMessengerClient +import DependencyInjection + +extension LaunchViewModel { + func setupBackupCallback() { + backupCallbackCancellable = messenger.registerBackupCallback(.init(handle: { [weak self] in + print(">>> Backup callback from bindings got called") + self?.backupService.updateLocalBackup($0) + })) + } + + func setupMessageCallback() { + messageListenerCallbacksCancellable = messenger.registerMessageListener(.init(handle: { + guard let payload = try? Payload(with: $0.payload) else { + fatalError("Couldn't decode payload: \(String(data: $0.payload, encoding: .utf8) ?? "nil")") + } + + try! self.database.saveMessage(.init( + networkId: $0.id, + senderId: $0.sender, + recipientId: self.messenger.e2e.get()!.getContact().getId(), + groupId: nil, + date: Date.fromTimestamp($0.timestamp), + status: .received, + isUnread: true, + text: payload.text, + replyMessageId: payload.reply?.messageId, + roundURL: $0.roundURL, + fileTransferId: nil + )) + + if var contact = try? self.database.fetchContacts(.init(id: [$0.sender])).first { + contact.isRecent = false + try! self.database.saveContact(contact) + } + })) + } + + func setupAuthCallback() { + authCallbacksCancellable = messenger.registerAuthCallbacks( + AuthCallbacks(handle: { + switch $0 { + case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleConfirm(from: contact) + case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleDirectRequest(from: contact) + case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleReset(from: contact) + } + }) + ) + } + + func handleReset(from user: XXClient.Contact) { + if var contact = try? database.fetchContacts(.init(id: [user.getId()])).first { + contact.authStatus = .friend + _ = try? database.saveContact(contact) + } + } + + func handleConfirm(from contact: XXClient.Contact) { + guard let id = try? contact.getId() else { + fatalError("Couldn't extract ID from contact confirmation arrived.") + } + + guard var existentContact = try? database.fetchContacts(.init(id: [id])).first else { + print(">>> Tried to handle a confirmation from someone that is not a contact yet") + return + } + + existentContact.isRecent = true + existentContact.authStatus = .friend + try! database.saveContact(existentContact) + } + + func setupLogWriter() { + _ = try! SetLogLevel.live(.fatal) + RegisterLogWriter.live(.init(handle: { XXLogger.live().debug($0) })) + } + + func listenToNetworkUpdates() { + networkMonitor.start() + networkCallbacksCancellable = messenger.cMix.get()!.addHealthCallback(.init(handle: { [weak self] in + guard let self else { return } + self.networkMonitor.update($0) + })) + } + + func handleIncomingTransfer(_ receivedFile: ReceivedFile) { + // if var model = try? database.saveFileTransfer(.init( + // id: receivedFile.transferId, + // contactId: receivedFile.senderId, + // name: receivedFile.name, + // type: receivedFile.type, + // data: nil, + // progress: 0.0, + // isIncoming: true, + // createdAt: Date() + // )) { + // try! database.saveMessage(.init( + // networkId: nil, + // senderId: receivedFile.senderId, + // recipientId: messenger.e2e.get()!.getContact().getId(), + // groupId: nil, + // date: Date(), + // status: .receiving, + // isUnread: false, + // text: "", + // replyMessageId: nil, + // roundURL: nil, + // fileTransferId: model.id + // )) + // + // if let manager: XXClient.FileTransfer = try? DependencyInjection.Container.shared.resolve() { + // print(">>> registerReceivedProgressCallback") + // + // try! manager.registerReceivedProgressCallback( + // transferId: receivedFile.transferId, + // period: 1_000, + // callback: .init(handle: { [weak self] in + // guard let self else { return } + // switch $0 { + // case .success(let cb): + // if cb.progress.completed { + // model.progress = 100 + // model.data = try! manager.receive(transferId: receivedFile.transferId) + // } else { + // model.progress = Float(cb.progress.transmitted/cb.progress.total) + // } + // + // model = try! self.database.saveFileTransfer(model) + // + // case .failure(let error): + // print(error.localizedDescription) + // } + // }) + // ) + // } else { + // //print(DependencyInjection.Container.shared.dependencies) + // } + // } + } + + func handleDirectRequest(from contact: XXClient.Contact) { + guard let id = try? contact.getId() else { + fatalError("Couldn't extract ID from contact request arrived.") + } + + if let _ = try? database.fetchContacts(.init(id: [id])).first { + print(">>> Tried to handle request from pre-existing contact.") + return + } + + let facts = try? contact.getFacts() + let email = facts?.first(where: { $0.type == .email })?.value + let phone = facts?.first(where: { $0.type == .phone })?.value + let username = facts?.first(where: { $0.type == .username })?.value + + var model = try! database.saveContact(.init( + id: id, + marshaled: contact.data, + username: username, + email: email, + phone: phone, + nickname: nil, + photo: nil, + authStatus: .verificationInProgress, + isRecent: true, + createdAt: Date() + )) + + do { + let messenger: Messenger = try DependencyInjection.Container.shared.resolve() + try messenger.waitForNetwork() + + if try messenger.verifyContact(contact) { + print(">>> [messenger.verifyContact \(#file):\(#line)]") + + model.authStatus = .verified + model = try database.saveContact(model) + } else { + print(">>> [messenger.verifyContact \(#file):\(#line)]") + try database.deleteContact(model) + } + } catch { + print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)") + + model.authStatus = .verificationFailed + model = try! database.saveContact(model) + } + } + + func handleGroupRequest(from group: XXClient.Group, messenger: Messenger) { + if let _ = try? database.fetchGroups(.init(id: [group.getId()])).first { + print(">>> Tried to handle a group request that is already handled") + return + } + + guard var members = try? group.getMembership(), let leader = members.first else { + fatalError("Failed to get group membership/leader") + } + + try! database.saveGroup(.init( + id: group.getId(), + name: String(data: group.getName(), encoding: .utf8)!, + leaderId: leader.id, + createdAt: Date.fromMSTimestamp(group.getCreatedMS()), + authStatus: .pending, + serialized: group.serialize() + )) + + if let initMessageData = group.getInitMessage(), + let initMessage = String(data: initMessageData, encoding: .utf8) { + try! database.saveMessage(.init( + senderId: leader.id, + recipientId: nil, + groupId: group.getId(), + date: Date.fromMSTimestamp(group.getCreatedMS()), + status: .received, + isUnread: true, + text: initMessage + )) + } + + print(">>> All members in the arrived group request:") + members.forEach { print(">>> \($0.id.base64EncodedString().prefix(10))...") } + print(">>> My ud.id is: \(try! messenger.ud.get()!.getContact().getId().base64EncodedString().prefix(10))...") + print(">>> My e2e.id is: \(try! messenger.e2e.get()!.getContact().getId().base64EncodedString().prefix(10))...") + + let friends = try! database.fetchContacts(.init( + id: Set(members.map(\.id)), + authStatus: [ + .friend, + .hidden, + .requesting, + .confirming, + .verificationInProgress, + .verified, + .requested, + .requestFailed, + .verificationFailed, + .confirmationFailed + ] + )) + + print(">>> These people I already know:") + friends.forEach { + print(">>> Username: \($0.username), authStatus: \($0.authStatus.rawValue), id: \($0.id.base64EncodedString().prefix(10))...") + } + + let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id))) + + strangers.forEach { + if let stranger = try? database.fetchContacts(.init(id: [$0])).first { + print(">>> This is a stranger, but I already knew about his/her existance: \(stranger.id.base64EncodedString().prefix(10))...") + } else { + print(">>> This is a complete stranger. Storing on the db: \($0.base64EncodedString().prefix(10))...") + + try! database.saveContact(.init( + id: $0, + marshaled: nil, + username: "Fetching...", + email: nil, + phone: nil, + nickname: nil, + photo: nil, + authStatus: .stranger, + isRecent: false, + isBlocked: false, + isBanned: false, + createdAt: Date.fromMSTimestamp(group.getCreatedMS()) + )) + } + } + + members.forEach { + let model = XXModels.GroupMember(groupId: group.getId(), contactId: $0.id) + _ = try? database.saveGroupMember(model) + } + + print(">>> Performing a multi-lookup for group strangers:") + + do { + let multiLookup = try messenger.lookupContacts(ids: strangers.map { $0 }) + + for user in multiLookup.contacts { + print(">>> Found stranger w/ id: \(try! user.getId().base64EncodedString().prefix(10))...") + + if var foo = try? self.database.fetchContacts(.init(id: [user.getId()])).first, + let username = try? user.getFact(.username)?.value { + foo.username = username + print(">>> Set username: \(username) for \(try! user.getId().base64EncodedString().prefix(10))...") + _ = try? self.database.saveContact(foo) + } + } + + for error in multiLookup.errors { + print(">>> Failure on Multilookup: \(error.localizedDescription)") + } + + for failedId in multiLookup.failedIds { + print(">>> Failed id: \(failedId.base64EncodedString().prefix(10))...") + } + } catch { + print(">>> Exception on multilookup: \(error.localizedDescription)") + } + } + + func generateGroupManager() throws { + let manager = try NewGroupChat.live( + e2eId: messenger.e2e()!.getId(), + groupRequest: .init(handle: { [weak self] group in + guard let self else { return } + self.handleGroupRequest(from: group, messenger: self.messenger) + }), + groupChatProcessor: .init(handle: { result in + switch result { + case .success(let cb): + + print("Incoming GroupMessage:") + print("- groupId: \(cb.decryptedMessage.groupId.base64EncodedString().prefix(10))...") + print("- senderId: \(cb.decryptedMessage.senderId.base64EncodedString().prefix(10))...") + print("- messageId: \(cb.decryptedMessage.messageId.base64EncodedString().prefix(10))...") + + if let payload = try? Payload(with: cb.decryptedMessage.payload) { + print("- payload.text: \(payload.text)") + + if let reply = payload.reply { + print("- payload.reply.senderId: \(reply.senderId.base64EncodedString().prefix(10))...") + print("- payload.reply.messageId: \(reply.messageId.base64EncodedString().prefix(10))...") + } else { + print("- payload.reply: ∅") + } + } + print("") + + guard let payload = try? Payload(with: cb.decryptedMessage.payload) else { + fatalError("Couldn't decode payload: \(String(data: cb.decryptedMessage.payload, encoding: .utf8) ?? "nil")") + } + + let msg = Message( + networkId: cb.decryptedMessage.messageId, + senderId: cb.decryptedMessage.senderId, + recipientId: nil, + groupId: cb.decryptedMessage.groupId, + date: Date.fromTimestamp(Int(cb.decryptedMessage.timestamp)), + status: .received, + isUnread: true, + text: payload.text, + replyMessageId: payload.reply?.messageId, + roundURL: "https://google.com.br", + fileTransferId: nil + ) + + _ = try? self.database.saveMessage(msg) + + case .failure(let error): + break + } + }) + ) + + DependencyInjection.Container.shared.register(manager) + } + + func generateTransferManager() throws { + // let manager = try InitFileTransfer.live( + // e2eId: messenger.e2e()!.getId(), + // callback: .init(handle: { [weak self] in + // guard let self else { return } + // + // switch $0 { + // case .success(let receivedFile): + // self.handleIncomingTransfer(receivedFile, messenger: messenger) + // case .failure(let error): + // print(error.localizedDescription) + // } + // }) + // ) + // + // DependencyInjection.Container.shared.register(manager) + } + + func generateTrafficManager() throws { + let manager = try NewDummyTrafficManager.live( + cMixId: messenger.e2e()!.getId() + ) + + DependencyInjection.Container.shared.register(manager) + try! manager.setStatus(dummyTrafficOn) + } +} diff --git a/Sources/LaunchFeature/LaunchViewModel+VersionCheck.swift b/Sources/LaunchFeature/LaunchViewModel+VersionCheck.swift new file mode 100644 index 0000000000000000000000000000000000000000..4496c75f3f4198ce391e1a5621d0de54127c6184 --- /dev/null +++ b/Sources/LaunchFeature/LaunchViewModel+VersionCheck.swift @@ -0,0 +1,33 @@ +import Shared +import VersionChecking + +extension LaunchViewModel { + func versionFailed(error: Error) { + hudController.show(.init( + title: Localized.Launch.Version.failed, + content: error.localizedDescription + )) + } + + func versionUpdateRequired(_ info: DappVersionInformation) { + hudController.dismiss() + routeSubject.send(.update(Update( + content: info.minimumMessage, + urlString: info.appUrl, + positiveActionTitle: Localized.Launch.Version.Required.positive, + negativeActionTitle: nil, + actionStyle: .brandColored + ))) + } + + func versionUpdateRecommended(_ info: DappVersionInformation) { + hudController.dismiss() + routeSubject.send(.update(Update( + content: Localized.Launch.Version.Recommended.title, + urlString: info.appUrl, + positiveActionTitle: Localized.Launch.Version.Recommended.positive, + negativeActionTitle: Localized.Launch.Version.Recommended.negative, + actionStyle: .simplestColoredRed + ))) + } +} diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index 92f46f14d1c534d100c40f8ec75f25d4ce984a8b..3639e1d1d8a7a31c755f20350dc83469446a40f6 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -1,5 +1,4 @@ import Shared -import Models import Combine import Defaults import XXModels @@ -42,6 +41,7 @@ enum LaunchRoute { final class LaunchViewModel { @Dependency var database: Database + @Dependency var messenger: Messenger @Dependency var hudController: HUDController @Dependency var backupService: BackupService @Dependency var versionChecker: VersionChecker @@ -55,6 +55,7 @@ final class LaunchViewModel { @KeyObject(.username, defaultValue: nil) var username: String? @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool + @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn: Bool var authCallbacksCancellable: Cancellable? @@ -66,7 +67,7 @@ final class LaunchViewModel { routeSubject.eraseToAnyPublisher() } - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = { + private var scheduler: AnySchedulerOf<DispatchQueue> = { DispatchQueue.global().eraseToAnyScheduler() }() @@ -82,31 +83,30 @@ final class LaunchViewModel { fileName: "" ) - private var cancellables = Set<AnyCancellable>() - private let routeSubject = PassthroughSubject<LaunchRoute, Never>() + var cancellables = Set<AnyCancellable>() + let routeSubject = PassthroughSubject<LaunchRoute, Never>() func viewDidAppear() { - backgroundScheduler.schedule(after: .init(.now() + 1)) { [weak self] in - guard let self = self else { return } - + scheduler.schedule(after: .init(.now() + 1)) { [weak self] in + guard let self else { return } self.hudController.show() - - self.versionChecker().sink { [unowned self] in - switch $0 { - case .upToDate: - self.updateBannedList { - self.updateErrors { - self.continueWithInitialization() + self.versionChecker() + .sink { [unowned self] in + switch $0 { + case .upToDate: + self.updateBannedList { + self.updateErrors { + self.continueWithInitialization() + } } + case .failure(let error): + self.versionFailed(error: error) + case .updateRequired(let info): + self.versionUpdateRequired(info) + case .updateRecommended(let info): + self.versionUpdateRecommended(info) } - case .failure(let error): - self.versionFailed(error: error) - case .updateRequired(let info): - self.versionUpdateRequired(info) - case .updateRecommended(let info): - self.versionUpdateRecommended(info) - } - }.store(in: &self.cancellables) + }.store(in: &self.cancellables) } } @@ -114,13 +114,10 @@ final class LaunchViewModel { do { try self.setupDatabase() - let messenger = makeMessenger() - DependencyInjection.Container.shared.register(messenger) - setupLogWriter() - setupAuthCallback(messenger) - setupBackupCallback(messenger) - setupMessageCallback(messenger) + setupAuthCallback() + setupBackupCallback() + setupMessageCallback() if messenger.isLoaded() == false { if messenger.isCreated() == false { @@ -137,10 +134,10 @@ final class LaunchViewModel { try messenger.listenForMessages() } - try generateGroupManager(messenger) - try generateTrafficManager(messenger) - try generateTransferManager(messenger) - listenToNetworkUpdates(messenger) + try generateGroupManager() + try generateTrafficManager() + try generateTransferManager() + listenToNetworkUpdates() if messenger.isLoggedIn() == false { if try messenger.isRegistered() { @@ -157,11 +154,9 @@ final class LaunchViewModel { hudController.dismiss() routeSubject.send(.chats) } - if !messenger.isBackupRunning() { try? messenger.resumeBackup() } - // TODO: Biometric auth } catch { @@ -180,109 +175,6 @@ final class LaunchViewModel { routeSubject.send(.onboarding) } - private func setupDatabase() throws { - let legacyOldPath = NSSearchPathForDirectoriesInDomains( - .documentDirectory, .userDomainMask, true - )[0].appending("/xxmessenger.sqlite") - - let legacyPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! - .appendingPathComponent("database") - .appendingPathExtension("sqlite").path - - let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath) - let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath) - - if dbExistsInLegacyOldPath && !dbExistsInLegacyPath { - try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath) - } - - let dbPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! - .appendingPathComponent("xxm_database") - .appendingPathExtension("sqlite").path - - let database = try Database.onDisk(path: dbPath) - - if dbExistsInLegacyPath { - try Migrator.live()( - try .init(path: legacyPath), - to: database, - myContactId: Data(), //client.bindings.myId, - meMarshaled: Data() //client.bindings.meMarshalled - ) - - try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup")) - } - - DependencyInjection.Container.shared.register(database) - - _ = try? database.bulkUpdateContacts(.init(authStatus: [.requesting]), .init(authStatus: .requestFailed)) - _ = try? database.bulkUpdateContacts(.init(authStatus: [.confirming]), .init(authStatus: .confirmationFailed)) - _ = try? database.bulkUpdateContacts(.init(authStatus: [.verificationInProgress]), .init(authStatus: .verificationFailed)) - } - - func getContactWith(userId: Data) -> XXModels.Contact? { - let query = Contact.Query( - id: [userId], - isBlocked: reportingStatus.isEnabled() ? false : nil, - isBanned: reportingStatus.isEnabled() ? false : nil - ) - - guard let database: Database = try? DependencyInjection.Container.shared.resolve(), - let contact = try? database.fetchContacts(query).first else { - return nil - } - - return contact - } - - func getGroupInfoWith(groupId: Data) -> GroupInfo? { - let query = GroupInfo.Query(groupId: groupId) - - guard let database: Database = try? DependencyInjection.Container.shared.resolve(), - let info = try? database.fetchGroupInfos(query).first else { - return nil - } - - return info - } - - private func versionFailed(error: Error) { - hudController.show(.init( - title: Localized.Launch.Version.failed, - content: error.localizedDescription - )) - } - - private func versionUpdateRequired(_ info: DappVersionInformation) { - hudController.dismiss() - - let model = Update( - content: info.minimumMessage, - urlString: info.appUrl, - positiveActionTitle: Localized.Launch.Version.Required.positive, - negativeActionTitle: nil, - actionStyle: .brandColored - ) - - routeSubject.send(.update(model)) - } - - private func versionUpdateRecommended(_ info: DappVersionInformation) { - hudController.dismiss() - - let model = Update( - content: Localized.Launch.Version.Recommended.title, - urlString: info.appUrl, - positiveActionTitle: Localized.Launch.Version.Recommended.positive, - negativeActionTitle: Localized.Launch.Version.Recommended.negative, - actionStyle: .simplestColoredRed - ) - - routeSubject.send(.update(model)) - } - private func checkBiometrics(completion: @escaping (Result<Bool, Error>) -> Void) { if permissionHandler.isBiometricsAvailable && isBiometricsOn { permissionHandler.requestBiometrics { @@ -303,7 +195,7 @@ final class LaunchViewModel { let errorsURLString = "https://git.xx.network/elixxir/client-error-database/-/raw/main/clientErrors.json" URLSession.shared.dataTask(with: URL(string: errorsURLString)!) { [weak self] data, _, error in - guard let self = self else { return } + guard let self else { return } guard error == nil else { print(">>> Issue when trying to download errors json: \(error!.localizedDescription)") @@ -324,490 +216,50 @@ final class LaunchViewModel { } }.resume() } - - private func updateBannedList(completion: @escaping () -> Void) { - fetchBannedList { result in - switch result { - case .failure(_): - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - self.updateBannedList(completion: completion) - } - case .success(let data): - self.processBannedList(data, completion: completion) - } - } - } - - private func processBannedList(_ data: Data, completion: @escaping () -> Void) { - processBannedList( - data: data, - forEach: { result in - switch result { - case .success(let userId): - let query = Contact.Query(id: [userId]) - if var contact = try! database.fetchContacts(query).first { - if contact.isBanned == false { - contact.isBanned = true - try! database.saveContact(contact) - self.enqueueBanWarning(contact: contact) - } - } else { - try! database.saveContact(.init(id: userId, isBanned: true)) - } - - case .failure(_): - break - } - }, - completion: { result in - switch result { - case .failure(_): - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - self.updateBannedList(completion: completion) - } - - case .success(_): - completion() - } - } - ) - } - - private func enqueueBanWarning(contact: XXModels.Contact) { - let name = (contact.nickname ?? contact.username) ?? "One of your contacts" - toastController.enqueueToast(model: .init( - title: "\(name) has been banned for offensive content.", - leftImage: Asset.requestSentToaster.image - )) - } -} - -extension LaunchViewModel { - private func generateGroupManager(_ messenger: Messenger) throws { - let manager = try NewGroupChat.live( - e2eId: messenger.e2e()!.getId(), - groupRequest: .init(handle: { [weak self] group in - guard let self = self else { return } - self.handleGroupRequest(from: group, messenger: messenger) - }), - groupChatProcessor: .init(handle: { result in - switch result { - case .success(let cb): - - print("Incoming GroupMessage:") - print("- groupId: \(cb.decryptedMessage.groupId.base64EncodedString().prefix(10))...") - print("- senderId: \(cb.decryptedMessage.senderId.base64EncodedString().prefix(10))...") - print("- messageId: \(cb.decryptedMessage.messageId.base64EncodedString().prefix(10))...") - - if let payload = try? Payload(with: cb.decryptedMessage.payload) { - print("- payload.text: \(payload.text)") - - if let reply = payload.reply { - print("- payload.reply.senderId: \(reply.senderId.base64EncodedString().prefix(10))...") - print("- payload.reply.messageId: \(reply.messageId.base64EncodedString().prefix(10))...") - } else { - print("- payload.reply: ∅") - } - } - print("") - - guard let payload = try? Payload(with: cb.decryptedMessage.payload) else { - fatalError("Couldn't decode payload: \(String(data: cb.decryptedMessage.payload, encoding: .utf8) ?? "nil")") - } - - let msg = Message( - networkId: cb.decryptedMessage.messageId, - senderId: cb.decryptedMessage.senderId, - recipientId: nil, - groupId: cb.decryptedMessage.groupId, - date: Date.fromTimestamp(Int(cb.decryptedMessage.timestamp)), - status: .received, - isUnread: true, - text: payload.text, - replyMessageId: payload.reply?.messageId, - roundURL: "https://google.com.br", - fileTransferId: nil - ) - - _ = try? self.database.saveMessage(msg) - - case .failure(let error): - break - } - }) - ) - - DependencyInjection.Container.shared.register(manager) - } - - private func generateTransferManager(_ messenger: Messenger) throws { - // let manager = try InitFileTransfer.live( - // e2eId: messenger.e2e()!.getId(), - // callback: .init(handle: { [weak self] in - // guard let self = self else { return } - // - // switch $0 { - // case .success(let receivedFile): - // self.handleIncomingTransfer(receivedFile, messenger: messenger) - // case .failure(let error): - // print(error.localizedDescription) - // } - // }) - // ) - // - // DependencyInjection.Container.shared.register(manager) - } - - private func generateTrafficManager(_ messenger: Messenger) throws { - let manager = try NewDummyTrafficManager.live( - cMixId: messenger.e2e()!.getId() - ) - - DependencyInjection.Container.shared.register(manager) - try! manager.setStatus(dummyTrafficOn) - } -} - -extension LaunchViewModel { - private func handleDirectRequest(from contact: XXClient.Contact) { - guard let id = try? contact.getId() else { - fatalError("Couldn't extract ID from contact request arrived.") - } - - if let _ = try? database.fetchContacts(.init(id: [id])).first { - print(">>> Tried to handle request from pre-existing contact.") - return - } - - let facts = try? contact.getFacts() - let email = facts?.first(where: { $0.type == .email })?.value - let phone = facts?.first(where: { $0.type == .phone })?.value - let username = facts?.first(where: { $0.type == .username })?.value - - var model = try! database.saveContact(.init( - id: id, - marshaled: contact.data, - username: username, - email: email, - phone: phone, - nickname: nil, - photo: nil, - authStatus: .verificationInProgress, - isRecent: true, - createdAt: Date() - )) - - do { - let messenger: Messenger = try DependencyInjection.Container.shared.resolve() - try messenger.waitForNetwork() - - if try messenger.verifyContact(contact) { - print(">>> [messenger.verifyContact \(#file):\(#line)]") - - model.authStatus = .verified - model = try database.saveContact(model) - } else { - print(">>> [messenger.verifyContact \(#file):\(#line)]") - try database.deleteContact(model) - } - } catch { - print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)") - - model.authStatus = .verificationFailed - model = try! database.saveContact(model) - } - } - - private func handleConfirm(from contact: XXClient.Contact) { - guard let id = try? contact.getId() else { - fatalError("Couldn't extract ID from contact confirmation arrived.") - } - - guard var existentContact = try? database.fetchContacts(.init(id: [id])).first else { - print(">>> Tried to handle a confirmation from someone that is not a contact yet") - return - } - - existentContact.isRecent = true - existentContact.authStatus = .friend - try! database.saveContact(existentContact) - } - - private func handleReset(from user: XXClient.Contact) { - if var contact = try? database.fetchContacts(.init(id: [user.getId()])).first { - contact.authStatus = .friend - _ = try? database.saveContact(contact) - } - } - - private func handleGroupRequest(from group: XXClient.Group, messenger: Messenger) { - if let _ = try? database.fetchGroups(.init(id: [group.getId()])).first { - print(">>> Tried to handle a group request that is already handled") - return - } - - guard var members = try? group.getMembership(), let leader = members.first else { - fatalError("Failed to get group membership/leader") - } - - try! database.saveGroup(.init( - id: group.getId(), - name: String(data: group.getName(), encoding: .utf8)!, - leaderId: leader.id, - createdAt: Date.fromMSTimestamp(group.getCreatedMS()), - authStatus: .pending, - serialized: group.serialize() - )) - - if let initMessageData = group.getInitMessage(), - let initMessage = String(data: initMessageData, encoding: .utf8) { - try! database.saveMessage(.init( - senderId: leader.id, - recipientId: nil, - groupId: group.getId(), - date: Date.fromMSTimestamp(group.getCreatedMS()), - status: .received, - isUnread: true, - text: initMessage - )) - } - - print(">>> All members in the arrived group request:") - members.forEach { print(">>> \($0.id.base64EncodedString().prefix(10))...") } - print(">>> My ud.id is: \(try! messenger.ud.get()!.getContact().getId().base64EncodedString().prefix(10))...") - print(">>> My e2e.id is: \(try! messenger.e2e.get()!.getContact().getId().base64EncodedString().prefix(10))...") - - let friends = try! database.fetchContacts(.init( - id: Set(members.map(\.id)), - authStatus: [ - .friend, - .hidden, - .requesting, - .confirming, - .verificationInProgress, - .verified, - .requested, - .requestFailed, - .verificationFailed, - .confirmationFailed - ] - )) - - print(">>> These people I already know:") - friends.forEach { - print(">>> Username: \($0.username), authStatus: \($0.authStatus.rawValue), id: \($0.id.base64EncodedString().prefix(10))...") - } - - let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id))) - - strangers.forEach { - if let stranger = try? database.fetchContacts(.init(id: [$0])).first { - print(">>> This is a stranger, but I already knew about his/her existance: \(stranger.id.base64EncodedString().prefix(10))...") - } else { - print(">>> This is a complete stranger. Storing on the db: \($0.base64EncodedString().prefix(10))...") - - try! database.saveContact(.init( - id: $0, - marshaled: nil, - username: "Fetching...", - email: nil, - phone: nil, - nickname: nil, - photo: nil, - authStatus: .stranger, - isRecent: false, - isBlocked: false, - isBanned: false, - createdAt: Date.fromMSTimestamp(group.getCreatedMS()) - )) - } - } - - members.forEach { - let model = XXModels.GroupMember(groupId: group.getId(), contactId: $0.id) - _ = try? database.saveGroupMember(model) - } - - print(">>> Performing a multi-lookup for group strangers:") - - do { - let multiLookup = try messenger.lookupContacts(ids: strangers.map { $0 }) - - for user in multiLookup.contacts { - print(">>> Found stranger w/ id: \(try! user.getId().base64EncodedString().prefix(10))...") - - if var foo = try? self.database.fetchContacts(.init(id: [user.getId()])).first, - let username = try? user.getFact(.username)?.value { - foo.username = username - print(">>> Set username: \(username) for \(try! user.getId().base64EncodedString().prefix(10))...") - _ = try? self.database.saveContact(foo) - } - } - - for error in multiLookup.errors { - print(">>> Failure on Multilookup: \(error.localizedDescription)") - } - - for failedId in multiLookup.failedIds { - print(">>> Failed id: \(failedId.base64EncodedString().prefix(10))...") - } - } catch { - print(">>> Exception on multilookup: \(error.localizedDescription)") - } - } } -extension LaunchViewModel { - private func handleIncomingTransfer(_ receivedFile: ReceivedFile, messenger: Messenger) { - if var model = try? database.saveFileTransfer(.init( - id: receivedFile.transferId, - contactId: receivedFile.senderId, - name: receivedFile.name, - type: receivedFile.type, - data: nil, - progress: 0.0, - isIncoming: true, - createdAt: Date() - )) { - try! database.saveMessage(.init( - networkId: nil, - senderId: receivedFile.senderId, - recipientId: messenger.e2e.get()!.getContact().getId(), - groupId: nil, - date: Date(), - status: .receiving, - isUnread: false, - text: "", - replyMessageId: nil, - roundURL: nil, - fileTransferId: model.id - )) - - if let manager: XXClient.FileTransfer = try? DependencyInjection.Container.shared.resolve() { - print(">>> registerReceivedProgressCallback") - - try! manager.registerReceivedProgressCallback( - transferId: receivedFile.transferId, - period: 1_000, - callback: .init(handle: { [weak self] in - guard let self = self else { return } - switch $0 { - case .success(let cb): - if cb.progress.completed { - model.progress = 100 - model.data = try! manager.receive(transferId: receivedFile.transferId) - } else { - model.progress = Float(cb.progress.transmitted/cb.progress.total) - } - - model = try! self.database.saveFileTransfer(model) - - case .failure(let error): - print(error.localizedDescription) - } - }) - ) - } else { - //print(DependencyInjection.Container.shared.dependencies) - } - } - } - - private func setupLogWriter() { - _ = try! SetLogLevel.live(.fatal) - RegisterLogWriter.live(.init(handle: { XXLogger.live().debug($0) })) - } - - private func makeMessenger() -> Messenger { - var environment: MessengerEnvironment = .live() - environment.ndfEnvironment = .mainnet - environment.udEnvironment = .init( - address: "46.101.98.49:18001", - cert: """ - -----BEGIN CERTIFICATE----- - MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV - BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx - GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp - cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT - MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV - BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw - DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh - Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs - WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE - tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA - m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9 - bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA - AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA - neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf - U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2 - qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4 - cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R - tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5 - 6m52PyzMNV+2N21IPppKwA== - -----END CERTIFICATE----- - """.data(using: .utf8)!, - contact: """ - <xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc> - """.data(using: .utf8)! - ) - - return Messenger.live(environment) - } - - private func setupAuthCallback(_ messenger: Messenger) { - authCallbacksCancellable = messenger.registerAuthCallbacks( - AuthCallbacks(handle: { - switch $0 { - case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): - self.handleConfirm(from: contact) - case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): - self.handleDirectRequest(from: contact) - case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): - self.handleReset(from: contact) - } - }) - ) - } - - private func setupBackupCallback(_ messenger: Messenger) { - backupCallbackCancellable = messenger.registerBackupCallback(.init(handle: { [weak self] in - print(">>> Backup callback from bindings got called") - self?.backupService.updateLocalBackup($0) - })) - } - - private func setupMessageCallback(_ messenger: Messenger) { - messageListenerCallbacksCancellable = messenger.registerMessageListener(.init(handle: { - guard let payload = try? Payload(with: $0.payload) else { - fatalError("Couldn't decode payload: \(String(data: $0.payload, encoding: .utf8) ?? "nil")") - } - - try! self.database.saveMessage(.init( - networkId: $0.id, - senderId: $0.sender, - recipientId: messenger.e2e.get()!.getContact().getId(), - groupId: nil, - date: Date.fromTimestamp($0.timestamp), - status: .received, - isUnread: true, - text: payload.text, - replyMessageId: payload.reply?.messageId, - roundURL: $0.roundURL, - fileTransferId: nil - )) - - if var contact = try? self.database.fetchContacts(.init(id: [$0.sender])).first { - contact.isRecent = false - try! self.database.saveContact(contact) - } - })) - } - - private func listenToNetworkUpdates(_ messenger: Messenger) { - networkMonitor.start() - networkCallbacksCancellable = messenger.cMix.get()!.addHealthCallback(.init(handle: { [weak self] in - guard let self = self else { return } - self.networkMonitor.update($0) - })) - } -} +// viewModel.routePublisher +// .receive(on: DispatchQueue.main) +// .sink { [unowned self] in +// switch $0 { +// case .chats: +// guard didAcceptTerms == true else { +// navigator.perform(PresentTermsAndConditions(popAllowed: false)) +// return +// } +// +// if let pushRoute = pendingPushRoute { +// switch pushRoute { +// case .requests: +// navigator.perform(PresentRequests()) +// +// case .search(username: let username): +// navigator.perform(PresentSearch(searching: username)) +// +// case .groupChat(id: let groupId): +// if let info = viewModel.getGroupInfoWith(groupId: groupId) { +// navigator.perform(PresentGroupChat(model: info)) +// return +// } +// navigator.perform(PresentChatList()) +// +// case .contactChat(id: let userId): +// if let model = viewModel.getContactWith(userId: userId) { +// navigator.perform(PresentChat(contact: model)) +// return +// } +// navigator.perform(PresentChatList()) +// } +// +// return +// } +// +// navigator.perform(PresentChatList()) +// +// case .onboarding: +// navigator.perform(PresentOnboardingStart()) +// +// case .update(let model): +// offerUpdate(model: model) +// } +// }.store(in: &cancellables) diff --git a/Sources/MenuFeature/Controllers/MenuController.swift b/Sources/MenuFeature/Controllers/MenuController.swift index 49b86d3a90686a2b460d0256a8ff040376486672..22cee48831683ba0ba47619f640dfd6990b2310c 100644 --- a/Sources/MenuFeature/Controllers/MenuController.swift +++ b/Sources/MenuFeature/Controllers/MenuController.swift @@ -20,7 +20,7 @@ public final class MenuController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: MenuCoordinating - lazy private var screenView = MenuView() + private lazy var screenView = MenuView() private let previousItem: MenuItem private let viewModel = MenuViewModel() diff --git a/Sources/Models/AttributeConfirmation.swift b/Sources/Models/AttributeConfirmation.swift deleted file mode 100644 index 2f8b99ea4a332674224eeffb1559295d112f2099..0000000000000000000000000000000000000000 --- a/Sources/Models/AttributeConfirmation.swift +++ /dev/null @@ -1,15 +0,0 @@ -public struct AttributeConfirmation: Equatable { - public var content: String - public var isEmail: Bool = false - public var confirmationId: String? - - public init( - content: String, - isEmail: Bool = false, - confirmationId: String? = nil - ) { - self.content = content - self.isEmail = isEmail - self.confirmationId = confirmationId - } -} diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift similarity index 58% rename from Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift rename to Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift index 4ae996adb6822770888832baf95b95ee7bf627d9..b8fe915aee1e3df24665104a1c99db4e745e71a1 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift @@ -1,29 +1,37 @@ import UIKit import Shared -import Models import Combine +import Navigation +import XXNavigation import DrawerFeature import DependencyInjection import ScrollViewController -public final class OnboardingEmailConfirmationController: UIViewController { +public final class OnboardingCodeController: UIViewController { + @Dependency var navigator: Navigator @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating - lazy private var screenView = OnboardingEmailConfirmationView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = OnboardingCodeView() + private lazy var scrollViewController = ScrollViewController() + private let isEmail: Bool + private let content: String + private let viewModel: OnboardingCodeViewModel private var cancellables = Set<AnyCancellable>() - private let completion: (UIViewController) -> Void private var drawerCancellables = Set<AnyCancellable>() - private let viewModel: OnboardingEmailConfirmationViewModel public init( - _ confirmation: AttributeConfirmation, - _ completion: @escaping (UIViewController) -> Void + _ isEmail: Bool, + _ content: String, + _ confirmationId: String ) { - self.completion = completion - self.viewModel = OnboardingEmailConfirmationViewModel(confirmation) + self.viewModel = .init( + isEmail: isEmail, + content: content, + confirmationId: confirmationId + ) + self.isEmail = isEmail + self.content = content super.init(nibName: nil, bundle: nil) } @@ -42,14 +50,24 @@ public final class OnboardingEmailConfirmationController: UIViewController { setupBindings() screenView.setupSubtitle( - Localized.Onboarding.EmailConfirmation.subtitle(viewModel.confirmation.content) + isEmail ? + Localized.Onboarding.EmailConfirmation.subtitle(content) : + Localized.Onboarding.PhoneConfirmation.subtitle(content) ) screenView.didTapInfo = { [weak self] in - self?.presentInfo( - title: Localized.Onboarding.EmailConfirmation.Info.title, - subtitle: Localized.Onboarding.EmailConfirmation.Info.subtitle - ) + guard let self else { return } + if self.isEmail { + self.presentInfo( + title: Localized.Onboarding.EmailConfirmation.Info.title, + subtitle: Localized.Onboarding.EmailConfirmation.Info.subtitle + ) + } else { + self.presentInfo( + title: Localized.Onboarding.PhoneConfirmation.Info.title, + subtitle: Localized.Onboarding.PhoneConfirmation.Info.subtitle + ) + } } } @@ -63,40 +81,57 @@ public final class OnboardingEmailConfirmationController: UIViewController { } private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) + screenView + .inputField + .textPublisher + .sink { [unowned self] in + viewModel.didInput($0) + }.store(in: &cancellables) - viewModel.state + viewModel + .statePublisher .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) + .sink { [unowned self] in + screenView.update(status: $0) + }.store(in: &cancellables) - screenView.nextButton - .publisher(for: .touchUpInside) + viewModel + .statePublisher + .map(\.didConfirm) .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) + .sink { [unowned self] in + guard $0 == true else { return } + if isEmail { + navigator.perform(PresentOnboardingPhone()) + } else { + navigator.perform(PresentChatList()) + } + }.store(in: &cancellables) - viewModel.completionPublisher + screenView + .nextButton + .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] _ in completion(self) } - .store(in: &cancellables) + .sink { [unowned self] in + viewModel.didTapNext() + }.store(in: &cancellables) - screenView.resendButton + screenView + .resendButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapResend() } - .store(in: &cancellables) + .sink { [unowned self] in + viewModel.didTapResend() + }.store(in: &cancellables) - viewModel.state + viewModel + .statePublisher .map(\.resendDebouncer) .receive(on: DispatchQueue.main) .sink { [unowned self] in screenView.resendButton.isEnabled = $0 == 0 - if $0 == 0 { screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal) } else { @@ -140,6 +175,6 @@ public final class OnboardingEmailConfirmationController: UIViewController { } }.store(in: &drawerCancellables) - coordinator.toDrawer(drawer, from: self) + navigator.perform(PresentDrawer()) } } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift index 2f65daf755a9d65f796c9e3bd0ed352cc73f40fe..b6554d3dad3252d2e22fda105c0fc614fd45c9e3 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift @@ -1,16 +1,18 @@ import UIKit import Shared import Combine +import Navigation +import XXNavigation import DrawerFeature import DependencyInjection import ScrollViewController public final class OnboardingEmailController: UIViewController { + @Dependency var navigator: Navigator @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating - lazy private var screenView = OnboardingEmailView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = OnboardingEmailView() + private lazy var scrollViewController = ScrollViewController() private var cancellables = Set<AnyCancellable>() private let viewModel = OnboardingEmailViewModel() @@ -25,12 +27,11 @@ public final class OnboardingEmailController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() navigationItem.backButtonTitle = " " - setupScrollView() setupBindings() - screenView.didTapInfo = { [weak self] in - self?.presentInfo( + guard let self else { return } + self.presentInfo( title: Localized.Onboarding.Email.Info.title, subtitle: Localized.Onboarding.Email.Info.subtitle, urlString: "https://links.xx.network/ud" @@ -41,52 +42,66 @@ public final class OnboardingEmailController: UIViewController { private func setupScrollView() { addChild(scrollViewController) view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } + scrollViewController.view.snp.makeConstraints { + $0.edges.equalToSuperview() + } scrollViewController.didMove(toParent: self) scrollViewController.contentView = screenView scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color } private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) + screenView + .inputField + .textPublisher + .sink { [unowned self] in + viewModel.didInput($0) + }.store(in: &cancellables) - screenView.inputField.returnPublisher - .sink { [unowned self] in screenView.inputField.endEditing(true) } - .store(in: &cancellables) + screenView + .inputField + .returnPublisher + .sink { [unowned self] in + screenView.inputField.endEditing(true) + }.store(in: &cancellables) - viewModel.state - .map(\.confirmation) + viewModel + .statePublisher .receive(on: DispatchQueue.main) - .compactMap { $0 } .sink { [unowned self] in + guard let id = $0.confirmationId else { return } viewModel.clearUp() - coordinator.toEmailConfirmation(with: $0, from: self) { controller in - let successModel = OnboardingSuccessModel( - title: Localized.Onboarding.Success.Email.title, - subtitle: nil, - nextController: self.coordinator.toPhone(from:) + navigator.perform( + PresentOnboardingCode( + isEmail: true, + content: $0.input, + confirmationId: id ) - - self.coordinator.toSuccess(with: successModel, from: controller) - } + ) }.store(in: &cancellables) - viewModel.state + viewModel + .statePublisher .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) + .sink { [unowned self] in + screenView.update(status: $0) + }.store(in: &cancellables) - screenView.nextButton.publisher(for: .touchUpInside) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) + screenView + .nextButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + viewModel.didTapNext() + }.store(in: &cancellables) - screenView.skipButton.publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toPhone(from: self) } - .store(in: &cancellables) + screenView + .skipButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + navigator.perform(PresentOnboardingPhone()) + }.store(in: &cancellables) } private func presentInfo( @@ -128,6 +143,16 @@ public final class OnboardingEmailController: UIViewController { } }.store(in: &drawerCancellables) - coordinator.toDrawer(drawer, from: self) + navigator.perform(PresentDrawer()) } } + +// coordinator.toEmailConfirmation(with: $0, from: self) { controller in +// let successModel = OnboardingSuccessModel( +// title: Localized.Onboarding.Success.Email.title, +// subtitle: nil, +// nextController: self.coordinator.toPhone(from:) +// ) +// +// self.coordinator.toSuccess(with: successModel, from: controller) +// } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift deleted file mode 100644 index ab9cbca2292a8ebe29407080ecbb13a858fa7635..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift +++ /dev/null @@ -1,145 +0,0 @@ -import UIKit -import Shared -import Models -import Combine -import DrawerFeature -import DependencyInjection -import ScrollViewController - -public final class OnboardingPhoneConfirmationController: UIViewController { - @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating - - lazy private var screenView = OnboardingPhoneConfirmationView() - lazy private var scrollViewController = ScrollViewController() - - private var cancellables = Set<AnyCancellable>() - private let completion: (UIViewController) -> Void - private var drawerCancellables = Set<AnyCancellable>() - private let viewModel: OnboardingPhoneConfirmationViewModel - - public init( - _ confirmation: AttributeConfirmation, - _ completion: @escaping (UIViewController) -> Void - ) { - self.completion = completion - self.viewModel = OnboardingPhoneConfirmationViewModel(confirmation) - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { nil } - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationItem.backButtonTitle = "" - barStylist.styleSubject.send(.darkContent) - navigationController?.navigationBar.customize(translucent: true) - } - - public override func viewDidLoad() { - super.viewDidLoad() - setupScrollView() - setupBindings() - - screenView.setupSubtitle( - Localized.Onboarding.PhoneConfirmation.subtitle(viewModel.confirmation.content) - ) - - screenView.didTapInfo = { [weak self] in - self?.presentInfo( - title: Localized.Onboarding.PhoneConfirmation.Info.title, - subtitle: Localized.Onboarding.PhoneConfirmation.Info.subtitle - ) - } - } - - private func setupScrollView() { - addChild(scrollViewController) - view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } - scrollViewController.didMove(toParent: self) - scrollViewController.contentView = screenView - scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color - } - - private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) - - viewModel.state - .map(\.status) - .removeDuplicates() - .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) - - screenView.nextButton - .publisher(for: .touchUpInside) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) - - viewModel.completionPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] _ in completion(self) } - .store(in: &cancellables) - - screenView.resendButton - .publisher(for: .touchUpInside) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapResend() } - .store(in: &cancellables) - - viewModel.state - .map(\.resendDebouncer) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - screenView.resendButton.isEnabled = $0 == 0 - - if $0 == 0 { - screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal) - } else { - screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled) - } - }.store(in: &cancellables) - } - - private func presentInfo(title: String, subtitle: String) { - let actionButton = CapsuleButton() - actionButton.set(style: .seeThrough, title: Localized.Settings.InfoDrawer.action) - - let drawer = DrawerController(with: [ - DrawerText( - font: Fonts.Mulish.bold.font(size: 26.0), - text: title, - color: Asset.neutralActive.color, - alignment: .left, - spacingAfter: 19 - ), - DrawerText( - font: Fonts.Mulish.regular.font(size: 16.0), - text: subtitle, - color: Asset.neutralBody.color, - alignment: .left, - lineHeightMultiple: 1.1, - spacingAfter: 37 - ), - DrawerStack(views: [ - actionButton, - FlexibleSpace() - ]) - ]) - - actionButton.publisher(for: .touchUpInside) - .receive(on: DispatchQueue.main) - .sink { - drawer.dismiss(animated: true) { [weak self] in - guard let self = self else { return } - self.drawerCancellables.removeAll() - } - }.store(in: &drawerCancellables) - - coordinator.toDrawer(drawer, from: self) - } -} diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift index e593fbd207286f08bd1ad44aebbd61d5c3f70750..dc9df4ea436f0e6cfa10f24449f4daa060b5d1b8 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift @@ -1,16 +1,18 @@ import UIKit import Shared import Combine +import Navigation +import XXNavigation import DrawerFeature import DependencyInjection import ScrollViewController public final class OnboardingPhoneController: UIViewController { + @Dependency var navigator: Navigator @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating - lazy private var screenView = OnboardingPhoneView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = OnboardingPhoneView() + private lazy var scrollViewController = ScrollViewController() private var cancellables = Set<AnyCancellable>() private let viewModel = OnboardingPhoneViewModel() @@ -25,12 +27,11 @@ public final class OnboardingPhoneController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() navigationItem.backButtonTitle = " " - setupScrollView() setupBindings() - screenView.didTapInfo = { [weak self] in - self?.presentInfo( + guard let self else { return } + self.presentInfo( title: Localized.Onboarding.Phone.Info.title, subtitle: Localized.Onboarding.Phone.Info.subtitle, urlString: "https://links.xx.network/ud" @@ -41,58 +42,78 @@ public final class OnboardingPhoneController: UIViewController { private func setupScrollView() { addChild(scrollViewController) view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } + scrollViewController.view.snp.makeConstraints { + $0.edges.equalToSuperview() + } scrollViewController.didMove(toParent: self) scrollViewController.contentView = screenView scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color } private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) + screenView + .inputField + .textPublisher + .sink { [unowned self] in + viewModel.didInput($0) + }.store(in: &cancellables) - screenView.inputField.returnPublisher - .sink { [unowned self] in screenView.inputField.endEditing(true) } - .store(in: &cancellables) + screenView + .inputField + .returnPublisher + .sink { [unowned self] in + screenView.inputField.endEditing(true) + }.store(in: &cancellables) - viewModel.state.map(\.status) + viewModel + .statePublisher + .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) + .sink { [unowned self] in + screenView.update(status: $0) + }.store(in: &cancellables) - screenView.nextButton.publisher(for: .touchUpInside) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) + screenView + .nextButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + viewModel.didTapNext() + }.store(in: &cancellables) - screenView.skipButton.publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toChats(from: self) } - .store(in: &cancellables) + screenView + .skipButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + navigator.perform(PresentChatList()) + }.store(in: &cancellables) - screenView.inputField.codePublisher + screenView + .inputField + .codePublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in - coordinator.toCountries(from: self) { self.viewModel.didChooseCountry($0) } + navigator.perform(PresentCountryList()) }.store(in: &cancellables) - viewModel.state.map(\.confirmation) + viewModel + .statePublisher .receive(on: DispatchQueue.main) - .compactMap { $0 } .sink { [unowned self] in + guard let id = $0.confirmationId, let content = $0.content else { return } viewModel.clearUp() - coordinator.toPhoneConfirmation(with: $0, from: self) { controller in - let successModel = OnboardingSuccessModel( - title: Localized.Onboarding.Success.Phone.title, - subtitle: nil, - nextController: self.coordinator.toChats(from:) + navigator.perform( + PresentOnboardingCode( + isEmail: false, + content: content, + confirmationId: id ) - - self.coordinator.toSuccess(with: successModel, from: controller) - } + ) }.store(in: &cancellables) - viewModel.state.map(\.country) + viewModel + .statePublisher + .map(\.country) .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [unowned self] in @@ -140,6 +161,16 @@ public final class OnboardingPhoneController: UIViewController { } }.store(in: &drawerCancellables) - coordinator.toDrawer(drawer, from: self) + navigator.perform(PresentDrawer()) } } + +// coordinator.toPhoneConfirmation(with: $0, from: self) { controller in +// let successModel = OnboardingSuccessModel( +// title: Localized.Onboarding.Success.Phone.title, +// subtitle: nil, +// nextController: self.coordinator.toChats(from:) +// ) +// +// self.coordinator.toSuccess(with: successModel, from: controller) +// } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift index 23ff5f77896757c5e087e154f5ef4745657a238e..ac5df16e70833d47132b4c0f4ab8ac8d20c5c486 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift @@ -1,49 +1,52 @@ import UIKit import Combine +import Navigation +import XXNavigation import DependencyInjection public final class OnboardingStartController: UIViewController { - @Dependency private var coordinator: OnboardingCoordinating - - lazy private var screenView = OnboardingStartView() - - private var cancellables = Set<AnyCancellable>() - - public override func loadView() { - view = screenView - } - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationItem.backButtonTitle = "" - navigationController?.navigationBar.customize(translucent: true) - } - - public override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - let gradient = CAGradientLayer() - gradient.colors = [ - UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor, - UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor, - UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor, - UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor - ] - - gradient.startPoint = CGPoint(x: 0, y: 0) - gradient.endPoint = CGPoint(x: 1, y: 1) - - gradient.frame = screenView.bounds - screenView.layer.insertSublayer(gradient, at: 0) - } - - public override func viewDidLoad() { - super.viewDidLoad() - - screenView - .startButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toTerms(from: self) } - .store(in: &cancellables) - } + @Dependency var navigator: Navigator + + private lazy var screenView = OnboardingStartView() + + private var cancellables = Set<AnyCancellable>() + + public override func loadView() { + view = screenView + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationItem.backButtonTitle = "" + navigationController?.navigationBar.customize(translucent: true) + } + + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + let gradient = CAGradientLayer() + gradient.colors = [ + UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor, + UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor, + UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor, + UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor + ] + + gradient.startPoint = CGPoint(x: 0, y: 0) + gradient.endPoint = CGPoint(x: 1, y: 1) + + gradient.frame = screenView.bounds + screenView.layer.insertSublayer(gradient, at: 0) + } + + public override func viewDidLoad() { + super.viewDidLoad() + + screenView + .startButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + navigator.perform(PresentTermsAndConditions()) + }.store(in: &cancellables) + } } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift b/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift deleted file mode 100644 index d072efad04cb9446f229614025c1adc515db9f2f..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift +++ /dev/null @@ -1,67 +0,0 @@ -import UIKit -import Models -import Shared -import Combine -import Countries -import DependencyInjection - -public struct OnboardingSuccessModel { - var title: String - var subtitle: String? - var nextController: (UIViewController) -> Void -} - -public final class OnboardingSuccessController: UIViewController { - @Dependency private var coordinator: OnboardingCoordinating - - lazy private var screenView = OnboardingSuccessView() - private var cancellables = Set<AnyCancellable>() - - private var model: OnboardingSuccessModel - - public override func loadView() { - view = screenView - } - - public init(_ model: OnboardingSuccessModel) { - self.model = model - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { nil } - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationController?.navigationBar.customize(translucent: true) - } - - public override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - let gradient = CAGradientLayer() - gradient.colors = [ - UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor, - UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor, - UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor, - UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor - ] - - gradient.startPoint = CGPoint(x: 0, y: 0) - gradient.endPoint = CGPoint(x: 1, y: 1) - - gradient.frame = screenView.bounds - screenView.layer.insertSublayer(gradient, at: 0) - } - - public override func viewDidLoad() { - super.viewDidLoad() - - screenView.setTitle(model.title) - screenView.setSubtitle(model.subtitle) - - screenView.nextButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in model.nextController(self) } - .store(in: &cancellables) - } -} diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift index 566cc2ed94afb1f61776a07f5254c15de1381f5d..5939dab891e93f9031990eaca31da3ee8a86c7f9 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift @@ -1,16 +1,18 @@ import UIKit import Shared import Combine +import Navigation +import XXNavigation import DrawerFeature import DependencyInjection import ScrollViewController public final class OnboardingUsernameController: UIViewController { + @Dependency var navigator: Navigator @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating - lazy private var screenView = OnboardingUsernameView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = OnboardingUsernameView() + private lazy var scrollViewController = ScrollViewController() private var cancellables = Set<AnyCancellable>() private let viewModel = OnboardingUsernameViewModel() @@ -27,9 +29,9 @@ public final class OnboardingUsernameController: UIViewController { super.viewDidLoad() setupScrollView() setupBindings() - screenView.didTapInfo = { [weak self] in - self?.presentInfo( + guard let self else { return } + self.presentInfo( title: Localized.Onboarding.Username.Info.title, subtitle: Localized.Onboarding.Username.Info.subtitle, urlString: "https://links.xx.network/ud" @@ -39,28 +41,37 @@ public final class OnboardingUsernameController: UIViewController { private func setupScrollView() { scrollViewController.scrollView.backgroundColor = .white - addChild(scrollViewController) view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } + scrollViewController.view.snp.makeConstraints { + $0.edges.equalToSuperview() + } scrollViewController.didMove(toParent: self) scrollViewController.contentView = screenView } private func setupBindings() { - screenView.inputField.textPublisher + screenView + .inputField + .textPublisher .removeDuplicates() .compactMap { $0 } - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) + .sink { [unowned self] in + viewModel.didInput($0) + }.store(in: &cancellables) - screenView.restoreView.restoreButton + screenView + .restoreView + .restoreButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toRestoreList(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + navigator.perform(PresentRestoreList()) + }.store(in: &cancellables) - screenView.inputField.returnPublisher + screenView + .inputField + .returnPublisher .sink { [unowned self] in if screenView.nextButton.isEnabled { viewModel.didTapRegister() @@ -69,21 +80,29 @@ public final class OnboardingUsernameController: UIViewController { } }.store(in: &cancellables) - screenView.nextButton.publisher(for: .touchUpInside) - .sink { [unowned self] in viewModel.didTapRegister() } - .store(in: &cancellables) + screenView + .nextButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + viewModel.didTapRegister() + }.store(in: &cancellables) - viewModel.greenPublisher + viewModel + .statePublisher .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toWelcome(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + guard $0.didConfirm == true else { return } + navigator.perform(PresentOnboardingWelcome()) + }.store(in: &cancellables) - viewModel.state + viewModel + .statePublisher .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) + .sink { [unowned self] in + screenView.update(status: $0) + }.store(in: &cancellables) } private func presentInfo( @@ -125,6 +144,6 @@ public final class OnboardingUsernameController: UIViewController { } }.store(in: &drawerCancellables) - coordinator.toDrawer(drawer, from: self) + navigator.perform(PresentDrawer()) } } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift index 9bed33d2acdb20c9e684c1614583bff9f8c129f6..c979f8121e6f02758d1fbc8ec37f7a64b96fc5d3 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift @@ -2,16 +2,17 @@ import UIKit import Shared import Combine import Defaults +import Navigation +import XXNavigation import DrawerFeature import DependencyInjection public final class OnboardingWelcomeController: UIViewController { - @KeyObject(.username, defaultValue: "") var username: String - + @Dependency var navigator: Navigator @Dependency var barStylist: StatusBarStylist - @Dependency var coordinator: OnboardingCoordinating + @KeyObject(.username, defaultValue: "") var username: String - lazy private var screenView = OnboardingWelcomeView() + private lazy var screenView = OnboardingWelcomeView() private var cancellables = Set<AnyCancellable>() private var drawerCancellables = Set<AnyCancellable>() @@ -43,12 +44,14 @@ public final class OnboardingWelcomeController: UIViewController { private func setupBindings() { screenView.continueButton.publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toEmail(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + navigator.perform(PresentOnboardingEmail()) + }.store(in: &cancellables) screenView.skipButton.publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toChats(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + navigator.perform(PresentChatList()) + }.store(in: &cancellables) } private func presentInfo( @@ -90,6 +93,6 @@ public final class OnboardingWelcomeController: UIViewController { } }.store(in: &drawerCancellables) - coordinator.toDrawer(drawer, from: self) + navigator.perform(PresentDrawer()) } } diff --git a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift b/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift deleted file mode 100644 index dd26a3f6dc34173d156ff8724c6af949e5e3e86d..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift +++ /dev/null @@ -1,153 +0,0 @@ -import UIKit -import Shared -import Models -import Countries -import Presentation - -public typealias AttributeControllerClosure = (UIViewController) -> Void - -public protocol OnboardingCoordinating { - func toChats(from: UIViewController) - func toEmail(from: UIViewController) - func toPhone(from: UIViewController) - func toTerms(from: UIViewController) - func toWelcome(from: UIViewController) - func toUsername(from: UIViewController) - func toRestoreList(from: UIViewController) - func toDrawer(_: UIViewController, from: UIViewController) - func toSuccess(with: OnboardingSuccessModel, from: UIViewController) - - func toEmailConfirmation( - with: AttributeConfirmation, - from: UIViewController, - completion: @escaping (UIViewController) -> Void - ) - - func toPhoneConfirmation( - with: AttributeConfirmation, - from: UIViewController, - completion: @escaping (UIViewController) -> Void - ) - - func toCountries( - from: UIViewController, - _ onChoose: @escaping (Country) -> Void - ) -} - -public struct OnboardingCoordinator: OnboardingCoordinating { - var pushPresenter: Presenting = PushPresenter() - var bottomPresenter: Presenting = BottomPresenter() - var replacePresenter: Presenting = ReplacePresenter() - - var emailFactory: () -> UIViewController - var phoneFactory: () -> UIViewController - var searchFactory: (String?) -> UIViewController - var welcomeFactory: () -> UIViewController - var chatListFactory: () -> UIViewController - var termsFactory: () -> UIViewController - var usernameFactory: () -> UIViewController - var restoreListFactory: () -> UIViewController - var successFactory: (OnboardingSuccessModel) -> UIViewController - var countriesFactory: (@escaping (Country) -> Void) -> UIViewController - var phoneConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController - var emailConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController - - public init( - emailFactory: @escaping () -> UIViewController, - phoneFactory: @escaping () -> UIViewController, - searchFactory: @escaping (String?) -> UIViewController, - welcomeFactory: @escaping () -> UIViewController, - chatListFactory: @escaping () -> UIViewController, - termsFactory: @escaping () -> UIViewController, - usernameFactory: @escaping () -> UIViewController, - restoreListFactory: @escaping () -> UIViewController, - successFactory: @escaping (OnboardingSuccessModel) -> UIViewController, - countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController, - phoneConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController, - emailConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController - ) { - self.emailFactory = emailFactory - self.termsFactory = termsFactory - self.phoneFactory = phoneFactory - self.searchFactory = searchFactory - self.welcomeFactory = welcomeFactory - self.successFactory = successFactory - self.usernameFactory = usernameFactory - self.chatListFactory = chatListFactory - self.countriesFactory = countriesFactory - self.restoreListFactory = restoreListFactory - self.phoneConfirmationFactory = phoneConfirmationFactory - self.emailConfirmationFactory = emailConfirmationFactory - } -} - -public extension OnboardingCoordinator { - func toTerms(from parent: UIViewController) { - let screen = termsFactory() - pushPresenter.present(screen, from: parent) - } - - func toEmail(from parent: UIViewController) { - let screen = emailFactory() - replacePresenter.present(screen, from: parent) - } - - func toPhone(from parent: UIViewController) { - let screen = phoneFactory() - replacePresenter.present(screen, from: parent) - } - - func toWelcome(from parent: UIViewController) { - let screen = welcomeFactory() - replacePresenter.present(screen, from: parent) - } - - func toRestoreList(from parent: UIViewController) { - let screen = restoreListFactory() - pushPresenter.present(screen, from: parent) - } - - func toSuccess(with model: OnboardingSuccessModel, from parent: UIViewController) { - let screen = successFactory(model) - replacePresenter.present(screen, from: parent) - } - - func toUsername(from parent: UIViewController) { - let screen = usernameFactory() - replacePresenter.present(screen, from: parent) - } - - func toDrawer(_ drawer: UIViewController, from parent: UIViewController) { - bottomPresenter.present(drawer, from: parent) - } - - func toChats(from parent: UIViewController) { - let searchScreen = searchFactory(nil) - let chatListScreen = chatListFactory() - replacePresenter.present(chatListScreen, searchScreen, from: parent) - } - - func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) { - let screen = countriesFactory(onChoose) - pushPresenter.present(screen, from: parent) - } - - func toEmailConfirmation( - with confirmation: AttributeConfirmation, - from parent: UIViewController, - completion: @escaping (UIViewController) -> Void - ) { - let screen = emailConfirmationFactory(confirmation, completion) - pushPresenter.present(screen, from: parent) - } - - func toPhoneConfirmation( - with confirmation: AttributeConfirmation, - from parent: UIViewController, - completion: @escaping (UIViewController) -> Void - ) { - let screen = phoneConfirmationFactory(confirmation, completion) - pushPresenter.present(screen, from: parent) - } -} diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..47fde15f13ce19fb1767eecaf97b8efc8389a36a --- /dev/null +++ b/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift @@ -0,0 +1,96 @@ +import Shared +import Combine +import Defaults +import XXClient +import InputField +import Foundation +import CombineSchedulers +import XXMessengerClient +import DependencyInjection + +final class OnboardingCodeViewModel { + struct ViewState: Equatable { + var input: String = "" + var status: InputField.ValidationStatus = .unknown(nil) + var resendDebouncer: Int = 0 + var didConfirm: Bool = false + } + + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } + + @Dependency var messenger: Messenger + @Dependency var hudController: HUDController + @KeyObject(.email, defaultValue: nil) var email: String? + @KeyObject(.phone, defaultValue: nil) var phone: String? + + private var timer: Timer? + private let isEmail: Bool + private let content: String + private let confirmationId: String + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + + init( + isEmail: Bool, + content: String, + confirmationId: String + ) { + self.isEmail = isEmail + self.content = content + self.confirmationId = confirmationId + didTapResend() + } + + func didInput(_ string: String) { + stateSubject.value.input = string + validate() + } + + func didTapResend() { + guard stateSubject.value.resendDebouncer == 0 else { return } + stateSubject.value.resendDebouncer = 60 + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in + guard let self = self, self.stateSubject.value.resendDebouncer > 0 else { + $0.invalidate() + return + } + self.stateSubject.value.resendDebouncer -= 1 + } + } + + func didTapNext() { + hudController.show() + scheduler.schedule { [weak self] in + guard let self else { return } + do { + try self.messenger.ud.get()!.confirmFact( + confirmationId: self.confirmationId, + code: self.stateSubject.value.input + ) + if self.isEmail { + self.email = self.content + } else { + self.phone = self.content + } + self.timer?.invalidate() + self.hudController.dismiss() + self.stateSubject.value.didConfirm = true + } catch { + self.hudController.dismiss() + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.stateSubject.value.status = .invalid(xxError) + } + } + } + + private func validate() { + switch Validator.code.validate(stateSubject.value.input) { + case .success: + stateSubject.value.status = .valid(nil) + case .failure(let error): + stateSubject.value.status = .invalid(error) + } + } +} diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift deleted file mode 100644 index d008997d4a83b18a415100b2ec9b20250dcf9032..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift +++ /dev/null @@ -1,92 +0,0 @@ -import UIKit -import Models -import Shared -import Combine -import Defaults -import InputField -import XXClient -import CombineSchedulers -import DependencyInjection -import XXMessengerClient - -struct OnboardingEmailConfirmationViewState: Equatable { - var input: String = "" - var status: InputField.ValidationStatus = .unknown(nil) - var resendDebouncer: Int = 0 -} - -final class OnboardingEmailConfirmationViewModel { - @Dependency var messenger: Messenger - @Dependency var hudController: HUDController - - @KeyObject(.email, defaultValue: nil) var email: String? - - var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() } - private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>() - - var timer: Timer? - let confirmation: AttributeConfirmation - - var state: AnyPublisher<OnboardingEmailConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<OnboardingEmailConfirmationViewState, Never>(.init()) - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - - init(_ confirmation: AttributeConfirmation) { - self.confirmation = confirmation - didTapResend() - } - - func didInput(_ string: String) { - stateRelay.value.input = string - validate() - } - - func didTapResend() { - guard stateRelay.value.resendDebouncer == 0 else { return } - - stateRelay.value.resendDebouncer = 60 - - timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in - guard let self = self, self.stateRelay.value.resendDebouncer > 0 else { - $0.invalidate() - return - } - - self.stateRelay.value.resendDebouncer -= 1 - } - } - - func didTapNext() { - hudController.show() - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - - do { - try self.messenger.ud.get()!.confirmFact( - confirmationId: self.confirmation.confirmationId!, - code: self.stateRelay.value.input - ) - - self.email = self.confirmation.content - - self.timer?.invalidate() - self.hudController.dismiss() - self.completionRelay.send(self.confirmation) - } catch { - let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) - self.hudController.show(.init(content: xxError)) - } - } - } - - private func validate() { - switch Validator.code.validate(stateRelay.value.input) { - case .success: - stateRelay.value.status = .valid(nil) - case .failure(let error): - stateRelay.value.status = .invalid(error) - } - } -} diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift index 6e1159ae8b7cb74b66ada962cd8d480a613142cb..ad07d4a74bd7f42300b9577d8a66c3615f9482e0 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift @@ -1,70 +1,62 @@ -import UIKit -import Models import Shared import Combine -import Defaults import XXClient import InputField +import Foundation import CombineSchedulers import XXMessengerClient import DependencyInjection -struct OnboardingEmailViewState: Equatable { - var input: String = "" - var confirmation: AttributeConfirmation? = nil - var status: InputField.ValidationStatus = .unknown(nil) -} - final class OnboardingEmailViewModel { + struct ViewState: Equatable { + var input: String = "" + var confirmationId: String? + var status: InputField.ValidationStatus = .unknown(nil) + } + @Dependency var messenger: Messenger @Dependency var hudController: HUDController - - @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications - - var state: AnyPublisher<OnboardingEmailViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<OnboardingEmailViewState, Never>(.init()) - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - + + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } + + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + func clearUp() { - stateRelay.value.confirmation = nil + stateSubject.value.confirmationId = nil } - + func didInput(_ string: String) { - stateRelay.value.input = string + stateSubject.value.input = string validate() } - + func didTapNext() { hudController.show() - - backgroundScheduler.schedule { [weak self] in + scheduler.schedule { [weak self] in guard let self = self else { return } - do { let confirmationId = try self.messenger.ud.get()!.sendRegisterFact( - .init(type: .email, value: self.stateRelay.value.input) + .init(type: .email, value: self.stateSubject.value.input) ) - self.hudController.dismiss() - self.stateRelay.value.confirmation = .init( - content: self.stateRelay.value.input, - isEmail: true, - confirmationId: confirmationId - ) + self.stateSubject.value.confirmationId = confirmationId } catch { + self.hudController.dismiss() let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) - self.hudController.show(.init(content: xxError)) + self.stateSubject.value.status = .invalid(xxError) } } } - + private func validate() { - switch Validator.email.validate(stateRelay.value.input) { + switch Validator.email.validate(stateSubject.value.input) { case .success: - stateRelay.value.status = .valid(nil) + stateSubject.value.status = .valid(nil) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift deleted file mode 100644 index 387993d54ad136ef2ec01bd397eb035d2ea0dd45..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift +++ /dev/null @@ -1,92 +0,0 @@ -import UIKit -import Models -import Shared -import Combine -import Defaults -import InputField -import XXClient -import CombineSchedulers -import XXMessengerClient -import DependencyInjection - -struct OnboardingPhoneConfirmationViewState: Equatable { - var input: String = "" - var status: InputField.ValidationStatus = .unknown(nil) - var resendDebouncer: Int = 0 -} - -final class OnboardingPhoneConfirmationViewModel { - @Dependency var messenger: Messenger - @Dependency var hudController: HUDController - - @KeyObject(.phone, defaultValue: nil) var phone: String? - - var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() } - private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>() - - var timer: Timer? - let confirmation: AttributeConfirmation - - var state: AnyPublisher<OnboardingPhoneConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<OnboardingPhoneConfirmationViewState, Never>(.init()) - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - - init(_ confirmation: AttributeConfirmation) { - self.confirmation = confirmation - didTapResend() - } - - func didInput(_ string: String) { - stateRelay.value.input = string - validate() - } - - func didTapResend() { - guard stateRelay.value.resendDebouncer == 0 else { return } - - stateRelay.value.resendDebouncer = 60 - - timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in - guard let self = self, self.stateRelay.value.resendDebouncer > 0 else { - $0.invalidate() - return - } - - self.stateRelay.value.resendDebouncer -= 1 - } - } - - func didTapNext() { - hudController.show() - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - - do { - try self.messenger.ud.get()!.confirmFact( - confirmationId: self.confirmation.confirmationId!, - code: self.stateRelay.value.input - ) - - self.phone = self.confirmation.content - - self.timer?.invalidate() - self.hudController.dismiss() - self.completionRelay.send(self.confirmation) - } catch { - let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) - self.hudController.show(.init(content: xxError)) - } - } - } - - private func validate() { - switch Validator.code.validate(stateRelay.value.input) { - case .success: - stateRelay.value.status = .valid(nil) - case .failure(let error): - stateRelay.value.status = .invalid(error) - } - } -} diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift index 346a7bf15f5a75e7136f2be1f08ff734869a9b51..435d12a9ade3c59eefb4d977ebdabd68765e22e6 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift @@ -1,5 +1,4 @@ import Shared -import Models import Combine import XXClient import Countries @@ -9,71 +8,65 @@ import CombineSchedulers import XXMessengerClient import DependencyInjection -struct OnboardingPhoneViewState: Equatable { - var input: String = "" - var confirmation: AttributeConfirmation? - var status: InputField.ValidationStatus = .unknown(nil) - var country: Country = .fromMyPhone() -} - final class OnboardingPhoneViewModel { + struct ViewState: Equatable { + var input: String = "" + var content: String? + var confirmationId: String? + var status: InputField.ValidationStatus = .unknown(nil) + var country: Country = .fromMyPhone() + } + @Dependency var messenger: Messenger @Dependency var hudController: HUDController - - var state: AnyPublisher<OnboardingPhoneViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<OnboardingPhoneViewState, Never>(.init()) - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } + + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() func clearUp() { - stateRelay.value.confirmation = nil + stateSubject.value.confirmationId = nil } - + func didInput(_ string: String) { - stateRelay.value.input = string + stateSubject.value.input = string validate() } - + func didChooseCountry(_ country: Country) { - stateRelay.value.country = country + stateSubject.value.country = country validate() } - - func didGoForward() { - stateRelay.value.confirmation = nil - } - + func didTapNext() { hudController.show() - - backgroundScheduler.schedule { [weak self] in + scheduler.schedule { [weak self] in guard let self = self else { return } - - let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)" - + let content = "\(self.stateSubject.value.input)\(self.stateSubject.value.country.code)" do { let confirmationId = try self.messenger.ud.get()!.sendRegisterFact( .init(type: .phone, value: content) ) - self.hudController.dismiss() - self.stateRelay.value.confirmation = .init( - content: content, - confirmationId: confirmationId - ) + self.stateSubject.value.content = content + self.stateSubject.value.confirmationId = confirmationId } catch { + self.hudController.dismiss() let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) - self.hudController.show(.init(content: xxError)) + self.stateSubject.value.status = .invalid(xxError) } } } - + private func validate() { - switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) { + switch Validator.phone.validate((stateSubject.value.country.regex, stateSubject.value.input)) { case .success: - stateRelay.value.status = .valid(nil) + stateSubject.value.status = .valid(nil) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift index 1483525b62f65afd5f7a8a01aea97cdc46cc3ed4..06a5f5fab2d15213305d4036f400a7ea64412d63 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift @@ -1,5 +1,4 @@ import Shared -import Models import Combine import Defaults import XXModels @@ -10,53 +9,47 @@ import XXMessengerClient import CombineSchedulers import DependencyInjection -struct OnboardingUsernameViewState: Equatable { - var input: String = "" - var status: InputField.ValidationStatus = .unknown(nil) -} - final class OnboardingUsernameViewModel { + struct ViewState: Equatable { + var input: String = "" + var status: InputField.ValidationStatus = .unknown(nil) + var didConfirm: Bool = false + } + @Dependency var database: Database @Dependency var messenger: Messenger @Dependency var hudController: HUDController - @KeyObject(.username, defaultValue: "") var username: String - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> - = DispatchQueue.global().eraseToAnyScheduler() - - var greenPublisher: AnyPublisher<Void, Never> { greenRelay.eraseToAnyPublisher() } - private let greenRelay = PassthroughSubject<Void, Never>() - var state: AnyPublisher<OnboardingUsernameViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<OnboardingUsernameViewState, Never>(.init()) - + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } + + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + func didInput(_ string: String) { - stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines) - - switch Validator.username.validate(stateRelay.value.input) { + stateSubject.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines) + switch Validator.username.validate(stateSubject.value.input) { case .success(let text): - stateRelay.value.status = .valid(text) + stateSubject.value.status = .valid(text) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } - + func didTapRegister() { hudController.show() - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - + scheduler.schedule { [weak self] in + guard let self else { return } do { try self.messenger.register( - username: self.stateRelay.value.input + username: self.stateSubject.value.input ) - try self.database.saveContact(.init( id: self.messenger.e2e.get()!.getContact().getId(), marshaled: self.messenger.e2e.get()!.getContact().data, - username: self.stateRelay.value.input, + username: self.stateSubject.value.input, email: nil, phone: nil, nickname: nil, @@ -67,13 +60,13 @@ final class OnboardingUsernameViewModel { isBanned: false, createdAt: Date() )) - - self.username = self.stateRelay.value.input + self.username = self.stateSubject.value.input self.hudController.dismiss() - self.greenRelay.send() + self.stateSubject.value.didConfirm = true } catch { self.hudController.dismiss() - self.stateRelay.value.status = .invalid(CreateUserFriendlyErrorMessage.live(error.localizedDescription)) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.stateSubject.value.status = .invalid(xxError) } } } diff --git a/Sources/OnboardingFeature/Views/OnboardingCodeView.swift b/Sources/OnboardingFeature/Views/OnboardingCodeView.swift new file mode 100644 index 0000000000000000000000000000000000000000..9f4b48d5385f69a7de78f0be828494829dbd2751 --- /dev/null +++ b/Sources/OnboardingFeature/Views/OnboardingCodeView.swift @@ -0,0 +1,117 @@ +import UIKit +import Shared +import InputField + +final class OnboardingCodeView: UIView { + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let inputField = InputField() + let nextButton = CapsuleButton() + let resendButton = UIButton() + let stackView = UIStackView() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + + setupTitle(Localized.Onboarding.EmailConfirmation.title) + + inputField.setup( + placeholder: Localized.Onboarding.EmailConfirmation.input, + subtitleColor: Asset.neutralWeak.color, + allowsEmptySpace: false, + keyboardType: .numberPad, + autocapitalization: .none, + contentType: .oneTimeCode + ) + + resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal) + resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled) + resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0) + resendButton.setTitle(Localized.Onboarding.EmailConfirmation.resend(""), for: .normal) + + nextButton.set(style: .brandColored, title: Localized.Onboarding.EmailConfirmation.next) + nextButton.isEnabled = false + + stackView.spacing = 15 + stackView.axis = .vertical + stackView.addArrangedSubview(nextButton) + stackView.addArrangedSubview(resendButton) + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(inputField) + addSubview(stackView) + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) + } + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) + } + inputField.snp.makeConstraints { + $0.top.equalTo(subtitleView.snp.bottom).offset(24) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-38) + } + stackView.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) + } + } + + required init?(coder: NSCoder) { nil } + + func update(status: InputField.ValidationStatus) { + inputField.update(status: status) + + switch status { + case .valid: + nextButton.isEnabled = true + case .invalid, .unknown: + nextButton.isEnabled = false + } + } + + private func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.0 + + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + public func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } +} diff --git a/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift b/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift deleted file mode 100644 index 8d41227472b0f0fdb7018f7f0dc02140f8900bdc..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift +++ /dev/null @@ -1,121 +0,0 @@ -import UIKit -import Shared -import InputField - -final class OnboardingEmailConfirmationView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let inputField = InputField() - let nextButton = CapsuleButton() - let resendButton = UIButton() - let stackView = UIStackView() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupTitle(Localized.Onboarding.EmailConfirmation.title) - - inputField.setup( - placeholder: Localized.Onboarding.EmailConfirmation.input, - subtitleColor: Asset.neutralWeak.color, - allowsEmptySpace: false, - keyboardType: .numberPad, - autocapitalization: .none, - contentType: .oneTimeCode - ) - - resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal) - resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled) - resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0) - resendButton.setTitle(Localized.Onboarding.EmailConfirmation.resend(""), for: .normal) - - nextButton.set(style: .brandColored, title: Localized.Onboarding.EmailConfirmation.next) - nextButton.isEnabled = false - - stackView.spacing = 15 - stackView.axis = .vertical - stackView.addArrangedSubview(nextButton) - stackView.addArrangedSubview(resendButton) - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(inputField) - addSubview(stackView) - - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - inputField.snp.makeConstraints { make in - make.top.equalTo(subtitleView.snp.bottom).offset(24) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-38) - } - - stackView.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } - } - - required init?(coder: NSCoder) { nil } - - func update(status: InputField.ValidationStatus) { - inputField.update(status: status) - - switch status { - case .valid: - nextButton.isEnabled = true - case .invalid, .unknown: - nextButton.isEnabled = false - } - } - - private func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.0 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString - } - - public func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) - } -} diff --git a/Sources/OnboardingFeature/Views/OnboardingEmailView.swift b/Sources/OnboardingFeature/Views/OnboardingEmailView.swift index 760996abb549a1658c573b9602de6777f11022dc..c681215e691626cdb694b16f229f6c4065e134a2 100644 --- a/Sources/OnboardingFeature/Views/OnboardingEmailView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingEmailView.swift @@ -3,118 +3,111 @@ import Shared import InputField final class OnboardingEmailView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let inputField = InputField() - let nextButton = CapsuleButton() - let skipButton = UIButton() - let stackView = UIStackView() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupTitle(Localized.Onboarding.Email.title) - setupSubtitle(Localized.Onboarding.Email.subtitle) - - inputField.setup( - placeholder: Localized.Onboarding.Email.input, - subtitleColor: Asset.neutralWeak.color, - allowsEmptySpace: false, - keyboardType: .emailAddress, - autocapitalization: .none, - contentType: .emailAddress - ) - - skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0) - skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal) - skipButton.setTitle(Localized.Onboarding.Email.skip, for: .normal) - nextButton.set(style: .brandColored, title: Localized.Onboarding.Email.action) - nextButton.isEnabled = false - - stackView.spacing = 20 - stackView.axis = .vertical - stackView.addArrangedSubview(nextButton) - stackView.addArrangedSubview(skipButton) - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(inputField) - addSubview(stackView) - - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - inputField.snp.makeConstraints { make in - make.top.equalTo(subtitleView.snp.bottom).offset(24) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-38) - } - - stackView.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let inputField = InputField() + let nextButton = CapsuleButton() + let skipButton = UIButton() + let stackView = UIStackView() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + + setupTitle(Localized.Onboarding.Email.title) + setupSubtitle(Localized.Onboarding.Email.subtitle) + + inputField.setup( + placeholder: Localized.Onboarding.Email.input, + subtitleColor: Asset.neutralWeak.color, + allowsEmptySpace: false, + keyboardType: .emailAddress, + autocapitalization: .none, + contentType: .emailAddress + ) + + skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0) + skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal) + skipButton.setTitle(Localized.Onboarding.Email.skip, for: .normal) + nextButton.set(style: .brandColored, title: Localized.Onboarding.Email.action) + nextButton.isEnabled = false + + stackView.spacing = 20 + stackView.axis = .vertical + stackView.addArrangedSubview(nextButton) + stackView.addArrangedSubview(skipButton) + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(inputField) + addSubview(stackView) + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - required init?(coder: NSCoder) { nil } - - func update(status: InputField.ValidationStatus) { - inputField.update(status: status) - - switch status { - case .valid: - nextButton.isEnabled = true - case .invalid, .unknown: - nextButton.isEnabled = false - } + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - private func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString + inputField.snp.makeConstraints { + $0.top.equalTo(subtitleView.snp.bottom).offset(24) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-38) + } + stackView.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) } + } - private func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 + required init?(coder: NSCoder) { nil } - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) + func update(status: InputField.ValidationStatus) { + inputField.update(status: status) + switch status { + case .valid: + nextButton.isEnabled = true + case .invalid, .unknown: + nextButton.isEnabled = false } + } + + private func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + private func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } } diff --git a/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift b/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift index 0509f5d52517dfe4f0b78614eca764250f4fb285..8caa27679273d4ab0485e6d8254723e78fbb31c7 100644 --- a/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift @@ -3,119 +3,109 @@ import Shared import InputField final class OnboardingPhoneConfirmationView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let inputField = InputField() - let nextButton = CapsuleButton() - let resendButton = UIButton() - let stackView = UIStackView() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupTitle(Localized.Onboarding.PhoneConfirmation.title) - - inputField.setup( - placeholder: Localized.Onboarding.PhoneConfirmation.input, - subtitleColor: Asset.neutralWeak.color, - allowsEmptySpace: false, - keyboardType: .numberPad, - autocapitalization: .none, - contentType: .oneTimeCode - ) - - resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal) - resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled) - resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0) - resendButton.setTitle(Localized.Onboarding.PhoneConfirmation.resend(""), for: .normal) - - nextButton.set(style: .brandColored, title: Localized.Onboarding.PhoneConfirmation.next) - nextButton.isEnabled = false - - stackView.spacing = 15 - stackView.axis = .vertical - stackView.addArrangedSubview(nextButton) - stackView.addArrangedSubview(resendButton) - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(inputField) - addSubview(stackView) - - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - inputField.snp.makeConstraints { make in - make.top.equalTo(subtitleView.snp.bottom).offset(24) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-38) - } - - stackView.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let inputField = InputField() + let nextButton = CapsuleButton() + let resendButton = UIButton() + let stackView = UIStackView() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + setupTitle(Localized.Onboarding.PhoneConfirmation.title) + + inputField.setup( + placeholder: Localized.Onboarding.PhoneConfirmation.input, + subtitleColor: Asset.neutralWeak.color, + allowsEmptySpace: false, + keyboardType: .numberPad, + autocapitalization: .none, + contentType: .oneTimeCode + ) + + resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal) + resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled) + resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0) + resendButton.setTitle(Localized.Onboarding.PhoneConfirmation.resend(""), for: .normal) + nextButton.set(style: .brandColored, title: Localized.Onboarding.PhoneConfirmation.next) + nextButton.isEnabled = false + + stackView.spacing = 15 + stackView.axis = .vertical + stackView.addArrangedSubview(nextButton) + stackView.addArrangedSubview(resendButton) + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(inputField) + addSubview(stackView) + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - required init?(coder: NSCoder) { nil } - - func update(status: InputField.ValidationStatus) { - inputField.update(status: status) - - switch status { - case .valid: - nextButton.isEnabled = true - case .invalid, .unknown: - nextButton.isEnabled = false - } + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - private func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.0 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString + inputField.snp.makeConstraints { + $0.top.equalTo(subtitleView.snp.bottom).offset(24) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-38) + } + stackView.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) } + } - public func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 + required init?(coder: NSCoder) { nil } - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) + func update(status: InputField.ValidationStatus) { + inputField.update(status: status) + switch status { + case .valid: + nextButton.isEnabled = true + case .invalid, .unknown: + nextButton.isEnabled = false } + } + + private func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.0 + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + public func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } } diff --git a/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift b/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift index caec887d542c4d37f98486ef85c790d5bcda6ebb..a8df7c864d75041a34c23863595150dcda094ecc 100644 --- a/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift @@ -3,118 +3,110 @@ import Shared import InputField final class OnboardingPhoneView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let inputField = InputField() - let nextButton = CapsuleButton() - let skipButton = UIButton() - let stackView = UIStackView() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupTitle(Localized.Onboarding.Phone.title) - setupSubtitle(Localized.Onboarding.Phone.subtitle) - - inputField.setup( - style: .phone, - placeholder: Localized.Onboarding.Phone.input, - subtitleColor: Asset.neutralWeak.color, - keyboardType: .phonePad, - contentType: .telephoneNumber, - codeAccessibility: Localized.Accessibility.Onboarding.Phone.code - ) - - skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0) - skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal) - skipButton.setTitle(Localized.Onboarding.Phone.skip, for: .normal) - nextButton.set(style: .brandColored, title: Localized.Onboarding.Phone.action) - nextButton.isEnabled = false - - stackView.spacing = 20 - stackView.axis = .vertical - stackView.addArrangedSubview(nextButton) - stackView.addArrangedSubview(skipButton) - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(inputField) - addSubview(stackView) - - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - inputField.snp.makeConstraints { make in - make.top.equalTo(subtitleView.snp.bottom).offset(24) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-38) - } - - stackView.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let inputField = InputField() + let nextButton = CapsuleButton() + let skipButton = UIButton() + let stackView = UIStackView() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + + setupTitle(Localized.Onboarding.Phone.title) + setupSubtitle(Localized.Onboarding.Phone.subtitle) + + inputField.setup( + style: .phone, + placeholder: Localized.Onboarding.Phone.input, + subtitleColor: Asset.neutralWeak.color, + keyboardType: .phonePad, + contentType: .telephoneNumber, + codeAccessibility: Localized.Accessibility.Onboarding.Phone.code + ) + + skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0) + skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal) + skipButton.setTitle(Localized.Onboarding.Phone.skip, for: .normal) + nextButton.set(style: .brandColored, title: Localized.Onboarding.Phone.action) + nextButton.isEnabled = false + + stackView.spacing = 20 + stackView.axis = .vertical + stackView.addArrangedSubview(nextButton) + stackView.addArrangedSubview(skipButton) + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(inputField) + addSubview(stackView) + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - required init?(coder: NSCoder) { nil } - - func update(status: InputField.ValidationStatus) { - inputField.update(status: status) - - switch status { - case .valid: - nextButton.isEnabled = true - case .invalid, .unknown: - nextButton.isEnabled = false - } + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - private func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString + inputField.snp.makeConstraints { + $0.top.equalTo(subtitleView.snp.bottom).offset(24) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-38) + } + stackView.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) } + } - private func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 + required init?(coder: NSCoder) { nil } - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) + func update(status: InputField.ValidationStatus) { + inputField.update(status: status) + switch status { + case .valid: + nextButton.isEnabled = true + case .invalid, .unknown: + nextButton.isEnabled = false } + } + + private func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + private func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } } diff --git a/Sources/OnboardingFeature/Views/OnboardingStartView.swift b/Sources/OnboardingFeature/Views/OnboardingStartView.swift index a3e953aad2108ae43036f43c56530b8c63937ec9..2a83c1b6aa004c3ca068ff27b419212ea9353480 100644 --- a/Sources/OnboardingFeature/Views/OnboardingStartView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingStartView.swift @@ -2,48 +2,47 @@ import UIKit import Shared final class OnboardingStartView: UIView { - let titleLabel = UILabel() - let stackView = UIStackView() - let logoImageView = UIImageView() - let startButton = CapsuleButton() - let bottomImageView = UIImageView() - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - logoImageView.image = Asset.onboardingLogoStart.image - bottomImageView.image = Asset.onboardingBottomLogoStart.image - - titleLabel.textAlignment = .center - titleLabel.textColor = Asset.neutralWhite.color - titleLabel.font = Fonts.Mulish.bold.font(size: 18.0) - titleLabel.text = Localized.Onboarding.Start.title - startButton.set(style: .white, title: Localized.Onboarding.Start.action) - - logoImageView.contentMode = .center - bottomImageView.contentMode = .center - - stackView.spacing = 40 - stackView.axis = .vertical - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(startButton) - stackView.addArrangedSubview(bottomImageView) - stackView.setCustomSpacing(70, after: startButton) - - addSubview(logoImageView) - addSubview(stackView) - - logoImageView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(130) - make.centerX.equalToSuperview() - } - - stackView.snp.makeConstraints { make in - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-40) - } + let titleLabel = UILabel() + let stackView = UIStackView() + let logoImageView = UIImageView() + let startButton = CapsuleButton() + let bottomImageView = UIImageView() + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + logoImageView.image = Asset.onboardingLogoStart.image + bottomImageView.image = Asset.onboardingBottomLogoStart.image + + titleLabel.textAlignment = .center + titleLabel.textColor = Asset.neutralWhite.color + titleLabel.font = Fonts.Mulish.bold.font(size: 18.0) + titleLabel.text = Localized.Onboarding.Start.title + startButton.set(style: .white, title: Localized.Onboarding.Start.action) + + logoImageView.contentMode = .center + bottomImageView.contentMode = .center + + stackView.spacing = 40 + stackView.axis = .vertical + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(startButton) + stackView.addArrangedSubview(bottomImageView) + stackView.setCustomSpacing(70, after: startButton) + + addSubview(logoImageView) + addSubview(stackView) + + logoImageView.snp.makeConstraints { + $0.top.equalToSuperview().offset(130) + $0.centerX.equalToSuperview() } + stackView.snp.makeConstraints { + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-40) + } + } - required init?(coder: NSCoder) { nil } + required init?(coder: NSCoder) { nil } } diff --git a/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift b/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift deleted file mode 100644 index 7e02167c2786cb11c4fcc04d6ad4930b9fab92e2..0000000000000000000000000000000000000000 --- a/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift +++ /dev/null @@ -1,75 +0,0 @@ -import UIKit -import Shared - -final class OnboardingSuccessView: UIView { - let iconImageView = UIImageView() - let titleLabel = UILabel() - let subtitleLabel = UILabel() - let nextButton = CapsuleButton() - - init() { - super.init(frame: .zero) - - iconImageView.contentMode = .center - iconImageView.image = Asset.onboardingSuccess.image - nextButton.set(style: .white, title: Localized.Onboarding.Success.action) - - subtitleLabel.numberOfLines = 0 - subtitleLabel.textColor = Asset.neutralWhite.color - subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0) - - addSubview(iconImageView) - addSubview(titleLabel) - addSubview(subtitleLabel) - addSubview(nextButton) - - iconImageView.snp.makeConstraints { make in - make.top.equalTo(safeAreaLayoutGuide).offset(40) - make.left.equalToSuperview().offset(40) - } - - titleLabel.snp.makeConstraints { make in - make.top.equalTo(iconImageView.snp.bottom).offset(40) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-90) - } - - subtitleLabel.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(30) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-90) - } - - nextButton.snp.makeConstraints { make in - make.left.equalToSuperview().offset(24) - make.right.equalToSuperview().offset(-24) - make.bottom.equalToSuperview().offset(-60) - } - } - - required init?(coder: NSCoder) { nil } - - func setTitle(_ title: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.1 - - let attrString = NSMutableAttributedString(string: title) - - attrString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 39.0)) - attrString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color) - - attrString.addAttribute( - name: .foregroundColor, - value: Asset.neutralBody.color, - betweenCharacters: "#" - ) - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attrString - } - - func setSubtitle(_ subtitle: String?) { - subtitleLabel.text = subtitle - } -} diff --git a/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift b/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift index f03437558a9157fd6846ce76436f1ed1c4d8822b..170d0a6344239781af13acd2835c5fa4519d9569 100644 --- a/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift @@ -2,46 +2,44 @@ import UIKit import Shared final class OnboardingUsernameRestoreView: UIView { - let titleLabel = UILabel() - let restoreButton = CapsuleButton() - let separatorView = UIView() - - init() { - super.init(frame: .zero) - - titleLabel.text = Localized.Onboarding.Username.Restore.title - restoreButton.set(style: .seeThrough, title: Localized.Onboarding.Username.Restore.action) - - titleLabel.numberOfLines = 0 - titleLabel.textAlignment = .center - titleLabel.font = Fonts.Mulish.bold.font(size: 24) - - addSubview(titleLabel) - addSubview(restoreButton) - addSubview(separatorView) - - separatorView.backgroundColor = Asset.neutralLine.color - - separatorView.snp.makeConstraints { make in - make.top.equalToSuperview() - make.left.equalToSuperview().offset(24) - make.right.equalToSuperview().offset(-24) - make.height.equalTo(1) - } - - titleLabel.snp.makeConstraints { make in - make.top.equalTo(separatorView.snp.bottom).offset(40) - make.left.equalToSuperview().offset(20) - make.right.equalToSuperview().offset(-20) - } - - restoreButton.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(34) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalToSuperview() - } + let titleLabel = UILabel() + let restoreButton = CapsuleButton() + let separatorView = UIView() + + init() { + super.init(frame: .zero) + + titleLabel.text = Localized.Onboarding.Username.Restore.title + restoreButton.set(style: .seeThrough, title: Localized.Onboarding.Username.Restore.action) + + titleLabel.numberOfLines = 0 + titleLabel.textAlignment = .center + titleLabel.font = Fonts.Mulish.bold.font(size: 24) + + addSubview(titleLabel) + addSubview(restoreButton) + addSubview(separatorView) + + separatorView.backgroundColor = Asset.neutralLine.color + + separatorView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) + $0.height.equalTo(1) + } + titleLabel.snp.makeConstraints { + $0.top.equalTo(separatorView.snp.bottom).offset(40) + $0.left.equalToSuperview().offset(20) + $0.right.equalToSuperview().offset(-20) + } + restoreButton.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(34) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalToSuperview() } + } - required init?(coder: NSCoder) { nil } + required init?(coder: NSCoder) { nil } } diff --git a/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift b/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift index 32ec1e43e312aee7880f1003c9e3f4ec0f18e899..7bd6e1026563d5a7de6fb17dce7e6a872b5c473d 100644 --- a/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift @@ -3,114 +3,105 @@ import Shared import InputField final class OnboardingUsernameView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let inputField = InputField() - let nextButton = CapsuleButton() - let restoreView = OnboardingUsernameRestoreView() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupTitle(Localized.Onboarding.Username.title) - setupSubtitle(Localized.Onboarding.Username.subtitle) - - inputField.setup( - placeholder: Localized.Onboarding.Username.input, - subtitleColor: Asset.neutralWeak.color, - allowsEmptySpace: false, - autocapitalization: .none - ) - - nextButton.set(style: .brandColored, title: Localized.Onboarding.Username.next) - nextButton.isEnabled = false - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(inputField) - addSubview(nextButton) - addSubview(restoreView) - - titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - inputField.snp.makeConstraints { make in - make.top.equalTo(subtitleView.snp.bottom).offset(24) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-38) - } - - nextButton.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - } - - restoreView.snp.makeConstraints { make in - make.top.greaterThanOrEqualTo(nextButton.snp.bottom).offset(30) - make.left.equalToSuperview() - make.right.equalToSuperview() - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let inputField = InputField() + let nextButton = CapsuleButton() + let restoreView = OnboardingUsernameRestoreView() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + + setupTitle(Localized.Onboarding.Username.title) + setupSubtitle(Localized.Onboarding.Username.subtitle) + + inputField.setup( + placeholder: Localized.Onboarding.Username.input, + subtitleColor: Asset.neutralWeak.color, + allowsEmptySpace: false, + autocapitalization: .none + ) + + nextButton.set(style: .brandColored, title: Localized.Onboarding.Username.next) + nextButton.isEnabled = false + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(inputField) + addSubview(nextButton) + addSubview(restoreView) + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - required init?(coder: NSCoder) { nil } - - func update(status: InputField.ValidationStatus) { - inputField.update(status: status) - - switch status { - case .valid: - nextButton.isEnabled = true - case .invalid, .unknown: - nextButton.isEnabled = false - } + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - private func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString + inputField.snp.makeConstraints { + $0.top.equalTo(subtitleView.snp.bottom).offset(24) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-38) + } + nextButton.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20) + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + } + restoreView.snp.makeConstraints { + $0.top.greaterThanOrEqualTo(nextButton.snp.bottom).offset(30) + $0.left.equalToSuperview() + $0.right.equalToSuperview() + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) } + } - private func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 + required init?(coder: NSCoder) { nil } - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) + func update(status: InputField.ValidationStatus) { + inputField.update(status: status) + switch status { + case .valid: + nextButton.isEnabled = true + case .invalid, .unknown: + nextButton.isEnabled = false } + } + + private func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + private func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } } diff --git a/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift b/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift index d3581a4d9f987e7e1e19f6f09831793cf57d5640..ae4b5a609864ffdeed59ddc164ca303eeb474a7a 100644 --- a/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift +++ b/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift @@ -2,85 +2,79 @@ import UIKit import Shared final class OnboardingWelcomeView: UIView { - let titleLabel = UILabel() - let subtitleView = TextWithInfoView() - let stackView = UIStackView() - let continueButton = CapsuleButton() - let skipButton = CapsuleButton() - - var didTapInfo: (() -> Void)? - - init() { - super.init(frame: .zero) - backgroundColor = Asset.neutralWhite.color - - setupSubtitle(Localized.Onboarding.Welcome.subtitle) - - skipButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.skip) - continueButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.continue) - - stackView.spacing = 15 - stackView.axis = .vertical - stackView.addArrangedSubview(continueButton) - stackView.addArrangedSubview(skipButton) - - addSubview(titleLabel) - addSubview(subtitleView) - addSubview(stackView) - - titleLabel.snp.makeConstraints { make in - make.top.equalTo(safeAreaLayoutGuide).offset(30) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - subtitleView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.left.equalToSuperview().offset(38) - make.right.equalToSuperview().offset(-41) - } - - stackView.snp.makeConstraints { make in - make.left.equalToSuperview().offset(40) - make.right.equalToSuperview().offset(-40) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-50) - } + let titleLabel = UILabel() + let subtitleView = TextWithInfoView() + let stackView = UIStackView() + let continueButton = CapsuleButton() + let skipButton = CapsuleButton() + var didTapInfo: (() -> Void)? + + init() { + super.init(frame: .zero) + backgroundColor = Asset.neutralWhite.color + + setupSubtitle(Localized.Onboarding.Welcome.subtitle) + + skipButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.skip) + continueButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.continue) + + stackView.spacing = 15 + stackView.axis = .vertical + stackView.addArrangedSubview(continueButton) + stackView.addArrangedSubview(skipButton) + + addSubview(titleLabel) + addSubview(subtitleView) + addSubview(stackView) + + titleLabel.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide).offset(30) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - required init?(coder: NSCoder) { nil } - - func setupTitle(_ title: String) { - let attString = NSMutableAttributedString(string: title) - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.1 - - attString.addAttribute(.paragraphStyle, value: paragraph) - attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) - attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) - - attString.addAttributes(attributes: [ - .font: Fonts.Mulish.bold.font(size: 34.0) as Any, - .foregroundColor: Asset.brandPrimary.color - ], betweenCharacters: "#") - - titleLabel.numberOfLines = 0 - titleLabel.attributedText = attString + subtitleView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.left.equalToSuperview().offset(38) + $0.right.equalToSuperview().offset(-41) } - - private func setupSubtitle(_ subtitle: String) { - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .left - paragraph.lineHeightMultiple = 1.15 - - subtitleView.setup( - text: subtitle, - attributes: [ - .foregroundColor: Asset.neutralBody.color, - .font: Fonts.Mulish.regular.font(size: 16.0) as Any, - .paragraphStyle: paragraph - ], - didTapInfo: { [weak self] in self?.didTapInfo?() } - ) + stackView.snp.makeConstraints { + $0.left.equalToSuperview().offset(40) + $0.right.equalToSuperview().offset(-40) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50) } + } + + required init?(coder: NSCoder) { nil } + + func setupTitle(_ title: String) { + let attString = NSMutableAttributedString(string: title) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.1 + attString.addAttribute(.paragraphStyle, value: paragraph) + attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color) + attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any) + attString.addAttributes(attributes: [ + .font: Fonts.Mulish.bold.font(size: 34.0) as Any, + .foregroundColor: Asset.brandPrimary.color + ], betweenCharacters: "#") + + titleLabel.numberOfLines = 0 + titleLabel.attributedText = attString + } + + private func setupSubtitle(_ subtitle: String) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .left + paragraph.lineHeightMultiple = 1.15 + subtitleView.setup( + text: subtitle, + attributes: [ + .foregroundColor: Asset.neutralBody.color, + .font: Fonts.Mulish.regular.font(size: 16.0) as Any, + .paragraphStyle: paragraph + ], + didTapInfo: { [weak self] in self?.didTapInfo?() } + ) + } } diff --git a/Sources/Permissions/RequestPermissionController.swift b/Sources/Permissions/RequestPermissionController.swift index 6aa33347a74af25cd0abba5e1a30211bda19ac84..d424df1507181051669db9b0eac0b93c7033f663 100644 --- a/Sources/Permissions/RequestPermissionController.swift +++ b/Sources/Permissions/RequestPermissionController.swift @@ -13,7 +13,7 @@ public final class RequestPermissionController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var permissions: PermissionHandling - lazy private var screenView = RequestPermissionView() + private lazy var screenView = RequestPermissionView() private var type: PermissionType! private var cancellables = Set<AnyCancellable>() diff --git a/Sources/Presentation/Presenting.swift b/Sources/Presentation/Presenting.swift index 7de9932742a7f81f90e0dab7ba1f958d36e86757..b79da38e915bca7cefe9a82997ade14eae846135 100644 --- a/Sources/Presentation/Presenting.swift +++ b/Sources/Presentation/Presenting.swift @@ -24,7 +24,7 @@ public struct ModalPresenter: Presenting { public init() {} public func present(_ target: UIViewController..., from parent: UIViewController) { - let statusBarVC = RootViewController(target.first!) + let statusBarVC = RootViewController(UINavigationController(rootViewController: target.first!)) statusBarVC.modalPresentationStyle = .fullScreen parent.present(statusBarVC, animated: true) } diff --git a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift index 037ac0413123cdccdd1d1fb7d4fdbc19a96b59d3..60036d37c7fb75b60eff10ef9ed16ba81abe27a3 100644 --- a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift +++ b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift @@ -1,115 +1,114 @@ import UIKit -import Models import Shared import Combine -import Countries +import Navigation +import XXNavigation import DependencyInjection import ScrollViewController -public typealias ControllerClosure = (UIViewController, AttributeConfirmation) -> Void - public final class ProfileCodeController: UIViewController { - lazy private var screenView = ProfileCodeView() - lazy private var scrollViewController = ScrollViewController() - - private let completion: ControllerClosure - private let confirmation: AttributeConfirmation - private var cancellables = Set<AnyCancellable>() - lazy private var viewModel = ProfileCodeViewModel(confirmation) - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - navigationItem.backButtonTitle = "" - navigationController?.navigationBar - .customize(backgroundColor: Asset.neutralWhite.color) - } - - public init( - _ confirmation: AttributeConfirmation, - _ completion: @escaping ControllerClosure - ) { - self.completion = completion - self.confirmation = confirmation - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { nil } - - public override func viewDidLoad() { - super.viewDidLoad() - setupScrollView() - setupBindings() - setupDetail() - } - - private func setupScrollView() { - addChild(scrollViewController) - view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } - scrollViewController.didMove(toParent: self) - scrollViewController.contentView = screenView - scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color - } - - private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) - - viewModel.state - .map(\.status) - .removeDuplicates() - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - switch $0 { - case .valid: - screenView.saveButton.isEnabled = true - case .invalid, .unknown: - screenView.saveButton.isEnabled = false - } - }.store(in: &cancellables) - - screenView.saveButton - .publisher(for: .touchUpInside) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) - - viewModel.state - .map(\.resendDebouncer) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - screenView.resendButton.isEnabled = $0 == 0 - - if $0 == 0 { - screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal) - } else { - screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled) - } - }.store(in: &cancellables) - - screenView.resendButton - .publisher(for: .touchUpInside) - .receive(on: DispatchQueue.main) - .sink { [unowned self] in viewModel.didTapResend() } - .store(in: &cancellables) - - viewModel.completionPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in completion(self, $0) } - .store(in: &cancellables) - } - - private func setupDetail() { - var content: String! - - if confirmation.isEmail { - content = confirmation.content + @Dependency var navigator: Navigator + @Dependency var barStylist: StatusBarStylist + + private lazy var screenView = ProfileCodeView() + private lazy var scrollViewController = ScrollViewController() + + private let isEmail: Bool + private let content: String + private let viewModel: ProfileCodeViewModel + private var cancellables = Set<AnyCancellable>() + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationItem.backButtonTitle = "" + navigationController?.navigationBar + .customize(backgroundColor: Asset.neutralWhite.color) + } + + public init( + _ isEmail: Bool, + _ content: String, + _ confirmationId: String + ) { + self.viewModel = .init( + isEmail: isEmail, + content: content, + confirmationId: confirmationId + ) + self.isEmail = isEmail + self.content = content + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + public override func viewDidLoad() { + super.viewDidLoad() + setupScrollView() + setupBindings() + +// var content: String! +// +// if confirmation.isEmail { +// content = confirmation.content +// } else { +// let country = Country.findFrom(confirmation.content) +// content = "\(country.prefix)\(confirmation.content.dropLast(2))" +// } +// +// screenView.set(content, isEmail: confirmation.isEmail) + } + + private func setupScrollView() { + addChild(scrollViewController) + view.addSubview(scrollViewController.view) + scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } + scrollViewController.didMove(toParent: self) + scrollViewController.contentView = screenView + scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color + } + + private func setupBindings() { + screenView.inputField.textPublisher + .sink { [unowned self] in viewModel.didInput($0) } + .store(in: &cancellables) + + viewModel.statePublisher + .map(\.status) + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [unowned self] in + switch $0 { + case .valid: + screenView.saveButton.isEnabled = true + case .invalid, .unknown: + screenView.saveButton.isEnabled = false + } + }.store(in: &cancellables) + + screenView.saveButton + .publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [unowned self] in viewModel.didTapNext() } + .store(in: &cancellables) + + viewModel.statePublisher + .map(\.resendDebouncer) + .receive(on: DispatchQueue.main) + .sink { [unowned self] in + screenView.resendButton.isEnabled = $0 == 0 + + if $0 == 0 { + screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal) } else { - let country = Country.findFrom(confirmation.content) - content = "\(country.prefix)\(confirmation.content.dropLast(2))" + screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled) } - - screenView.set(content, isEmail: confirmation.isEmail) - } + }.store(in: &cancellables) + + screenView.resendButton + .publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [unowned self] in viewModel.didTapResend() } + .store(in: &cancellables) + } } diff --git a/Sources/ProfileFeature/Controllers/ProfileController.swift b/Sources/ProfileFeature/Controllers/ProfileController.swift index 154996066df64c037906593484f4db56129c543f..787f83f6e0d429262780485b11cef70dae17a917 100644 --- a/Sources/ProfileFeature/Controllers/ProfileController.swift +++ b/Sources/ProfileFeature/Controllers/ProfileController.swift @@ -8,7 +8,7 @@ public final class ProfileController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ProfileCoordinating - lazy private var screenView = ProfileView() + private lazy var screenView = ProfileView() private let viewModel = ProfileViewModel() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift index 39396d0a2a48ba94338f283bff3016d06d4d1cc7..4607733668aff428fbbd43c074b03ece88244039 100644 --- a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift +++ b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift @@ -8,8 +8,8 @@ public final class ProfileEmailController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ProfileCoordinating - lazy private var screenView = ProfileEmailView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = ProfileEmailView() + private lazy var scrollViewController = ScrollViewController() private let viewModel = ProfileEmailViewModel() private var cancellables = Set<AnyCancellable>() @@ -31,46 +31,60 @@ public final class ProfileEmailController: UIViewController { private func setupScrollView() { addChild(scrollViewController) view.addSubview(scrollViewController.view) - scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } + scrollViewController.view.snp.makeConstraints { + $0.edges.equalToSuperview() + } scrollViewController.didMove(toParent: self) scrollViewController.contentView = screenView scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color } private func setupBindings() { - screenView.inputField.textPublisher - .sink { [unowned self] in viewModel.didInput($0) } - .store(in: &cancellables) + screenView + .inputField + .textPublisher + .sink { [unowned self] in + viewModel.didInput($0) + }.store(in: &cancellables) - screenView.inputField.returnPublisher - .sink { [unowned self] in screenView.inputField.endEditing(true) } - .store(in: &cancellables) + screenView + .inputField + .returnPublisher + .sink { [unowned self] in + screenView.inputField.endEditing(true) + }.store(in: &cancellables) - viewModel.state - .map(\.confirmation) + viewModel + .statePublisher + .map(\.confirmationId) .receive(on: DispatchQueue.main) .compactMap { $0 } .sink { [unowned self] in viewModel.clearUp() - coordinator.toCode(with: $0, from: self) { _, _ in - if let viewControllers = self.navigationController?.viewControllers { - self.navigationController?.popToViewController( - viewControllers[viewControllers.count - 3], - animated: true - ) - } - } - } - .store(in: &cancellables) +// coordinator.toCode(with: $0, from: self) { _, _ in +// if let viewControllers = self.navigationController?.viewControllers { +// self.navigationController?.popToViewController( +// viewControllers[viewControllers.count - 3], +// animated: true +// ) +// } +// } + }.store(in: &cancellables) - viewModel.state.map(\.status) + viewModel + .statePublisher + .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { [unowned self] in screenView.update(status: $0) } - .store(in: &cancellables) + .sink { [unowned self] in + screenView.update(status: $0) + }.store(in: &cancellables) - screenView.saveButton.publisher(for: .touchUpInside) - .sink { [unowned self] in viewModel.didTapNext() } - .store(in: &cancellables) + screenView + .saveButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + viewModel.didTapNext() + }.store(in: &cancellables) } } diff --git a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift index 6b220c37a4a935119abfa5172149dcf80d76fb24..1708fc9e3211c274b616f89db6b4e23e22961bae 100644 --- a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift +++ b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift @@ -8,8 +8,8 @@ public final class ProfilePhoneController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ProfileCoordinating - lazy private var screenView = ProfilePhoneView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = ProfilePhoneView() + private lazy var scrollViewController = ScrollViewController() private let viewModel = ProfilePhoneViewModel() private var cancellables = Set<AnyCancellable>() @@ -52,23 +52,23 @@ public final class ProfilePhoneController: UIViewController { coordinator.toCountries(from: self) { self.viewModel.didChooseCountry($0) } }.store(in: &cancellables) - viewModel.state - .map(\.confirmation) + viewModel.statePublisher + .map(\.confirmationId) .receive(on: DispatchQueue.main) .compactMap { $0 } .sink { [unowned self] in viewModel.clearUp() - coordinator.toCode(with: $0, from: self) { _, _ in - if let viewControllers = self.navigationController?.viewControllers { - self.navigationController?.popToViewController( - viewControllers[viewControllers.count - 3], - animated: true - ) - } - } +// coordinator.toCode(with: $0, from: self) { _, _ in +// if let viewControllers = self.navigationController?.viewControllers { +// self.navigationController?.popToViewController( +// viewControllers[viewControllers.count - 3], +// animated: true +// ) +// } +// } }.store(in: &cancellables) - viewModel.state + viewModel.statePublisher .map(\.country) .removeDuplicates() .receive(on: DispatchQueue.main) @@ -78,7 +78,7 @@ public final class ProfilePhoneController: UIViewController { } .store(in: &cancellables) - viewModel.state + viewModel.statePublisher .map(\.status) .removeDuplicates() .receive(on: DispatchQueue.main) diff --git a/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift b/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift index 947937b403a4f29cee289e590908589a66c57754..59b569efd6bf53c6e5a670edea90eb3810a308f5 100644 --- a/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift +++ b/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Countries import Permissions import MenuFeature @@ -14,12 +13,6 @@ public protocol ProfileCoordinating { func toDrawer(_: UIViewController, from: UIViewController) func toPermission(type: PermissionType, from: UIViewController) - func toCode( - with: AttributeConfirmation, - from: UIViewController, - _: @escaping ControllerClosure - ) - func toCountries( from: UIViewController, _: @escaping (Country) -> Void @@ -38,7 +31,6 @@ public struct ProfileCoordinator: ProfileCoordinating { var permissionFactory: () -> RequestPermissionController var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController var countriesFactory: (@escaping (Country) -> Void) -> UIViewController - var codeFactory: (AttributeConfirmation, @escaping ControllerClosure) -> UIViewController public init( emailFactory: @escaping () -> UIViewController, @@ -46,10 +38,8 @@ public struct ProfileCoordinator: ProfileCoordinating { imagePickerFactory: @escaping () -> UIImagePickerController, permissionFactory: @escaping () -> RequestPermissionController, // ⚠️ sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController, - countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController, - codeFactory: @escaping (AttributeConfirmation, @escaping ControllerClosure) -> UIViewController + countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController ) { - self.codeFactory = codeFactory self.emailFactory = emailFactory self.phoneFactory = phoneFactory self.sideMenuFactory = sideMenuFactory @@ -70,15 +60,6 @@ public extension ProfileCoordinator { pushPresenter.present(screen, from: parent) } - func toCode( - with confirmation: AttributeConfirmation, - from parent: UIViewController, - _ completion: @escaping ControllerClosure - ) { - let screen = codeFactory(confirmation, completion) - pushPresenter.present(screen, from: parent) - } - func toPermission(type: PermissionType, from parent: UIViewController) { let screen = permissionFactory() screen.setup(type: type) diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift index 926c6d49a4f01eb1088c0431edcdf00a7dec7e1b..173c8e199ad09c928387672e165ef4a6049b4388 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift @@ -1,101 +1,95 @@ import Shared -import Models import Combine import Defaults import XXClient -import Foundation import InputField -import BackupFeature +import Foundation import CombineSchedulers import XXMessengerClient import DependencyInjection -struct ProfileCodeViewState: Equatable { - var input: String = "" - var status: InputField.ValidationStatus = .unknown(nil) - var resendDebouncer: Int = 0 -} - final class ProfileCodeViewModel { + struct ViewState: Equatable { + var input: String = "" + var status: InputField.ValidationStatus = .unknown(nil) + var resendDebouncer: Int = 0 + var didConfirm: Bool = false + } + + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } + @Dependency var messenger: Messenger @Dependency var hudController: HUDController - @Dependency var backupService: BackupService - @KeyObject(.email, defaultValue: nil) var email: String? @KeyObject(.phone, defaultValue: nil) var phone: String? - let confirmation: AttributeConfirmation - - var timer: Timer? - - var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() } - private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>() - - var state: AnyPublisher<ProfileCodeViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<ProfileCodeViewState, Never>(.init()) - - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - - init(_ confirmation: AttributeConfirmation) { - self.confirmation = confirmation + private var timer: Timer? + private let isEmail: Bool + private let content: String + private let confirmationId: String + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + + init( + isEmail: Bool, + content: String, + confirmationId: String + ) { + self.isEmail = isEmail + self.content = content + self.confirmationId = confirmationId didTapResend() } func didInput(_ string: String) { - stateRelay.value.input = string + stateSubject.value.input = string validate() } func didTapResend() { - guard stateRelay.value.resendDebouncer == 0 else { return } - - stateRelay.value.resendDebouncer = 60 - + guard stateSubject.value.resendDebouncer == 0 else { return } + stateSubject.value.resendDebouncer = 60 timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in - guard let self = self, self.stateRelay.value.resendDebouncer > 0 else { + guard let self = self, self.stateSubject.value.resendDebouncer > 0 else { $0.invalidate() return } - - self.stateRelay.value.resendDebouncer -= 1 + self.stateSubject.value.resendDebouncer -= 1 } } func didTapNext() { hudController.show() - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - + scheduler.schedule { [weak self] in + guard let self else { return } do { try self.messenger.ud.get()!.confirmFact( - confirmationId: self.confirmation.confirmationId!, - code: self.stateRelay.value.input + confirmationId: self.confirmationId, + code: self.stateSubject.value.input ) - - if self.confirmation.isEmail { - self.email = self.confirmation.content + if self.isEmail { + self.email = self.content } else { - self.phone = self.confirmation.content + self.phone = self.content } - self.timer?.invalidate() self.hudController.dismiss() - self.completionRelay.send(self.confirmation) - - self.backupService.didUpdateFacts() + self.stateSubject.value.didConfirm = true } catch { - self.hudController.show(.init(error: error)) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudController.show(.init(content: xxError)) } } } private func validate() { - switch Validator.code.validate(stateRelay.value.input) { + switch Validator.code.validate(stateSubject.value.input) { case .success: - stateRelay.value.status = .valid(nil) + stateSubject.value.status = .valid(nil) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift index 79b36b2f15b638ecffd3dc17177c08caf7c13055..6e0f17fe540e2b7271adacc3a330965efeda45e3 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift @@ -1,4 +1,3 @@ -import Models import Shared import Combine import XXClient @@ -8,47 +7,44 @@ import CombineSchedulers import XXMessengerClient import DependencyInjection -struct ProfileEmailViewState: Equatable { - var input: String = "" - var confirmation: AttributeConfirmation? = nil - var status: InputField.ValidationStatus = .unknown(nil) -} - final class ProfileEmailViewModel { + struct ViewState: Equatable { + var input: String = "" + var content: String? + var confirmationId: String? + var status: InputField.ValidationStatus = .unknown(nil) + } + @Dependency var messenger: Messenger @Dependency var hudController: HUDController - var state: AnyPublisher<ProfileEmailViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<ProfileEmailViewState, Never>(.init()) + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() func didInput(_ string: String) { - stateRelay.value.input = string + stateSubject.value.input = string validate() } func clearUp() { - stateRelay.value.confirmation = nil + stateSubject.value.confirmationId = nil } func didTapNext() { hudController.show() - - backgroundScheduler.schedule { [weak self] in + scheduler.schedule { [weak self] in guard let self = self else { return } - do { let confirmationId = try self.messenger.ud.get()!.sendRegisterFact( - .init(type: .email, value: self.stateRelay.value.input) + .init(type: .email, value: self.stateSubject.value.input) ) - self.hudController.dismiss() - self.stateRelay.value.confirmation = .init( - content: self.stateRelay.value.input, - isEmail: true, - confirmationId: confirmationId - ) + self.stateSubject.value.confirmationId = confirmationId + self.stateSubject.value.content = self.stateSubject.value.input } catch { let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) self.hudController.show(.init(content: xxError)) @@ -57,11 +53,11 @@ final class ProfileEmailViewModel { } private func validate() { - switch Validator.email.validate(stateRelay.value.input) { + switch Validator.email.validate(stateSubject.value.input) { case .success: - stateRelay.value.status = .valid(nil) + stateSubject.value.status = .valid(nil) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift index b2b3e34db5b6acd8520fba1d812459b41a5ce1d3..8adc37cc12c0631c39a3320d20d9158d5c2d478b 100644 --- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift @@ -1,63 +1,59 @@ import Shared -import Models import Combine import XXClient import Countries import InputField import Foundation import CombineSchedulers -import DependencyInjection import XXMessengerClient - -struct ProfilePhoneViewState: Equatable { - var input: String = "" - var confirmation: AttributeConfirmation? = nil - var status: InputField.ValidationStatus = .unknown(nil) - var country: Country = .fromMyPhone() -} +import DependencyInjection final class ProfilePhoneViewModel { + struct ViewState: Equatable { + var input: String = "" + var content: String? + var confirmationId: String? + var status: InputField.ValidationStatus = .unknown(nil) + var country: Country = .fromMyPhone() + } + @Dependency var messenger: Messenger @Dependency var hudController: HUDController - var state: AnyPublisher<ProfilePhoneViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<ProfilePhoneViewState, Never>(.init()) + var statePublisher: AnyPublisher<ViewState, Never> { + stateSubject.eraseToAnyPublisher() + } - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + private let stateSubject = CurrentValueSubject<ViewState, Never>(.init()) + private var scheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() func didInput(_ string: String) { - stateRelay.value.input = string + stateSubject.value.input = string validate() } func clearUp() { - stateRelay.value.confirmation = nil + stateSubject.value.confirmationId = nil } func didChooseCountry(_ country: Country) { - stateRelay.value.country = country + stateSubject.value.country = country validate() } func didTapNext() { hudController.show() - - backgroundScheduler.schedule { [weak self] in + scheduler.schedule { [weak self] in guard let self = self else { return } - - let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)" - + let content = "\(self.stateSubject.value.input)\(self.stateSubject.value.country.code)" do { let confirmationId = try self.messenger.ud.get()!.sendRegisterFact( .init(type: .phone, value: content) ) self.hudController.dismiss() - self.stateRelay.value.confirmation = .init( - content: content, - confirmationId: confirmationId - ) - + self.stateSubject.value.content = content + self.stateSubject.value.confirmationId = confirmationId } catch { let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) self.hudController.show(.init(content: xxError)) @@ -66,11 +62,11 @@ final class ProfilePhoneViewModel { } private func validate() { - switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) { + switch Validator.phone.validate((stateSubject.value.country.regex, stateSubject.value.input)) { case .success: - stateRelay.value.status = .valid(nil) + stateSubject.value.status = .valid(nil) case .failure(let error): - stateRelay.value.status = .invalid(error) + stateSubject.value.status = .invalid(error) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift index d8aa77527d7bbf10fe4c1a6728e145e574822587..996af851422553118ed892921bbcb6a585a4fdcc 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import Combine import Defaults import XXClient diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift index e6407316c52c9fa5569dbc25396c7a40e16f5b63..b43a575d275a44c3c6e63979ecf37c5d1cf3fdbd 100644 --- a/Sources/PushFeature/PushHandler.swift +++ b/Sources/PushFeature/PushHandler.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Defaults import XXClient import XXModels diff --git a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift index ef723bcb358bbbd0d532c1138a0053be75334883..7a878ab38d46b56b292d35456e8c3494e326c185 100644 --- a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift @@ -8,7 +8,7 @@ public final class RequestsContainerController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: RequestsCoordinating - lazy private var screenView = RequestsContainerView() + private lazy var screenView = RequestsContainerView() private var cancellables = Set<AnyCancellable>() public override func loadView() { diff --git a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift index 40d6b4cd068bcf0928ffa6c8519f2a7452530e85..bb62744b37d88cec22b8362938be2cb32ef954d2 100644 --- a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift @@ -3,7 +3,7 @@ import Combine import DependencyInjection final class RequestsFailedController: UIViewController { - lazy private var screenView = RequestsFailedView() + private lazy var screenView = RequestsFailedView() private var cancellables = Set<AnyCancellable>() private let viewModel = RequestsFailedViewModel() private var dataSource: UICollectionViewDiffableDataSource<Section, Request>? diff --git a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift index b6fc65c14009e4911a32cccc4ad39d62700f0d06..feec2f2873d90a2fbbbaa492e03e4c22a4ac5567 100644 --- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels @@ -11,7 +10,7 @@ final class RequestsReceivedController: UIViewController { @Dependency var toaster: ToastController @Dependency var coordinator: RequestsCoordinating - lazy private var screenView = RequestsReceivedView() + private lazy var screenView = RequestsReceivedView() private var cancellables = Set<AnyCancellable>() private let viewModel = RequestsReceivedViewModel() private var drawerCancellables = Set<AnyCancellable>() diff --git a/Sources/RequestsFeature/Controllers/RequestsSentController.swift b/Sources/RequestsFeature/Controllers/RequestsSentController.swift index b632e8216dda25340fa1c263aa62f7c2f275ce1a..7edec9380f4adba716f64797ddef883821f1ddcf 100644 --- a/Sources/RequestsFeature/Controllers/RequestsSentController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsSentController.swift @@ -7,7 +7,7 @@ final class RequestsSentController: UIViewController { connectionSubject.eraseToAnyPublisher() } - lazy private var screenView = RequestsSentView() + private lazy var screenView = RequestsSentView() private let viewModel = RequestsSentViewModel() private var cancellables = Set<AnyCancellable>() private let tapSubject = PassthroughSubject<Request, Never>() diff --git a/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift b/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift index 571bf625fc1a98b1cf2905154a660fd4cbebd92e..9e8297c0e7af060767d4711271d124d72a596ae5 100644 --- a/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift +++ b/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift @@ -1,6 +1,5 @@ import UIKit import Shared -import Models import XXModels import MenuFeature import Presentation diff --git a/Sources/RequestsFeature/Models/Request.swift b/Sources/RequestsFeature/Models/Request.swift index 595410d43257294a139a0f7b77a717992ed6ffcf..aac0fd8c068a3e5c873e2d4d3edef334202c749b 100644 --- a/Sources/RequestsFeature/Models/Request.swift +++ b/Sources/RequestsFeature/Models/Request.swift @@ -1,4 +1,3 @@ -import Models import XXModels import Foundation diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift index 97159b02162bb1f069045060895c9f6c21c0feb8..351d3b37dc6a61dc181c4de6b6e372564e371bb5 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import XXModels diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift index c459bf79b1649595882956830a443a646a1d6e28..055b7236fe3bb5a870414f1cf1de5bc1e2f169c6 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import Defaults diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift index 9faa49547ab7766986f238ecdf1dab8902d3079b..4936e676dc099bd4de02bba55e88b5765e5368fb 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import Defaults diff --git a/Sources/RestoreFeature/Controllers/RestoreController.swift b/Sources/RestoreFeature/Controllers/RestoreController.swift index 27031b7556a1aa0c7550358bc704b0fa181e257c..12bc3a7d244e147e3fc7433a1eaa036db828db01 100644 --- a/Sources/RestoreFeature/Controllers/RestoreController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreController.swift @@ -7,7 +7,7 @@ import DependencyInjection public final class RestoreController: UIViewController { @Dependency private var coordinator: RestoreCoordinating - lazy private var screenView = RestoreView() + private lazy var screenView = RestoreView() private let viewModel: RestoreViewModel private var cancellables = Set<AnyCancellable>() diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift index bfea5df338a13928ee2fb68fa3aa989c5f9ce7e0..026bdbd0fe809898df7ead2282729f44732f1e47 100644 --- a/Sources/RestoreFeature/Controllers/RestoreListController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift @@ -7,7 +7,7 @@ import DependencyInjection public final class RestoreListController: UIViewController { @Dependency var coordinator: RestoreCoordinating - lazy private var screenView = RestoreListView() + private lazy var screenView = RestoreListView() private let viewModel = RestoreListViewModel() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift b/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift index 67dd450b68baef63bccc07257c652705288d97d0..45bd32d7d3c8f6a5c252c2f9c5ae54767eb5ee27 100644 --- a/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift +++ b/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift @@ -4,7 +4,7 @@ import Combine import InputField public final class RestorePassphraseController: UIViewController { - lazy private var screenView = RestorePassphraseView() + private lazy var screenView = RestorePassphraseView() private var passphrase = "" { didSet { diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift index 2ddf4ab1160f6c09aa7644eb857f46f6e2fffaa3..3486ea103f3efd157c7aac415ae7dc9560fb24ee 100644 --- a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift @@ -4,8 +4,8 @@ import DependencyInjection import ScrollViewController public final class RestoreSFTPController: UIViewController { - lazy private var screenView = RestoreSFTPView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = RestoreSFTPView() + private lazy var scrollViewController = ScrollViewController() private let completion: (String, String, String) -> Void private let viewModel = RestoreSFTPViewModel() diff --git a/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift b/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift index 4f6f1d9b9a0befadf8ea5060e556cf890207e64c..13c313630e6607b91526922991df2191de0766c0 100644 --- a/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift @@ -7,7 +7,7 @@ public final class RestoreSuccessController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: RestoreCoordinating - lazy private var screenView = RestoreSuccessView() + private lazy var screenView = RestoreSuccessView() private var cancellables = Set<AnyCancellable>() public override func loadView() { diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift index 98b68d83c36a974dfcea2ba1f447ba29a6df9f83..14fe7a357499280599ff880287b667dd2859ff6c 100644 --- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift +++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Shared import Combine import Defaults diff --git a/Sources/ScanFeature/Controllers/ScanContainerController.swift b/Sources/ScanFeature/Controllers/ScanContainerController.swift index 9105e1329ddbbeab058bc0497100dfb7945db2f0..883f95fdb92a27d2b4f860ec2356f450a7ceeaa5 100644 --- a/Sources/ScanFeature/Controllers/ScanContainerController.swift +++ b/Sources/ScanFeature/Controllers/ScanContainerController.swift @@ -8,7 +8,7 @@ public final class ScanContainerController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: ScanCoordinating - lazy private var screenView = ScanContainerView() + private lazy var screenView = ScanContainerView() private let scanController = ScanController() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ScanFeature/Controllers/ScanController.swift b/Sources/ScanFeature/Controllers/ScanController.swift index 52055ac4eae74504e4f53edfb78617099079716e..8f05a9446e07f4dacbca8620964dada79e23bd12 100644 --- a/Sources/ScanFeature/Controllers/ScanController.swift +++ b/Sources/ScanFeature/Controllers/ScanController.swift @@ -9,7 +9,7 @@ final class ScanController: UIViewController { @Dependency private var coordinator: ScanCoordinating @Dependency private var permissions: PermissionHandling - lazy private var screenView = ScanView() + private lazy var screenView = ScanView() var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() diff --git a/Sources/ScanFeature/Controllers/ScanDisplayController.swift b/Sources/ScanFeature/Controllers/ScanDisplayController.swift index e373d4de40ca31bae004de3d80c85702d538b604..c439855ca678d594657c86a08896b7df2bbafa2d 100644 --- a/Sources/ScanFeature/Controllers/ScanDisplayController.swift +++ b/Sources/ScanFeature/Controllers/ScanDisplayController.swift @@ -2,7 +2,7 @@ import UIKit import Combine final class ScanDisplayController: UIViewController { - lazy private var screenView = ScanDisplayView() + private lazy var screenView = ScanDisplayView() private let viewModel = ScanDisplayViewModel() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift b/Sources/ScanFeature/Coordinator/ScanCoordinator.swift index 98e605775ccdf54b5ffda4b0a6ad0183a5c15fe1..cfdc386a14896148ffff2844e62df64a61d2f40d 100644 --- a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift +++ b/Sources/ScanFeature/Coordinator/ScanCoordinator.swift @@ -1,5 +1,4 @@ import UIKit -import Models import XXModels import MenuFeature import Presentation diff --git a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift index fb03482a4e6405f543aa310bf1c2d0661285a40d..00be78ca17fc1a4c30ce13e166bf5c0e18b7f6f3 100644 --- a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift @@ -1,5 +1,4 @@ import UIKit -import Models import Combine import Defaults import Countries diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift index 48e2ed2918b877233caea0b252525d6c45dfabe4..c46965cad50ae1f075941636a60db4e22dca6f0f 100644 --- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift @@ -1,5 +1,4 @@ import Shared -import Models import Combine import XXModels import XXClient diff --git a/Sources/SearchFeature/Controllers/SearchContainerController.swift b/Sources/SearchFeature/Controllers/SearchContainerController.swift index 69665f8c946e0887c5ff5ba2d186944bcd9728d4..77f2a535feca6a5dacafca6e9632e3f0396d26e4 100644 --- a/Sources/SearchFeature/Controllers/SearchContainerController.swift +++ b/Sources/SearchFeature/Controllers/SearchContainerController.swift @@ -9,7 +9,7 @@ public final class SearchContainerController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: SearchCoordinating - lazy private var screenView = SearchContainerView() + private lazy var screenView = SearchContainerView() private var contentOffset: CGPoint? private var cancellables = Set<AnyCancellable>() diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift index e850dfde12532dc74c85f7bc44d8e546e9543ecf..7eb72535120e8f30f86258365e4ca86d422572e4 100644 --- a/Sources/SearchFeature/Controllers/SearchLeftController.swift +++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift @@ -15,7 +15,7 @@ final class SearchLeftController: UIViewController { @KeyObject(.sharingEmail, defaultValue: false) var isSharingEmail: Bool @KeyObject(.sharingPhone, defaultValue: false) var isSharingPhone: Bool - lazy private var screenView = SearchLeftView() + private lazy var screenView = SearchLeftView() let viewModel: SearchLeftViewModel private var dataSource: SearchDiffableDataSource! diff --git a/Sources/SearchFeature/Controllers/SearchRightController.swift b/Sources/SearchFeature/Controllers/SearchRightController.swift index 35240054497992ff2b5a277618321dceffeb652a..4541af0f7fb5cf16f9411edfbe71661f9a2da3d1 100644 --- a/Sources/SearchFeature/Controllers/SearchRightController.swift +++ b/Sources/SearchFeature/Controllers/SearchRightController.swift @@ -5,7 +5,7 @@ import DependencyInjection final class SearchRightController: UIViewController { @Dependency var coordinator: SearchCoordinating - lazy private var screenView = SearchRightView() + private lazy var screenView = SearchRightView() private var cancellables = Set<AnyCancellable>() private let cameraController = CameraController() diff --git a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift index 21a9d7b3d5eb9b1cef283a410135157fb7571ef1..053e5b659fb1d3179c7d184e4ca30263d556b1cc 100644 --- a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift +++ b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift @@ -1,5 +1,4 @@ import UIKit -import Models import XXModels import Countries import Presentation diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift index 5fd6243a1c177e489172231c549f969c3a4dc55d..969152bdb251cdbb5fcdb27f94931a9928e599c5 100644 --- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift @@ -1,6 +1,5 @@ import Retry import UIKit -import Models import Shared import Combine import XXModels diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift index c238960cb61657e98dcb7262965cdf87215e7302..de6a06eadb6119ca128e7f0aac9205712ec8aca6 100644 --- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift @@ -1,5 +1,4 @@ import Shared -import Models import Combine import XXModels import Defaults diff --git a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift index 7b23ea8008ed06973c602b2dd4e40653e38e3f4c..01ad5ad59576f8654f8ed66cf9250e42dbfc4f5d 100644 --- a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift +++ b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift @@ -11,8 +11,8 @@ public final class AccountDeleteController: UIViewController { @Dependency var coordinator: SettingsCoordinating - lazy private var screenView = AccountDeleteView() - lazy private var scrollViewController = ScrollViewController() + private lazy var screenView = AccountDeleteView() + private lazy var scrollViewController = ScrollViewController() private let viewModel = AccountDeleteViewModel() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift index efe25a0030b832db3507172bf4a966d26e0ed54f..62dcd6294bc38a0746a9cccef50bc2194a1269cb 100644 --- a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift +++ b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift @@ -6,7 +6,7 @@ import DependencyInjection public final class SettingsAdvancedController: UIViewController { @Dependency private var coordinator: SettingsCoordinating - lazy private var screenView = SettingsAdvancedView() + private lazy var screenView = SettingsAdvancedView() private var cancellables = Set<AnyCancellable>() private let viewModel = SettingsAdvancedViewModel() diff --git a/Sources/SettingsFeature/Controllers/SettingsController.swift b/Sources/SettingsFeature/Controllers/SettingsController.swift index de85af7cfb09a459cc8805e35a784cf035035c10..74ca0b94de5d3d367874e3474d3260b331608875 100644 --- a/Sources/SettingsFeature/Controllers/SettingsController.swift +++ b/Sources/SettingsFeature/Controllers/SettingsController.swift @@ -9,8 +9,8 @@ public final class SettingsController: UIViewController { @Dependency var barStylist: StatusBarStylist @Dependency var coordinator: SettingsCoordinating - lazy private var scrollViewController = ScrollViewController() - lazy private var screenView = SettingsView { + private lazy var scrollViewController = ScrollViewController() + private lazy var screenView = SettingsView { switch $0 { case .icognitoKeyboard: self.presentInfo( diff --git a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift index d03db85ada994987d0f2d06515120fe24e7337d8..bb1b4cb37d1a0f536ab355a2d67efd809e6bab9b 100644 --- a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift +++ b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift @@ -1,6 +1,5 @@ import Shared import Retry -import Models import Combine import Defaults import Keychain diff --git a/Sources/Shared/Controllers/RootViewController.swift b/Sources/Shared/Controllers/RootViewController.swift index 909af5d6a1193f15e81b92c5ff41d3c4293aaf62..86f20217da41497cebbce7f6667dd8c129feffc7 100644 --- a/Sources/Shared/Controllers/RootViewController.swift +++ b/Sources/Shared/Controllers/RootViewController.swift @@ -8,15 +8,15 @@ public final class RootViewController: UIViewController { @Dependency var toastDispatcher: ToastController var hud: HUDView? - let content: UIViewController? var cancellables = Set<AnyCancellable>() + public let navController: UINavigationController var toastTimer: Timer? let toastTopPadding: CGFloat = 10 var topToastConstraint: NSLayoutConstraint? - public init(_ content: UIViewController?) { - self.content = content + public init(_ content: UINavigationController) { + self.navController = content super.init(nibName: nil, bundle: nil) } @@ -29,15 +29,11 @@ public final class RootViewController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() - if let content { - addChild(content) - view.addSubview(content.view) - content.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - content.view.frame = view.bounds - content.didMove(toParent: self) - } else { - view.isUserInteractionEnabled = false - } + addChild(navController) + view.addSubview(navController.view) + navController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + navController.view.frame = view.bounds + navController.didMove(toParent: self) barStylist .styleSubject diff --git a/Sources/Models/Payload.swift b/Sources/Shared/Models/Payload.swift similarity index 100% rename from Sources/Models/Payload.swift rename to Sources/Shared/Models/Payload.swift diff --git a/Sources/Models/Reply.swift b/Sources/Shared/Models/Reply.swift similarity index 100% rename from Sources/Models/Reply.swift rename to Sources/Shared/Models/Reply.swift diff --git a/Sources/Models/pbpayload.pb.swift b/Sources/Shared/Models/pbpayload.pb.swift similarity index 100% rename from Sources/Models/pbpayload.pb.swift rename to Sources/Shared/Models/pbpayload.pb.swift diff --git a/Sources/TermsFeature/TermsConditionsController.swift b/Sources/TermsFeature/TermsConditionsController.swift index 08a6081f497f40ba68303c86fba00f8cedd4a705..d426489d9e8793bdaddc539020786585d7f0ac90 100644 --- a/Sources/TermsFeature/TermsConditionsController.swift +++ b/Sources/TermsFeature/TermsConditionsController.swift @@ -11,7 +11,7 @@ public final class TermsConditionsController: UIViewController { @KeyObject(.username, defaultValue: nil) var username: String? @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool - lazy private var screenView = TermsConditionsView() + private lazy var screenView = TermsConditionsView() private var cancellables = Set<AnyCancellable>() diff --git a/Sources/TestHelpers/Dummies.swift b/Sources/TestHelpers/Dummies.swift deleted file mode 100644 index e29064cdd83754306c24dc214ddf3b88d0dd1ab1..0000000000000000000000000000000000000000 --- a/Sources/TestHelpers/Dummies.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Models -import Foundation - -public extension Contact { - static let dummy = Contact( - photo: nil, - userId: Data(), - email: nil, - phone: nil, - status: .friend, - marshaled: Data(), - username: "username", - nickname: nil, - createdAt: Date() - ) -} - -public extension GroupChatInfo { - static let dummy = GroupChatInfo( - group: .dummy, - members: [] - ) -} - -public extension Group { - static let dummy = Group( - leader: Data(), - name: "name", - groupId: Data(), - accepted: true, - serialize: Data() - ) -} - -public extension SingleChatInfo { - static let dummy = SingleChatInfo(contact: .dummy, lastMessage: nil) -} diff --git a/Sources/TestHelpers/PresenterDouble.swift b/Sources/TestHelpers/PresenterDouble.swift deleted file mode 100644 index 603c6a46fc337df3c060451cbb2e83b708e1d065..0000000000000000000000000000000000000000 --- a/Sources/TestHelpers/PresenterDouble.swift +++ /dev/null @@ -1,17 +0,0 @@ -import UIKit -import Presentation - -public final class PresenterDouble: Presenting { - public var didPresentFrom: UIViewController? - public var didPresentTarget: UIViewController? - - public init() {} - - public func present( - _ target: UIViewController, - from parent: UIViewController - ) { - didPresentFrom = parent - didPresentTarget = target - } -} diff --git a/Sources/XXNavigation/Actions/PresentChat.swift b/Sources/XXNavigation/Actions/PresentChat.swift new file mode 100644 index 0000000000000000000000000000000000000000..db7e63b684ae1c3f876898b0a8be36235be25ad3 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentChat.swift @@ -0,0 +1,12 @@ +import XXModels +import Navigation + +public struct PresentChat: Navigation.Action { + public var contact: Contact + public var animated: Bool = true + + public init(contact: Contact, animated: Bool = true) { + self.contact = contact + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentChatList.swift b/Sources/XXNavigation/Actions/PresentChatList.swift new file mode 100644 index 0000000000000000000000000000000000000000..69f3f9423e6521c326a97676fb532f348fb990e9 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentChatList.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentChatList: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentCountryList.swift b/Sources/XXNavigation/Actions/PresentCountryList.swift new file mode 100644 index 0000000000000000000000000000000000000000..adb7ee1d5cd5524e6249cbfd7acb5e4870b25aa2 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentCountryList.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentCountryList: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentDrawer.swift b/Sources/XXNavigation/Actions/PresentDrawer.swift new file mode 100644 index 0000000000000000000000000000000000000000..44e85b9584e45614c46225fb83c302d130e52e1a --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentDrawer.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentDrawer: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentGroupChat.swift b/Sources/XXNavigation/Actions/PresentGroupChat.swift new file mode 100644 index 0000000000000000000000000000000000000000..39219036447e529dc504f88fb5787fec9a874bc0 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentGroupChat.swift @@ -0,0 +1,12 @@ +import XXModels +import Navigation + +public struct PresentGroupChat: Navigation.Action { + public var model: GroupInfo + public var animated: Bool = true + + public init(model: GroupInfo, animated: Bool = true) { + self.model = model + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingCode.swift b/Sources/XXNavigation/Actions/PresentOnboardingCode.swift new file mode 100644 index 0000000000000000000000000000000000000000..59f0926ffd0191eb7c7b644735ed084f257b2d5e --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingCode.swift @@ -0,0 +1,20 @@ +import Navigation + +public struct PresentOnboardingCode: Navigation.Action { + public var isEmail: Bool + public var content: String + public var animated: Bool = true + public var confirmationId: String + + public init( + isEmail: Bool, + content: String, + confirmationId: String, + animated: Bool = true + ) { + self.animated = animated + self.isEmail = isEmail + self.content = content + self.confirmationId = confirmationId + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingEmail.swift b/Sources/XXNavigation/Actions/PresentOnboardingEmail.swift new file mode 100644 index 0000000000000000000000000000000000000000..6d418378dff1fbbca972e4462eef57b696c66b81 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingEmail.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentOnboardingEmail: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingPhone.swift b/Sources/XXNavigation/Actions/PresentOnboardingPhone.swift new file mode 100644 index 0000000000000000000000000000000000000000..6a104049e8fe4d5b5fdd3b2d191dee07830e8882 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingPhone.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentOnboardingPhone: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingStart.swift b/Sources/XXNavigation/Actions/PresentOnboardingStart.swift new file mode 100644 index 0000000000000000000000000000000000000000..fc0bf22983ff45874174090db069f2425f01df0f --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingStart.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentOnboardingStart: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingUsername.swift b/Sources/XXNavigation/Actions/PresentOnboardingUsername.swift new file mode 100644 index 0000000000000000000000000000000000000000..5a828dc48edaf5b90b7c7261956566b578902448 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingUsername.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentOnboardingUsername: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentOnboardingWelcome.swift b/Sources/XXNavigation/Actions/PresentOnboardingWelcome.swift new file mode 100644 index 0000000000000000000000000000000000000000..1c4120896abf0bb0efdb12b2833d7ccfc68beca9 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentOnboardingWelcome.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentOnboardingWelcome: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentRequests.swift b/Sources/XXNavigation/Actions/PresentRequests.swift new file mode 100644 index 0000000000000000000000000000000000000000..0f9718f0bef74244a1f5d9db5ef7d4030d408ded --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentRequests.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentRequests: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentRestoreList.swift b/Sources/XXNavigation/Actions/PresentRestoreList.swift new file mode 100644 index 0000000000000000000000000000000000000000..8a7fbb5a13ec80c6db8e2795e5b2bca77b283511 --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentRestoreList.swift @@ -0,0 +1,9 @@ +import Navigation + +public struct PresentRestoreList: Navigation.Action { + public var animated: Bool = true + + public init(animated: Bool = true) { + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentSearch.swift b/Sources/XXNavigation/Actions/PresentSearch.swift new file mode 100644 index 0000000000000000000000000000000000000000..7cb33ba23d3822d86a8800dba3dcbe23cc7a857b --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentSearch.swift @@ -0,0 +1,11 @@ +import Navigation + +public struct PresentSearch: Navigation.Action { + public var searching: String? + public var animated: Bool = true + + public init(searching: String? = nil, animated: Bool = true) { + self.searching = searching + self.animated = animated + } +} diff --git a/Sources/XXNavigation/Actions/PresentTermsAndConditions.swift b/Sources/XXNavigation/Actions/PresentTermsAndConditions.swift new file mode 100644 index 0000000000000000000000000000000000000000..2df2ad38f1c0a317ba640620df29e214288b975c --- /dev/null +++ b/Sources/XXNavigation/Actions/PresentTermsAndConditions.swift @@ -0,0 +1,14 @@ +import Navigation + +public struct PresentTermsAndConditions: Navigation.Action { + public var animated: Bool = true + public var popAllowed: Bool = true + + public init( + animated: Bool = true, + popAllowed: Bool = true + ) { + self.animated = animated + self.popAllowed = popAllowed + } +} diff --git a/Sources/XXNavigation/Navigators/PresentChatListNavigator.swift b/Sources/XXNavigation/Navigators/PresentChatListNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..0d83657ff6061938ccdd2fa28be330470aa9240a --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentChatListNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentChatListNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentChatList, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentChatNavigator.swift b/Sources/XXNavigation/Navigators/PresentChatNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..2714bc558042e4a4f34a20755504db4761a7caac --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentChatNavigator.swift @@ -0,0 +1,23 @@ +import UIKit +import XXModels +import Navigation +import DependencyInjection + +public struct PresentChatNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: (Contact) -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentChat, completion: @escaping () -> Void) { + let pushAction = Push(screen(action.contact), on: navigationController(), animated: action.animated) + navigator.perform(pushAction, completion: completion) + } + + public init( + screen: @escaping (Contact) -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentCountryListNavigator.swift b/Sources/XXNavigation/Navigators/PresentCountryListNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..a5842927631bbdcfeabaf629a9d024ef332989fa --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentCountryListNavigator.swift @@ -0,0 +1,23 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentCountryListNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentCountryList, completion: @escaping () -> Void) { + if let topViewController = navigationController().topViewController { + let modalAction = PresentModal(screen(), from: topViewController) + } + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentDrawerNavigator.swift b/Sources/XXNavigation/Navigators/PresentDrawerNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentDrawerNavigator.swift @@ -0,0 +1 @@ + diff --git a/Sources/XXNavigation/Navigators/PresentGroupChatNavigator.swift b/Sources/XXNavigation/Navigators/PresentGroupChatNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..a44485bc3007338c6a0bc20654738dd77597aafd --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentGroupChatNavigator.swift @@ -0,0 +1,23 @@ +import UIKit +import XXModels +import Navigation +import DependencyInjection + +public struct PresentGroupChatNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: (GroupInfo) -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentGroupChat, completion: @escaping () -> Void) { + let pushAction = Push(screen(action.model), on: navigationController(), animated: action.animated) + navigator.perform(pushAction, completion: completion) + } + + public init( + screen: @escaping (GroupInfo) -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingCodeNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingCodeNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..359dc109c0988272e2f12fda44e00b995249873e --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingCodeNavigator.swift @@ -0,0 +1,23 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingCodeNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: (Bool, String, String) -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingCode, completion: @escaping () -> Void) { + let controller = screen(action.isEmail, action.content, action.confirmationId) + let pushAction = Push(controller, on: navigationController(), animated: action.animated) + navigator.perform(pushAction, completion: completion) + } + + public init( + screen: @escaping (Bool, String, String) -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingEmailNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingEmailNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..190941d857a7e8e917793cc8ebbd3e8f43fbb7eb --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingEmailNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingEmailNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingEmail, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingPhoneNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingPhoneNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..674a2ad3fcf41f635a749838a95a2cc76ef42b09 --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingPhoneNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingPhoneNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingPhone, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingStartNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingStartNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..050b67906daa1308a768f170d681006a580a0e55 --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingStartNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingStartNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingStart, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingUsernameNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingUsernameNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..964ee10098af06d6c43daf2ad38916dc0727bc3d --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingUsernameNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingUsernameNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingUsername, completion: @escaping () -> Void) { + let pushAction = Push(screen(), on: navigationController(), animated: action.animated) + navigator.perform(pushAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentOnboardingWelcomeNavigator.swift b/Sources/XXNavigation/Navigators/PresentOnboardingWelcomeNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..e5fc1ed453a2da56e468e21a09aab68e5691ddea --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentOnboardingWelcomeNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentOnboardingWelcomeNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentOnboardingWelcome, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentRequestsNavigator.swift b/Sources/XXNavigation/Navigators/PresentRequestsNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..c39d439b5e3ef13fb5cc8179b48353b3e14943d6 --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentRequestsNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentRequestsNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentRequests, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen()], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentRestoreListNavigator.swift b/Sources/XXNavigation/Navigators/PresentRestoreListNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..0e9a783a9eb3f21b5fb61653208fc740bf952b35 --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentRestoreListNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentRestoreListNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentRestoreList, completion: @escaping () -> Void) { + let pushAction = Push(screen(), on: navigationController(), animated: action.animated) + navigator.perform(pushAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentSearchNavigator.swift b/Sources/XXNavigation/Navigators/PresentSearchNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..d4ce74c1aa5c4459da7e77cf01b532656b4c6b2b --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentSearchNavigator.swift @@ -0,0 +1,22 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentSearchNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: (String?) -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentSearch, completion: @escaping () -> Void) { + let setStackAction = SetStack([screen(action.searching)], on: navigationController(), animated: action.animated) + navigator.perform(setStackAction, completion: completion) + } + + public init( + screen: @escaping (String?) -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +} diff --git a/Sources/XXNavigation/Navigators/PresentTermsAndConditionsNavigator.swift b/Sources/XXNavigation/Navigators/PresentTermsAndConditionsNavigator.swift new file mode 100644 index 0000000000000000000000000000000000000000..61ad413e57ea6a92a3e2712a8909e0bd7af217a1 --- /dev/null +++ b/Sources/XXNavigation/Navigators/PresentTermsAndConditionsNavigator.swift @@ -0,0 +1,27 @@ +import UIKit +import Navigation +import DependencyInjection + +public struct PresentTermsAndConditionsNavigator: TypedNavigator { + @Dependency var navigator: Navigator + var screen: () -> UIViewController + var navigationController: () -> UINavigationController + + public func perform(_ action: PresentTermsAndConditions, completion: @escaping () -> Void) { + let navAction: Action + if action.popAllowed { + navAction = Push(screen(), on: navigationController(), animated: action.animated) + } else { + navAction = SetStack([screen()], on: navigationController(), animated: action.animated) + } + navigator.perform(navAction, completion: completion) + } + + public init( + screen: @escaping () -> UIViewController, + navigationController: @escaping () -> UINavigationController + ) { + self.screen = screen + self.navigationController = navigationController + } +}