From f9330de58772e1fda49d15ca8f34fc72a79f22fb Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Fri, 28 Oct 2022 00:42:50 -0300
Subject: [PATCH] finished integrating xxm cloud providers

---
 App/client-ios.xcodeproj/project.pbxproj      |   8 +-
 Sources/App/AppDelegate.swift                 |   2 +
 Sources/App/DependencyRegistrator.swift       | 446 +++++++++---------
 .../Controllers/BackupConfigController.swift  |   5 +-
 .../Controllers/BackupSFTPController.swift    |  88 ++++
 .../BackupFeature/Models/BackupSettings.swift |  48 --
 .../BackupFeature/Service/BackupService.swift | 309 ++++++------
 .../ViewModels/BackupConfigViewModel.swift    |  14 +-
 .../ViewModels/BackupSFTPViewModel.swift      | 102 ++++
 .../ViewModels/BackupSetupViewModel.swift     |   3 +-
 .../BackupFeature/Views/RestoreSFTPView.swift |  83 ++++
 Sources/LaunchFeature/LaunchViewModel.swift   |  44 +-
 .../ViewModels/ProfileCodeViewModel.swift     |   2 +-
 .../ViewModels/ProfileViewModel.swift         |   2 +-
 .../RestoreFeature/Utils/PlistSecrets.swift   |  48 --
 .../ViewModels/RestoreListViewModel.swift     |  49 +-
 .../ViewModels/RestoreSFTPViewModel.swift     |   3 +-
 .../ViewModels/RestoreViewModel.swift         |   5 +-
 .../RestoreFeature/Views/RestoreView.swift    |   7 +-
 19 files changed, 699 insertions(+), 569 deletions(-)
 create mode 100644 Sources/BackupFeature/Controllers/BackupSFTPController.swift
 delete mode 100644 Sources/BackupFeature/Models/BackupSettings.swift
 create mode 100644 Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
 create mode 100644 Sources/BackupFeature/Views/RestoreSFTPView.swift
 delete mode 100644 Sources/RestoreFeature/Utils/PlistSecrets.swift

diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj
index 5a37a1fd..87ff542b 100644
--- a/App/client-ios.xcodeproj/project.pbxproj
+++ b/App/client-ios.xcodeproj/project.pbxproj
@@ -448,7 +448,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 288;
+				CURRENT_PROJECT_VERSION = 289;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -487,7 +487,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 288;
+				CURRENT_PROJECT_VERSION = 289;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -522,7 +522,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 288;
+				CURRENT_PROJECT_VERSION = 289;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -553,7 +553,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 288;
+				CURRENT_PROJECT_VERSION = 289;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index 9b7e3844..c4d76fde 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -15,6 +15,8 @@ import XXClient
 import XXMessengerClient
 
 import CloudFiles
