diff --git a/Package.swift b/Package.swift index dfcf799cff66faae28c44aa926cf822c5696e7d4..c9b7a2e02b33d1bd9f333da8ea79efa363bfd87d 100644 --- a/Package.swift +++ b/Package.swift @@ -469,10 +469,12 @@ let package = Package( .target(name: "HUD"), .target(name: "Shared"), .target(name: "Countries"), + .target(name: "PushFeature"), .target(name: "Presentation"), .target(name: "ContactFeature"), .target(name: "NetworkMonitor"), .target(name: "DependencyInjection"), + .product(name: "Retry", package: "Retry"), .product(name: "XXDatabase", package: "client-ios-db"), ] ), diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift index 557d8f3f9e137de6648cc4028952c81196592e31..d147ebbd99e224c4482f484042059250d2b9aa21 100644 --- a/Sources/ChatFeature/Controllers/GroupChatController.swift +++ b/Sources/ChatFeature/Controllers/GroupChatController.swift @@ -34,11 +34,11 @@ public final class GroupChatController: UIViewController { lazy private var header = GroupHeaderView() private let inputComponent: ChatInputView - private let chatLayout = ChatLayout() private var animator: ManualAnimator? private let viewModel: GroupChatViewModel private let layoutDelegate = LayoutDelegate() private var cancellables = Set<AnyCancellable>() + private let chatLayout = CollectionViewChatLayout() private var sections = [ArraySection<ChatSection, Message>]() private var currentInterfaceActions = SetActor<Set<InterfaceActions>, ReactionTypes>() diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift index 91aa66ec122b86593e3c0e9eab9c3eaf72793a31..4b41931743547862b2395e2a75201b85709c9464 100644 --- a/Sources/ChatFeature/Controllers/SingleChatController.swift +++ b/Sources/ChatFeature/Controllers/SingleChatController.swift @@ -45,11 +45,11 @@ public final class SingleChatController: UIViewController { private let inputComponent: ChatInputView private var collectionView: UICollectionView! - private let chatLayout = ChatLayout() private var animator: ManualAnimator? private let viewModel: SingleChatViewModel private let layoutDelegate = LayoutDelegate() private var cancellables = Set<AnyCancellable>() + private let chatLayout = CollectionViewChatLayout() private var sections = [ArraySection<ChatSection, Message>]() private var currentInterfaceActions: SetActor<Set<InterfaceActions>, ReactionTypes> = SetActor() diff --git a/Sources/ChatFeature/Helpers/LayoutDelegate.swift b/Sources/ChatFeature/Helpers/LayoutDelegate.swift index 8bbc4343318a3d7353e28a580c67777663020d9c..4a072358ea503bec6d7c8aa1b52eba20bac9c6a2 100644 --- a/Sources/ChatFeature/Helpers/LayoutDelegate.swift +++ b/Sources/ChatFeature/Helpers/LayoutDelegate.swift @@ -1,7 +1,7 @@ import UIKit import ChatLayout -extension ChatLayout { +extension CollectionViewChatLayout { func configure(_ layoutDelegate: ChatLayoutDelegate) { delegate = layoutDelegate settings.estimatedItemSize = CGSize(width: 100, height: 65) @@ -13,11 +13,11 @@ extension ChatLayout { } final class LayoutDelegate: ChatLayoutDelegate { - public func alignmentForItem(_: ChatLayout, of kind: ItemKind, at: IndexPath) -> ChatItemAlignment { + public func alignmentForItem(_: CollectionViewChatLayout, of kind: ItemKind, at: IndexPath) -> ChatItemAlignment { .fullWidth } - public func shouldPresentHeader(_ chatLayout: ChatLayout, at sectionIndex: Int) -> Bool { + public func shouldPresentHeader(_ chatLayout: CollectionViewChatLayout, at sectionIndex: Int) -> Bool { true } } diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift index 4412378ca380a9b5e45c35768f83b8e9a8d37cb1..b3bdbbf11b38c6ae9592bbebbaeef318baac3dda 100644 --- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift @@ -31,7 +31,7 @@ final class GroupChatViewModel { @KeyObject(.username, defaultValue: nil) var username: String? var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var hudPublisher: AnyPublisher<HUDStatus, Never> { diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift index 9b01d4d4ba81f4314b3dc289054a818edd09bbec..157b03b1c1d30c3602acf1163f7d2b3e5d3273fc 100644 --- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift @@ -58,7 +58,7 @@ final class SingleChatViewModel: NSObject { } var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactSubject.eraseToAnyPublisher() } diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index 557223c02e603d12d866013b8c8587cae65517c9..dbe20f1b03bc34c26f5cffc8492d30006f776efd 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -37,7 +37,7 @@ final class ChatListViewModel { } var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var chatsPublisher: AnyPublisher<[ChatInfo], Never> { diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift index 7f915791a88a55116733e7c8e7a51f8a209fe2bd..9170c81ba32def8cbb77211768a9082378e4dc2b 100644 --- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift +++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift @@ -41,7 +41,7 @@ final class ContactViewModel { private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init()) var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift index ad365d34bd46c7d3086c9e7b6e53f5f4597453e3..2e12479b46dd8e20538fc2e09ae4bbbb93c61856 100644 --- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift @@ -15,7 +15,7 @@ final class ContactListViewModel { @Dependency private var reportingStatus: ReportingStatus var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var contacts: AnyPublisher<[XXModels.Contact], Never> { diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift index 019e1333c8d98fca6d8402fa779f2a7a8f4d1323..f292c7cb7ed4589007e86a565cd0ad5100b91ff4 100644 --- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift @@ -23,7 +23,7 @@ final class CreateGroupViewModel { // MARK: Properties var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var selected: AnyPublisher<[XXModels.Contact], Never> { diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index e35515a2de49c40f4917bf82d09f6517478131a3..43ae7a6f6e2af4021fc1070c704a2ea0d499f250 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -450,7 +450,7 @@ extension LaunchViewModel { nickname: nil, photo: nil, authStatus: .verificationInProgress, - isRecent: false, + isRecent: true, createdAt: Date() )) diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift index d6d9b6902a3d061576df3a8ba5b7b7a4b079a1dd..39d71f1a673292cf001f531df614f0a1112b8d85 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift @@ -56,8 +56,8 @@ final class OnboardingUsernameViewModel { ) try self.database.saveContact(.init( - id: self.messenger.ud.get()!.getContact().getId(), - marshaled: self.messenger.ud.get()!.getContact().data, + id: self.messenger.e2e.get()!.getContact().getId(), + marshaled: self.messenger.e2e.get()!.getContact().data, username: self.stateRelay.value.input, email: nil, phone: nil, diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift index 326b72a9ddc76d4ea56534aa1484fdc32b620a64..a2e801aa3acc542a5366862fb06212d4a5bd1f6d 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift @@ -168,6 +168,33 @@ final class RequestsReceivedViewModel { e2eId: self.messenger.e2e.get()!.getId() ) + if ownershipResult == true { + contact.authStatus = .verified + _ = try? self.database.saveContact(contact) + } else { + _ = try? self.database.deleteContact(contact) + } + case .failure(let error): + print("^^^ \(#file):\(#line) \(error.localizedDescription)") + contact.authStatus = .verificationFailed + _ = try? self.database.saveContact(contact) + } + }) + ) + } else { + let _ = try SearchUD.live( + e2eId: self.messenger.e2e.get()!.getId(), + udContact: self.messenger.ud.get()!.getContact(), + facts: XXClient.Contact.live(contact.marshaled!).getFacts(), + callback: .init(handle: { + switch $0 { + case .success(let results): + let ownershipResult = try! self.messenger.e2e.get()!.verifyOwnership( + received: XXClient.Contact.live(contact.marshaled!), + verified: results.first!, + e2eId: self.messenger.e2e.get()!.getId() + ) + if ownershipResult == true { contact.authStatus = .verified _ = try? self.database.saveContact(contact) @@ -282,7 +309,7 @@ final class RequestsReceivedViewModel { do { try self.database.saveContact(contact) - let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(contact.marshaled!)) + let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: .live(contact.marshaled!)) contact.authStatus = .friend try self.database.saveContact(contact) diff --git a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift index d18e7ab87e52c450f3b762d6652459275b614569..d28de8f783f059455082177effb0639a463f51f3 100644 --- a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift @@ -18,10 +18,11 @@ struct ScanDisplayViewState: Equatable { final class ScanDisplayViewModel { @Dependency var messenger: Messenger - @KeyObject(.email, defaultValue: nil) private var email: String? - @KeyObject(.phone, defaultValue: nil) private var phone: String? - @KeyObject(.sharingEmail, defaultValue: false) private var sharingEmail: Bool - @KeyObject(.sharingPhone, defaultValue: false) private var sharingPhone: Bool + @KeyObject(.email, defaultValue: nil) var email: String? + @KeyObject(.phone, defaultValue: nil) var phone: String? + @KeyObject(.username, defaultValue: nil) var username: String? + @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool + @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool var statePublisher: AnyPublisher<ScanDisplayViewState, Never> { stateSubject.eraseToAnyPublisher() @@ -60,8 +61,7 @@ final class ScanDisplayViewModel { func generateQR() { guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return } - var facts: [Fact] = [] - let myContact = try! messenger.ud.get()!.getContact() + var facts: [Fact] = [Fact(fact: username!, type: FactType.username.rawValue)] if sharingPhone { facts.append(Fact(fact: phone!, type: FactType.phone.rawValue)) @@ -71,12 +71,11 @@ final class ScanDisplayViewModel { facts.append(Fact(fact: email!, type: FactType.email.rawValue)) } - let myContactWithFacts = try! SetFactsOnContact.live( - contactData: myContact.data, - facts: facts - ) + let e2e = messenger.e2e.get()! + let contactData = e2e.getContact().data + let wrappedContact = try! SetFactsOnContact.live(contactData: contactData, facts: facts) - filter.setValue(myContactWithFacts, forKey: "inputMessage") + filter.setValue(wrappedContact, forKey: "inputMessage") let transform = CGAffineTransform(scaleX: 5, y: 5) if let output = filter.outputImage?.transformed(by: transform) { diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift index c6158e731d929380d6e89d6675b100407376ebb6..c5ec4513510b9e37cf82cfa0ed8314984984c905 100644 --- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift @@ -1,19 +1,19 @@ import HUD +import Retry import UIKit +import Models import Shared import Combine import XXModels import XXClient import Defaults import Countries -import Models -import Defaults import CustomDump import NetworkMonitor import ReportingFeature import CombineSchedulers -import DependencyInjection import XXMessengerClient +import DependencyInjection typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem> @@ -33,7 +33,7 @@ final class SearchLeftViewModel { @KeyObject(.username, defaultValue: nil) var username: String? var myId: Data { - try! messenger.ud.get()!.getContact().getId() + try! messenger.e2e.get()!.getContact().getId() } var hudPublisher: AnyPublisher<HUDStatus, Never> { @@ -112,8 +112,33 @@ final class SearchLeftViewModel { content += stateSubject.value.country.code } - let nrr = try! messenger.cMix.get()!.getNodeRegistrationStatus() - print("^^^ NRR: \(nrr.ratio)") + enum NodeRegistrationError: Error { + case unhealthyNet + case belowMinimum + } + + retry(max: 5, retryStrategy: .delay(seconds: 2)) { [weak self] in + guard let self = self else { return } + + do { + let nrr = try self.messenger.cMix.get()!.getNodeRegistrationStatus() + if nrr.ratio < 0.8 { throw NodeRegistrationError.belowMinimum } + } catch { + throw NodeRegistrationError.unhealthyNet + } + }.finalCatch { [weak self] in + guard let self = self else { return } + + if case .unhealthyNet = $0 as? NodeRegistrationError { + self.hudSubject.send(.error(.init(content: "Network is not healthy yet, try again within the next minute or so."))) + } else if case .belowMinimum = $0 as? NodeRegistrationError { + self.hudSubject.send(.error(.init(content: "Node registration ratio is still below 80%, try again within the next minute or so."))) + } else { + self.hudSubject.send(.error(.init(with: $0))) + } + + return + } backgroundScheduler.schedule { [weak self] in guard let self = self else { return } @@ -137,7 +162,7 @@ final class SearchLeftViewModel { nickname: nil, photo: nil, authStatus: .stranger, - isRecent: false, + isRecent: true, isBlocked: false, isBanned: false, createdAt: Date() @@ -175,7 +200,7 @@ final class SearchLeftViewModel { myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel( - partner: XXClient.Contact.live(contact.marshaled!), + partner: .live(contact.marshaled!), myFacts: myFacts ) @@ -208,7 +233,7 @@ final class SearchLeftViewModel { myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel( - partner: XXClient.Contact.live(contact.marshaled!), + partner: .live(contact.marshaled!), myFacts: myFacts ) diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift index 8d41282ffd4d5044039920ed79f9ecffd8516e0a..b1b4020b4527e99f80d8eee5990b5f6aadc97844 100644 --- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift @@ -7,6 +7,7 @@ import XXClient import Foundation import Permissions import ReportingFeature +import XXMessengerClient import DependencyInjection enum ScanningStatus: Equatable { @@ -27,7 +28,6 @@ final class SearchRightViewModel { @Dependency var database: Database @Dependency var permissions: PermissionHandling @Dependency var reportingStatus: ReportingStatus - @Dependency var getFactsFromContact: GetFactsFromContact var foundPublisher: AnyPublisher<XXModels.Contact, Never> { foundSubject.eraseToAnyPublisher() @@ -72,20 +72,25 @@ final class SearchRightViewModel { /// Whatever got scanned, needs to have id and username /// otherwise is just noise or an unknown qr code /// - let userId = try? GetIdFromContact.live(data) - let facts = try? getFactsFromContact(data) - let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact + let user = XXClient.Contact.live(data) - guard let userId = userId, let username = username else { + guard + let uid = try? user.getId(), + let facts = try? user.getFacts(), + let username = facts.first(where: { $0.type == FactType.username.rawValue })?.fact + else { let errorTitle = Localized.Scan.Error.invalid statusSubject.send(.failed(.unknown(errorTitle))) return } + let email = facts.first { $0.type == FactType.email.rawValue }?.fact + let phone = facts.first { $0.type == FactType.phone.rawValue }?.fact + /// Make sure we are not processing a contact /// that we already have /// - if let alreadyContact = try? database.fetchContacts(.init(id: [userId])).first { + if let alreadyContact = try? database.fetchContacts(.init(id: [uid])).first { if alreadyContact.isBlocked, reportingStatus.isEnabled() { statusSubject.send(.failed(.unknown("You previously blocked this user."))) return @@ -113,16 +118,8 @@ final class SearchRightViewModel { statusSubject.send(.success) cameraSemaphoreSubject.send(false) - let email = try? GetFactsFromContact.live(data) - .first(where: { $0.type == FactType.email.rawValue }) - .map(\.fact) - - let phone = try? GetFactsFromContact.live(data) - .first(where: { $0.type == FactType.phone.rawValue }) - .map(\.fact) - foundSubject.send(.init( - id: userId, + id: uid, marshaled: data, username: username, email: email, diff --git a/Sources/Shared/Extensions/CollectionView.swift b/Sources/Shared/Extensions/CollectionView.swift index 27b041e093c060e28b17b8fc81af0e7fb9355cd4..ad5b3bf7ea0d77d43c664b22ea90271d9f96a63f 100644 --- a/Sources/Shared/Extensions/CollectionView.swift +++ b/Sources/Shared/Extensions/CollectionView.swift @@ -29,7 +29,7 @@ public extension UICollectionView { withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T } - convenience init(on view: UIView, with layout: ChatLayout) { + convenience init(on view: UIView, with layout: CollectionViewChatLayout) { self.init(frame: view.frame, collectionViewLayout: layout) view.addSubview(self) diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved index def30f909786b1a3dee029082a11de178bf0e989..cdfe1cbf5ce0aa3ed708268769239f3b4df26399 100644 --- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ekazaev/ChatLayout", "state" : { - "revision" : "d0edb6f3ae716a26842467c540a6bee909b80360", - "version" : "1.1.14" + "revision" : "88cbfc1c5c371be5fd51eaded329eb61386495dc", + "version" : "1.2.5" } }, { @@ -105,7 +105,7 @@ "location" : "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift", "state" : { "branch" : "development", - "revision" : "108dd0bfc00775b051f848cdc8b3921c26be47e7" + "revision" : "8e3717558823ea713fd6b516cc2fcbef01d003ed" } }, { @@ -210,7 +210,7 @@ { "identity" : "keychainaccess", "kind" : "remoteSourceControl", - "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", "state" : { "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", "version" : "4.2.2" @@ -335,7 +335,7 @@ { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", + "location" : "https://github.com/pointfreeco/swift-custom-dump.git", "state" : { "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7", "version" : "0.5.0" @@ -364,8 +364,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftcsv/SwiftCSV.git", "state" : { - "revision" : "048a1d3c2950b9c151ef9364b36f91baadc2c28c", - "version" : "0.8.0" + "revision" : "96fa14b92e88e0befdbc8bc31c7c2c9594a30060", + "version" : "0.8.1" } }, { @@ -389,7 +389,7 @@ { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git", "state" : { "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", "version" : "0.4.0"