Commit 7bc0562b authored by Bruno Muniz's avatar Bruno Muniz 🍎
Browse files

Merge branch 'development' into 'master'

Releasing 1.1.4

See merge request !54
parents 765f4875 62f1af47
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1340"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CollectionView"
BuildableName = "CollectionView"
BlueprintName = "CollectionView"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CollectionViewTests"
BuildableName = "CollectionViewTests"
BlueprintName = "CollectionViewTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CollectionView"
BuildableName = "CollectionView"
BlueprintName = "CollectionView"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
......@@ -27,6 +27,7 @@ let package = Package(
.library(name: "Integration", targets: ["Integration"]),
.library(name: "ChatFeature", targets: ["ChatFeature"]),
.library(name: "PushFeature", targets: ["PushFeature"]),
.library(name: "SFTPFeature", targets: ["SFTPFeature"]),
.library(name: "CrashService", targets: ["CrashService"]),
.library(name: "Presentation", targets: ["Presentation"]),
.library(name: "BackupFeature", targets: ["BackupFeature"]),
......@@ -34,6 +35,7 @@ let package = Package(
.library(name: "iCloudFeature", targets: ["iCloudFeature"]),
.library(name: "SearchFeature", targets: ["SearchFeature"]),
.library(name: "DrawerFeature", targets: ["DrawerFeature"]),
.library(name: "CollectionView", targets: ["CollectionView"]),
.library(name: "RestoreFeature", targets: ["RestoreFeature"]),
.library(name: "CrashReporting", targets: ["CrashReporting"]),
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
......@@ -66,9 +68,12 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.5.0"),
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.1"),
.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.6")),
.package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.8")),
.package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.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-custom-dump.git", .upToNextMajor(from: "0.5.0")),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", .upToNextMajor(from: "0.3.3")),
],
targets: [
.target(
......@@ -81,6 +86,7 @@ let package = Package(
"ChatFeature",
"MenuFeature",
"PushFeature",
"SFTPFeature",
"ToastFeature",
"CrashService",
"BackupFeature",
......@@ -208,6 +214,25 @@ let package = Package(
]
),
// MARK: - SFTPFeature
.target(
name: "SFTPFeature",
dependencies: [
"HUD",
"Models",
"Shared",
"Keychain",
"InputField",
"Presentation",
"DependencyInjection",
.product(
name: "Shout",
package: "Shout"
)
]
),
// MARK: - GoogleDriveFeature
.target(
......@@ -398,6 +423,7 @@ let package = Package(
dependencies: [
"HUD",
"Shared",
"SFTPFeature",
"Integration",
"Presentation",
"iCloudFeature",
......@@ -612,6 +638,7 @@ let package = Package(
"Shared",
"Models",
"InputField",
"SFTPFeature",
"Presentation",
"iCloudFeature",
"DrawerFeature",
......@@ -835,6 +862,23 @@ let package = Package(
.product(name: "Quick", package: "Quick"),
.product(name: "Nimble", package: "Nimble")
]
)
),
// MARK: - CollectionView
.target(
name: "CollectionView",
dependencies: [
.product(name: "ChatLayout", package: "ChatLayout"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
]
),
.testTarget(
name: "CollectionViewTests",
dependencies: [
.target(name: "CollectionView"),
.product(name: "CustomDump", package: "swift-custom-dump"),
]
),
]
)
import UIKit
import BackgroundTasks
import XXModels
import Theme
import XXModels
import XXLogger
import Defaults
import Integration
......
......@@ -18,6 +18,7 @@ import Voxophone
import Integration
import Permissions
import PushFeature
import SFTPFeature
import CrashService
import ToastFeature
import iCloudFeature
......@@ -63,6 +64,7 @@ struct DependencyRegistrator {
/// Restore / Backup
container.register(SFTPService.mock)
container.register(iCloudServiceMock() as iCloudInterface)
container.register(DropboxServiceMock() as DropboxInterface)
container.register(GoogleDriveServiceMock() as GoogleDriveInterface)
......@@ -86,6 +88,7 @@ struct DependencyRegistrator {
/// Restore / Backup
container.register(SFTPService.live)
container.register(iCloudService() as iCloudInterface)
container.register(DropboxService() as DropboxInterface)
container.register(GoogleDriveService() as GoogleDriveInterface)
......@@ -101,7 +104,7 @@ struct DependencyRegistrator {
// MARK: Isolated
container.register(HUD() as HUDType)
container.register(HUD())
container.register(ThemeController() as ThemeControlling)
container.register(ToastController())
container.register(StatusBarController() as StatusBarStyleControlling)
......@@ -134,6 +137,8 @@ struct DependencyRegistrator {
container.register(
SearchCoordinator(
contactsFactory: ContactListController.init,
requestsFactory: RequestsContainerController.init,
contactFactory: ContactController.init(_:),
countriesFactory: CountryListController.init(_:)
) as SearchCoordinating)
......@@ -185,7 +190,7 @@ struct DependencyRegistrator {
container.register(
RequestsCoordinator(
searchFactory: SearchController.init,
searchFactory: SearchContainerController.init,
contactFactory: ContactController.init(_:),
singleChatFactory: SingleChatController.init(_:),
groupChatFactory: GroupChatController.init(_:),
......@@ -197,7 +202,7 @@ struct DependencyRegistrator {
OnboardingCoordinator(
emailFactory: OnboardingEmailController.init,
phoneFactory: OnboardingPhoneController.init,
searchFactory: SearchController.init,
searchFactory: SearchContainerController.init,
welcomeFactory: OnboardingWelcomeController.init,
chatListFactory: ChatListController.init,
usernameFactory: OnboardingUsernameController.init(_:),
......@@ -211,7 +216,7 @@ struct DependencyRegistrator {
container.register(
ContactListCoordinator(
scanFactory: ScanContainerController.init,
searchFactory: SearchController.init,
searchFactory: SearchContainerController.init,
newGroupFactory: CreateGroupController.init,
requestsFactory: RequestsContainerController.init,
contactFactory: ContactController.init(_:),
......@@ -235,7 +240,7 @@ struct DependencyRegistrator {
container.register(
ChatListCoordinator(
scanFactory: ScanContainerController.init,
searchFactory: SearchController.init,
searchFactory: SearchContainerController.init,
newGroupFactory: CreateGroupController.init,
contactsFactory: ContactListController.init,
contactFactory: ContactController.init(_:),
......
......@@ -105,6 +105,11 @@ final class BackupConfigController: UIViewController {
.sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
.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
.publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
......@@ -115,6 +120,11 @@ final class BackupConfigController: UIViewController {
.sink { [unowned self] in viewModel.didTapService(.dropbox, self) }
.store(in: &cancellables)
screenView.sftpButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.sftp, self) }
.store(in: &cancellables)
screenView.iCloudButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.icloud, self) }
......@@ -128,16 +138,17 @@ final class BackupConfigController: UIViewController {
case .none:
break
case .icloud:
serviceName = "iCloud"
serviceName = Localized.Backup.iCloud
button = screenView.iCloudButton
case .dropbox:
serviceName = "Dropbox"
serviceName = Localized.Backup.dropbox
button = screenView.dropboxButton
case .drive:
serviceName = "Google Drive"
serviceName = Localized.Backup.googleDrive
button = screenView.googleDriveButton
case .sftp:
serviceName = Localized.Backup.sftp
button = screenView.sftpButton
}
screenView.enabledSubtitleLabel.text
......@@ -146,10 +157,12 @@ final class BackupConfigController: UIViewController {
= Localized.Backup.Config.frequency(serviceName).uppercased()
guard let button = button else {
screenView.sftpButton.isHidden = false
screenView.iCloudButton.isHidden = false
screenView.dropboxButton.isHidden = false
screenView.googleDriveButton.isHidden = false
screenView.sftpButton.switcherView.isOn = false
screenView.iCloudButton.switcherView.isOn = false
screenView.dropboxButton.switcherView.isOn = false
screenView.googleDriveButton.switcherView.isOn = false
......@@ -166,11 +179,13 @@ final class BackupConfigController: UIViewController {
screenView.latestBackupDetailView.isHidden = false
screenView.infrastructureDetailView.isHidden = false
[screenView.iCloudButton, screenView.dropboxButton, screenView.googleDriveButton]
.forEach {
$0.isHidden = $0 != button
$0.switcherView.isOn = $0 == button
}
[screenView.iCloudButton,
screenView.dropboxButton,
screenView.googleDriveButton,
screenView.sftpButton].forEach {
$0.isHidden = $0 != button
$0.switcherView.isOn = $0 == button
}
}
private func decorate(connectedServices: Set<CloudService>) {
......@@ -191,6 +206,12 @@ final class BackupConfigController: UIViewController {
} else {
screenView.googleDriveButton.showChevron()
}
if connectedServices.contains(.sftp) {
screenView.sftpButton.showSwitcher(enabled: false)
} else {
screenView.sftpButton.showChevron()
}
}
private func presentInfrastructureDrawer(wifiOnly: Bool) {
......
......@@ -6,7 +6,7 @@ import Combine
import DependencyInjection
public final class BackupController: UIViewController {
@Dependency private var hud: HUDType
@Dependency var hud: HUD
private let viewModel = BackupViewModel.live()
private var cancellables = Set<AnyCancellable>()
......@@ -14,7 +14,7 @@ public final class BackupController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Asset.neutralWhite.color
hud.update(with: .on(nil))
hud.update(with: .on)
setupNavigationBar()
setupBindings()
......
......@@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController {
.publisher(for: .touchUpInside)
.sink { [unowned self] in viewModel.didTapService(.icloud, self) }
.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 {
public struct BackupCoordinator: BackupCoordinating {
var bottomPresenter: Presenting = BottomPresenter()
var passphraseFactory: (
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
public init(
passphraseFactory: @escaping (
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
) {
self.passphraseFactory = passphraseFactory
}
......
......@@ -2,6 +2,8 @@ import UIKit
import Models
import Combine
import Defaults
import Keychain
import SFTPFeature
import iCloudFeature
import DropboxFeature
import NetworkMonitor
......@@ -9,10 +11,12 @@ import GoogleDriveFeature
import DependencyInjection
public final class BackupService {
@Dependency private var sftpService: SFTPService
@Dependency private var icloudService: iCloudInterface
@Dependency private var dropboxService: DropboxInterface
@Dependency private var driveService: GoogleDriveInterface
@Dependency private var networkManager: NetworkMonitoring
@Dependency private var keychainHandler: KeychainHandling
@Dependency private var driveService: GoogleDriveInterface
@KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data
......@@ -149,6 +153,15 @@ extension BackupService {
self.refreshBackups()
}.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 {
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
guard let settings = settings else { return }
......@@ -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() {
dropboxService.downloadMetadata { [weak settings] in
guard let settings = settings else { return }
......@@ -241,7 +277,7 @@ extension BackupService {
.appendingPathComponent(UUID().uuidString)
do {
try data.write(to: url)
try data.write(to: url, options: .atomic)
} catch {
print("Couldn't write to temp: \(error.localizedDescription)")
return
......@@ -260,8 +296,6 @@ extension BackupService {
case .failure(let error):
print(error.localizedDescription)
}
// try? FileManager.default.removeItem(at: url)
}
case .icloud:
icloudService.uploadBackup(url) {
......@@ -275,8 +309,6 @@ extension BackupService {
case .failure(let error):
print(error.localizedDescription)
}
// try? FileManager.default.removeItem(at: url)
}
case .dropbox:
dropboxService.uploadBackup(url) {
......@@ -290,8 +322,15 @@ extension BackupService {
case .failure(let error):
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)
}
}
}
}
......
......@@ -30,7 +30,7 @@ struct BackupConfigViewModel {
extension BackupConfigViewModel {
static func live() -> Self {
class Context {
@Dependency var hud: HUDType
@Dependency var hud: HUD
@Dependency var service: BackupService
@Dependency var coordinator: BackupCoordinating
}
......@@ -40,7 +40,7 @@ extension BackupConfigViewModel {
return .init(
didTapBackupNow: {
context.service.performBackup()
context.hud.update(with: .on(nil))
context.hud.update(with: .on)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
context.hud.update(with: .none)
}
......@@ -57,7 +57,7 @@ extension BackupConfigViewModel {
context.service.toggle(service: service, enabling: false)
}, passphraseClosure: { passphrase in
context.service.passphrase = passphrase
context.hud.update(with: .on("Initializing and securing your backup file will take few seconds, please keep the app open."))
context.hud.update(with: .onTitle("Initializing and securing your backup file will take few seconds, please keep the app open."))
DispatchQueue.global().async {
context.service.toggle(service: service, enabling: enabling)
......
......@@ -7,6 +7,7 @@ final class BackupConfigView: UIView {
let actionView = BackupActionView()
let stackView = UIStackView()
let sftpButton = BackupSwitcherButton()
let iCloudButton = BackupSwitcherButton()
let dropboxButton = BackupSwitcherButton()
let googleDriveButton = BackupSwitcherButton()
......@@ -44,6 +45,9 @@ final class BackupConfigView: UIView {
subtitleLabel.numberOfLines = 0
subtitleLabel.attributedText = attString
sftpButton.titleLabel.text = Localized.Backup.sftp
sftpButton.logoImageView.image = Asset.restoreSFTP.image
iCloudButton.titleLabel.text = Localized.Backup.iCloud
iCloudButton.logoImageView.image = Asset.restoreIcloud.image
......@@ -65,6 +69,7 @@ final class BackupConfigView: UIView {
stackView.addArrangedSubview(googleDriveButton)
stackView.addArrangedSubview(iCloudButton)
stackView.addArrangedSubview(dropboxButton)
stackView.addArrangedSubview(sftpButton)
stackView.addArrangedSubview(enabledSubtitleView)
stackView.addArrangedSubview(latestBackupDetailView)
stackView.addArrangedSubview(frequencyDetailView)
......@@ -75,36 +80,36 @@ final class BackupConfigView: UIView {
addSubview(actionView)
addSubview(stackView)
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(15)
make.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-41)
titleLabel.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(38)
$0.right.equalToSuperview().offset(-41)
}
enabledSubtitleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(-10)
make.left.equalToSuperview().offset(92)
make.right.equalToSuperview().offset(-48)
make.bottom.equalToSuperview()
enabledSubtitleLabel.snp.makeConstraints {
$0.top.equalToSuperview().offset(-10)
$0.left.equalToSuperview().offset(92)
$0.right.equalToSuperview().offset(-48)
$0.bottom.equalToSuperview()
}