+import CloudFilesDrive
+import CloudFilesICloud
 import CloudFilesDropbox
 
 public class AppDelegate: UIResponder, UIApplicationDelegate {
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index b15fe648..5fe20189 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -43,233 +43,233 @@ import RequestsFeature
 import OnboardingFeature
 import ContactListFeature
 
-import KeychainAccess
 import XXClient
+import KeychainAccess
 
 struct DependencyRegistrator {
-    static private let container = DependencyInjection.Container.shared
-
-    // MARK: MOCK
-
-    static func registerForMock() {
-        container.register(XXLogger.noop)
-        container.register(CrashReporter.noop)
-        container.register(VersionChecker.mock)
-        container.register(ReportingStatus.mock())
-        container.register(SendReport.mock())
-        container.register(MockNetworkMonitor() as NetworkMonitoring)
-        container.register(KeyObjectStore.userDefaults)
-        container.register(MockPushHandler() as PushHandling)
-        container.register(MockKeychainHandler() as KeychainHandling)
-        container.register(MockPermissionHandler() as PermissionHandling)
-
-        registerCommonDependencies()
-    }
-
-    // MARK: LIVE
-
-    static func registerForLive() {
-        let cMixManager = CMixManager.live(passwordStorage: .keychain)
-        container.register(cMixManager)
-
-        container.register(GetIdFromContact.live)
-        container.register(GetFactsFromContact.live)
-
-        container.register(KeyObjectStore.userDefaults)
-        container.register(XXLogger.live())
-        container.register(CrashReporter.live)
-        container.register(VersionChecker.live())
-        container.register(ReportingStatus.live())
-        container.register(SendReport.live)
-
-        container.register(NetworkMonitor() as NetworkMonitoring)
-        container.register(PushHandler() as PushHandling)
-        container.register(KeychainHandler() as KeychainHandling)
-        container.register(PermissionHandler() as PermissionHandling)
-
-        registerCommonDependencies()
-    }
-
-    // MARK: COMMON
-
-    static private func registerCommonDependencies() {
-        container.register(Voxophone())
-        container.register(BackupService())
-        container.register(MakeAppScreenshot.live)
-        container.register(FetchBannedList.live)
-        container.register(ProcessBannedList.live)
-        container.register(MakeReportDrawer.live)
-
-        // MARK: Isolated
-
-        container.register(HUD())
-        container.register(ThemeController() as ThemeControlling)
-        container.register(ToastController())
-        container.register(StatusBarController() as StatusBarStyleControlling)
-
-        // MARK: Coordinators
-
-        container.register(
-            TermsCoordinator.live(
-                usernameFactory: OnboardingUsernameController.init,
-                chatListFactory: ChatListController.init
-            )
-        )
-
-        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(_:),
-                passphraseFactory: BackupPassphraseController.init(_:_:)
-            ) as BackupCoordinating)
-
-        container.register(
-            MenuCoordinator(
-                scanFactory: ScanContainerController.init,
-                chatsFactory: ChatListController.init,
-                profileFactory: ProfileController.init,
-                settingsFactory: SettingsController.init,
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init
-            ) as MenuCoordinating)
-
-        container.register(
-            SearchCoordinator(
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                countriesFactory: CountryListController.init(_:)
-            ) as SearchCoordinating)
-
-        container.register(
-            ProfileCoordinator(
-                emailFactory: ProfileEmailController.init,
-                phoneFactory: ProfilePhoneController.init,
-                imagePickerFactory: UIImagePickerController.init,
-                permissionFactory: RequestPermissionController.init,
-                sideMenuFactory: MenuController.init(_:_:),
-                countriesFactory: CountryListController.init(_:),
-                codeFactory: ProfileCodeController.init(_:_:)
-            ) as ProfileCoordinating)
-
-        container.register(
-            SettingsCoordinator(
-                backupFactory: BackupController.init,
-                advancedFactory: SettingsAdvancedController.init,
-                accountDeleteFactory: AccountDeleteController.init,
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as SettingsCoordinating)
-
-        container.register(
-            RestoreCoordinator(
-                successFactory: RestoreSuccessController.init,
-                chatListFactory: ChatListController.init,
-                restoreFactory: RestoreController.init(_:),
-                sftpFactory: RestoreSFTPController.init(_:),
-                passphraseFactory: RestorePassphraseController.init(_:_:)
-            ) as RestoreCoordinating)
-
-        container.register(
-            ChatCoordinator(
-                retryFactory: RetrySheetController.init,
-                webFactory: WebScreen.init(url:),
-                previewFactory: QLPreviewController.init,
-                contactFactory: ContactController.init(_:),
-                imagePickerFactory: UIImagePickerController.init,
-                permissionFactory: RequestPermissionController.init
-            ) as ChatCoordinating)
-
-        container.register(
-            ContactCoordinator(
-                requestsFactory: RequestsContainerController.init,
-                singleChatFactory: SingleChatController.init(_:),
-                imagePickerFactory: UIImagePickerController.init,
-                nicknameFactory: NicknameController.init(_:_:)
-            ) as ContactCoordinating)
-
-        container.register(
-            RequestsCoordinator(
-                searchFactory: SearchContainerController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:),
-                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,
-                searchFactory: SearchContainerController.init,
-                newGroupFactory: CreateGroupController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:),
-                groupDrawerFactory: CreateDrawerController.init(_:_:)
-            ) as ContactListCoordinating)
-
-        container.register(
-            ScanCoordinator(
-                emailFactory: ProfileEmailController.init,
-                phoneFactory: ProfilePhoneController.init,
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as ScanCoordinating)
-
-
-        container.register(
-            ChatListCoordinator(
-                scanFactory: ScanContainerController.init,
-                searchFactory: SearchContainerController.init,
-                newGroupFactory: CreateGroupController.init,
-                contactsFactory: ContactListController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as ChatListCoordinating)
-    }
+  static private let container = DependencyInjection.Container.shared
+
+  // MARK: MOCK
+
+  static func registerForMock() {
+    container.register(XXLogger.noop)
+    container.register(CrashReporter.noop)
+    container.register(VersionChecker.mock)
+    container.register(ReportingStatus.mock())
+    container.register(SendReport.mock())
+    container.register(MockNetworkMonitor() as NetworkMonitoring)
+    container.register(KeyObjectStore.userDefaults)
+    container.register(MockPushHandler() as PushHandling)
+    container.register(MockKeychainHandler() as KeychainHandling)
+    container.register(MockPermissionHandler() as PermissionHandling)
+
+    registerCommonDependencies()
+  }
+
+  // MARK: LIVE
+
+  static func registerForLive() {
+    let cMixManager = CMixManager.live(passwordStorage: .keychain)
+    container.register(cMixManager)
+
+    container.register(GetIdFromContact.live)
+    container.register(GetFactsFromContact.live)
+
+    container.register(KeyObjectStore.userDefaults)
+    container.register(XXLogger.live())
+    container.register(CrashReporter.live)
+    container.register(VersionChecker.live())
+    container.register(ReportingStatus.live())
+    container.register(SendReport.live)
+
+    container.register(NetworkMonitor() as NetworkMonitoring)
+    container.register(PushHandler() as PushHandling)
+    container.register(KeychainHandler() as KeychainHandling)
+    container.register(PermissionHandler() as PermissionHandling)
+
+    registerCommonDependencies()
+  }
+
+  // MARK: COMMON
+
+  static private func registerCommonDependencies() {
+    container.register(Voxophone())
+    container.register(BackupService())
+    container.register(MakeAppScreenshot.live)
+    container.register(FetchBannedList.live)
+    container.register(ProcessBannedList.live)
+    container.register(MakeReportDrawer.live)
+
+    // MARK: Isolated
+
+    container.register(HUD())
+    container.register(ThemeController() as ThemeControlling)
+    container.register(ToastController())
+    container.register(StatusBarController() as StatusBarStyleControlling)
+
+    // MARK: Coordinators
+
+    container.register(
+      TermsCoordinator.live(
+        usernameFactory: OnboardingUsernameController.init,
+        chatListFactory: ChatListController.init
+      )
+    )
+
+    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(_:),
+        passphraseFactory: BackupPassphraseController.init(_:_:)
+      ) as BackupCoordinating)
+
+    container.register(
+      MenuCoordinator(
+        scanFactory: ScanContainerController.init,
+        chatsFactory: ChatListController.init,
+        profileFactory: ProfileController.init,
+        settingsFactory: SettingsController.init,
+        contactsFactory: ContactListController.init,
+        requestsFactory: RequestsContainerController.init
+      ) as MenuCoordinating)
+
+    container.register(
+      SearchCoordinator(
+        contactsFactory: ContactListController.init,
+        requestsFactory: RequestsContainerController.init,
+        contactFactory: ContactController.init(_:),
+        countriesFactory: CountryListController.init(_:)
+      ) as SearchCoordinating)
+
+    container.register(
+      ProfileCoordinator(
+        emailFactory: ProfileEmailController.init,
+        phoneFactory: ProfilePhoneController.init,
+        imagePickerFactory: UIImagePickerController.init,
+        permissionFactory: RequestPermissionController.init,
+        sideMenuFactory: MenuController.init(_:_:),
+        countriesFactory: CountryListController.init(_:),
+        codeFactory: ProfileCodeController.init(_:_:)
+      ) as ProfileCoordinating)
+
+    container.register(
+      SettingsCoordinator(
+        backupFactory: BackupController.init,
+        advancedFactory: SettingsAdvancedController.init,
+        accountDeleteFactory: AccountDeleteController.init,
+        sideMenuFactory: MenuController.init(_:_:)
+      ) as SettingsCoordinating)
+
+    container.register(
+      RestoreCoordinator(
+        successFactory: RestoreSuccessController.init,
+        chatListFactory: ChatListController.init,
+        restoreFactory: RestoreController.init(_:),
+        sftpFactory: RestoreSFTPController.init(_:),
+        passphraseFactory: RestorePassphraseController.init(_:_:)
+      ) as RestoreCoordinating)
+
+    container.register(
+      ChatCoordinator(
+        retryFactory: RetrySheetController.init,
+        webFactory: WebScreen.init(url:),
+        previewFactory: QLPreviewController.init,
+        contactFactory: ContactController.init(_:),
+        imagePickerFactory: UIImagePickerController.init,
+        permissionFactory: RequestPermissionController.init
+      ) as ChatCoordinating)
+
+    container.register(
+      ContactCoordinator(
+        requestsFactory: RequestsContainerController.init,
+        singleChatFactory: SingleChatController.init(_:),
+        imagePickerFactory: UIImagePickerController.init,
+        nicknameFactory: NicknameController.init(_:_:)
+      ) as ContactCoordinating)
+
+    container.register(
+      RequestsCoordinator(
+        searchFactory: SearchContainerController.init,
+        contactFactory: ContactController.init(_:),
+        singleChatFactory: SingleChatController.init(_:),
+        groupChatFactory: GroupChatController.init(_:),
+        sideMenuFactory: MenuController.init(_:_:),
+        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,
+        searchFactory: SearchContainerController.init,
+        newGroupFactory: CreateGroupController.init,
+        requestsFactory: RequestsContainerController.init,
+        contactFactory: ContactController.init(_:),
+        singleChatFactory: SingleChatController.init(_:),
+        groupChatFactory: GroupChatController.init(_:),
+        sideMenuFactory: MenuController.init(_:_:),
+        groupDrawerFactory: CreateDrawerController.init(_:_:)
+      ) as ContactListCoordinating)
+
+    container.register(
+      ScanCoordinator(
+        emailFactory: ProfileEmailController.init,
+        phoneFactory: ProfilePhoneController.init,
+        contactsFactory: ContactListController.init,
+        requestsFactory: RequestsContainerController.init,
+        contactFactory: ContactController.init(_:),
+        sideMenuFactory: MenuController.init(_:_:)
+      ) as ScanCoordinating)
+
+
+    container.register(
+      ChatListCoordinator(
+        scanFactory: ScanContainerController.init,
+        searchFactory: SearchContainerController.init,
+        newGroupFactory: CreateGroupController.init,
+        contactsFactory: ContactListController.init,
+        contactFactory: ContactController.init(_:),
+        singleChatFactory: SingleChatController.init(_:),
+        groupChatFactory: GroupChatController.init(_:),
+        sideMenuFactory: MenuController.init(_:_:)
+      ) as ChatListCoordinating)
+  }
 }
 
 extension PasswordStorage {
-    static let keychain: PasswordStorage = {
-        let keychain = KeychainAccess.Keychain(
-            service: "XXM"
-        )
-        return PasswordStorage(
-            save: { password in keychain[data: "password"] = password},
-            load: { try keychain[data: "password"] ?? { throw MissingPasswordError() }() },
-            remove: { try keychain.remove("password") }
-        )
-    }()
+  static let keychain: PasswordStorage = {
+    let keychain = KeychainAccess.Keychain(
+      service: "XXM"
+    )
+    return PasswordStorage(
+      save: { password in keychain[data: "password"] = password},
+      load: { try keychain[data: "password"] ?? { throw MissingPasswordError() }() },
+      remove: { try keychain.remove("password") }
+    )
+  }()
 }
diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift
index 722e5530..f17bdbc8 100644
--- a/Sources/BackupFeature/Controllers/BackupConfigController.swift
+++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift
@@ -2,6 +2,7 @@ import UIKit
 import Models
 import Shared
 import Combine
+import CloudFiles
 import DrawerFeature
 import DependencyInjection
 
@@ -131,7 +132,7 @@ final class BackupConfigController: UIViewController {
             .store(in: &cancellables)
     }
 
-    private func decorate(enabledService: BackupProvider?) {
+    private func decorate(enabledService: CloudService?) {
         var button: BackupSwitcherButton?
 
         switch enabledService {
@@ -188,7 +189,7 @@ final class BackupConfigController: UIViewController {
         }
     }
 
-    private func decorate(connectedServices: Set<BackupProvider>) {
+    private func decorate(connectedServices: Set<CloudService>) {
         if connectedServices.contains(.icloud) {
             screenView.iCloudButton.showSwitcher(enabled: false)
         } else {
diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
new file mode 100644
index 00000000..7c5c9c7c
--- /dev/null
+++ b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
@@ -0,0 +1,88 @@
+import HUD
+import UIKit
+import Combine
+import DependencyInjection
+import ScrollViewController
+
+public final class BackupSFTPController: UIViewController {
+  @Dependency private var hud: HUD
+
+  lazy private var screenView = BackupSFTPView()
+  lazy private var scrollViewController = ScrollViewController()
+
+  private let completion: (String, String, String) -> Void
+  private let viewModel = BackupSFTPViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(_ completion: @escaping (String, String, String) -> Void) {
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
+
+  private func setupScrollView() {
+    scrollViewController.scrollView.backgroundColor = .white
+
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    viewModel.hudPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [hud] in hud.update(with: $0) }
+      .store(in: &cancellables)
+
+    viewModel.authPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] params in
+        completion(params.0, params.1, params.2)
+        navigationController?.popViewController(animated: true)
+      }.store(in: &cancellables)
+
+    screenView.hostField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterHost($0) }
+      .store(in: &cancellables)
+
+    screenView.usernameField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterUsername($0) }
+      .store(in: &cancellables)
+
+    screenView.passwordField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterPassword($0) }
+      .store(in: &cancellables)
+
+    viewModel.statePublisher
+      .receive(on: DispatchQueue.main)
+      .map(\.isButtonEnabled)
+      .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
+      .store(in: &cancellables)
+
+    screenView.loginButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapLogin() }
+      .store(in: &cancellables)
+  }
+}
diff --git a/Sources/BackupFeature/Models/BackupSettings.swift b/Sources/BackupFeature/Models/BackupSettings.swift
deleted file mode 100644
index 5442f301..00000000
--- a/Sources/BackupFeature/Models/BackupSettings.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-import Foundation
-import CloudFiles
-
-public struct BackupSettings: Equatable, Codable {
-  public var wifiOnlyBackup: Bool
-  public var automaticBackups: Bool
-  public var enabledService: BackupProvider?
-  public var connectedServices: Set<BackupProvider>
-  public var backups: [BackupProvider: Fetch.Metadata]
-
-  public init(
-    wifiOnlyBackup: Bool = false,
-    automaticBackups: Bool = false,
-    enabledService: BackupProvider? = nil,
-    connectedServices: Set<BackupProvider> = [],
-    backups: [BackupProvider: Fetch.Metadata] = [:]
-  ) {
-    self.wifiOnlyBackup = wifiOnlyBackup
-    self.automaticBackups = automaticBackups
-    self.enabledService = enabledService
-    self.connectedServices = connectedServices
-    self.backups = backups
-  }
-
-  public func toData() -> Data {
-    (try? PropertyListEncoder().encode(self)) ?? Data()
-  }
-
-  public init(fromData data: Data?) {
-    if let data = data, let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data) {
-      self.init(
-        wifiOnlyBackup: settings.wifiOnlyBackup,
-        automaticBackups: settings.automaticBackups,
-        enabledService: settings.enabledService,
-        connectedServices: settings.connectedServices,
-        backups: settings.backups
-      )
-    } else {
-      self.init(
-        wifiOnlyBackup: false,
-        automaticBackups: true,
-        enabledService: nil,
-        connectedServices: [],
-        backups: [:]
-      )
-    }
-  }
-}
diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index 1599a423..c99e9446 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -3,24 +3,12 @@ import Models
 import Combine
 import XXClient
 import Defaults
