diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift index 4b41931743547862b2395e2a75201b85709c9464..2028fc2de02497b066e8181bdf6912109973a06f 100644 --- a/Sources/ChatFeature/Controllers/SingleChatController.swift +++ b/Sources/ChatFeature/Controllers/SingleChatController.swift @@ -530,12 +530,14 @@ extension SingleChatController: KeyboardListenerDelegate { } func keyboardWillChangeFrame(info: KeyboardInfo) { - let keyWindow: UIWindow? = UIApplication.shared.connectedScenes - .filter { $0.activationState == .foregroundActive } - .compactMap { $0 as? UIWindowScene } - .first? - .windows - .first(where: \.isKeyWindow) + let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first + +// let keyWindow: UIWindow? = UIApplication.shared.connectedScenes +// .filter { $0.activationState == .foregroundActive } +// .compactMap { $0 as? UIWindowScene } +// .first? +// .windows +// .first(where: \.isKeyWindow) guard let keyWindow = keyWindow else { fatalError("[keyboardWillChangeFrame]: Couldn't get key window") diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index 43ae7a6f6e2af4021fc1070c704a2ea0d499f250..6c27c1de9b35b4b3f2fe55d8eca4948e984848c8 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -81,7 +81,11 @@ final class LaunchViewModel { self.versionChecker().sink { [unowned self] in switch $0 { case .upToDate: - self.updateBannedList { self.continueWithInitialization() } + self.updateBannedList { + self.updateErrors { + self.continueWithInitialization() + } + } case .failure(let error): self.versionFailed(error: error) case .updateRequired(let info): @@ -97,7 +101,7 @@ final class LaunchViewModel { do { try self.setupDatabase() - try SetLogLevel.live(.trace) + _ = try SetLogLevel.live(.trace) guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"), let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else { @@ -326,6 +330,32 @@ final class LaunchViewModel { } } + private func updateErrors(completion: @escaping () -> Void) { + let errorsURLString = "https://git.xx.network/elixxir/client-error-database/-/raw/main/clientErrors.json" + + URLSession.shared.dataTask(with: URL(string: errorsURLString)!) { [weak self] data, _, error in + guard let self = self else { return } + + guard error == nil else { + print(">>> Issue when trying to download errors json: \(error!.localizedDescription)") + self.updateErrors(completion: completion) + return + } + + guard let data = data, let json = String(data: data, encoding: .utf8) else { + print(">>> Issue when trying to unwrap errors json") + return + } + + do { + try UpdateCommonErrors.live(jsonFile: json) + completion() + } catch { + print(">>> Issue when trying to update common errors: \(error.localizedDescription)") + } + }.resume() + } + private func updateBannedList(completion: @escaping () -> Void) { fetchBannedList { result in switch result { diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift index d379b9cc0326fde68d59cf7ba386a20a7007c2d8..756479e25a56b544766ab694596bccc688216828 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift @@ -78,7 +78,8 @@ final class OnboardingEmailConfirmationViewModel { self.hudRelay.send(.none) self.completionRelay.send(self.confirmation) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift index 982623286266f322bff1f264c7da42bc4e16f6e9..cf21d93b4fd005bb991595e991b066c703bf48fe 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift @@ -4,8 +4,8 @@ import Models import Shared import Combine import Defaults -import InputField import XXClient +import InputField import CombineSchedulers import DependencyInjection import XXMessengerClient @@ -56,7 +56,8 @@ final class OnboardingEmailViewModel { confirmationId: confirmationId ) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift index 49e1570802766348885c1e339eda39497aa14a15..ba335ba8092e751dae7e1092947bb48ce31b9348 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift @@ -78,7 +78,8 @@ final class OnboardingPhoneConfirmationViewModel { self.hudRelay.send(.none) self.completionRelay.send(self.confirmation) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift index a3141d2c25e8379bc190ddad86aae5a06ff8dab1..f226397f4faa6d8afc4c9de36c505ffa5b6762fe 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift @@ -67,7 +67,8 @@ final class OnboardingPhoneViewModel { confirmationId: confirmationId ) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift index 39d71f1a673292cf001f531df614f0a1112b8d85..b042341ce209a880374a9fcba1841f7e67e4cfe2 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift @@ -4,6 +4,7 @@ import Models import Combine import Defaults import XXModels +import XXClient import InputField import Foundation import XXMessengerClient @@ -75,7 +76,7 @@ final class OnboardingUsernameViewModel { self.greenRelay.send() } catch { self.hudRelay.send(.none) - self.stateRelay.value.status = .invalid(error.localizedDescription) + self.stateRelay.value.status = .invalid(CreateUserFriendlyErrorMessage.live(error.localizedDescription)) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift index 8edf40aacd81caceb86882ff1096cf20360cc164..4a192c306e329bfadb92142ee0d453fc19c8de6c 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift @@ -59,7 +59,8 @@ final class ProfileEmailViewModel { confirmationId: confirmationId ) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift index e5504140093d8157af0b6ef1b06c2ebaf7baeea5..bbf580a249e2da6e8041dce235e8a3769ea19952 100644 --- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift @@ -68,7 +68,8 @@ final class ProfilePhoneViewModel { ) } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift index 57334dcf20f25d00d0e4b6acfdb9dde45f911218..36006fa591b7841c762bfb429f1f67a1a5c9e07b 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift @@ -109,7 +109,8 @@ final class ProfileViewModel { self.hudRelay.send(.none) self.refresh() } catch { - self.hudRelay.send(.error(.init(with: error))) + let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription) + self.hudRelay.send(.error(.init(content: xxError))) } } } diff --git a/Sources/ScanFeature/Controllers/ScanController.swift b/Sources/ScanFeature/Controllers/ScanController.swift index 2a1b99aa6da269007f9692b5ebb0b3b81e6ca377..52055ac4eae74504e4f53edfb78617099079716e 100644 --- a/Sources/ScanFeature/Controllers/ScanController.swift +++ b/Sources/ScanFeature/Controllers/ScanController.swift @@ -83,8 +83,7 @@ final class ScanController: UIViewController { .sink { [unowned self] in coordinator.toContact($0, from: self) } .store(in: &cancellables) - viewModel.state - .map(\.status) + viewModel.statePublisher .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [unowned self] in diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift index 365533cdc2b20c4eb6b97d89c92feee72d8f2172..b58546fb542279cdef56390e1c0111c8c502ed5e 100644 --- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift @@ -2,9 +2,9 @@ import Shared import Models import Combine import XXModels -import Foundation import XXClient -import CombineSchedulers +import Foundation +import ReportingFeature import DependencyInjection enum ScanStatus: Equatable { @@ -21,93 +21,77 @@ enum ScanError: Equatable { case alreadyFriends(String) } -struct ScanViewState: Equatable { - var status: ScanStatus = .reading -} - final class ScanViewModel { @Dependency var database: Database - @Dependency var getFactsFromContact: GetFactsFromContact + @Dependency var reportingStatus: ReportingStatus - var backgroundScheduler: AnySchedulerOf<DispatchQueue> - = DispatchQueue.global().eraseToAnyScheduler() + var contactPublisher: AnyPublisher<XXModels.Contact, Never> { + contactSubject.eraseToAnyPublisher() + } - var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactRelay.eraseToAnyPublisher() } - private let contactRelay = PassthroughSubject<XXModels.Contact, Never>() + var statePublisher: AnyPublisher<ScanStatus, Never> { + stateSubject.eraseToAnyPublisher() + } - var state: AnyPublisher<ScanViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<ScanViewState, Never>(.init()) + private let contactSubject = PassthroughSubject<XXModels.Contact, Never>() + private let stateSubject = CurrentValueSubject<ScanStatus, Never>(.reading) func resetScanner() { - stateRelay.value.status = .reading + stateSubject.send(.reading) } func didScanData(_ data: Data) { - guard stateRelay.value.status == .reading else { return } - stateRelay.value.status = .processing - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - - do { - guard let usernameAndId = try self.verifyScanned(data) else { - self.stateRelay.value.status = .failed(.unknown(Localized.Scan.Error.general)) - return - } - - - - if let previouslyAdded = try? self.database.fetchContacts(.init(id: [usernameAndId.1])).first { - var error = ScanError.unknown(Localized.Scan.Error.general) - - switch previouslyAdded.authStatus { - case .friend: - error = .alreadyFriends(usernameAndId.0) - case .requested, .verified: - error = .requestOpened - default: - break - } - - self.stateRelay.value.status = .failed(error) - return - } - - let facts = try? self.getFactsFromContact(data) - let contactEmail = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact - let contactPhone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact - - let contact = Contact( - id: usernameAndId.1, - marshaled: data, - username: usernameAndId.0, - email: contactEmail, - phone: contactPhone, - nickname: nil, - photo: nil, - authStatus: .stranger, - isRecent: false, - createdAt: Date() - ) - - self.succeed(with: contact) - } catch { - self.stateRelay.value.status = .failed(.unknown(Localized.Scan.Error.invalid)) - } + guard stateSubject.value == .reading else { return } + stateSubject.send(.processing) + + let user = XXClient.Contact.live(data) + + 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 + stateSubject.send(.failed(.unknown(errorTitle))) + return } - } - private func verifyScanned(_ data: Data) throws -> (String, Data)? { - let id = try? GetIdFromContact.live(data) - let facts = try? getFactsFromContact(data) - let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact + let email = facts.first { $0.type == FactType.email.rawValue }?.fact + let phone = facts.first { $0.type == FactType.phone.rawValue }?.fact - guard let id = id, let username = username else { return nil } - return (username, id) - } + if let alreadyContact = try? database.fetchContacts(.init(id: [uid])).first { + if alreadyContact.isBlocked, reportingStatus.isEnabled() { + stateSubject.send(.failed(.unknown("You previously blocked this user."))) + return + } + + if alreadyContact.isBanned, reportingStatus.isEnabled() { + stateSubject.send(.failed(.unknown("This user was banned."))) + return + } + + if alreadyContact.authStatus == .friend { + stateSubject.send(.failed(.alreadyFriends(username))) + } else if [.requested, .verified].contains(alreadyContact.authStatus) { + stateSubject.send(.failed(.requestOpened)) + } else { + let generalErrorTitle = Localized.Scan.Error.general + stateSubject.send(.failed(.unknown(generalErrorTitle))) + } + + return + } - private func succeed(with contact: XXModels.Contact) { - stateRelay.value.status = .success - contactRelay.send(contact) + stateSubject.send(.success) + contactSubject.send(.init( + id: uid, + marshaled: data, + username: username, + email: email, + phone: phone, + nickname: nil, + photo: nil, + authStatus: .stranger, + isRecent: false, + createdAt: Date() + )) } }