diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj index 285241f51b9d10fa05735bcf96ef93899f022be2..95dbf04121acad50caa29de4f69bcd25c2c680f7 100644 --- a/App/client-ios.xcodeproj/project.pbxproj +++ b/App/client-ios.xcodeproj/project.pbxproj @@ -448,7 +448,7 @@ CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; @@ -487,7 +487,7 @@ CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 242; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; @@ -522,7 +522,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 242; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -553,7 +553,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 242; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/App/client-ios/Resources/Info.plist b/App/client-ios/Resources/Info.plist index ee4d813d2e0ccc94a932d66a36070d80705cab5b..d8cb845b40392068f453251c9c0aa72cd00979db 100644 --- a/App/client-ios/Resources/Info.plist +++ b/App/client-ios/Resources/Info.plist @@ -105,6 +105,6 @@ <key>UIViewControllerBasedStatusBarAppearance</key> <true/> <key>isReportingOptional</key> - <true/> + <false/> </dict> </plist> diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index f8113b1337336157dee853cb1ddd5fb6672c09a6..4ea40cf53608c20392ba77811f139963e8300e07 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -77,10 +77,13 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { let backgroundTask = application.beginBackgroundTask(withName: "xx.stop.network") {} backgroundTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in + print(">>> .backgroundTimeRemaining: \(UIApplication.shared.backgroundTimeRemaining)") + guard UIApplication.shared.backgroundTimeRemaining > 8 else { if !self.calledStopNetwork { self.calledStopNetwork = true try! cMix.stopNetworkFollower() + print(">>> Called stopNetworkFollower") } else { if cMix.hasRunningProcesses() == false { application.endBackgroundTask(backgroundTask) @@ -126,12 +129,14 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { if backgroundTimer != nil { backgroundTimer?.invalidate() backgroundTimer = nil + print(">>> Invalidated background timer") } if let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger, let cMix = messenger.cMix.get() { guard self.calledStopNetwork == true else { return } try? cMix.startNetworkFollower(timeoutMS: 10_000) + print(">>> Called startNetworkFollower") self.calledStopNetwork = false } } @@ -156,11 +161,11 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { ) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingURL = userActivity.webpageURL, - let username = getUsernameFromInvitationDeepLink(incomingURL) else { + let username = getUsernameFromInvitationDeepLink(incomingURL), + let router = try? DependencyInjection.Container.shared.resolve() as PushRouter else { return false } - let router = try! DependencyInjection.Container.shared.resolve() as PushRouter router.navigateTo(.search(username: username), {}) return true } diff --git a/Sources/App/PushRouter.swift b/Sources/App/PushRouter.swift index 4e1e432362954af9030921e9813676d7b3471af9..65536c1604c6c6bf34b72d0b8b097ac9b731d79e 100644 --- a/Sources/App/PushRouter.swift +++ b/Sources/App/PushRouter.swift @@ -7,6 +7,7 @@ import ChatListFeature import RequestsFeature import DependencyInjection import XXModels +import XXMessengerClient extension PushRouter { static func live(navigationController: UINavigationController) -> PushRouter { @@ -20,11 +21,16 @@ extension PushRouter { navigationController.setViewControllers([RequestsContainerController()], animated: true) } case .search(username: let username): - if !(navigationController.viewControllers.last is SearchContainerController) { - navigationController.setViewControllers([ - ChatListController(), - SearchContainerController(username) - ], animated: true) + if let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger, + let _ = try? messenger.ud.get()?.getContact() { + if !(navigationController.viewControllers.last is SearchContainerController) { + navigationController.setViewControllers([ + ChatListController(), + SearchContainerController(username) + ], animated: true) + } else { + (navigationController.viewControllers.last as? SearchContainerController)?.startSearchingFor(username) + } } case .contactChat(id: let id): if let database: Database = try? DependencyInjection.Container.shared.resolve(), diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift index 2e12479b46dd8e20538fc2e09ae4bbbb93c61856..548cc89bcdb37202d318b75d9ff82d8042170d01 100644 --- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift @@ -12,7 +12,7 @@ import XXClient final class ContactListViewModel { @Dependency var database: Database @Dependency var messenger: Messenger - @Dependency private var reportingStatus: ReportingStatus + @Dependency var reportingStatus: ReportingStatus var myId: Data { try! messenger.e2e.get()!.getContact().getId() diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index d2a22b0a99b0ba23179ddb1bb5aca6f91579951e..37bb82f93d89d4c20d2400ac74bd7642723ff9e5 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -59,6 +59,7 @@ final class LaunchViewModel { } var authCallbacksCancellable: Cancellable? + var networkCallbacksCancellable: Cancellable? var routePublisher: AnyPublisher<LaunchRoute, Never> { routeSubject.eraseToAnyPublisher() @@ -105,7 +106,7 @@ final class LaunchViewModel { do { try self.setupDatabase() - _ = try SetLogLevel.live(.info) + _ = try SetLogLevel.live(.error) RegisterLogWriter.live(.init(handle: { XXLogger.live().debug($0) @@ -207,6 +208,12 @@ final class LaunchViewModel { networkMonitor.start() + networkCallbacksCancellable = messenger.cMix.get()!.addHealthCallback(.init(handle: { [weak self] in + guard let self = self else { return } + print(">>> healthCallback: \($0)") + self.networkMonitor.update($0) + })) + if messenger.isLoggedIn() == false { if try messenger.isRegistered() == false { hudSubject.send(.none) @@ -536,44 +543,17 @@ extension LaunchViewModel { )) do { - if email == nil, phone == nil { - try performLookup(on: contact) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(let lookedUpContact): - if try! self.verifyOwnership(contact, lookedUpContact) { // How could this ever throw? - model.authStatus = .verified - try! self.database.saveContact(model) - } else { - try! self.database.deleteContact(model) - } - case .failure(let error): - model.authStatus = .verificationFailed - print(">>> Error \(#file):\(#line): \(error.localizedDescription)") - try! self.database.saveContact(model) - } - } - } else { - try performSearch(on: contact) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(let searchedContact): - if try! self.verifyOwnership(contact, searchedContact) { // How could this ever throw? - model.authStatus = .verified - try! self.database.saveContact(model) - } else { - try! self.database.deleteContact(model) - } - case .failure(let error): - model.authStatus = .verificationFailed - print(">>> Error \(#file):\(#line): \(error.localizedDescription)") - try! self.database.saveContact(model) - } + if let messenger: Messenger = try? DependencyInjection.Container.shared.resolve() { + if try messenger.verifyContact(contact) { + model.authStatus = .verified + try database.saveContact(model) + } else { + try database.deleteContact(model) } } } catch { + model.authStatus = .verificationFailed + try! database.saveContact(model) print(">>> Error \(#file):\(#line): \(error.localizedDescription)") } } @@ -663,70 +643,4 @@ extension LaunchViewModel { // // Multilookup on strangers // } } - - private func performLookup( - on contact: XXClient.Contact, - completion: @escaping (Result<XXClient.Contact, Error>) -> Void - ) throws { - guard let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger else { - fatalError(">>> Tried to lookup, but there's no messenger instance on DI container") - } - - print(">>> Performing Lookup") - - let _ = try LookupUD.live( - e2eId: messenger.e2e.get()!.getId(), - udContact: try messenger.ud.get()!.getContact(), - lookupId: contact.getId(), - callback: .init(handle: { - switch $0 { - case .success(let otherContact): - print(">>> Lookup succeeded") - completion(.success(otherContact)) - case .failure(let error): - print(">>> Lookup failed: \(error.localizedDescription)") - completion(.failure(error)) - } - }) - ) - } - - private func performSearch( - on contact: XXClient.Contact, - completion: @escaping (Result<XXClient.Contact, Error>) -> Void - ) throws { - guard let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger else { - fatalError(">>> Tried to search, but there's no messenger instance on DI container") - } - - print(">>> Performing Search") - - let _ = try SearchUD.live( - e2eId: messenger.e2e.get()!.getId(), - udContact: try messenger.ud.get()!.getContact(), - facts: contact.getFacts(), - callback: .init(handle: { - switch $0 { - case .success(let otherContact): - print(">>> Search succeeded") - completion(.success(otherContact.first!)) - case .failure(let error): - print(">>> Search failed: \(error.localizedDescription)") - completion(.failure(error)) - } - }) - ) - } - - private func verifyOwnership( - _ lhs: XXClient.Contact, - _ rhs: XXClient.Contact - ) throws -> Bool { - guard let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger else { - fatalError(">>> Tried to verify ownership, but there's no messenger instance on DI container") - } - - let e2e = messenger.e2e.get()! - return try e2e.verifyOwnership(received: lhs, verified: rhs, e2eId: e2e.getId()) - } } diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift index 3e719cfd64cf87ab1ec7e4cbb0bb7728c9686308..497dac0a69912c3f05a9eed29f8f620bd8e7a3bd 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift @@ -43,7 +43,10 @@ final class RequestsFailedViewModel { func didTapStateButtonFor(request: Request) { guard case var .contact(contact) = request, - request.status == .failedToRequest || request.status == .failedToConfirm else { return } + request.status == .failedToRequest || + request.status == .failedToConfirm else { + return + } hudSubject.send(.on) backgroundScheduler.schedule { [weak self] in @@ -71,7 +74,8 @@ final class RequestsFailedViewModel { try self.database.saveContact(contact) self.hudSubject.send(.none) } catch { - self.hudSubject.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudSubject.send(.error(.init(content: xxError))) } } } diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift index 269b199607abf8d450f59d3dbc14d38ab0bfc35c..bbee533cab4ef7647c5ca02cd2bfedb1551a9003 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift @@ -63,7 +63,14 @@ final class RequestsSentViewModel { } func didTapStateButtonFor(request item: RequestSent) { - guard case let .contact(contact) = item.request, item.request.status == .requested else { return } + guard case let .contact(contact) = item.request, + item.request.status == .requested || + item.request.status == .requesting || + item.request.status == .failedToRequest else { + return + } + + let name = (contact.nickname ?? contact.username) ?? "" hudSubject.send(.on) backgroundScheduler.schedule { [weak self] in @@ -90,8 +97,6 @@ final class RequestsSentViewModel { item.isResent = true allRequests.append(item) - let name = (contact.nickname ?? contact.username) ?? "" - self.toastController.enqueueToast(model: .init( title: Localized.Requests.Sent.Toast.resent(name), leftImage: Asset.requestSentToaster.image @@ -102,6 +107,11 @@ final class RequestsSentViewModel { snapshot.appendItems(allRequests, toSection: .appearing) self.itemsSubject.send(snapshot) } catch { + self.toastController.enqueueToast(model: .init( + title: Localized.Requests.Sent.Toast.resentFailed(name), + leftImage: Asset.requestFailedToaster.image + )) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) self.hudSubject.send(.error(.init(content: xxError))) } diff --git a/Sources/ScanFeature/Controllers/ScanContainerController.swift b/Sources/ScanFeature/Controllers/ScanContainerController.swift index c61a6f59373299cd02cb632d953017aaff560b48..41df6a8705056d9f68ef82366b890d6e07f4140f 100644 --- a/Sources/ScanFeature/Controllers/ScanContainerController.swift +++ b/Sources/ScanFeature/Controllers/ScanContainerController.swift @@ -12,39 +12,31 @@ public final class ScanContainerController: UIViewController { lazy private var screenView = ScanContainerView() private let scanController = ScanController() - private let displayController = ScanDisplayController() - private var cancellables = Set<AnyCancellable>() private var drawerCancellables = Set<AnyCancellable>() + private let displayController = ScanDisplayController() + private let pageController = UIPageViewController( + transitionStyle: .scroll, + navigationOrientation: .horizontal + ) public override func loadView() { view = screenView - screenView.scrollView.delegate = self - addChild(scanController) - addChild(displayController) - - screenView.scrollView.addSubview(scanController.view) - screenView.scrollView.addSubview(displayController.view) - - scanController.view.snp.makeConstraints { - $0.top.equalTo(screenView) - $0.width.equalTo(screenView) - $0.bottom.equalTo(screenView) + addChild(pageController) + screenView.addSubview(pageController.view) + pageController.view.snp.makeConstraints { + $0.top.equalTo(screenView.stackView.snp.bottom) $0.left.equalToSuperview() - $0.right.equalTo(displayController.view.snp.left) - } - - displayController.view.snp.makeConstraints { - $0.top.equalTo(screenView.segmentedControl.snp.bottom) - $0.width.equalTo(scanController.view) - $0.bottom.equalTo(scanController.view) + $0.right.equalToSuperview() + $0.bottom.equalTo(screenView) } - scanController.didMove(toParent: self) - displayController.didMove(toParent: self) - - screenView.bringSubviewToFront(screenView.segmentedControl) + pageController.delegate = self + pageController.dataSource = self + pageController.didMove(toParent: self) + pageController.setViewControllers([scanController], direction: .forward, animated: true) + screenView.bringSubviewToFront(screenView.stackView) } public override func viewWillAppear(_ animated: Bool) { @@ -96,16 +88,20 @@ public final class ScanContainerController: UIViewController { } private func setupBindings() { - screenView.segmentedControl.leftButton + screenView.leftButton .publisher(for: .touchUpInside) - .sink { [unowned self] _ in screenView.scrollView.setContentOffset(.zero, animated: true) } - .store(in: &cancellables) + .sink { [unowned self] _ in + screenView.leftButton.set(selected: true) + screenView.rightButton.set(selected: false) + pageController.setViewControllers([scanController], direction: .reverse, animated: true, completion: nil) + }.store(in: &cancellables) - screenView.segmentedControl.rightButton + screenView.rightButton .publisher(for: .touchUpInside) .sink { [unowned self] _ in - let point = CGPoint(x: screenView.frame.width, y: 0.0) - screenView.scrollView.setContentOffset(point, animated: true) + screenView.leftButton.set(selected: false) + screenView.rightButton.set(selected: true) + pageController.setViewControllers([displayController], direction: .forward, animated: true, completion: nil) }.store(in: &cancellables) } @@ -113,14 +109,6 @@ public final class ScanContainerController: UIViewController { coordinator.toSideMenu(from: self) } - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - let percentage = scrollView.contentOffset.x / view.frame.width - - scanController.view.alpha = 1 - percentage - displayController.view.alpha = percentage - screenView.segmentedControl.updateLeftConstraint(percentage) - } - private func presentInfo(title: String, subtitle: String) { let actionButton = CapsuleButton() actionButton.set( @@ -163,4 +151,40 @@ public final class ScanContainerController: UIViewController { } } -extension ScanContainerController: UIScrollViewDelegate {} +extension ScanContainerController: UIPageViewControllerDataSource { + public func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerAfter viewController: UIViewController + ) -> UIViewController? { + guard viewController != displayController else { return nil } + return displayController + } + + public func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerBefore viewController: UIViewController + ) -> UIViewController? { + guard viewController != scanController else { return nil } + return scanController + } +} + + +extension ScanContainerController: UIPageViewControllerDelegate { + public func pageViewController( + _ pageViewController: UIPageViewController, + didFinishAnimating finished: Bool, + previousViewControllers: [UIViewController], + transitionCompleted completed: Bool + ) { + guard finished, completed else { return } + + if previousViewControllers.contains(scanController) { + screenView.leftButton.set(selected: false) + screenView.rightButton.set(selected: true) + } else { + screenView.leftButton.set(selected: true) + screenView.rightButton.set(selected: false) + } + } +} diff --git a/Sources/ScanFeature/Views/ScanContainerView.swift b/Sources/ScanFeature/Views/ScanContainerView.swift index 7e5eeee37a1a32765a33735ec8e1c65a05473dd5..5b9717598d93484ce74eb58e37b1ed465afc9230 100644 --- a/Sources/ScanFeature/Views/ScanContainerView.swift +++ b/Sources/ScanFeature/Views/ScanContainerView.swift @@ -2,24 +2,32 @@ import UIKit import Shared final class ScanContainerView: UIView { - let scrollView = UIScrollView() - let segmentedControl = ScanSegmentedControl() + let stackView = UIStackView() + let leftButton = ScanSegmentedControlButton() + let rightButton = ScanSegmentedControlButton() init() { super.init(frame: .zero) backgroundColor = Asset.neutralDark.color - addSubview(segmentedControl) - addSubview(scrollView) - scrollView.snp.makeConstraints { - $0.edges.equalToSuperview() - } + leftButton.set(selected: true) + rightButton.set(selected: false) + leftButton.imageView.image = Asset.scanScan.image + rightButton.imageView.image = Asset.scanQr.image + leftButton.titleLabel.text = Localized.Scan.SegmentedControl.left + rightButton.titleLabel.text = Localized.Scan.SegmentedControl.right + + stackView.distribution = .fillEqually + stackView.addArrangedSubview(leftButton) + stackView.addArrangedSubview(rightButton) + + addSubview(stackView) - segmentedControl.snp.makeConstraints { + stackView.snp.makeConstraints { $0.top.equalTo(safeAreaLayoutGuide).offset(10) - $0.left.equalToSuperview() - $0.right.equalToSuperview() + $0.left.equalToSuperview().offset(50) + $0.right.equalToSuperview().offset(-50) $0.height.equalTo(60) } } diff --git a/Sources/ScanFeature/Views/ScanSegmentedControl.swift b/Sources/ScanFeature/Views/ScanSegmentedControl.swift deleted file mode 100644 index f3fd72acf3326b05b8057e5a9fe0948aa113688c..0000000000000000000000000000000000000000 --- a/Sources/ScanFeature/Views/ScanSegmentedControl.swift +++ /dev/null @@ -1,80 +0,0 @@ -import UIKit -import Shared -import SnapKit - -final class ScanSegmentedControl: UIView { - private let trackHeight = 2.0 - private let numberOfTabs = 2.0 - private let trackView = UIView() - private let stackView = UIStackView() - private var leftConstraint: Constraint? - private let trackIndicatorView = UIView() - private(set) var leftButton = ScanSegmentedControlButton() - private(set) var rightButton = ScanSegmentedControlButton() - - init() { - super.init(frame: .zero) - - rightButton.setup( - title: Localized.Scan.SegmentedControl.right, - icon: Asset.scanQr.image - ) - - leftButton.setup( - title: Localized.Scan.SegmentedControl.left, - icon: Asset.scanScan.image - ) - - trackView.backgroundColor = Asset.neutralLine.color - trackIndicatorView.backgroundColor = Asset.brandPrimary.color - - stackView.distribution = .fillEqually - stackView.addArrangedSubview(leftButton) - stackView.addArrangedSubview(rightButton) - - addSubview(stackView) - addSubview(trackView) - trackView.addSubview(trackIndicatorView) - - stackView.snp.makeConstraints { - $0.top.equalToSuperview() - $0.left.equalToSuperview() - $0.right.equalToSuperview() - $0.bottom.equalTo(trackView.snp.top) - } - - trackView.snp.makeConstraints { - $0.height.equalTo(trackHeight) - $0.left.equalToSuperview() - $0.right.equalToSuperview() - $0.bottom.equalToSuperview() - } - - trackIndicatorView.snp.makeConstraints { - $0.top.equalToSuperview() - leftConstraint = $0.left.equalToSuperview().constraint - $0.width.equalToSuperview().dividedBy(numberOfTabs) - $0.bottom.equalToSuperview() - } - } - - required init?(coder: NSCoder) { nil } - - func updateLeftConstraint(_ percentageScrolled: CGFloat) { - let tabWidth = bounds.width / numberOfTabs - let leftOffset = percentageScrolled * tabWidth - leftConstraint?.update(offset: leftOffset) - - leftButton.update(color: .fade( - from: Asset.brandPrimary.color, - to: Asset.neutralLine.color, - pcent: percentageScrolled - )) - - rightButton.update(color: .fade( - from: Asset.brandPrimary.color, - to: Asset.neutralLine.color, - pcent: 1 - percentageScrolled - )) - } -} diff --git a/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift b/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift index b9e711c614b239dfb55158c530adbe121f0ca691..85983848b9c60656f2f08944532e8c8b3553a165 100644 --- a/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift +++ b/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift @@ -2,18 +2,25 @@ import UIKit import Shared final class ScanSegmentedControlButton: UIControl { - private let titleLabel = UILabel() - private let imageView = UIImageView() + let titleLabel = UILabel() + let separatorView = UIView() + let imageView = UIImageView() init() { super.init(frame: .zero) + separatorView.alpha = 0.0 titleLabel.textAlignment = .center - titleLabel.textColor = Asset.neutralWhite.color - titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0) + imageView.tintColor = Asset.neutralWeak.color + titleLabel.textColor = Asset.neutralWeak.color + separatorView.backgroundColor = Asset.neutralWhite.color + titleLabel.font = Fonts.Mulish.semiBold.font(size: 13) + imageView.transform = imageView.transform.scaledBy(x: 0.9, y: 0.9) + titleLabel.transform = titleLabel.transform.scaledBy(x: 0.9, y: 0.9) addSubview(titleLabel) addSubview(imageView) + addSubview(separatorView) imageView.snp.makeConstraints { $0.top.equalToSuperview().offset(7.5) @@ -25,17 +32,42 @@ final class ScanSegmentedControlButton: UIControl { $0.centerX.equalToSuperview() $0.bottom.equalToSuperview().offset(-7.5) } + + separatorView.snp.makeConstraints { + $0.height.equalTo(2) + $0.left.equalToSuperview().offset(20) + $0.right.equalToSuperview().offset(-20) + $0.bottom.equalToSuperview() + } } required init?(coder: NSCoder) { nil } - func setup(title: String, icon: UIImage) { - titleLabel.text = title - imageView.image = icon - } + func set(selected: Bool) { + switch (isSelected, selected) { + case (true, false): + UIView.animate(withDuration: 0.25) { + self.imageView.transform = self.imageView.transform.scaledBy(x: 0.9, y: 0.9) + self.titleLabel.transform = self.titleLabel.transform.scaledBy(x: 0.9, y: 0.9) + self.imageView.tintColor = Asset.neutralWeak.color + self.titleLabel.textColor = Asset.neutralWeak.color + self.separatorView.alpha = 0.0 + } completion: { _ in + self.isSelected = false + } - func update(color: UIColor) { - imageView.tintColor = color - titleLabel.textColor = color + case (false, true): + UIView.animate(withDuration: 0.25) { + self.imageView.transform = .identity + self.titleLabel.transform = .identity + self.imageView.tintColor = Asset.neutralWhite.color + self.titleLabel.textColor = Asset.neutralWhite.color + self.separatorView.alpha = 1.0 + } completion: { _ in + self.isSelected = true + } + case (true, true), (false, false): + break + } } } diff --git a/Sources/SearchFeature/Controllers/SearchContainerController.swift b/Sources/SearchFeature/Controllers/SearchContainerController.swift index 3a1bf1e45af9077b7bc70315f5584be93b116242..15cb91be299d187b5dffb2f82bb68bb1c9f19d80 100644 --- a/Sources/SearchFeature/Controllers/SearchContainerController.swift +++ b/Sources/SearchFeature/Controllers/SearchContainerController.swift @@ -31,6 +31,11 @@ public final class SearchContainerController: UIViewController { embedControllers() } + public func startSearchingFor(_ string: String) { + leftController.viewModel.invitation = string + leftController.viewModel.viewDidAppear() + } + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationItem.backButtonTitle = "" diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift index 1ff0b69106cf6c41ef4b7f9e0be785c8ac81a273..9dbfe2b36b73fca6d5b72fd1d2784240ba2a9a90 100644 --- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift @@ -52,7 +52,7 @@ final class SearchLeftViewModel { var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - private var invitation: String? + var invitation: String? private var searchCancellables = Set<AnyCancellable>() private let successSubject = PassthroughSubject<XXModels.Contact, Never>() private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) @@ -71,17 +71,18 @@ final class SearchLeftViewModel { networkCancellable.removeAll() -// networkMonitor.statusPublisher -// .first { $0 == .available } -// .eraseToAnyPublisher() -// .flatMap { _ in self.session.waitForNodes(timeout: 5) } -// .sink { -// if case .failure(let error) = $0 { -// self.hudSubject.send(.error(.init(with: error))) -// } -// } receiveValue: { -// self.didStartSearching() -// }.store(in: &networkCancellable) + networkMonitor.statusPublisher + .first { $0 == .available } + .eraseToAnyPublisher() + .flatMap { _ in + self.waitForNodes(timeout: 5) + }.sink(receiveCompletion: { + if case .failure(let error) = $0 { + self.hudSubject.send(.error(.init(with: error))) + } + }, receiveValue: { + self.didStartSearching() + }).store(in: &networkCancellable) } } @@ -322,4 +323,18 @@ final class SearchLeftViewModel { leftImage: Asset.sharedSuccess.image )) } + + private func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> { + Deferred { + Future { promise in + retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in + guard let self = self else { return } + _ = try self.messenger.cMix.get()!.getNodeRegistrationStatus() + promise(.success(())) + }.finalCatch { + promise(.failure($0)) + } + } + }.eraseToAnyPublisher() + } }