-import NetworkMonitor
-import XXMessengerClient
-import DependencyInjection
-
 import CloudFiles
 import CloudFilesSFTP
-import CloudFilesDrive
-import CloudFilesICloud
-import CloudFilesDropbox
-
+import NetworkMonitor
 import KeychainAccess
-
-public enum BackupProvider: Equatable, Codable {
-  case sftp
-  case drive
-  case icloud
-  case dropbox
-}
+import XXMessengerClient
+import DependencyInjection
 
 public final class BackupService {
   @Dependency var messenger: Messenger
@@ -31,17 +19,20 @@ public final class BackupService {
   @KeyObject(.username, defaultValue: nil) var username: String?
   @KeyObject(.backupSettings, defaultValue: nil) var storedSettings: Data?
 
-  public var settingsPublisher: AnyPublisher<BackupSettings, Never> {
+  public var settingsPublisher: AnyPublisher<CloudSettings, Never> {
     settings.handleEvents(receiveSubscription: { [weak self] _ in
       guard let self = self else { return }
-      self.refreshConnections()
-      self.refreshBackups()
+      self.settings.value.connectedServices = CloudFilesManager.all.linkedServices()
+      CloudFilesManager.all.lastBackups { [weak self] in
+        guard let self else { return }
+        self.settings.value.backups = $0
+      }
     }).eraseToAnyPublisher()
   }
 
   private var connType: ConnectionType = .wifi
   private var cancellables = Set<AnyCancellable>()
-  private lazy var settings = CurrentValueSubject<BackupSettings, Never>(.init(fromData: storedSettings))
+  private lazy var settings = CurrentValueSubject<CloudSettings, Never>(.init(fromData: storedSettings))
 
   public init() {
     settings
@@ -56,173 +47,160 @@ public final class BackupService {
       .store(in: &cancellables)
   }
 
-  public func setupSFTP(host: String, username: String, password: String) {
-    managers[.sftp] = .sftp(
-      host: host,
-      username: username,
-      password: password,
-      fileName: "backup.xxm"
-    )
-    refreshBackups()
-    refreshConnections()
+  func didSetWiFiOnly(enabled: Bool) {
+    settings.value.wifiOnlyBackup = enabled
   }
-}
 
-extension BackupService {
-  public func stopBackups() {
-    if messenger.isBackupRunning() == true {
-      try! messenger.stopBackup()
-    }
+  func didSetAutomaticBackup(enabled: Bool) {
+    settings.value.automaticBackups = enabled
+    shouldBackupIfSetAutomatic()
   }
 
-  public func initializeBackup(passphrase: String) {
-    try! messenger.startBackup(
-      password: passphrase,
-      params: .init(
-        username: username!,
-        email: email,
-        phone: phone
-      )
-    )
+  func toggle(service: CloudService, enabling: Bool) {
+    settings.value.enabledService = enabling ? service : nil
   }
 
-  public func performBackupIfAutomaticIsEnabled() {
-    guard settings.value.automaticBackups == true else { return }
-    performBackup()
+  func didForceBackup() {
+    if let lastBackup = try? Data(contentsOf: getBackupURL()) {
+      performUpload(of: lastBackup)
+    }
   }
 
-  public func performBackup() {
-    guard let directoryUrl = try? FileManager.default.url(
-      for: .applicationSupportDirectory,
-      in: .userDomainMask,
-      appropriateFor: nil,
-      create: true
-    ) else { fatalError("Couldn't generate the URL to persist the backup") }
-
-    let fileUrl = directoryUrl
-      .appendingPathComponent("backup")
-      .appendingPathExtension("xxm")
-
-    guard let data = try? Data(contentsOf: fileUrl) else { return }
-    performBackup(data: data)
+  public func didUpdateFacts() {
+    storeFacts()
   }
 
-  public func updateBackup(data: Data) {
-    guard let directoryUrl = try? FileManager.default.url(
-      for: .applicationSupportDirectory,
-      in: .userDomainMask,
-      appropriateFor: nil,
-      create: true
-    ) else { fatalError("Couldn't generate the URL to persist the backup") }
-
-    let fileUrl = directoryUrl
-      .appendingPathComponent("backup")
-      .appendingPathExtension("xxm")
-
+  public func updateLocalBackup(_ data: Data) {
     do {
-      try data.write(to: fileUrl)
+      try data.write(to: getBackupURL())
+      shouldBackupIfSetAutomatic()
     } catch {
       fatalError("Couldn't write backup to fileurl")
     }
+  }
 
-    let isWifiOnly = settings.value.wifiOnlyBackup
-    let isAutomaticEnabled = settings.value.automaticBackups
-    let hasEnabledService = settings.value.enabledService != nil
-
-    if isWifiOnly {
-      guard connType == .wifi else { return }
+  private func shouldBackupIfSetAutomatic() {
+    guard let lastBackup = try? Data(contentsOf: getBackupURL()) else {
+      print(">>> No stored backup so won't upload anything.")
+      return
+    }
+    guard settings.value.automaticBackups else {
+      print(">>> Backups are not set to automatic")
+      return
+    }
+    guard settings.value.enabledService != nil else {
+      print(">>> No service enabled to upload")
+      return
+    }
+    if settings.value.wifiOnlyBackup {
+      guard connType == .wifi else {
+        print(">>> WiFi only backups, and connType != Wifi")
+        return
+      }
     } else {
-      guard connType != .unknown else { return }
+      guard connType != .unknown else {
+        print(">>> Connectivity is unknown")
+        return
+      }
     }
+    performUpload(of: lastBackup)
+  }
 
-    if isAutomaticEnabled && hasEnabledService {
-      performBackup()
+  // MARK: - Messenger
+
+  func initializeBackup(passphrase: String) {
+    do {
+      try messenger.startBackup(
+        password: passphrase,
+        params: .init(
+          username: username!,
+          email: email,
+          phone: phone
+        )
+      )
+    } catch {
+      print(">>> Exception when calling `messenger.startBackup`: \(error.localizedDescription)")
     }
+  }
 
-    refreshBackups()
+  func stopBackups() {
+    if messenger.isBackupRunning() == true {
+      do {
+        try messenger.stopBackup()
+      } catch {
+        print(">>> Exception when calling `messenger.stopBackup`: \(error.localizedDescription)")
+      }
+    }
   }
 
-  public func setBackupOnlyOnWifi(_ enabled: Bool) {
-    settings.value.wifiOnlyBackup = enabled
+  func storeFacts() {
+    var facts: [String: String] = [:]
+    facts["username"] = username!
+    facts["email"] = email
+    facts["phone"] = phone
+    facts["timestamp"] = "\(Date.asTimestamp)"
+    guard let backupManager = messenger.backup.get() else {
+      print(">>> Tried to store facts in JSON but there's no backup manager instance")
+      return
+    }
+    guard let data = try? JSONSerialization.data(withJSONObject: facts) else {
+      print(">>> Tried to generate data with json dictionary but failed")
+      return
+    }
+    guard let string = String(data: data, encoding: .utf8) else {
+      print(">>> Tried to extract string from json dict object but failed")
+      return
+    }
+    backupManager.addJSON(string)
   }
 
-  public func setBackupAutomatically(_ enabled: Bool) {
-    settings.value.automaticBackups = enabled
+  // MARK: - CloudProviders
 
-    guard enabled else { return }
-    performBackup()
-  }
+  func setupSFTP(host: String, username: String, password: String) {
+    let sftpManager = CloudFilesManager.sftp(
+      host: host,
+      username: username,
+      password: password,
+      fileName: "backup.xxm"
+    )
 
-  public func toggle(service: BackupProvider, enabling: Bool) {
-    settings.value.enabledService = enabling ? service : nil
-  }
+    CloudFilesManager.all[.sftp] = sftpManager
 
-  public func authorize(
-    service: BackupProvider,
-    presenting screen: UIViewController
-  ) {
     do {
-      try managers[service]?.link(screen) { [weak self] in
-        guard let self else { return }
+      try sftpManager.fetch {
         switch $0 {
-        case .success:
-          self.refreshConnections()
-          self.refreshBackups()
+        case .success(let metadata):
+          self.settings.value.backups[.sftp] = metadata
         case .failure(let error):
-          print(error.localizedDescription)
+          print(">>> Error fetching sftp: \(error.localizedDescription)")
         }
       }
     } catch {
-      print(error.localizedDescription)
+      print(">>> Exception fetching sftp: \(error.localizedDescription)")
     }
   }
-}
 
-extension BackupService {
-  private func refreshConnections() {
-    managers.forEach { provider, manager in
-      if manager.isLinked() && !settings.value.connectedServices.contains(provider) {
-        settings.value.connectedServices.insert(provider)
-      } else if !manager.isLinked() && settings.value.connectedServices.contains(provider) {
-        settings.value.connectedServices.remove(provider)
-      }
-    }
-  }
-
-  private func refreshBackups() {
-    managers.forEach { provider, manager in
-      if manager.isLinked() {
-        do {
-          try manager.fetch { [weak self] in
-            guard let self else { return }
-
-            switch $0 {
-            case .success(let metadata):
-              self.settings.value.backups[provider] = metadata
-            case .failure(let error):
-              print(error.localizedDescription)
-            }
-          }
-        } catch {
-          print(error.localizedDescription)
+  func authorize(
+    service: CloudService,
+    presenting screen: UIViewController
+  ) {
+    service.authorize(presenting: screen) {
+      switch $0 {
+      case .success:
+        self.settings.value.connectedServices = CloudFilesManager.all.linkedServices()
+        CloudFilesManager.all.lastBackups { [weak self] in
+          guard let self else { return }
+          self.settings.value.backups = $0
         }
+      case .failure(let error):
+        print(">>> Tried to authorize \(service) but failed: \(error.localizedDescription)")
       }
     }
   }
 
-  private func performBackup(data: Data) {
+  func performUpload(of data: Data) {
     guard let enabledService = settings.value.enabledService else {
-      fatalError("Trying to backup but nothing is enabled")
-    }
-
-    let url = URL(fileURLWithPath: NSTemporaryDirectory())
-      .appendingPathComponent(UUID().uuidString)
-
-    do {
-      try data.write(to: url, options: .atomic)
-    } catch {
-      print(">>> Couldn't write to temp: \(error.localizedDescription)")
-      return
+      fatalError(">>> Trying to backup but nothing is enabled")
     }
 
     if enabledService == .sftp {
@@ -230,10 +208,10 @@ extension BackupService {
       guard let host = try? keychain.get("host"),
             let password = try? keychain.get("pwd"),
             let username = try? keychain.get("username") else {
-        fatalError("Tried to perform an sftp backup but its not configured")
+        fatalError(">>> Tried to perform an sftp backup but its not configured")
       }
 
-      managers[.sftp] = .sftp(
+      CloudFilesManager.all[.sftp] = .sftp(
         host: host,
         username: username,
         password: password,
@@ -241,24 +219,29 @@ extension BackupService {
       )
     }
 
-    if let manager = managers[enabledService] {
-      do {
-        try manager.upload(data) { [weak self] in
-          guard let self else { return }
-
-          switch $0 {
-          case .success(let metadata):
-            self.settings.value.backups[enabledService] = .init(
-              size: metadata.size,
-              lastModified: metadata.lastModified
-            )
-          case .failure(let error):
-            print(error.localizedDescription)
-          }
-        }
-      } catch {
-        print(error.localizedDescription)
+    enabledService.backup(data: data) {
+      switch $0 {
+      case .success(let metadata):
+        self.settings.value.backups[enabledService] = .init(
+          size: metadata.size,
+          lastModified: metadata.lastModified
+        )
+      case .failure(let error):
+        print(">>> Failed to perform a backup upload: \(error.localizedDescription)")
       }
     }
   }
+
+  private func getBackupURL() -> URL {
+    guard let folderURL = try? FileManager.default.url(
+      for: .applicationSupportDirectory,
+      in: .userDomainMask,
+      appropriateFor: nil,
+      create: true
+    ) else { fatalError(">>> Couldn't generate the URL for backup") }
+
+    return folderURL
+      .appendingPathComponent("backup")
+      .appendingPathExtension("xxm")
+  }
 }
diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
index ba2ce35e..6d5daa39 100644
--- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
@@ -21,15 +21,15 @@ struct BackupConfigViewModel {
   var didTapBackupNow: () -> Void
   var didChooseWifiOnly: (Bool) -> Void
   var didChooseAutomatic: (Bool) -> Void
-  var didToggleService: (UIViewController, BackupProvider, Bool) -> Void
-  var didTapService: (BackupProvider, UIViewController) -> Void
+  var didToggleService: (UIViewController, CloudService, Bool) -> Void
+  var didTapService: (CloudService, UIViewController) -> Void
 
   var wifiOnly: () -> AnyPublisher<Bool, Never>
   var automatic: () -> AnyPublisher<Bool, Never>
   var lastBackup: () -> AnyPublisher<Fetch.Metadata?, Never>
   var actionState: () -> AnyPublisher<BackupActionState, Never>
-  var enabledService: () -> AnyPublisher<BackupProvider?, Never>
-  var connectedServices: () -> AnyPublisher<Set<BackupProvider>, Never>
+  var enabledService: () -> AnyPublisher<CloudService?, Never>
+  var connectedServices: () -> AnyPublisher<Set<CloudService>, Never>
 }
 
 extension BackupConfigViewModel {
@@ -44,14 +44,14 @@ extension BackupConfigViewModel {
 
     return .init(
       didTapBackupNow: {
-        context.service.performBackup()
+        context.service.didForceBackup()
         context.hud.update(with: .on)
         DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
           context.hud.update(with: .none)
         }
       },
-      didChooseWifiOnly: context.service.setBackupOnlyOnWifi(_:),
-      didChooseAutomatic: context.service.setBackupAutomatically(_:),
+      didChooseWifiOnly: context.service.didSetWiFiOnly(enabled:),
+      didChooseAutomatic: context.service.didSetAutomaticBackup(enabled:),
       didToggleService: { controller, service, enabling in
         guard enabling == true else {
           context.service.toggle(service: service, enabling: false)
diff --git a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
new file mode 100644
index 00000000..9b113c9f
--- /dev/null
+++ b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
@@ -0,0 +1,102 @@
+import UIKit
+
+import HUD
+import Shout
+import Socket
+import Combine
+import Foundation
+import CloudFiles
+import CloudFilesSFTP
+
+struct SFTPViewState {
+  var host: String = ""
+  var username: String = ""
+  var password: String = ""
+  var isButtonEnabled: Bool = false
+}
+
+final class BackupSFTPViewModel {
+  var hudPublisher: AnyPublisher<HUDStatus, Never> {
+    hudSubject.eraseToAnyPublisher()
+  }
+
+  var statePublisher: AnyPublisher<SFTPViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  var authPublisher: AnyPublisher<(String, String, String), Never> {
+    authSubject.eraseToAnyPublisher()
+  }
+
+  private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
+  private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
+  private let authSubject = PassthroughSubject<(String, String, String), Never>()
+
+  func didEnterHost(_ string: String) {
+    stateSubject.value.host = string
+    validate()
+  }
+
+  func didEnterUsername(_ string: String) {
+    stateSubject.value.username = string
+    validate()
+  }
+
+  func didEnterPassword(_ string: String) {
+    stateSubject.value.password = string
+    validate()
+  }
+
+  func didTapLogin() {
+    hudSubject.send(.on)
+
+    let host = stateSubject.value.host
+    let username = stateSubject.value.username
+    let password = stateSubject.value.password
+
+    let anyController = UIViewController()
+
+    DispatchQueue.global().async { [weak self] in
+      guard let self = self else { return }
+      do {
+        try CloudFilesManager.sftp(
+          host: host,
+          username: username,
+          password: password,
+          fileName: "backup.xxm"
+        ).link(anyController) {
+          switch $0 {
+          case .success:
+            self.hudSubject.send(.none)
+            self.authSubject.send((host, username, password))
+          case .failure(let error):
+            var message = "An error occurred while trying to link SFTP: "
+
+            if case let CloudFilesSFTP.SFTP.SFTPError.link(linkError) = error {
+              if let sshError = linkError as? SSHError {
+                message.append(sshError.message)
+              } else if let socketError = linkError as? Socket.Error, let reason = socketError.errorReason {
+                message.append(reason)
+              } else {
+                message.append(error.localizedDescription)
+              }
+            } else {
+              message.append(error.localizedDescription)
+            }
+
+            self.hudSubject.send(.error(.init(content: message)))
+          }
+        }
+      } catch {
+        self.hudSubject.send(.error(.init(with: error)))
+      }
+    }
+  }
+
+  private func validate() {
+    stateSubject.value.isButtonEnabled =
+    !stateSubject.value.host.isEmpty &&
+    !stateSubject.value.username.isEmpty &&
+    !stateSubject.value.password.isEmpty
+  }
+}
diff --git a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
index c504cd0d..1dc8e612 100644
--- a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
@@ -2,10 +2,11 @@ import UIKit
 import Models
 import Shared
 import Combine
+import CloudFiles
 import DependencyInjection
 
 struct BackupSetupViewModel {
-    var didTapService: (BackupProvider, UIViewController) -> Void
+    var didTapService: (CloudService, UIViewController) -> Void
 }
 
 extension BackupSetupViewModel {
diff --git a/Sources/BackupFeature/Views/RestoreSFTPView.swift b/Sources/BackupFeature/Views/RestoreSFTPView.swift
new file mode 100644
index 00000000..dfb4d73a
--- /dev/null
+++ b/Sources/BackupFeature/Views/RestoreSFTPView.swift
@@ -0,0 +1,83 @@
+import UIKit
+import Shared
+import InputField
+
+final class BackupSFTPView: UIView {
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let hostField = OutlinedInputField()
+  let usernameField = OutlinedInputField()
+  let passwordField = OutlinedInputField()
+  let loginButton = CapsuleButton()
+  let stackView = UIStackView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.text = Localized.AccountRestore.Sftp.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSMutableAttributedString(
+      string: Localized.AccountRestore.Sftp.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    attString.setAttributes(
+      attributes: [
+        .foregroundColor: Asset.neutralDark.color,
+        .font: Fonts.Mulish.bold.font(size: 12.0) as Any,
+        .paragraphStyle: paragraph
+      ], betweenCharacters: "*")
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = attString
+
+    hostField.setup(title: Localized.AccountRestore.Sftp.host)
+    usernameField.setup(title: Localized.AccountRestore.Sftp.username)
+    passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
+
+    loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
+
+    stackView.spacing = 30
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(hostField)
+    stackView.addArrangedSubview(usernameField)
+    stackView.addArrangedSubview(passwordField)
+    stackView.addArrangedSubview(loginButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index a207de77..66ba1a1e 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -448,21 +448,21 @@ extension LaunchViewModel {
   }
 
   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)
+    //    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 {
@@ -728,8 +728,9 @@ extension LaunchViewModel {
   private func makeMessenger() -> Messenger {
     var environment: MessengerEnvironment = .live()
     environment.ndfEnvironment = .mainnet
-    environment.udAddress = "46.101.98.49:18001"
-    environment.udCert = """
+    environment.udEnvironment = .init(
+      address: "46.101.98.49:18001",
+      cert: """
             -----BEGIN CERTIFICATE-----
             MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
             BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx
@@ -751,10 +752,11 @@ extension LaunchViewModel {
             tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5
             6m52PyzMNV+2N21IPppKwA==
             -----END CERTIFICATE-----
-            """.data(using: .utf8)!
-    environment.udContact = """
+            """.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)
+      """.data(using: .utf8)!
+    )
 
     return Messenger.live(environment)
   }
@@ -777,7 +779,7 @@ extension LaunchViewModel {
   private func setupBackupCallback(_ messenger: Messenger) {
     backupCallbackCancellable = messenger.registerBackupCallback(.init(handle: { [weak self] in
       print(">>> Backup callback from bindings got called")
-      self?.backupService.updateBackup(data: $0)
+      self?.backupService.updateLocalBackup($0)
     }))
   }
 
diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
index b7564509..40c7abb5 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
@@ -86,7 +86,7 @@ final class ProfileCodeViewModel {
         self.hudRelay.send(.none)
         self.completionRelay.send(self.confirmation)
 
-        self.backupService.performBackup()
+        self.backupService.didUpdateFacts()
       } catch {
         self.hudRelay.send(.error(.init(with: error)))
       }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
index b1bddd42..d700516e 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
@@ -108,7 +108,7 @@ final class ProfileViewModel {
           self.isPhoneSharing = false
         }
 
-        self.backupService.performBackup()
+        self.backupService.didUpdateFacts()
         self.hudRelay.send(.none)
         self.refresh()
       } catch {
diff --git a/Sources/RestoreFeature/Utils/PlistSecrets.swift b/Sources/RestoreFeature/Utils/PlistSecrets.swift
deleted file mode 100644
index a96d25f9..00000000
--- a/Sources/RestoreFeature/Utils/PlistSecrets.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-import Foundation
-
-struct PlistSecrets {
-  struct GooglePlist: Decodable {
-    let apiKey: String
-    let clientId: String
-
-    enum CodingKeys: String, CodingKey {
-      case apiKey = "API_KEY"
-      case clientId = "CLIENT_ID"
-    }
-  }
-
-  struct InfoPlist: Decodable {
-    let dropboxAppKey: String
-
-    enum CodingKeys: String, CodingKey {
-      case dropboxAppKey = "DROPBOX_APP_KEY"
-    }
-  }
-
-  static var googleAPIKey: String {
-    guard let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
-          let data = try? Data(contentsOf: url),
-          let plist = try? PropertyListDecoder().decode(GooglePlist.self, from: data) else {
-      fatalError("Can't decode GoogleService-Info.plist")
-    }
-    return plist.apiKey
-  }
-
-  static var googleClientId: String {
-    guard let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
-          let data = try? Data(contentsOf: url),
-          let plist = try? PropertyListDecoder().decode(GooglePlist.self, from: data) else {
-      fatalError("Can't decode GoogleService-Info.plist")
-    }
-    return plist.clientId
-  }
-
-  static var dropboxAppKey: String {
-    guard let url = Bundle.main.url(forResource: "Info", withExtension: "plist"),
-          let data = try? Data(contentsOf: url),
-          let plist = try? PropertyListDecoder().decode(InfoPlist.self, from: data) else {
-      fatalError("Can't decode info.plist")
-    }
-    return plist.dropboxAppKey
-  }
-}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index ec247ddc..2b992439 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -1,24 +1,12 @@
 import HUD
 import UIKit
 import Combine
-
 import CloudFiles
 import CloudFilesSFTP
-import CloudFilesDrive
-import CloudFilesICloud
-import CloudFilesDropbox
-
 import DependencyInjection
 
-enum RestorationProvider: String, Equatable, Hashable {
-  case sftp
-  case drive
-  case icloud
-  case dropbox
-}
-
 public struct RestorationDetails {
-  var provider: RestorationProvider
+  var provider: CloudService
   var metadata: Fetch.Metadata?
 }
 
@@ -35,27 +23,12 @@ final class RestoreListViewModel {
     detailsSubject.eraseToAnyPublisher()
   }
 
-  private var managers: [RestorationProvider: CloudFilesManager] = [
-    .icloud: .iCloud(
-      fileName: "backup.xxm"
-    ),
-    .dropbox: .dropbox(
-      appKey: PlistSecrets.dropboxAppKey,
-      path: "/backup/backup.xxm"
-    ),
-    .drive: .drive(
-      apiKey: PlistSecrets.googleAPIKey,
-      clientId: PlistSecrets.googleClientId,
-      fileName: "backup.xxm"
-    )
-  ]
-
   private let sftpSubject = PassthroughSubject<Void, Never>()
   private let hudSubject = PassthroughSubject<HUDStatus, Never>()
   private let detailsSubject = PassthroughSubject<RestorationDetails, Never>()
 
   func setupSFTP(host: String, username: String, password: String) {
-    managers[.sftp] = .sftp(
+    CloudFilesManager.all[.sftp] = .sftp(
       host: host,
       username: username,
       password: password,
@@ -65,7 +38,7 @@ final class RestoreListViewModel {
   }
 
   func link(
-    provider: RestorationProvider,
+    provider: CloudService,
     from controller: UIViewController,
     onSuccess: @escaping () -> Void
   ) {
@@ -73,11 +46,8 @@ final class RestoreListViewModel {
       sftpSubject.send(())
       return
     }
-    guard let manager = managers[provider] else {
-      return
-    }
     do {
-      try manager.link(controller) { [weak self] in
+      try CloudFilesManager.all[provider]!.link(controller) { [weak self] in
         guard let self else {return }
 
         switch $0 {
@@ -92,24 +62,19 @@ final class RestoreListViewModel {
     }
   }
 
-  func fetch(provider: RestorationProvider) {
-    guard let manager = managers[provider] else {
-      return
-    }
+  func fetch(provider: CloudService) {
     hudSubject.send(.on)
     do {
-      try manager.fetch { [weak self] in
+      try CloudFilesManager.all[provider]!.fetch { [weak self] in
         guard let self else { return }
 
         switch $0 {
         case .success(let metadata):
-          DependencyInjection.Container.shared.register(manager)
-
+          self.hudSubject.send(.none)
           self.detailsSubject.send(.init(
             provider: provider,
             metadata: metadata
           ))
-          self.hudSubject.send(.none)
         case .failure(let error):
           self.hudSubject.send(.error(.init(with: error)))
         }
diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
index a24baf97..6c509cbb 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
@@ -1,6 +1,5 @@
-import UIKit
-
 import HUD
+import UIKit
 import Combine
 import Foundation
 import CloudFiles
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index 390c756d..98b68d83 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -17,7 +17,7 @@ enum Step {
   case parsingData
   case failDownload(Error)
   case downloading(Float, Float)
-  case idle(RestorationProvider, CloudFiles.Fetch.Metadata?)
+  case idle(CloudService, CloudFiles.Fetch.Metadata?)
 }
 
 extension Step: Equatable {
@@ -39,7 +39,6 @@ extension Step: Equatable {
 final class RestoreViewModel {
   @Dependency var database: Database
   @Dependency var messenger: Messenger
-  @Dependency var manager: CloudFilesManager
 
   @KeyObject(.phone, defaultValue: nil) var phone: String?
   @KeyObject(.email, defaultValue: nil) var email: String?
@@ -77,7 +76,7 @@ final class RestoreViewModel {
     stepSubject.send(.downloading(0.0, metadata.size))
 
     do {
-      try manager.download { [weak self] in
+      try CloudFilesManager.all[details.provider]!.download { [weak self] in
         guard let self else { return }
 
         switch $0 {
diff --git a/Sources/RestoreFeature/Views/RestoreView.swift b/Sources/RestoreFeature/Views/RestoreView.swift
index 5f1648ff..720128d3 100644
--- a/Sources/RestoreFeature/Views/RestoreView.swift
+++ b/Sources/RestoreFeature/Views/RestoreView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import CloudFiles
 
 final class RestoreView: UIView {
   let titleLabel = UILabel()
@@ -104,7 +105,7 @@ final class RestoreView: UIView {
   }
 
   private func displayDetailsFrom(
-    _ provider: RestorationProvider,
+    _ provider: CloudService,
     size: Float,
     lastDate: Date
   ) {
@@ -132,7 +133,7 @@ final class RestoreView: UIView {
     progressView.isHidden = true
   }
 
-  private func missingMetadataFor(_ provider: RestorationProvider) {
+  private func missingMetadataFor(_ provider: CloudService) {
     titleLabel.text = Localized.AccountRestore.NotFound.title
     subtitleLabel.text = Localized.AccountRestore.NotFound.subtitle(provider.name())
 
@@ -144,7 +145,7 @@ final class RestoreView: UIView {
   }
 }
 
-private extension RestorationProvider {
+private extension CloudService {
   func name() -> String {
     switch self {
     case .drive:
-- 
GitLab