Commit fca1754c authored by Ahmed Shehata's avatar Ahmed Shehata
Browse files

Merge branch 'development' into 'master'

ABR with Passphrase

See merge request !15
parents d9b0a1c7 c1d37c34
......@@ -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 = 48;
CURRENT_PROJECT_VERSION = 58;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = S6JDM2WW29;
ENABLE_BITCODE = NO;
......@@ -463,7 +463,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.7;
MARKETING_VERSION = 1.0.8;
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock;
PRODUCT_NAME = "$(TARGET_NAME)";
......@@ -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 = 48;
CURRENT_PROJECT_VERSION = 58;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = S6JDM2WW29;
ENABLE_BITCODE = NO;
......@@ -503,7 +503,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.7;
MARKETING_VERSION = 1.0.8;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = xx.messenger;
PRODUCT_NAME = "$(TARGET_NAME)";
......@@ -522,7 +522,7 @@
CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 58;
DEVELOPMENT_TEAM = S6JDM2WW29;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
......@@ -536,7 +536,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
MARKETING_VERSION = 1.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock.notifications;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
......@@ -553,7 +553,7 @@
CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 58;
DEVELOPMENT_TEAM = S6JDM2WW29;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
......@@ -567,7 +567,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.7;
MARKETING_VERSION = 1.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.notifications;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
......
......@@ -657,6 +657,7 @@ let package = Package(
"HUD",
"Shared",
"Models",
"InputField",
"Presentation",
"GoogleDriveFeature",
"iCloudFeature",
......
......@@ -105,7 +105,9 @@ struct DependencyRegistrator {
// MARK: Coordinators
container.register(BackupCoordinator() as BackupCoordinating)
container.register(BackupCoordinator(
passphraseFactory: BackupPassphraseController.init(_:_:)
) as BackupCoordinating)
container.register(
SearchCoordinator(
......@@ -134,7 +136,8 @@ struct DependencyRegistrator {
RestoreCoordinator(
successFactory: RestoreSuccessController.init,
chatListFactory: ChatListController.init,
restoreFactory: RestoreController.init(_:_:)
restoreFactory: RestoreController.init(_:_:),
passphraseFactory: RestorePassphraseController.init(_:)
) as RestoreCoordinating)
container.register(
......
......@@ -97,17 +97,17 @@ final class BackupConfigController: UIViewController {
screenView.googleDriveButton.switcherView
.publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(.drive, screenView.googleDriveButton.switcherView.isOn) }
.sink { [unowned self] in viewModel.didToggleService(self, .drive, screenView.googleDriveButton.switcherView.isOn) }
.store(in: &cancellables)
screenView.dropboxButton.switcherView
.publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(.dropbox, screenView.dropboxButton.switcherView.isOn) }
.sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
.store(in: &cancellables)
screenView.iCloudButton.switcherView
.publisher(for: .valueChanged)
.sink { [unowned self] in viewModel.didToggleService(.icloud, screenView.iCloudButton.switcherView.isOn) }
.sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
.store(in: &cancellables)
screenView.dropboxButton
......
......@@ -14,7 +14,7 @@ public final class BackupController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Asset.neutralWhite.color
hud.update(with: .on)
hud.update(with: .on(nil))
setupNavigationBar()
setupBindings()
......
import UIKit
import Shared
import Combine
import InputField
import ScrollViewController
public final class BackupPassphraseController: UIViewController {
lazy private var screenView = BackupPassphraseView()
private var passphrase = "" {
didSet {
switch Validator.backupPassphrase.validate(passphrase) {
case .success:
screenView.continueButton.isEnabled = true
case .failure:
screenView.continueButton.isEnabled = false
}
}
}
private let cancelClosure: EmptyClosure
private let stringClosure: StringClosure
private var cancellables = Set<AnyCancellable>()
private let keyboardListener = KeyboardFrameChangeListener(notificationCenter: .default)
public init(
_ cancelClosure: @escaping EmptyClosure,
_ stringClosure: @escaping StringClosure
) {
self.stringClosure = stringClosure
self.cancelClosure = cancelClosure
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { nil }
public override func loadView() {
let view = UIView()
view.addSubview(screenView)
screenView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.right.equalToSuperview()
make.bottom.equalToSuperview().offset(0)
}
self.view = view
}
public override func viewDidLoad() {
super.viewDidLoad()
setupKeyboard()
setupBindings()
screenView.continueButton.isEnabled = false
}
private func setupKeyboard() {
keyboardListener.keyboardFrameWillChange = { [weak self] keyboard in
guard let self = self else { return }
let inset = self.view.frame.height - self.view.convert(keyboard.frame, from: nil).minY
self.screenView.snp.updateConstraints {
$0.bottom.equalToSuperview().offset(-inset)
}
self.view.setNeedsLayout()
UIView.animate(withDuration: keyboard.animationDuration) {
self.view.layoutIfNeeded()
}
}
}
private func setupBindings() {
screenView.inputField.returnPublisher
.sink { [unowned self] in screenView.inputField.endEditing(true) }
.store(in: &cancellables)
screenView.cancelButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in dismiss(animated: true) { self.cancelClosure() }}
.store(in: &cancellables)
screenView.inputField
.textPublisher
.sink { [unowned self] in passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.store(in: &cancellables)
screenView.continueButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in
dismiss(animated: true) {
self.stringClosure(self.passphrase)
}
}.store(in: &cancellables)
}
}
import UIKit
import Shared
import Presentation
public protocol BackupCoordinating {
func toPopup(_: UIViewController, from: UIViewController)
func toPopup(
_: UIViewController,
from: UIViewController
)
func toPassphrase(
from: UIViewController,
cancelClosure: @escaping EmptyClosure,
passphraseClosure: @escaping StringClosure
)
}
public struct BackupCoordinator: BackupCoordinating {
var bottomPresenter: Presenting = BottomPresenter()
public init() {}
var passphraseFactory: (
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
public init(
passphraseFactory: @escaping (
@escaping EmptyClosure,
@escaping StringClosure
) -> UIViewController
) {
self.passphraseFactory = passphraseFactory
}
}
public extension BackupCoordinator {
func toPopup(_ screen: UIViewController, from parent: UIViewController) {
func toPopup(
_ screen: UIViewController,
from parent: UIViewController
) {
bottomPresenter.present(screen, from: parent)
}
func toPassphrase(
from parent: UIViewController,
cancelClosure: @escaping EmptyClosure,
passphraseClosure: @escaping StringClosure
) {
let screen = passphraseFactory(cancelClosure, passphraseClosure)
bottomPresenter.present(screen, from: parent)
}
}
......@@ -16,6 +16,8 @@ public final class BackupService {
@KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data
public var passphrase: String?
public var settingsPublisher: AnyPublisher<BackupSettings, Never> {
settings.handleEvents(receiveSubscription: { [weak self] _ in
guard let self = self else { return }
......
......@@ -16,7 +16,7 @@ struct BackupConfigViewModel {
var didTapBackupNow: () -> Void
var didChooseWifiOnly: (Bool) -> Void
var didChooseAutomatic: (Bool) -> Void
var didToggleService: (CloudService, Bool) -> Void
var didToggleService: (UIViewController, CloudService, Bool) -> Void
var didTapService: (CloudService, UIViewController) -> Void
var wifiOnly: () -> AnyPublisher<Bool, Never>
......@@ -32,6 +32,7 @@ extension BackupConfigViewModel {
class Context {
@Dependency var hud: HUDType
@Dependency var service: BackupService
@Dependency var coordinator: BackupCoordinating
}
let context = Context()
......@@ -39,14 +40,33 @@ extension BackupConfigViewModel {
return .init(
didTapBackupNow: {
context.service.performBackup()
context.hud.update(with: .on)
context.hud.update(with: .on(nil))
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
context.hud.update(with: .none)
}
},
didChooseWifiOnly: context.service.setBackupOnlyOnWifi(_:),
didChooseAutomatic: context.service.setBackupAutomatically(_:),
didToggleService: context.service.toggle,
didToggleService: { controller, service, enabling in
guard enabling == true else {
context.service.toggle(service: service, enabling: enabling)
return
}
context.coordinator.toPassphrase(from: controller, cancelClosure: {
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."))
DispatchQueue.global().async {
context.service.toggle(service: service, enabling: enabling)
DispatchQueue.main.async {
context.hud.update(with: .none)
}
}
})
},
didTapService: context.service.authorize,
wifiOnly: {
context.service.settingsPublisher
......
import UIKit
import Shared
import InputField
final class BackupPassphraseView: UIView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let inputField = InputField()
let stackView = UIStackView()
let continueButton = CapsuleButton()
let cancelButton = CapsuleButton()
init() {
super.init(frame: .zero)
setup()
}
required init?(coder: NSCoder) { nil }
private func setup() {
layer.cornerRadius = 40
backgroundColor = Asset.neutralWhite.color
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
subtitleLabel.numberOfLines = 0
titleLabel.textColor = Asset.neutralActive.color
subtitleLabel.textColor = Asset.neutralActive.color
inputField.setup(
style: .regular,
title: "Passphrase",
placeholder: "* * * * * *",
subtitleColor: Asset.neutralDisabled.color
)
titleLabel.text = "Secure your backup"
titleLabel.textAlignment = .left
titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
subtitleLabel.text = "Please select a password for your backup. If you lose this password, you will not be able to restore your account. Make sure to keep a record somewhere safe. Your password needs to be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number characters"
subtitleLabel.textAlignment = .left
subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
continueButton.setStyle(.brandColored)
continueButton.setTitle("Set password and continue", for: .normal)
cancelButton.setStyle(.seeThrough)
cancelButton.setTitle("Cancel", for: .normal)
stackView.spacing = 20
stackView.axis = .vertical
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(subtitleLabel)
stackView.addArrangedSubview(inputField)
stackView.addArrangedSubview(continueButton)
stackView.addArrangedSubview(cancelButton)
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(60)
make.left.equalToSuperview().offset(50)
make.right.equalToSuperview().offset(-50)
make.bottom.equalToSuperview().offset(-70)
}
}
}
......@@ -94,7 +94,7 @@ final class SingleChatViewModel {
func didSend(image: UIImage) {
guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
hudRelay.send(.on)
hudRelay.send(.on(nil))
session.send(imageData: imageData, to: contact) { [weak self] in
switch $0 {
......
......@@ -204,7 +204,7 @@ final class ChatListViewModel: ChatListViewModelType {
}
do {
hudRelay.send(.on)
hudRelay.send(.on(nil))
try session.leave(group: group)
hudRelay.send(.none)
} catch {
......
......@@ -61,7 +61,7 @@ final class ContactViewModel {
}
func didTapDelete() {
hudRelay.send(.on)
hudRelay.send(.on(nil))
do {
try session.deleteContact(contact)
......@@ -90,7 +90,7 @@ final class ContactViewModel {
}
func didTapResend() {
hudRelay.send(.on)
hudRelay.send(.on(nil))
backgroundScheduler.schedule { [weak self] in
guard let self = self else { return }
......@@ -106,7 +106,7 @@ final class ContactViewModel {
}
func didTapRequest(with nickname: String) {
hudRelay.send(.on)
hudRelay.send(.on(nil))
contact.nickname = nickname
backgroundScheduler.schedule { [weak self] in
......@@ -123,7 +123,7 @@ final class ContactViewModel {
}
func didTapAccept(_ nickname: String) {
hudRelay.send(.on)
hudRelay.send(.on(nil))
contact.nickname = nickname
backgroundScheduler.schedule { [weak self] in
......
......@@ -3,16 +3,12 @@ import Shared
import InputField
final class NickameView: UIView {
// MARK: UI
let title = UILabel()
let icon = UIImageView()
let input = InputField()
let stack = UIStackView()
let save = CapsuleButton()
// MARK: Lifecycle
init() {
super.init(frame: .zero)
setup()
......@@ -31,8 +27,6 @@ final class NickameView: UIView {
}
}
// MARK: Private
private func setup() {
layer.cornerRadius = 40
backgroundColor = Asset.neutralWhite.color
......
......@@ -69,7 +69,7 @@ final class CreateGroupViewModel {
}
func create(name: String, welcome: String?, members: [Contact]) {
hudRelay.send(.on)
hudRelay.send(.on(nil))
session.createGroup(name: name, welcome: welcome, members: members) { [weak self] in
guard let self = self else { return }
......
import UIKit
final class DotAnimation: UIView {
// MARK: UI
let leftDot = UIView()
let middleDot = UIView()
let rightDot = UIView()
// MARK: Properties
var leftInvert = false
var middleInvert = false
var rightInvert = false
......@@ -19,8 +15,6 @@ final class DotAnimation: UIView {
var displayLink: CADisplayLink?
// MARK: Lifecycle
init() {
super.init(frame: .zero)
setup()
......@@ -28,8 +22,6 @@ final class DotAnimation: UIView {
required init?(coder: NSCoder) { nil }
// MARK: Public
func setColor(
_ color: UIColor = UIColor(
red: 0,
......@@ -43,8 +35,6 @@ final class DotAnimation: UIView {
rightDot.backgroundColor = color
}
// MARK: Private
private func setup() {
setupCornerRadius()
setColor()
......@@ -86,8 +76,6 @@ final class DotAnimation: UIView {
}
}
// MARK: Selectors
@objc private func handleAnimations() {
let factor: CGFloat = 70
......
......@@ -3,24 +3,17 @@ import Shared
import SnapKit
final class ErrorView: UIView {
// MARK: UI
let title = UILabel()
let content = UILabel()
let stack = UIStackView()
let button = CapsuleButton()
// MARK: Lifecycle
init(with model: HUDError) {
super.init(frame: .zero)
setup(with: model)
}
required init?(coder: NSCoder) { nil }
// MARK: Private
private func setup(with model: HUDError) {
layer.cornerRadius = 6
......
......@@ -10,7 +10,7 @@ private enum Constants {
}
public enum HUDStatus: Equatable {
case on
case on(String?)
case none
case error(HUDError)