Skip to content
Snippets Groups Projects
Commit fc508cfd authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Merge branch 'sftp' into 'development'

Adding SFTP as a service to backup/restore

See merge request elixxir/client-ios!42
parents 97938119 16774fd2
No related branches found
No related tags found
2 merge requests!54Releasing 1.1.4,!42Adding SFTP as a service to backup/restore
Showing
with 322 additions and 83 deletions
...@@ -27,6 +27,7 @@ let package = Package( ...@@ -27,6 +27,7 @@ let package = Package(
.library(name: "Integration", targets: ["Integration"]), .library(name: "Integration", targets: ["Integration"]),
.library(name: "ChatFeature", targets: ["ChatFeature"]), .library(name: "ChatFeature", targets: ["ChatFeature"]),
.library(name: "PushFeature", targets: ["PushFeature"]), .library(name: "PushFeature", targets: ["PushFeature"]),
.library(name: "SFTPFeature", targets: ["SFTPFeature"]),
.library(name: "CrashService", targets: ["CrashService"]), .library(name: "CrashService", targets: ["CrashService"]),
.library(name: "Presentation", targets: ["Presentation"]), .library(name: "Presentation", targets: ["Presentation"]),
.library(name: "BackupFeature", targets: ["BackupFeature"]), .library(name: "BackupFeature", targets: ["BackupFeature"]),
...@@ -68,6 +69,7 @@ let package = Package( ...@@ -68,6 +69,7 @@ let package = Package(
.package(url: "https://github.com/google/google-api-objectivec-client-for-rest", from: "1.6.0"), .package(url: "https://github.com/google/google-api-objectivec-client-for-rest", from: "1.6.0"),
.package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.5")), .package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.5")),
.package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")), .package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")),
.package(url: "https://github.com/darrarski/Shout.git", revision: "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.0")) .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.0"))
], ],
targets: [ targets: [
...@@ -81,6 +83,7 @@ let package = Package( ...@@ -81,6 +83,7 @@ let package = Package(
"ChatFeature", "ChatFeature",
"MenuFeature", "MenuFeature",
"PushFeature", "PushFeature",
"SFTPFeature",
"ToastFeature", "ToastFeature",
"CrashService", "CrashService",
"BackupFeature", "BackupFeature",
...@@ -208,6 +211,23 @@ let package = Package( ...@@ -208,6 +211,23 @@ let package = Package(
] ]
), ),
// MARK: - SFTPFeature
.target(
name: "SFTPFeature",
dependencies: [
"HUD",
"Shared",
"Keychain",
"InputField",
"DependencyInjection",
.product(
name: "Shout",
package: "Shout"
)
]
),
// MARK: - GoogleDriveFeature // MARK: - GoogleDriveFeature
.target( .target(
...@@ -398,6 +418,7 @@ let package = Package( ...@@ -398,6 +418,7 @@ let package = Package(
dependencies: [ dependencies: [
"HUD", "HUD",
"Shared", "Shared",
"SFTPFeature",
"Integration", "Integration",
"Presentation", "Presentation",
"iCloudFeature", "iCloudFeature",
...@@ -612,6 +633,7 @@ let package = Package( ...@@ -612,6 +633,7 @@ let package = Package(
"Shared", "Shared",
"Models", "Models",
"InputField", "InputField",
"SFTPFeature",
"Presentation", "Presentation",
"iCloudFeature", "iCloudFeature",
"DrawerFeature", "DrawerFeature",
......
import UIKit import UIKit
import BackgroundTasks import BackgroundTasks
import XXModels
import Theme import Theme
import XXModels
import XXLogger import XXLogger
import Defaults import Defaults
import Integration import Integration
......
...@@ -18,6 +18,7 @@ import Voxophone ...@@ -18,6 +18,7 @@ import Voxophone
import Integration import Integration
import Permissions import Permissions
import PushFeature import PushFeature
import SFTPFeature
import CrashService import CrashService
import ToastFeature import ToastFeature
import iCloudFeature import iCloudFeature
...@@ -63,6 +64,7 @@ struct DependencyRegistrator { ...@@ -63,6 +64,7 @@ struct DependencyRegistrator {
/// Restore / Backup /// Restore / Backup
container.register(SFTPService.mock)
container.register(iCloudServiceMock() as iCloudInterface) container.register(iCloudServiceMock() as iCloudInterface)
container.register(DropboxServiceMock() as DropboxInterface) container.register(DropboxServiceMock() as DropboxInterface)
container.register(GoogleDriveServiceMock() as GoogleDriveInterface) container.register(GoogleDriveServiceMock() as GoogleDriveInterface)
...@@ -86,6 +88,7 @@ struct DependencyRegistrator { ...@@ -86,6 +88,7 @@ struct DependencyRegistrator {
/// Restore / Backup /// Restore / Backup
container.register(SFTPService.live)
container.register(iCloudService() as iCloudInterface) container.register(iCloudService() as iCloudInterface)
container.register(DropboxService() as DropboxInterface) container.register(DropboxService() as DropboxInterface)
container.register(GoogleDriveService() as GoogleDriveInterface) container.register(GoogleDriveService() as GoogleDriveInterface)
......
...@@ -105,6 +105,11 @@ final class BackupConfigController: UIViewController { ...@@ -105,6 +105,11 @@ final class BackupConfigController: UIViewController {
.sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) } .sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
.store(in: &cancellables) .store(in: &cancellables)
screenView.sftpButton.switcherView
.publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(self, .sftp, screenView.sftpButton.switcherView.isOn) }
.store(in: &cancellables)
screenView.iCloudButton.switcherView screenView.iCloudButton.switcherView
.publisher(for: .valueChanged) .publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) } .sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
...@@ -115,6 +120,11 @@ final class BackupConfigController: UIViewController { ...@@ -115,6 +120,11 @@ final class BackupConfigController: UIViewController {
.sink { [unowned self] in viewModel.didTapService(.dropbox, self) } .sink { [unowned self] in viewModel.didTapService(.dropbox, self) }
.store(in: &cancellables) .store(in: &cancellables)
screenView.sftpButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.sftp, self) }
.store(in: &cancellables)
screenView.iCloudButton screenView.iCloudButton
.publisher(for: .touchUpInside) .publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.icloud, self) } .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
...@@ -128,16 +138,17 @@ final class BackupConfigController: UIViewController { ...@@ -128,16 +138,17 @@ final class BackupConfigController: UIViewController {
case .none: case .none:
break break
case .icloud: case .icloud:
serviceName = "iCloud" serviceName = Localized.Backup.iCloud
button = screenView.iCloudButton button = screenView.iCloudButton
case .dropbox: case .dropbox:
serviceName = "Dropbox" serviceName = Localized.Backup.dropbox
button = screenView.dropboxButton button = screenView.dropboxButton
case .drive: case .drive:
serviceName = "Google Drive" serviceName = Localized.Backup.googleDrive
button = screenView.googleDriveButton button = screenView.googleDriveButton
case .sftp:
serviceName = Localized.Backup.sftp
button = screenView.sftpButton
} }
screenView.enabledSubtitleLabel.text screenView.enabledSubtitleLabel.text
...@@ -146,10 +157,12 @@ final class BackupConfigController: UIViewController { ...@@ -146,10 +157,12 @@ final class BackupConfigController: UIViewController {
= Localized.Backup.Config.frequency(serviceName).uppercased() = Localized.Backup.Config.frequency(serviceName).uppercased()
guard let button = button else { guard let button = button else {
screenView.sftpButton.isHidden = false
screenView.iCloudButton.isHidden = false screenView.iCloudButton.isHidden = false
screenView.dropboxButton.isHidden = false screenView.dropboxButton.isHidden = false
screenView.googleDriveButton.isHidden = false screenView.googleDriveButton.isHidden = false
screenView.sftpButton.switcherView.isOn = false
screenView.iCloudButton.switcherView.isOn = false screenView.iCloudButton.switcherView.isOn = false
screenView.dropboxButton.switcherView.isOn = false screenView.dropboxButton.switcherView.isOn = false
screenView.googleDriveButton.switcherView.isOn = false screenView.googleDriveButton.switcherView.isOn = false
...@@ -166,8 +179,10 @@ final class BackupConfigController: UIViewController { ...@@ -166,8 +179,10 @@ final class BackupConfigController: UIViewController {
screenView.latestBackupDetailView.isHidden = false screenView.latestBackupDetailView.isHidden = false
screenView.infrastructureDetailView.isHidden = false screenView.infrastructureDetailView.isHidden = false
[screenView.iCloudButton, screenView.dropboxButton, screenView.googleDriveButton] [screenView.iCloudButton,
.forEach { screenView.dropboxButton,
screenView.googleDriveButton,
screenView.sftpButton].forEach {
$0.isHidden = $0 != button $0.isHidden = $0 != button
$0.switcherView.isOn = $0 == button $0.switcherView.isOn = $0 == button
} }
...@@ -191,6 +206,12 @@ final class BackupConfigController: UIViewController { ...@@ -191,6 +206,12 @@ final class BackupConfigController: UIViewController {
} else { } else {
screenView.googleDriveButton.showChevron() screenView.googleDriveButton.showChevron()
} }
if connectedServices.contains(.sftp) {
screenView.sftpButton.showSwitcher(enabled: false)
} else {
screenView.sftpButton.showChevron()
}
} }
private func presentInfrastructureDrawer(wifiOnly: Bool) { private func presentInfrastructureDrawer(wifiOnly: Bool) {
......
...@@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController { ...@@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController {
.publisher(for: .touchUpInside) .publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.icloud, self) } .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
.store(in: &cancellables) .store(in: &cancellables)
screenView.sftpButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.sftp, self) }
.store(in: &cancellables)
} }
} }
...@@ -18,16 +18,10 @@ public protocol BackupCoordinating { ...@@ -18,16 +18,10 @@ public protocol BackupCoordinating {
public struct BackupCoordinator: BackupCoordinating { public struct BackupCoordinator: BackupCoordinating {
var bottomPresenter: Presenting = BottomPresenter() var bottomPresenter: Presenting = BottomPresenter()
var passphraseFactory: ( var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
public init( public init(
passphraseFactory: @escaping ( passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
) { ) {
self.passphraseFactory = passphraseFactory self.passphraseFactory = passphraseFactory
} }
......
...@@ -2,6 +2,8 @@ import UIKit ...@@ -2,6 +2,8 @@ import UIKit
import Models import Models
import Combine import Combine
import Defaults import Defaults
import Keychain
import SFTPFeature
import iCloudFeature import iCloudFeature
import DropboxFeature import DropboxFeature
import NetworkMonitor import NetworkMonitor
...@@ -9,10 +11,12 @@ import GoogleDriveFeature ...@@ -9,10 +11,12 @@ import GoogleDriveFeature
import DependencyInjection import DependencyInjection
public final class BackupService { public final class BackupService {
@Dependency private var sftpService: SFTPService
@Dependency private var icloudService: iCloudInterface @Dependency private var icloudService: iCloudInterface
@Dependency private var dropboxService: DropboxInterface @Dependency private var dropboxService: DropboxInterface
@Dependency private var driveService: GoogleDriveInterface
@Dependency private var networkManager: NetworkMonitoring @Dependency private var networkManager: NetworkMonitoring
@Dependency private var keychainHandler: KeychainHandling
@Dependency private var driveService: GoogleDriveInterface
@KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data @KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data
...@@ -149,6 +153,15 @@ extension BackupService { ...@@ -149,6 +153,15 @@ extension BackupService {
self.refreshBackups() self.refreshBackups()
}.store(in: &cancellables) }.store(in: &cancellables)
} }
case .sftp:
if !sftpService.isAuthorized() {
sftpService.authorizeFlow((screen, { [weak self] in
guard let self = self else { return }
screen.navigationController?.popViewController(animated: true)
self.refreshConnections()
self.refreshBackups()
}))
}
} }
} }
} }
...@@ -167,6 +180,12 @@ extension BackupService { ...@@ -167,6 +180,12 @@ extension BackupService {
settings.value.connectedServices.remove(.dropbox) settings.value.connectedServices.remove(.dropbox)
} }
if sftpService.isAuthorized() && !settings.value.connectedServices.contains(.sftp) {
settings.value.connectedServices.insert(.sftp)
} else if !sftpService.isAuthorized() && settings.value.connectedServices.contains(.sftp) {
settings.value.connectedServices.remove(.sftp)
}
driveService.isAuthorized { [weak settings] isAuthorized in driveService.isAuthorized { [weak settings] isAuthorized in
guard let settings = settings else { return } guard let settings = settings else { return }
...@@ -196,6 +215,23 @@ extension BackupService { ...@@ -196,6 +215,23 @@ extension BackupService {
} }
} }
if sftpService.isAuthorized() {
sftpService.fetchMetadata { [weak settings] in
guard let settings = settings else { return }
guard let metadata = try? $0.get()?.backup else {
settings.value.backups[.sftp] = nil
return
}
settings.value.backups[.sftp] = Backup(
id: metadata.id,
date: metadata.date,
size: metadata.size
)
}
}
if dropboxService.isAuthorized() { if dropboxService.isAuthorized() {
dropboxService.downloadMetadata { [weak settings] in dropboxService.downloadMetadata { [weak settings] in
guard let settings = settings else { return } guard let settings = settings else { return }
...@@ -241,7 +277,7 @@ extension BackupService { ...@@ -241,7 +277,7 @@ extension BackupService {
.appendingPathComponent(UUID().uuidString) .appendingPathComponent(UUID().uuidString)
do { do {
try data.write(to: url) try data.write(to: url, options: .atomic)
} catch { } catch {
print("Couldn't write to temp: \(error.localizedDescription)") print("Couldn't write to temp: \(error.localizedDescription)")
return return
...@@ -260,8 +296,6 @@ extension BackupService { ...@@ -260,8 +296,6 @@ extension BackupService {
case .failure(let error): case .failure(let error):
print(error.localizedDescription) print(error.localizedDescription)
} }
// try? FileManager.default.removeItem(at: url)
} }
case .icloud: case .icloud:
icloudService.uploadBackup(url) { icloudService.uploadBackup(url) {
...@@ -275,8 +309,6 @@ extension BackupService { ...@@ -275,8 +309,6 @@ extension BackupService {
case .failure(let error): case .failure(let error):
print(error.localizedDescription) print(error.localizedDescription)
} }
// try? FileManager.default.removeItem(at: url)
} }
case .dropbox: case .dropbox:
dropboxService.uploadBackup(url) { dropboxService.uploadBackup(url) {
...@@ -290,8 +322,15 @@ extension BackupService { ...@@ -290,8 +322,15 @@ extension BackupService {
case .failure(let error): case .failure(let error):
print(error.localizedDescription) print(error.localizedDescription)
} }
}
// try? FileManager.default.removeItem(at: url) case .sftp:
sftpService.uploadBackup(url: url) {
switch $0 {
case .success(let backup):
self.settings.value.backups[.sftp] = backup
case .failure(let error):
print(error.localizedDescription)
}
} }
} }
} }
......
...@@ -7,6 +7,7 @@ final class BackupConfigView: UIView { ...@@ -7,6 +7,7 @@ final class BackupConfigView: UIView {
let actionView = BackupActionView() let actionView = BackupActionView()
let stackView = UIStackView() let stackView = UIStackView()
let sftpButton = BackupSwitcherButton()
let iCloudButton = BackupSwitcherButton() let iCloudButton = BackupSwitcherButton()
let dropboxButton = BackupSwitcherButton() let dropboxButton = BackupSwitcherButton()
let googleDriveButton = BackupSwitcherButton() let googleDriveButton = BackupSwitcherButton()
...@@ -44,6 +45,9 @@ final class BackupConfigView: UIView { ...@@ -44,6 +45,9 @@ final class BackupConfigView: UIView {
subtitleLabel.numberOfLines = 0 subtitleLabel.numberOfLines = 0
subtitleLabel.attributedText = attString subtitleLabel.attributedText = attString
sftpButton.titleLabel.text = Localized.Backup.sftp
sftpButton.logoImageView.image = Asset.restoreSFTP.image
iCloudButton.titleLabel.text = Localized.Backup.iCloud iCloudButton.titleLabel.text = Localized.Backup.iCloud
iCloudButton.logoImageView.image = Asset.restoreIcloud.image iCloudButton.logoImageView.image = Asset.restoreIcloud.image
...@@ -65,6 +69,7 @@ final class BackupConfigView: UIView { ...@@ -65,6 +69,7 @@ final class BackupConfigView: UIView {
stackView.addArrangedSubview(googleDriveButton) stackView.addArrangedSubview(googleDriveButton)
stackView.addArrangedSubview(iCloudButton) stackView.addArrangedSubview(iCloudButton)
stackView.addArrangedSubview(dropboxButton) stackView.addArrangedSubview(dropboxButton)
stackView.addArrangedSubview(sftpButton)
stackView.addArrangedSubview(enabledSubtitleView) stackView.addArrangedSubview(enabledSubtitleView)
stackView.addArrangedSubview(latestBackupDetailView) stackView.addArrangedSubview(latestBackupDetailView)
stackView.addArrangedSubview(frequencyDetailView) stackView.addArrangedSubview(frequencyDetailView)
...@@ -75,36 +80,36 @@ final class BackupConfigView: UIView { ...@@ -75,36 +80,36 @@ final class BackupConfigView: UIView {
addSubview(actionView) addSubview(actionView)
addSubview(stackView) addSubview(stackView)
titleLabel.snp.makeConstraints { make in titleLabel.snp.makeConstraints {
make.top.equalToSuperview().offset(15) $0.top.equalToSuperview().offset(15)
make.left.equalToSuperview().offset(38) $0.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-41) $0.right.equalToSuperview().offset(-41)
} }
enabledSubtitleLabel.snp.makeConstraints { make in enabledSubtitleLabel.snp.makeConstraints {
make.top.equalToSuperview().offset(-10) $0.top.equalToSuperview().offset(-10)
make.left.equalToSuperview().offset(92) $0.left.equalToSuperview().offset(92)
make.right.equalToSuperview().offset(-48) $0.right.equalToSuperview().offset(-48)
make.bottom.equalToSuperview() $0.bottom.equalToSuperview()
} }
subtitleLabel.snp.makeConstraints { make in subtitleLabel.snp.makeConstraints {
make.top.equalTo(titleLabel.snp.bottom).offset(8) $0.top.equalTo(titleLabel.snp.bottom).offset(8)
make.left.equalToSuperview().offset(38) $0.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-41) $0.right.equalToSuperview().offset(-41)
} }
actionView.snp.makeConstraints { make in actionView.snp.makeConstraints {
make.top.equalTo(subtitleLabel.snp.bottom).offset(15) $0.top.equalTo(subtitleLabel.snp.bottom).offset(15)
make.left.equalToSuperview().offset(38) $0.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-38) $0.right.equalToSuperview().offset(-38)
} }
stackView.snp.makeConstraints { make in stackView.snp.makeConstraints {
make.top.equalTo(actionView.snp.bottom).offset(28) $0.top.equalTo(actionView.snp.bottom).offset(28)
make.left.equalToSuperview() $0.left.equalToSuperview()
make.right.equalToSuperview() $0.right.equalToSuperview()
make.bottom.lessThanOrEqualToSuperview() $0.bottom.lessThanOrEqualToSuperview()
} }
} }
......
...@@ -6,6 +6,7 @@ final class BackupSetupView: UIView { ...@@ -6,6 +6,7 @@ final class BackupSetupView: UIView {
let subtitleLabel = UILabel() let subtitleLabel = UILabel()
let stackView = UIStackView() let stackView = UIStackView()
let sftpButton = BackupSwitcherButton()
let iCloudButton = BackupSwitcherButton() let iCloudButton = BackupSwitcherButton()
let dropboxButton = BackupSwitcherButton() let dropboxButton = BackupSwitcherButton()
let googleDriveButton = BackupSwitcherButton() let googleDriveButton = BackupSwitcherButton()
...@@ -60,32 +61,37 @@ final class BackupSetupView: UIView { ...@@ -60,32 +61,37 @@ final class BackupSetupView: UIView {
googleDriveButton.logoImageView.image = Asset.restoreDrive.image googleDriveButton.logoImageView.image = Asset.restoreDrive.image
googleDriveButton.showChevron() googleDriveButton.showChevron()
sftpButton.titleLabel.text = Localized.Backup.sftp
sftpButton.logoImageView.image = Asset.restoreSFTP.image
sftpButton.showChevron()
stackView.axis = .vertical stackView.axis = .vertical
stackView.addArrangedSubview(googleDriveButton) stackView.addArrangedSubview(googleDriveButton)
stackView.addArrangedSubview(iCloudButton) stackView.addArrangedSubview(iCloudButton)
stackView.addArrangedSubview(dropboxButton) stackView.addArrangedSubview(dropboxButton)
stackView.addArrangedSubview(sftpButton)
addSubview(titleLabel) addSubview(titleLabel)
addSubview(subtitleLabel) addSubview(subtitleLabel)
addSubview(stackView) addSubview(stackView)
titleLabel.snp.makeConstraints { make in titleLabel.snp.makeConstraints {
make.top.equalToSuperview().offset(15) $0.top.equalToSuperview().offset(15)
make.left.equalToSuperview().offset(38) $0.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-41) $0.right.equalToSuperview().offset(-41)
} }
subtitleLabel.snp.makeConstraints { make in subtitleLabel.snp.makeConstraints {
make.top.equalTo(titleLabel.snp.bottom).offset(8) $0.top.equalTo(titleLabel.snp.bottom).offset(8)
make.left.equalToSuperview().offset(38) $0.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-41) $0.right.equalToSuperview().offset(-41)
} }
stackView.snp.makeConstraints { make in stackView.snp.makeConstraints {
make.top.equalTo(subtitleLabel.snp.bottom).offset(28) $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
make.left.equalToSuperview() $0.left.equalToSuperview()
make.right.equalToSuperview() $0.right.equalToSuperview()
make.bottom.lessThanOrEqualToSuperview() $0.bottom.lessThanOrEqualToSuperview()
} }
} }
......
import UIKit
import Shared
import Combine
public final class OutlinedInputField: UIView {
private let stackView = UIStackView()
private let textField = UITextField()
private let placeholderLabel = UILabel()
private let inputContainerView = UIView()
private let secureInputButton = SecureInputButton()
public var textPublisher: AnyPublisher<String, Never> {
textField.textPublisher
}
public init() {
super.init(frame: .zero)
layer.borderWidth = 1.0
layer.cornerRadius = 4.0
layer.masksToBounds = true
layer.borderColor = Asset.neutralWeak.color.cgColor
textField.delegate = self
textField.backgroundColor = .clear
textField.textColor = Asset.neutralDark.color
placeholderLabel.textColor = Asset.neutralWeak.color
placeholderLabel.font = Fonts.Mulish.regular.font(size: 16.0)
secureInputButton.button.addTarget(self, action: #selector(didTapRight), for: .touchUpInside)
inputContainerView.addSubview(placeholderLabel)
inputContainerView.addSubview(textField)
stackView.addArrangedSubview(inputContainerView)
stackView.addArrangedSubview(secureInputButton)
addSubview(stackView)
placeholderLabel.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.lessThanOrEqualToSuperview().offset(-15)
$0.bottom.equalToSuperview().offset(-18)
}
textField.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.equalToSuperview().offset(-15)
$0.bottom.equalToSuperview().offset(-18)
}
stackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
public func setup(title: String, sensitive: Bool = false) {
placeholderLabel.text = title
textField.isSecureTextEntry = sensitive
secureInputButton.isHidden = !sensitive
}
@objc private func didTapRight() {
textField.isSecureTextEntry.toggle()
secureInputButton.setSecure(textField.isSecureTextEntry)
}
}
extension OutlinedInputField: UITextFieldDelegate {
public func textField(
_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
placeholderLabel.alpha = (textField.text! as NSString)
.replacingCharacters(in: range, with: string)
.count > 0 ? 0.0 : 1.0
return true
}
}
import UIKit
import Shared
final class SecureInputButton: UIView {
private(set) var button = UIButton()
private let color = Asset.neutralSecondaryAlternative.color
private lazy var openedImage = Asset.eyeOpen.image.withTintColor(color)
private lazy var closedImage = Asset.eyeClosed.image.withTintColor(color)
init() {
super.init(frame: .zero)
button.setContentCompressionResistancePriority(.required, for: .horizontal)
button.setImage(Asset.eyeClosed.image.withTintColor(color), for: .normal)
addSubview(button)
button.snp.makeConstraints {
$0.top.equalToSuperview()
$0.left.equalToSuperview().offset(10)
$0.right.equalToSuperview().offset(-10)
$0.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
func setSecure(_ bool: Bool) {
button.setImage(bool ? closedImage : openedImage, for: .normal)
}
}
...@@ -89,7 +89,12 @@ public class Client { ...@@ -89,7 +89,12 @@ public class Client {
// } // }
public func addJson(_ string: String) { public func addJson(_ string: String) {
guard let backupManager = backupManager else { return } guard let backupManager = backupManager else {
fatalError()
}
print("^^^ Set params: \(string) to backup")
backupManager.addJson(string) backupManager.addJson(string)
} }
......
...@@ -499,11 +499,11 @@ extension BindingsClient: BindingsInterface { ...@@ -499,11 +499,11 @@ extension BindingsClient: BindingsInterface {
fatalError("Couldn't retrieve cert.") fatalError("Couldn't retrieve cert.")
} }
try! udb!.setAlternative( // try! udb!.setAlternative(
"18.198.117.203:11420".data(using: .utf8), // "18.198.117.203:11420".data(using: .utf8),
cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)), // cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath)) // contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
) // )
guard let error = error else { return udb! } guard let error = error else { return udb! }
throw error.friendly() throw error.friendly()
...@@ -525,11 +525,11 @@ extension BindingsClient: BindingsInterface { ...@@ -525,11 +525,11 @@ extension BindingsClient: BindingsInterface {
fatalError("Couldn't retrieve cert.") fatalError("Couldn't retrieve cert.")
} }
try! udb!.setAlternative( // try! udb!.setAlternative(
"18.198.117.203:11420".data(using: .utf8), // "18.198.117.203:11420".data(using: .utf8),
cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)), // cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath)) // contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
) // )
guard let error = error else { return udb! } guard let error = error else { return udb! }
throw error.friendly() throw error.friendly()
......
...@@ -356,6 +356,11 @@ public final class Session: SessionType { ...@@ -356,6 +356,11 @@ public final class Session: SessionType {
).jsonFormat ).jsonFormat
client.addJson(params) client.addJson(params)
guard username!.isEmpty == false else {
fatalError("Tried to build a backup with my username but an empty string was set to it")
}
backupService.performBackupIfAutomaticIsEnabled() backupService.performBackupIfAutomaticIsEnabled()
} }
...@@ -381,7 +386,6 @@ public final class Session: SessionType { ...@@ -381,7 +386,6 @@ public final class Session: SessionType {
.store(in: &cancellables) .store(in: &cancellables)
client.backup client.backup
.throttle(for: .seconds(5), scheduler: DispatchQueue.main, latest: true)
.sink { [unowned self] in backupService.updateBackup(data: $0) } .sink { [unowned self] in backupService.updateBackup(data: $0) }
.store(in: &cancellables) .store(in: &cancellables)
...@@ -402,7 +406,6 @@ public final class Session: SessionType { ...@@ -402,7 +406,6 @@ public final class Session: SessionType {
if $0 == true { if $0 == true {
guard let passphrase = backupService.passphrase else { guard let passphrase = backupService.passphrase else {
client.resumeBackup() client.resumeBackup()
updateFactsOnBackup()
return return
} }
...@@ -416,10 +419,6 @@ public final class Session: SessionType { ...@@ -416,10 +419,6 @@ public final class Session: SessionType {
} }
.store(in: &cancellables) .store(in: &cancellables)
networkMonitor.statusPublisher
.sink { print($0) }
.store(in: &cancellables)
client.messages client.messages
.sink { [unowned self] in .sink { [unowned self] in
if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first { if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
......
import Foundation import Foundation
import KeychainAccess import KeychainAccess
public enum KeychainSFTP: String {
case pwd
case host
case username
}
public protocol KeychainHandling { public protocol KeychainHandling {
func clear() throws func clear() throws
func getPassword() throws -> Data? func getPassword() throws -> Data?
func store(password pwd: Data) throws func store(password pwd: Data) throws
func get(key: KeychainSFTP) throws -> String?
func store(key: KeychainSFTP, value: String) throws
} }
public struct KeychainHandler: KeychainHandling { public struct KeychainHandler: KeychainHandling {
private let password = "password"
private let keychain: Keychain private let keychain: Keychain
private let password = "password"
public init() { public init() {
self.keychain = Keychain(service: "XXM") self.keychain = Keychain(service: "XXM")
...@@ -26,4 +35,12 @@ public struct KeychainHandler: KeychainHandling { ...@@ -26,4 +35,12 @@ public struct KeychainHandler: KeychainHandling {
public func getPassword() throws -> Data? { public func getPassword() throws -> Data? {
try keychain.getData(password) try keychain.getData(password)
} }
public func get(key: KeychainSFTP) throws -> String? {
try keychain.get(key.rawValue)
}
public func store(key: KeychainSFTP, value: String) throws {
try keychain.set(value, key: key.rawValue)
}
} }
...@@ -6,4 +6,6 @@ public struct MockKeychainHandler: KeychainHandling { ...@@ -6,4 +6,6 @@ public struct MockKeychainHandler: KeychainHandling {
public func clear() throws {} public func clear() throws {}
public func store(password pwd: Data) throws {} public func store(password pwd: Data) throws {}
public func getPassword() throws -> Data? { Data() } public func getPassword() throws -> Data? { Data() }
public func get(key: KeychainSFTP) throws -> String? { nil }
public func store(key: KeychainSFTP, value: String) throws {}
} }
...@@ -5,6 +5,7 @@ import Models ...@@ -5,6 +5,7 @@ import Models
import Combine import Combine
import Defaults import Defaults
import XXModels import XXModels
import Keychain
import Foundation import Foundation
import Integration import Integration
import Permissions import Permissions
...@@ -31,6 +32,7 @@ final class LaunchViewModel { ...@@ -31,6 +32,7 @@ final class LaunchViewModel {
@Dependency private var network: XXNetworking @Dependency private var network: XXNetworking
@Dependency private var versionChecker: VersionChecker @Dependency private var versionChecker: VersionChecker
@Dependency private var dropboxService: DropboxInterface @Dependency private var dropboxService: DropboxInterface
@Dependency private var keychainHandler: KeychainHandling
@Dependency private var permissionHandler: PermissionHandling @Dependency private var permissionHandler: PermissionHandling
@KeyObject(.username, defaultValue: nil) var username: String? @KeyObject(.username, defaultValue: nil) var username: String?
...@@ -90,6 +92,7 @@ final class LaunchViewModel { ...@@ -90,6 +92,7 @@ final class LaunchViewModel {
self.hudSubject.send(.none) self.hudSubject.send(.none)
self.routeSubject.send(.onboarding(ndf)) self.routeSubject.send(.onboarding(ndf))
self.dropboxService.unlink() self.dropboxService.unlink()
try? self.keychainHandler.clear()
return return
} }
...@@ -98,6 +101,7 @@ final class LaunchViewModel { ...@@ -98,6 +101,7 @@ final class LaunchViewModel {
self.hudSubject.send(.none) self.hudSubject.send(.none)
self.routeSubject.send(.onboarding(ndf)) self.routeSubject.send(.onboarding(ndf))
self.dropboxService.unlink() self.dropboxService.unlink()
try? self.keychainHandler.clear()
return return
} }
......
...@@ -2,4 +2,5 @@ public enum CloudService: Equatable, Codable { ...@@ -2,4 +2,5 @@ public enum CloudService: Equatable, Codable {
case drive case drive
case icloud case icloud
case dropbox case dropbox
case sftp
} }
...@@ -32,11 +32,11 @@ public struct MockNetworkMonitor: NetworkMonitoring { ...@@ -32,11 +32,11 @@ public struct MockNetworkMonitor: NetworkMonitoring {
statusRelay.send(status) statusRelay.send(status)
if status == .available { if status == .available {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
simulateOscilation(.internetNotAvailable) simulateOscilation(.internetNotAvailable)
} }
} else if status == .internetNotAvailable { } else if status == .internetNotAvailable {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
simulateOscilation(.available) simulateOscilation(.available)
} }
} }
......
import HUD import HUD
import DrawerFeature
import Theme import Theme
import UIKit import UIKit
import Shared import Shared
import Combine import Combine
import DrawerFeature
import DependencyInjection import DependencyInjection
import ScrollViewController import ScrollViewController
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment