Skip to content
Snippets Groups Projects
Commit ac39085f authored by Ahmed Shehata's avatar Ahmed Shehata
Browse files

Merge branch 'ud-after-registration' into 'development'

Using variadic param to navigate straight to UD after registration

See merge request elixxir/client-ios!9
parents 903d38d8 bb0879c1
Branches
Tags
3 merge requests!361.1.1b141,!29Development 2.1 version release,!9Using variadic param to navigate straight to UD after registration
Showing
with 231 additions and 196 deletions
...@@ -170,6 +170,7 @@ struct DependencyRegistrator { ...@@ -170,6 +170,7 @@ struct DependencyRegistrator {
OnboardingCoordinator( OnboardingCoordinator(
emailFactory: OnboardingEmailController.init, emailFactory: OnboardingEmailController.init,
phoneFactory: OnboardingPhoneController.init, phoneFactory: OnboardingPhoneController.init,
searchFactory: SearchController.init,
welcomeFactory: OnboardingWelcomeController.init, welcomeFactory: OnboardingWelcomeController.init,
chatListFactory: ChatListController.init, chatListFactory: ChatListController.init,
startFactory: OnboardingStartController.init(_:), startFactory: OnboardingStartController.init(_:),
......
...@@ -61,11 +61,6 @@ public final class ChatListController: UIViewController { ...@@ -61,11 +61,6 @@ public final class ChatListController: UIViewController {
navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color) navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
} }
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.viewDidAppear()
}
public override func viewDidLoad() { public override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupNavigationBar() setupNavigationBar()
...@@ -143,18 +138,6 @@ public final class ChatListController: UIViewController { ...@@ -143,18 +138,6 @@ public final class ChatListController: UIViewController {
.sink { [unowned self] in coordinator.toScan(from: self) } .sink { [unowned self] in coordinator.toScan(from: self) }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.askDummyTrafficPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
presentPopup(
title: Localized.ChatList.Traffic.title,
subtitle: Localized.ChatList.Traffic.subtitle,
actionTitle: Localized.ChatList.Traffic.positive,
cancelTitle: Localized.ChatList.Traffic.negative,
action: { [weak self] in self?.viewModel.didEnableDummyTraffic() }
)
}.store(in: &cancellables)
tableController.deletePublisher tableController.deletePublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [unowned self] ip in .sink { [unowned self] ip in
...@@ -295,68 +278,6 @@ public final class ChatListController: UIViewController { ...@@ -295,68 +278,6 @@ public final class ChatListController: UIViewController {
coordinator.toPopup(popup, from: self) coordinator.toPopup(popup, from: self)
} }
private func presentPopup(
title: String,
subtitle: String,
actionTitle: String,
cancelTitle: String,
action: @escaping () -> Void
) {
let actionButton = CapsuleButton()
actionButton.set(style: .brandColored, title: actionTitle)
let cancelButton = CapsuleButton()
cancelButton.set(style: .seeThrough, title: cancelTitle)
let popup = BottomPopup(with: [
PopupLabel(
font: Fonts.Mulish.bold.font(size: 26.0),
text: title,
color: Asset.neutralActive.color,
alignment: .left,
spacingAfter: 19
),
PopupLabel(
font: Fonts.Mulish.regular.font(size: 16.0),
text: subtitle,
color: Asset.neutralBody.color,
alignment: .left,
lineHeightMultiple: 1.1,
spacingAfter: 39
),
PopupStackView(
axis: .horizontal,
spacing: 20,
distribution: .fillEqually,
views: [actionButton, cancelButton]
)
])
actionButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
popup.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.popupCancellables.removeAll()
action()
}
}.store(in: &popupCancellables)
cancelButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
popup.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.popupCancellables.removeAll()
}
}.store(in: &popupCancellables)
coordinator.toPopup(popup, from: self)
}
} }
extension ChatListController: MenuDelegate {} extension ChatListController: MenuDelegate {}
import HUD import HUD
import UIKit
import Shared import Shared
import Combine
import Models import Models
import Combine
import Defaults import Defaults
import Foundation import Foundation
import Integration import Integration
import PushNotifications
import DependencyInjection import DependencyInjection
protocol ChatListViewModelType { protocol ChatListViewModelType {
...@@ -24,12 +22,8 @@ protocol ChatListViewModelType { ...@@ -24,12 +22,8 @@ protocol ChatListViewModelType {
final class ChatListViewModel: ChatListViewModelType { final class ChatListViewModel: ChatListViewModelType {
@Dependency private var session: SessionType @Dependency private var session: SessionType
@Dependency private var pushHandler: PushHandling
@KeyObject(.username, defaultValue: "") var myUsername: String @KeyObject(.username, defaultValue: "") var myUsername: String
@KeyObject(.dummyTrafficOn, defaultValue: false) var isDummyTrafficOn: Bool
@KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
@KeyObject(.askedDummyTrafficOnce, defaultValue: false) var askedDummyTraffic: Bool
let editState = EditStateHandler() let editState = EditStateHandler()
let chatsRelay = CurrentValueSubject<[GenericChatInfo], Never>([]) let chatsRelay = CurrentValueSubject<[GenericChatInfo], Never>([])
...@@ -39,9 +33,6 @@ final class ChatListViewModel: ChatListViewModelType { ...@@ -39,9 +33,6 @@ final class ChatListViewModel: ChatListViewModelType {
var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
var askDummyTrafficPublisher: AnyPublisher<Void, Never> { askDummyTrafficSubject.eraseToAnyPublisher() }
private let askDummyTrafficSubject = PassthroughSubject<Void, Never>()
var badgeCount: AnyPublisher<Int, Never> { var badgeCount: AnyPublisher<Int, Never> {
Publishers.CombineLatest( Publishers.CombineLatest(
session.contacts(.received), session.contacts(.received),
...@@ -162,38 +153,6 @@ final class ChatListViewModel: ChatListViewModelType { ...@@ -162,38 +153,6 @@ final class ChatListViewModel: ChatListViewModelType {
.store(in: &cancellables) .store(in: &cancellables)
} }
func viewDidAppear() {
verifyDummyTraffic()
verifyNotifications()
}
private func verifyDummyTraffic() {
guard askedDummyTraffic == false else { return }
askedDummyTraffic = true
askDummyTrafficSubject.send()
}
private func verifyNotifications() {
guard pushNotifications == false else { return }
pushHandler.didRequestAuthorization { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let granted):
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
self.pushNotifications = granted
case .failure:
self.pushNotifications = false
}
}
}
func isGroup(indexPath: IndexPath) -> Bool { func isGroup(indexPath: IndexPath) -> Bool {
chatsRelay.value[indexPath.row].contact == nil chatsRelay.value[indexPath.row].contact == nil
} }
...@@ -228,9 +187,4 @@ final class ChatListViewModel: ChatListViewModelType { ...@@ -228,9 +187,4 @@ final class ChatListViewModel: ChatListViewModelType {
groups.forEach(session.deleteAll(from:)) groups.forEach(session.deleteAll(from:))
contacts.forEach(session.deleteAll(from:)) contacts.forEach(session.deleteAll(from:))
} }
func didEnableDummyTraffic() {
isDummyTrafficOn = true
session.setDummyTraffic(status: true)
}
} }
...@@ -13,10 +13,9 @@ public protocol OnboardingCoordinating { ...@@ -13,10 +13,9 @@ public protocol OnboardingCoordinating {
func toWelcome(from: UIViewController) func toWelcome(from: UIViewController)
func toStart(with: String, from: UIViewController) func toStart(with: String, from: UIViewController)
func toUsername(with: String, from: UIViewController) func toUsername(with: String, from: UIViewController)
func toRestoreList(with: String, from: UIViewController)
func toPopup(_: UIViewController, from: UIViewController) func toPopup(_: UIViewController, from: UIViewController)
func toSuccess(with: OnboardingSuccessModel, from: UIViewController) func toSuccess(with: OnboardingSuccessModel, from: UIViewController)
func toRestoreList(with: String, from: UIViewController)
func toEmailConfirmation( func toEmailConfirmation(
with: AttributeConfirmation, with: AttributeConfirmation,
...@@ -43,6 +42,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -43,6 +42,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
var emailFactory: () -> UIViewController var emailFactory: () -> UIViewController
var phoneFactory: () -> UIViewController var phoneFactory: () -> UIViewController
var searchFactory: () -> UIViewController
var welcomeFactory: () -> UIViewController var welcomeFactory: () -> UIViewController
var chatListFactory: () -> UIViewController var chatListFactory: () -> UIViewController
var startFactory: (String) -> UIViewController var startFactory: (String) -> UIViewController
...@@ -56,6 +56,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -56,6 +56,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
public init( public init(
emailFactory: @escaping () -> UIViewController, emailFactory: @escaping () -> UIViewController,
phoneFactory: @escaping () -> UIViewController, phoneFactory: @escaping () -> UIViewController,
searchFactory: @escaping () -> UIViewController,
welcomeFactory: @escaping () -> UIViewController, welcomeFactory: @escaping () -> UIViewController,
chatListFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController,
startFactory: @escaping (String) -> UIViewController, startFactory: @escaping (String) -> UIViewController,
...@@ -69,12 +70,13 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -69,12 +70,13 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
self.emailFactory = emailFactory self.emailFactory = emailFactory
self.phoneFactory = phoneFactory self.phoneFactory = phoneFactory
self.startFactory = startFactory self.startFactory = startFactory
self.searchFactory = searchFactory
self.welcomeFactory = welcomeFactory self.welcomeFactory = welcomeFactory
self.successFactory = successFactory
self.usernameFactory = usernameFactory self.usernameFactory = usernameFactory
self.chatListFactory = chatListFactory self.chatListFactory = chatListFactory
self.restoreListFactory = restoreListFactory
self.successFactory = successFactory
self.countriesFactory = countriesFactory self.countriesFactory = countriesFactory
self.restoreListFactory = restoreListFactory
self.phoneConfirmationFactory = phoneConfirmationFactory self.phoneConfirmationFactory = phoneConfirmationFactory
self.emailConfirmationFactory = emailConfirmationFactory self.emailConfirmationFactory = emailConfirmationFactory
} }
...@@ -91,11 +93,6 @@ public extension OnboardingCoordinator { ...@@ -91,11 +93,6 @@ public extension OnboardingCoordinator {
replacePresenter.present(screen, from: parent) replacePresenter.present(screen, from: parent)
} }
func toChats(from parent: UIViewController) {
let screen = chatListFactory()
replacePresenter.present(screen, from: parent)
}
func toWelcome(from parent: UIViewController) { func toWelcome(from parent: UIViewController) {
let screen = welcomeFactory() let screen = welcomeFactory()
replacePresenter.present(screen, from: parent) replacePresenter.present(screen, from: parent)
...@@ -125,6 +122,12 @@ public extension OnboardingCoordinator { ...@@ -125,6 +122,12 @@ public extension OnboardingCoordinator {
bottomPresenter.present(popup, from: parent) bottomPresenter.present(popup, from: parent)
} }
func toChats(from parent: UIViewController) {
let searchScreen = searchFactory()
let chatListScreen = chatListFactory()
replacePresenter.present(chatListScreen, searchScreen, from: parent)
}
func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) { func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) {
let screen = countriesFactory(onChoose) let screen = countriesFactory(onChoose)
pushPresenter.present(screen, from: parent) pushPresenter.present(screen, from: parent)
......
...@@ -3,11 +3,14 @@ import UIKit ...@@ -3,11 +3,14 @@ import UIKit
public final class BottomPresenter: NSObject, Presenting { public final class BottomPresenter: NSObject, Presenting {
private var transition: BottomTransition? private var transition: BottomTransition?
public func present(_ viewController: UIViewController, from parent: UIViewController) { public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
viewController.modalPresentationStyle = .overFullScreen guard let screen = viewControllers.first else {
viewController.transitioningDelegate = self fatalError("Tried to present empty list of view controllers")
}
parent.present(viewController, animated: true) screen.modalPresentationStyle = .overFullScreen
screen.transitioningDelegate = self
parent.present(screen, animated: true)
} }
} }
......
...@@ -5,11 +5,14 @@ public protocol CenterPresenterNonDismissingTarget: UIViewController {} ...@@ -5,11 +5,14 @@ public protocol CenterPresenterNonDismissingTarget: UIViewController {}
public final class CenterPresenter: NSObject, Presenting { public final class CenterPresenter: NSObject, Presenting {
private var transition: CenterTransition? private var transition: CenterTransition?
public func present(_ viewController: UIViewController, from parent: UIViewController) { public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
viewController.modalPresentationStyle = .overFullScreen guard let screen = viewControllers.first else {
viewController.transitioningDelegate = self fatalError("Tried to present empty list of view controllers")
}
parent.present(viewController, animated: true) screen.modalPresentationStyle = .overFullScreen
screen.transitioningDelegate = self
parent.present(screen, animated: true)
} }
} }
......
...@@ -3,11 +3,14 @@ import UIKit ...@@ -3,11 +3,14 @@ import UIKit
public final class FadePresenter: NSObject, Presenting { public final class FadePresenter: NSObject, Presenting {
private var transition: FadeTransition? private var transition: FadeTransition?
public func present(_ target: UIViewController, from parent: UIViewController) { public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
target.modalPresentationStyle = .overFullScreen guard let screen = viewControllers.first else {
target.transitioningDelegate = self fatalError("Tried to present empty list of view controllers")
}
parent.present(target, animated: true) screen.modalPresentationStyle = .overFullScreen
screen.transitioningDelegate = self
parent.present(screen, animated: true)
} }
} }
......
...@@ -3,11 +3,14 @@ import UIKit ...@@ -3,11 +3,14 @@ import UIKit
public final class FullscreenPresenter: NSObject, Presenting { public final class FullscreenPresenter: NSObject, Presenting {
private var transition: FullscreenTransition? private var transition: FullscreenTransition?
public func present(_ viewController: UIViewController, from parent: UIViewController) { public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
viewController.modalPresentationStyle = .overFullScreen guard let screen = viewControllers.first else {
viewController.transitioningDelegate = self fatalError("Tried to present empty list of view controllers")
}
parent.present(viewController, animated: true) screen.modalPresentationStyle = .overFullScreen
screen.transitioningDelegate = self
parent.present(screen, animated: true)
} }
} }
......
...@@ -2,7 +2,7 @@ import UIKit ...@@ -2,7 +2,7 @@ import UIKit
import Theme import Theme
public protocol Presenting { public protocol Presenting {
func present(_ target: UIViewController, from parent: UIViewController) func present(_ target: UIViewController..., from parent: UIViewController)
func dismiss(from parent: UIViewController) func dismiss(from parent: UIViewController)
} }
...@@ -15,16 +15,16 @@ public extension Presenting { ...@@ -15,16 +15,16 @@ public extension Presenting {
public struct PushPresenter: Presenting { public struct PushPresenter: Presenting {
public init() {} public init() {}
public func present(_ target: UIViewController, from parent: UIViewController) { public func present(_ target: UIViewController..., from parent: UIViewController) {
parent.navigationController?.pushViewController(target, animated: true) parent.navigationController?.pushViewController(target.first!, animated: true)
} }
} }
public struct ModalPresenter: Presenting { public struct ModalPresenter: Presenting {
public init() {} public init() {}
public func present(_ target: UIViewController, from parent: UIViewController) { public func present(_ target: UIViewController..., from parent: UIViewController) {
let statusBarVC = StatusBarViewController(target) let statusBarVC = StatusBarViewController(target.first!)
statusBarVC.modalPresentationStyle = .fullScreen statusBarVC.modalPresentationStyle = .fullScreen
parent.present(statusBarVC, animated: true) parent.present(statusBarVC, animated: true)
} }
...@@ -43,12 +43,12 @@ public struct ReplacePresenter: Presenting { ...@@ -43,12 +43,12 @@ public struct ReplacePresenter: Presenting {
self.mode = mode self.mode = mode
} }
public func present(_ target: UIViewController, from parent: UIViewController) { public func present(_ target: UIViewController..., from parent: UIViewController) {
guard let navigationController = parent.navigationController else { return } guard let navigationController = parent.navigationController else { return }
switch mode { switch mode {
case .replaceAll: case .replaceAll:
navigationController.setViewControllers([target], animated: true) navigationController.setViewControllers(target, animated: true)
case .replaceBackwards(let OlderInStack): case .replaceBackwards(let OlderInStack):
if let oldScreen = navigationController.viewControllers.filter({ $0.isKind(of: OlderInStack.self) }).first, if let oldScreen = navigationController.viewControllers.filter({ $0.isKind(of: OlderInStack.self) }).first,
...@@ -61,20 +61,20 @@ public struct ReplacePresenter: Presenting { ...@@ -61,20 +61,20 @@ public struct ReplacePresenter: Presenting {
if let coordinator = navigationController.transitionCoordinator { if let coordinator = navigationController.transitionCoordinator {
coordinator.animate(alongsideTransition: nil) { _ in coordinator.animate(alongsideTransition: nil) { _ in
navigationController.setViewControllers(viewControllersBefore + [target] , animated: true) navigationController.setViewControllers(viewControllersBefore + target , animated: true)
} }
} else { } else {
navigationController.setViewControllers(viewControllersBefore + [target] , animated: true) navigationController.setViewControllers(viewControllersBefore + target , animated: true)
} }
} else { } else {
navigationController.pushViewController(target, animated: true) navigationController.pushViewController(target.first!, animated: true)
} }
case .replaceLast: case .replaceLast:
let viewControllersBefore = navigationController.viewControllers.dropLast() let viewControllersBefore = navigationController.viewControllers.dropLast()
func replace() { func replace() {
navigationController.setViewControllers(viewControllersBefore + [target] , animated: true) navigationController.setViewControllers(viewControllersBefore + target , animated: true)
} }
if let coordinator = navigationController.transitionCoordinator { if let coordinator = navigationController.transitionCoordinator {
...@@ -91,10 +91,10 @@ public struct ReplacePresenter: Presenting { ...@@ -91,10 +91,10 @@ public struct ReplacePresenter: Presenting {
public struct PopReplacePresenter: Presenting { public struct PopReplacePresenter: Presenting {
public init() {} public init() {}
public func present(_ target: UIViewController, from parent: UIViewController) { public func present(_ target: UIViewController..., from parent: UIViewController) {
if let lastViewController = parent.navigationController?.viewControllers.last { if let lastViewController = parent.navigationController?.viewControllers.last {
parent.navigationController?.setViewControllers([target, lastViewController], animated: false) parent.navigationController?.setViewControllers([target.first!, lastViewController], animated: false)
parent.navigationController?.setViewControllers([target], animated: true) parent.navigationController?.setViewControllers([target.first!], animated: true)
} }
} }
} }
...@@ -19,11 +19,14 @@ public final class SideMenuPresenter: NSObject, ...@@ -19,11 +19,14 @@ public final class SideMenuPresenter: NSObject,
// MARK: Presenting // MARK: Presenting
public func present(_ target: UIViewController, public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
from parent: UIViewController) { guard let screen = viewControllers.first else {
target.modalPresentationStyle = .overFullScreen fatalError("Tried to present empty list of view controllers")
target.transitioningDelegate = self }
parent.present(target, animated: true)
screen.modalPresentationStyle = .overFullScreen
screen.transitioningDelegate = self
parent.present(screen, animated: true)
} }
// MARK: UIViewControllerTransitioningDelegate // MARK: UIViewControllerTransitioningDelegate
......
...@@ -66,6 +66,11 @@ public final class SearchController: UIViewController { ...@@ -66,6 +66,11 @@ public final class SearchController: UIViewController {
) )
} }
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.didAppear()
}
public override func viewDidLoad() { public override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupNavigationBar() setupNavigationBar()
...@@ -106,11 +111,23 @@ public final class SearchController: UIViewController { ...@@ -106,11 +111,23 @@ public final class SearchController: UIViewController {
} }
private func setupBindings() { private func setupBindings() {
viewModel.hud viewModel.hudPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [hud] in hud.update(with: $0) } .sink { [hud] in hud.update(with: $0) }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.coverTrafficPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
presentPopup(
title: Localized.ChatList.Traffic.title,
subtitle: Localized.ChatList.Traffic.subtitle,
actionTitle: Localized.ChatList.Traffic.positive,
cancelTitle: Localized.ChatList.Traffic.negative,
action: { [weak self] in self?.viewModel.didEnableCoverTraffic() }
)
}.store(in: &cancellables)
viewModel viewModel
.itemsRelay .itemsRelay
.removeDuplicates() .removeDuplicates()
...@@ -124,7 +141,7 @@ public final class SearchController: UIViewController { ...@@ -124,7 +141,7 @@ public final class SearchController: UIViewController {
.sink { [unowned self] in screenView.placeholder.isHidden = !$0 } .sink { [unowned self] in screenView.placeholder.isHidden = !$0 }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.state viewModel.statePublisher
.map(\.country) .map(\.country)
.removeDuplicates() .removeDuplicates()
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
...@@ -183,13 +200,13 @@ public final class SearchController: UIViewController { ...@@ -183,13 +200,13 @@ public final class SearchController: UIViewController {
.sink { [unowned self] _ in viewModel.didSelect(filter: .email) } .sink { [unowned self] _ in viewModel.didSelect(filter: .email) }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.state viewModel.statePublisher
.map(\.selectedFilter) .map(\.selectedFilter)
.removeDuplicates() .removeDuplicates()
.sink { [unowned self] in screenView.alternateFieldsOver(filter: $0) } .sink { [unowned self] in screenView.alternateFieldsOver(filter: $0) }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.state viewModel.statePublisher
.map(\.selectedFilter) .map(\.selectedFilter)
.removeDuplicates() .removeDuplicates()
.dropFirst() .dropFirst()
...@@ -205,6 +222,67 @@ public final class SearchController: UIViewController { ...@@ -205,6 +222,67 @@ public final class SearchController: UIViewController {
didSelectRowAt indexPath: IndexPath) { didSelectRowAt indexPath: IndexPath) {
coordinator.toContact(viewModel.itemsRelay.value[indexPath.row], from: self) coordinator.toContact(viewModel.itemsRelay.value[indexPath.row], from: self)
} }
private func presentPopup(
title: String,
subtitle: String,
actionTitle: String,
cancelTitle: String,
action: @escaping () -> Void
) {
let actionButton = CapsuleButton()
actionButton.set(style: .brandColored, title: actionTitle)
let cancelButton = CapsuleButton()
cancelButton.set(style: .seeThrough, title: cancelTitle)
let popup = BottomPopup(with: [
PopupLabel(
font: Fonts.Mulish.bold.font(size: 26.0),
text: title,
color: Asset.neutralActive.color,
alignment: .left,
spacingAfter: 19
),
PopupLabel(
font: Fonts.Mulish.regular.font(size: 16.0),
text: subtitle,
color: Asset.neutralBody.color,
alignment: .left,
lineHeightMultiple: 1.1,
spacingAfter: 39
),
PopupStackView(
axis: .horizontal,
spacing: 20,
distribution: .fillEqually,
views: [actionButton, cancelButton]
)
])
actionButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
popup.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.popupCancellables.removeAll()
action()
}
}.store(in: &popupCancellables)
cancelButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
popup.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.popupCancellables.removeAll()
}
}.store(in: &popupCancellables)
coordinator.toPopup(popup, from: self)
}
} }
extension SearchController: UITableViewDelegate {} extension SearchController: UITableViewDelegate {}
import HUD import HUD
import Combine import UIKit
import Models import Models
import Combine
import Defaults
import Countries import Countries
import Foundation import Foundation
import Integration import Integration
import PushNotifications
import CombineSchedulers import CombineSchedulers
import DependencyInjection import DependencyInjection
...@@ -32,32 +35,62 @@ struct SearchViewState: Equatable { ...@@ -32,32 +35,62 @@ struct SearchViewState: Equatable {
} }
final class SearchViewModel { final class SearchViewModel {
@KeyObject(.dummyTrafficOn, defaultValue: false) var isCoverTrafficEnabled: Bool
@KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
@KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic: Bool
@Dependency private var session: SessionType @Dependency private var session: SessionType
@Dependency private var pushHandler: PushHandling
let itemsRelay = CurrentValueSubject<[Contact], Never>([]) var hudPublisher: AnyPublisher<HUDStatus, Never> {
private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) hudSubject.eraseToAnyPublisher()
private let placeholderRelay = CurrentValueSubject<Bool, Never>(true) }
private let stateRelay = CurrentValueSubject<SearchViewState, Never>(.init())
var placeholderPublisher: AnyPublisher<Bool, Never> {
placeholderSubject.eraseToAnyPublisher()
}
var coverTrafficPublisher: AnyPublisher<Void, Never> {
coverTrafficSubject.eraseToAnyPublisher()
}
var statePublisher: AnyPublisher<SearchViewState, Never> {
stateSubject.eraseToAnyPublisher()
}
var backgroundScheduler: AnySchedulerOf<DispatchQueue>
= DispatchQueue.global().eraseToAnyScheduler()
var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } let itemsRelay = CurrentValueSubject<[Contact], Never>([])
var state: AnyPublisher<SearchViewState, Never> { stateRelay.eraseToAnyPublisher() } private let coverTrafficSubject = PassthroughSubject<Void, Never>()
var placeholderPublisher: AnyPublisher<Bool, Never> { placeholderRelay.eraseToAnyPublisher() } private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() private let placeholderSubject = CurrentValueSubject<Bool, Never>(true)
private let stateSubject = CurrentValueSubject<SearchViewState, Never>(.init())
func didAppear() {
verifyCoverTraffic()
verifyNotifications()
}
func didSelect(filter: SelectedFilter) { func didSelect(filter: SelectedFilter) {
stateRelay.value.selectedFilter = filter stateSubject.value.selectedFilter = filter
} }
func didInput(_ string: String) { func didInput(_ string: String) {
stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines) stateSubject.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
} }
func didInputPhone(_ string: String) { func didInputPhone(_ string: String) {
stateRelay.value.phoneInput = string.trimmingCharacters(in: .whitespacesAndNewlines) stateSubject.value.phoneInput = string.trimmingCharacters(in: .whitespacesAndNewlines)
} }
func didChooseCountry(_ country: Country) { func didChooseCountry(_ country: Country) {
stateRelay.value.country = country stateSubject.value.country = country
}
func didEnableCoverTraffic() {
isCoverTrafficEnabled = true
session.setDummyTraffic(status: true)
} }
func didTapSearch() { func didTapSearch() {
...@@ -67,28 +100,58 @@ final class SearchViewModel { ...@@ -67,28 +100,58 @@ final class SearchViewModel {
guard let self = self else { return } guard let self = self else { return }
do { do {
var content = self.stateRelay.value.selectedFilter.prefix var content = self.stateSubject.value.selectedFilter.prefix
if self.stateRelay.value.selectedFilter == .phone { if self.stateSubject.value.selectedFilter == .phone {
content += self.stateRelay.value.phoneInput + self.stateRelay.value.country.code content += self.stateSubject.value.phoneInput + self.stateSubject.value.country.code
} else { } else {
content += self.stateRelay.value.input content += self.stateSubject.value.input
} }
try self.session.search(fact: content) { result in try self.session.search(fact: content) { result in
self.placeholderRelay.send(false) self.placeholderSubject.send(false)
switch result { switch result {
case .success(let searched): case .success(let searched):
self.hudRelay.send(.none) self.hudSubject.send(.none)
self.itemsRelay.send([searched]) self.itemsRelay.send([searched])
case .failure(let error): case .failure(let error):
self.hudRelay.send(.error(.init(with: error))) self.hudSubject.send(.error(.init(with: error)))
self.itemsRelay.send([]) self.itemsRelay.send([])
} }
} }
} catch { } catch {
self.hudRelay.send(.error(.init(with: error))) self.hudSubject.send(.error(.init(with: error)))
}
}
}
private func verifyCoverTraffic() {
guard offeredCoverTraffic == false else {
return
}
offeredCoverTraffic = true
coverTrafficSubject.send()
}
private func verifyNotifications() {
guard pushNotifications == false else { return }
pushHandler.didRequestAuthorization { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let granted):
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
self.pushNotifications = granted
case .failure:
self.pushNotifications = false
} }
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment