diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj index b13be6187f4691c5ee9d95e4db8864367a979327..0f9849c8e5b29c44bd270caee0cc7ba2c25f0a8d 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 = 292; + CURRENT_PROJECT_VERSION = 294; 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 = 292; + CURRENT_PROJECT_VERSION = 294; 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 = 292; + CURRENT_PROJECT_VERSION = 294; 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 = 292; + CURRENT_PROJECT_VERSION = 294; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/Package.swift b/Package.swift index b1ec908e6564028f197b352659674d5c971a15ad..7bf71c7d49a94848af789fc8e75c5de498248a21 100644 --- a/Package.swift +++ b/Package.swift @@ -177,8 +177,21 @@ let package = Package( .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), - .target(name: "CheckVersion"), - .target(name: "Voxophone"), + .target( + name: "CheckVersion", + dependencies: [ + .product( + name: "Dependencies", + package: "swift-composable-architecture" + ), + ] + ), + .target( + name: "Voxophone", + dependencies: [ + .target(name: "Shared"), + ] + ), .target(name: "WebsiteFeature"), .target( name: "CrashReport", @@ -271,12 +284,24 @@ let package = Package( .target( name: "Keychain", dependencies: [ - .product(name: "KeychainAccess", package: "KeychainAccess"), + .product( + name: "KeychainAccess", + package: "KeychainAccess" + ), + .product( + name: "Dependencies", + package: "swift-composable-architecture" + ), ] ), .target( name: "Defaults", - dependencies: [] + dependencies: [ + .product( + name: "Dependencies", + package: "swift-composable-architecture" + ), + ] ), .target( name: "CountryListFeature", diff --git a/Sources/AppCore/AppDependencies.swift b/Sources/AppCore/AppDependencies.swift index f974596a680bfe8b671315094fc07287bb661787..9c4c392e6b47067a5ca46d226e6ce3ecf623e59e 100644 --- a/Sources/AppCore/AppDependencies.swift +++ b/Sources/AppCore/AppDependencies.swift @@ -10,6 +10,8 @@ public struct AppDependencies { public var backupHandler: BackupCallbackHandler public var hudManager: HUDManager public var dbManager: DBManager + public var groupRequest: GroupRequestHandler + public var groupMessageHandler: GroupMessageHandler public var statusBar: StatusBarStylist public var messenger: Messenger public var authHandler: AuthCallbackHandler @@ -53,6 +55,14 @@ extension AppDependencies { ), hudManager: .live(), dbManager: dbManager, + groupRequest: .live( + messenger: messenger, + db: dbManager.getDB + ), + groupMessageHandler: .live( + messenger: messenger, + db: dbManager.getDB + ), statusBar: .live(), messenger: messenger, authHandler: .live( @@ -67,7 +77,7 @@ extension AppDependencies { ), backupStorage: .onDisk(), mainQueue: DispatchQueue.main.eraseToAnyScheduler(), - bgQueue: DispatchQueue.global(qos: .background).eraseToAnyScheduler(), + bgQueue: DispatchQueue(label: "xx-messenger", qos: .userInitiated).eraseToAnyScheduler(), now: now, sendMessage: .live( messenger: messenger, @@ -99,6 +109,8 @@ extension AppDependencies { backupHandler: .unimplemented, hudManager: .unimplemented, dbManager: .unimplemented, + groupRequest: .unimplemented, + groupMessageHandler: .unimplemented, statusBar: .unimplemented, messenger: .unimplemented, authHandler: .unimplemented, @@ -171,15 +183,3 @@ extension DependencyValues { set { self[StoredDummyTrafficKey.self] = newValue } } } - -private enum StoredNewGroupChatKey: DependencyKey { - static var liveValue = Stored<GroupChat?>.inMemory() - static var testValue = Stored<GroupChat?>.unimplemented() -} - -extension DependencyValues { - public var groupManager: Stored<GroupChat?> { - get { self[StoredNewGroupChatKey.self] } - set { self[StoredNewGroupChatKey.self] = newValue } - } -} diff --git a/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift b/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift new file mode 100644 index 0000000000000000000000000000000000000000..262a53eb914987433dca7cb6a14a3d6f9f1cdb39 --- /dev/null +++ b/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift @@ -0,0 +1,55 @@ +import XXModels +import XXClient +import Foundation +import XXMessengerClient +import XCTestDynamicOverlay + +public struct GroupMessageHandler { + public typealias OnError = (Error) -> Void + + public var run: (@escaping OnError) -> Cancellable + + public func callAsFunction(onError: @escaping OnError) -> Cancellable { + run(onError) + } +} + +extension GroupMessageHandler { + public static func live( + messenger: Messenger, + db: DBManagerGetDB + ) -> GroupMessageHandler { + GroupMessageHandler { onError in + messenger.registerGroupChatProcessor(.init { result in + switch result { + case .success(let callback): + do { + let payload = try MessagePayload.decode(callback.decryptedMessage.payload) + try db().saveMessage(.init( + networkId: callback.decryptedMessage.messageId, + senderId: callback.decryptedMessage.senderId, + recipientId: nil, + groupId: callback.decryptedMessage.groupId, + date: Date.fromTimestamp(Int(callback.decryptedMessage.timestamp)), + status: .received, + isUnread: true, + text: payload.text, + replyMessageId: payload.replyingTo, + roundURL: callback.roundUrl + )) + } catch { + onError(error) + } + case .failure(let error): + onError(error) + } + }) + } + } +} + +extension GroupMessageHandler { + public static let unimplemented = GroupMessageHandler( + run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {}) + ) +} diff --git a/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift b/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift new file mode 100644 index 0000000000000000000000000000000000000000..14afb9026aaf52338c7b6af7b7c5f8d5b094a268 --- /dev/null +++ b/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift @@ -0,0 +1,99 @@ +import XXModels +import XXClient +import Foundation +import XXMessengerClient +import XCTestDynamicOverlay + +public struct GroupRequestHandler { + public typealias OnError = (Error) -> Void + + public var run: (@escaping OnError) -> Cancellable + + public func callAsFunction(onError: @escaping OnError) -> Cancellable { + run(onError) + } +} + +extension GroupRequestHandler { + public static func live( + messenger: Messenger, + db: DBManagerGetDB + ) -> GroupRequestHandler { + GroupRequestHandler { onError in + messenger.registerGroupRequestHandler(.init { group in + do { + if let _ = try db().fetchGroups(.init(id: [group.getId()])).first { + return + } + guard let leader = try group.getMembership().first else { + return // Failed to get group membership/leader + } + try db().saveGroup(.init( + id: group.getId(), + name: String(data: group.getName(), encoding: .utf8)!, + leaderId: leader.id, + createdAt: Date.fromMSTimestamp(group.getCreatedMS()), + authStatus: .pending, + serialized: group.serialize() + )) + if let initialMessageData = group.getInitMessage(), + let initialMessage = String(data: initialMessageData, encoding: .utf8) { + try db().saveMessage(.init( + senderId: leader.id, + recipientId: nil, + groupId: group.getId(), + date: Date.fromMSTimestamp(group.getCreatedMS()), + status: .received, + isUnread: true, + text: initialMessage + )) + } + let members = try group.getMembership() + let friends = try db().fetchContacts(.init(id: Set(members.map(\.id)), authStatus: [ + .friend, .hidden, .confirming, + .verified, .requested, .requesting, + .verificationInProgress, .requestFailed, + .verificationFailed, .confirmationFailed + ])) + let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id))) + try strangers.forEach { + if let stranger = try? db().fetchContacts(.init(id: [$0])).first { + print(stranger) + } else { + try db().saveContact(.init( + id: $0, + username: "Fetching...", + authStatus: .stranger, + isRecent: false, + isBlocked: false, + isBanned: false, + createdAt: Date.fromMSTimestamp(group.getCreatedMS()) + )) + } + } + try members.map { + XXModels.GroupMember(groupId: group.getId(), contactId: $0.id) + }.forEach { + try db().saveGroupMember($0) + } + let multilookup = try messenger.lookupContacts(ids: strangers.map { $0 }) + for user in multilookup.contacts { + if var foo = try? db().fetchContacts(.init(id: [user.getId()])).first, + let username = try? user.getFact(.username)?.value { + foo.username = username + _ = try? db().saveContact(foo) + } + } + } catch { + onError(error) + } + }) + } + } +} + +extension GroupRequestHandler { + public static let unimplemented = GroupRequestHandler( + run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {}) + ) +} diff --git a/Sources/AppFeature/AppDelegate.swift b/Sources/AppFeature/AppDelegate.swift index 7cc572aa26a9f469c6787c4927b29da6b821df77..cb3dce57e45259b11df8addbfe7a8f6a04150ec5 100644 --- a/Sources/AppFeature/AppDelegate.swift +++ b/Sources/AppFeature/AppDelegate.swift @@ -1,10 +1,14 @@ import UIKit import AppCore +import Defaults import LaunchFeature public class AppDelegate: UIResponder, UIApplicationDelegate { + public var coverView: UIView? public var window: UIWindow? + @KeyObject(.hideAppList, defaultValue: false) var shouldHideAppInAppList + public func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -16,4 +20,18 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { window?.makeKeyAndVisible() return true } + + public func applicationWillResignActive(_ application: UIApplication) { + if shouldHideAppInAppList { + coverView?.removeFromSuperview() + coverView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) + coverView?.frame = window?.bounds ?? .zero + window?.addSubview(coverView!) + } + } + + public func applicationDidBecomeActive(_ application: UIApplication) { + application.applicationIconBadgeNumber = 0 + coverView?.removeFromSuperview() + } } diff --git a/Sources/AppFeature/DependencyRegistrator.swift b/Sources/AppFeature/DependencyRegistrator.swift index ed05ba40e4225ee6ce3345aca596276c3fc2915e..3504173f1b48691362bb581d7bd2a40702166318 100644 --- a/Sources/AppFeature/DependencyRegistrator.swift +++ b/Sources/AppFeature/DependencyRegistrator.swift @@ -1,32 +1,12 @@ -// MARK: SDK - -import UIKit -import Network -import QuickLook -import MobileCoreServices - -// MARK: Isolated features - -import Bindings -import Keychain -import Defaults -import Voxophone -import PushFeature -import CrashReporting -import VersionChecking -import ReportingFeature -import CountryListFeature - -// MARK: UI Features - import ScanFeature import ChatFeature import MenuFeature import TermsFeature +import Dependencies +import AppNavigation import BackupFeature import DrawerFeature import SearchFeature -import LaunchFeature import RestoreFeature import ContactFeature import WebsiteFeature @@ -36,18 +16,11 @@ import SettingsFeature import RequestsFeature import GroupDraftFeature import OnboardingFeature +import CountryListFeature import CreateGroupFeature import ContactListFeature import RequestPermissionFeature -import Shared -import XXClient -import AppNavigation -import KeychainAccess -import XXMessengerClient - -import ComposableArchitecture - extension NavigatorKey: DependencyKey { public static let liveValue: Navigator = CombinedNavigator( PresentModalNavigator(), @@ -114,6 +87,7 @@ extension NavigatorKey: DependencyKey { ProfilePhoneController.init ), PresentSearchNavigator( + ChatListController.init, SearchContainerController.init(_:) ), PresentRequestsNavigator( diff --git a/Sources/AppNavigation/PresentSearch.swift b/Sources/AppNavigation/PresentSearch.swift index 4dc89878f03d712d3ccf5486a4be4c3dcab82673..4b553b174121c096bd8673ce0636478b181ad459 100644 --- a/Sources/AppNavigation/PresentSearch.swift +++ b/Sources/AppNavigation/PresentSearch.swift @@ -4,17 +4,17 @@ import UIKit public struct PresentSearch: Action { /// - Parameters: /// - searching: Optional string to be searched upon further viewModel intialization - /// - replacing: Flag to differentiate if should be a push or a set stack + /// - fromOnboarding: Flag that differentiates if should be a push or a set stack /// - navigationController: Navigation controller on which will be pushed or stack should be set /// - animated: Animate the transition public init( - searching: String?, - replacing: Bool, + searching: String? = nil, + fromOnboarding: Bool = false, on navigationController: UINavigationController, animated: Bool = true ) { self.searching = searching - self.replacing = replacing + self.fromOnboarding = fromOnboarding self.navigationController = navigationController self.animated = animated } @@ -22,8 +22,8 @@ public struct PresentSearch: Action { /// Optional string to be searched upon further viewModel intialization public var searching: String? - /// Flag to differentiate if should be a push or a set stack - public var replacing: Bool + /// Flag that differentiates if should be a push or a set stack + public var fromOnboarding: Bool /// Navigation controller on which stack should be set public var navigationController: UINavigationController @@ -37,15 +37,23 @@ public struct PresentSearchNavigator: TypedNavigator { /// View controller which should be pushed or set in navigation stack var viewController: (String?) -> UIViewController + /// View controller which might have to be pushed below in navigation stack + var otherViewController: () -> UIViewController + /// - Parameters: /// - viewController: View controller which should be pushed or set in navigation stack - public init(_ viewController: @escaping (String?) -> UIViewController) { + /// - otherViewController: View controller which might have to be pushed below in navigation stack + public init( + _ otherViewController: @escaping () -> UIViewController, + _ viewController: @escaping (String?) -> UIViewController + ) { self.viewController = viewController + self.otherViewController = otherViewController } public func perform(_ action: PresentSearch, completion: @escaping () -> Void) { - if action.replacing { - action.navigationController.setViewControllers([viewController(action.searching)], animated: action.animated) + if action.fromOnboarding { + action.navigationController.setViewControllers([otherViewController(), viewController(action.searching)], animated: action.animated) } else { action.navigationController.pushViewController(viewController(action.searching), animated: action.animated) } diff --git a/Sources/ChatFeature/Helpers/CellConfigurator.swift b/Sources/ChatFeature/Helpers/CellConfigurator.swift index 63b7998cc93808fe34d6d97d32f6c9ddecb903e2..0abf3cca2b166b5fc63da479a23a019dbfb87046 100644 --- a/Sources/ChatFeature/Helpers/CellConfigurator.swift +++ b/Sources/ChatFeature/Helpers/CellConfigurator.swift @@ -198,7 +198,7 @@ extension CellFactory { return false } - return transfer(item.fileTransferId!).type == "jpeg" + return transfer(item.fileTransferId!).type == "image" }, build: { item, collectionView, indexPath in let ft = transfer(item.fileTransferId!) @@ -228,7 +228,7 @@ extension CellFactory { return false } - return transfer(item.fileTransferId!).type == "jpeg" + return transfer(item.fileTransferId!).type == "image" }, build: { item, collectionView, indexPath in let ft = transfer(item.fileTransferId!) diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift index 2456b811a8af7c96ced78c76b14f1e88e4a606bc..e083c9c4abe8c76603091b542bf5e78e8dc00cb5 100644 --- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift @@ -23,7 +23,6 @@ final class GroupChatViewModel { @Dependency(\.sendReport) var sendReport @Dependency(\.app.dbManager) var dbManager @Dependency(\.app.messenger) var messenger - @Dependency(\.groupManager) var groupManager @Dependency(\.app.hudManager) var hudManager @Dependency(\.app.toastManager) var toastManager @Dependency(\.reportingStatus) var reportingStatus @@ -106,18 +105,18 @@ final class GroupChatViewModel { replyMessageId: stagedReply?.messageId ) message = try dbManager.getDB().saveMessage(message) - let report = try groupManager.get()?.send( + let report = try messenger.groupChat()!.send( groupId: info.id, message: MessagePayload( text: text.trimmingCharacters(in: .whitespacesAndNewlines), replyingTo: stagedReply?.messageId ).encode() ) - message.networkId = report!.messageId - message.date = Date.fromTimestamp(Int(report!.timestamp)) + message.networkId = report.messageId + message.date = Date.fromTimestamp(Int(report.timestamp)) message = try dbManager.getDB().saveMessage(message) try messenger.cMix.get()?.waitForRoundResult( - roundList: try report!.encode(), + roundList: try report.encode(), timeoutMS: 15_000, callback: .init(handle: { result in switch result { @@ -139,18 +138,18 @@ final class GroupChatViewModel { var message = message message.status = .sending message = try dbManager.getDB().saveMessage(message) - let report = try groupManager.get()?.send( + let report = try messenger.groupChat()!.send( groupId: info.id, message: MessagePayload( text: message.text.trimmingCharacters(in: .whitespacesAndNewlines), replyingTo: stagedReply?.messageId ).encode() ) - message.networkId = report!.messageId - message.date = Date.fromTimestamp(Int(report!.timestamp)) + message.networkId = report.messageId + message.date = Date.fromTimestamp(Int(report.timestamp)) message = try dbManager.getDB().saveMessage(message) try messenger.cMix.get()?.waitForRoundResult( - roundList: try report!.encode(), + roundList: try report.encode(), timeoutMS: 15_000, callback: .init(handle: { result in switch result { diff --git a/Sources/ChatListFeature/Controller/ChatListController.swift b/Sources/ChatListFeature/Controller/ChatListController.swift index 8ad43f6efa8fea9ea98d3894f6726d9d7b75bbe4..7383b423c17c6acca5df900204af90d7db50c9d9 100644 --- a/Sources/ChatListFeature/Controller/ChatListController.swift +++ b/Sources/ChatListFeature/Controller/ChatListController.swift @@ -71,11 +71,7 @@ public final class ChatListController: UIViewController { .sink { [unowned self] in switch $0 { case .didTapSearch: - navigator.perform(PresentSearch( - searching: nil, - replacing: false, - on: navigationController! - )) + navigator.perform(PresentSearch(on: navigationController!)) case .didTapNewGroup: navigator.perform( PresentGroupDraft(on: navigationController!) @@ -218,11 +214,7 @@ public final class ChatListController: UIViewController { .searchButton .publisher(for: .touchUpInside) .sink { [unowned self] in - navigator.perform(PresentSearch( - searching: nil, - replacing: false, - on: navigationController! - )) + navigator.perform(PresentSearch(on: navigationController!)) }.store(in: &cancellables) screenView diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index 0e4c28520f58202435ff65d4d2c0a9069fd537fd..8fea8877f547bfdf7d205e95412cf2b783f9762d 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -27,7 +27,6 @@ typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchIte final class ChatListViewModel { @Dependency(\.app.dbManager) var dbManager @Dependency(\.app.messenger) var messenger - @Dependency(\.groupManager) var groupManager @Dependency(\.app.hudManager) var hudManager @Dependency(\.reportingStatus) var reportingStatus @@ -175,12 +174,9 @@ final class ChatListViewModel { } func leave(_ group: Group) { - guard let manager = groupManager.get() else { - return - } hudManager.show() do { - try manager.leaveGroup(groupId: group.id) + try messenger.groupChat()!.leaveGroup(groupId: group.id) try dbManager.getDB().deleteMessages(.init(chat: .group(group.id))) try dbManager.getDB().deleteGroup(group) hudManager.hide() diff --git a/Sources/ContactListFeature/ContactListController.swift b/Sources/ContactListFeature/ContactListController.swift index f44b63c0d760b2c0cd55d3a00f972dcb2d1b0e0d..36d8e8b4c65c2801480e0aca20ae60528e706b77 100644 --- a/Sources/ContactListFeature/ContactListController.swift +++ b/Sources/ContactListFeature/ContactListController.swift @@ -115,11 +115,7 @@ public final class ContactListController: UIViewController { .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) .sink { [unowned self] in - navigator.perform(PresentSearch( - searching: nil, - replacing: false, - on: navigationController! - )) + navigator.perform(PresentSearch(on: navigationController!)) }.store(in: &cancellables) viewModel @@ -141,11 +137,7 @@ public final class ContactListController: UIViewController { } @objc private func didTapSearch() { - navigator.perform(PresentSearch( - searching: nil, - replacing: false, - on: navigationController! - )) + navigator.perform(PresentSearch(on: navigationController!)) } @objc private func didTapScan() { diff --git a/Sources/CrashReport/CrashReport.swift b/Sources/CrashReport/CrashReport.swift index b5eceb577f093b6d61aafe6290cfe943c597ed36..8e6d2b7e4e793c70f16dfed720def386c783f1d0 100644 --- a/Sources/CrashReport/CrashReport.swift +++ b/Sources/CrashReport/CrashReport.swift @@ -1,5 +1,4 @@ import Firebase -import CrashReporting import FirebaseCrashlytics import XCTestDynamicOverlay diff --git a/Sources/CreateGroupFeature/CreateGroupViewModel.swift b/Sources/CreateGroupFeature/CreateGroupViewModel.swift index f63eccede0feef855c3b973d948627160c6d6f89..25ef6090123ff307f2f6555d56bade868c5f5ce8 100644 --- a/Sources/CreateGroupFeature/CreateGroupViewModel.swift +++ b/Sources/CreateGroupFeature/CreateGroupViewModel.swift @@ -19,7 +19,6 @@ struct CreateGroupViewModel { @Dependency(\.app.bgQueue) var bgQueue @Dependency(\.app.dbManager) var dbManager @Dependency(\.app.messenger) var messenger - @Dependency(\.groupManager) var groupManager @Dependency(\.app.hudManager) var hudManager var statePublisher: AnyPublisher<ViewState, Never> { @@ -44,10 +43,7 @@ struct CreateGroupViewModel { bgQueue.schedule { do { - guard let manager = groupManager.get() else { - fatalError("Can't create a group w/out a manager") - } - let report = try manager.makeGroup( + let report = try messenger.groupChat()!.makeGroup( membership: members.map(\.id), message: welcome?.data(using: .utf8), name: name.data(using: .utf8) diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift index 290a567e68cb5e72f81ed09f2ada856e1bfe16c3..99635cdab25184351ea1b3421562e185d9628870 100644 --- a/Sources/Defaults/KeyObject.swift +++ b/Sources/Defaults/KeyObject.swift @@ -1,5 +1,5 @@ import Foundation -import ComposableArchitecture +import Dependencies public enum Key: String { case email diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift index 9371455d9dc07804847e6642d731cccb3d452ec8..32e9b9b6fde7a67a9fafc4ef995bb1ae39d914d2 100644 --- a/Sources/LaunchFeature/LaunchController.swift +++ b/Sources/LaunchFeature/LaunchController.swift @@ -57,7 +57,7 @@ public final class LaunchController: UIViewController { case .search(username: let username): navigator.perform(PresentSearch( searching: username, - replacing: true, + fromOnboarding: true, on: navigationController!)) case .groupChat(id: let groupId): if let info = viewModel.getGroupInfoWith(groupId: groupId) { diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index 82798860455c662ca9e773e4dc2ea641852a3a08..68587689fcb00e19365560b9b1752c8f09db69d8 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -48,7 +48,6 @@ final class LaunchViewModel { @Dependency(\.app.dbManager) var dbManager @Dependency(\.keychain) var keychainManager @Dependency(\.updateErrors) var updateErrors - @Dependency(\.groupManager) var groupManager @Dependency(\.app.hudManager) var hudManager @Dependency(\.checkVersion) var checkVersion @Dependency(\.dummyTraffic) var dummyTraffic @@ -60,14 +59,18 @@ final class LaunchViewModel { @Dependency(\.processBannedList) var processBannedList @Dependency(\.app.authHandler) var authHandler + @Dependency(\.app.groupRequest) var groupRequest @Dependency(\.app.backupHandler) var backupHandler @Dependency(\.app.messageListener) var messageListener @Dependency(\.app.receiveFileHandler) var receiveFileHandler + @Dependency(\.app.groupMessageHandler) var groupMessageHandler var authHandlerCancellable: Cancellable? + var groupRequestCancellable: Cancellable? var backupHandlerCancellable: Cancellable? var networkHandlerCancellable: Cancellable? var receiveFileHandlerCancellable: Cancellable? + var groupMessageHandlerCancellable: Cancellable? var messageListenerHandlerCancellable: Cancellable? @KeyObject(.username, defaultValue: nil) var username: String? @@ -164,16 +167,16 @@ final class LaunchViewModel { extension LaunchViewModel { func setupMessenger() throws { authHandlerCancellable = authHandler { - print("\($0.localizedDescription)") + print($0.localizedDescription) } backupHandlerCancellable = backupHandler { - print("\($0.localizedDescription)") + print($0.localizedDescription) } receiveFileHandlerCancellable = receiveFileHandler { - print("\($0.localizedDescription)") + print($0.localizedDescription) } messageListenerHandlerCancellable = messageListener { - print("\($0.localizedDescription)") + print($0.localizedDescription) } if messenger.isLoaded() == false { @@ -214,10 +217,18 @@ extension LaunchViewModel { try? messenger.resumeBackup() } - try generateGroupManager() + groupRequestCancellable = groupRequest { + print($0) + } + + groupMessageHandlerCancellable = groupMessageHandler { + print($0) + } + + try messenger.startGroupChat() try messenger.trackServices { - print("\($0.localizedDescription)") + print($0.localizedDescription) } try messenger.startFileTransfer() diff --git a/Sources/LaunchFeature/ViewModel+GroupManager.swift b/Sources/LaunchFeature/ViewModel+GroupManager.swift deleted file mode 100644 index c0bef6c8933501f14705df1a1022aaed2ca643e8..0000000000000000000000000000000000000000 --- a/Sources/LaunchFeature/ViewModel+GroupManager.swift +++ /dev/null @@ -1,192 +0,0 @@ -import Shared -import AppCore -import XXModels -import XXClient -import Foundation -import XXMessengerClient - -extension LaunchViewModel { - func generateGroupManager() throws { - groupManager.set(try NewGroupChat.live( - e2eId: messenger.e2e()!.getId(), - groupRequest: .init(handle: { [weak self] group in - guard let self else { return } - self.handleGroupRequest(from: group, messenger: self.messenger) - }), - groupChatProcessor: .init(handle: { [weak self] result in - guard let self else { return } - - switch result { - case .success(let cb): - do { - let payload = try MessagePayload.decode(cb.decryptedMessage.payload) - - try self.dbManager.getDB().saveMessage( - .init( - networkId: cb.decryptedMessage.messageId, - senderId: cb.decryptedMessage.senderId, - recipientId: nil, - groupId: cb.decryptedMessage.groupId, - date: Date.fromTimestamp(Int(cb.decryptedMessage.timestamp)), - status: .received, - isUnread: true, - text: payload.text, - replyMessageId: payload.replyingTo, - roundURL: cb.roundUrl - ) - ) - } catch { - print(error.localizedDescription) - } - case .failure(let error): - print(error.localizedDescription) - } - }) - )) - } - - func handleGroupRequest(from group: XXClient.Group, messenger: Messenger) { - if let _ = try? dbManager.getDB().fetchGroups(.init(id: [group.getId()])).first { return } - - guard var members = try? group.getMembership(), let leader = members.first else { - fatalError("Failed to get group membership/leader") - } - - try! dbManager.getDB().saveGroup(.init( - id: group.getId(), - name: String(data: group.getName(), encoding: .utf8)!, - leaderId: leader.id, - createdAt: Date.fromMSTimestamp(group.getCreatedMS()), - authStatus: .pending, - serialized: group.serialize() - )) - - if let initMessageData = group.getInitMessage(), - let initMessage = String(data: initMessageData, encoding: .utf8) { - try! dbManager.getDB().saveMessage(.init( - senderId: leader.id, - recipientId: nil, - groupId: group.getId(), - date: Date.fromMSTimestamp(group.getCreatedMS()), - status: .received, - isUnread: true, - text: initMessage - )) - } - - let friends = try! dbManager.getDB().fetchContacts(.init( - id: Set(members.map(\.id)), - authStatus: [ - .friend, - .hidden, - .requesting, - .confirming, - .verificationInProgress, - .verified, - .requested, - .requestFailed, - .verificationFailed, - .confirmationFailed - ] - )) - - let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id))) - - strangers.forEach { - if let stranger = try? dbManager.getDB().fetchContacts(.init(id: [$0])).first { - print(">>> This is a stranger, but I already knew about his/her existance: \(stranger.id.base64EncodedString().prefix(10))...") - } else { - try! dbManager.getDB().saveContact(.init( - id: $0, - marshaled: nil, - username: "Fetching...", - email: nil, - phone: nil, - nickname: nil, - photo: nil, - authStatus: .stranger, - isRecent: false, - isBlocked: false, - isBanned: false, - createdAt: Date.fromMSTimestamp(group.getCreatedMS()) - )) - } - } - - members.forEach { - let model = XXModels.GroupMember(groupId: group.getId(), contactId: $0.id) - _ = try? dbManager.getDB().saveGroupMember(model) - } - - do { - let multiLookup = try messenger.lookupContacts(ids: strangers.map { $0 }) - - for user in multiLookup.contacts { - if var foo = try? self.dbManager.getDB().fetchContacts(.init(id: [user.getId()])).first, - let username = try? user.getFact(.username)?.value { - foo.username = username - _ = try? self.dbManager.getDB().saveContact(foo) - } - } - } catch { - // TODO - } - } -} - - - -//func handleIncomingTransfer(_ receivedFile: ReceivedFile) { -// if var model = try? dbManager.getDB().saveFileTransfer(.init( -// id: receivedFile.transferId, -// contactId: receivedFile.senderId, -// name: receivedFile.name, -// type: receivedFile.type, -// data: nil, -// progress: 0.0, -// isIncoming: true, -// createdAt: Date() -// )) { -// try! dbManager.getDB().saveMessage(.init( -// networkId: nil, -// senderId: receivedFile.senderId, -// recipientId: messenger.e2e.get()!.getContact().getId(), -// groupId: nil, -// date: Date(), -// status: .receiving, -// isUnread: false, -// text: "", -// replyMessageId: nil, -// roundURL: nil, -// fileTransferId: model.id -// )) -// -// if let manager: XXClient.FileTransfer = try? DI.Container.shared.resolve() { -// print(">>> registerReceivedProgressCallback") -// -// try! manager.registerReceivedProgressCallback( -// transferId: receivedFile.transferId, -// period: 1_000, -// callback: .init(handle: { [weak self] in -// guard let self else { return } -// switch $0 { -// case .success(let cb): -// if cb.progress.completed { -// model.progress = 100 -// model.data = try! manager.receive(transferId: receivedFile.transferId) -// } else { -// model.progress = Float(cb.progress.transmitted/cb.progress.total) -// } -// -// model = try! self.dbManager.getDB().saveFileTransfer(model) -// -// case .failure(let error): -// print(error.localizedDescription) -// } -// }) -// ) -// } else { -// //print(DI.Container.shared.dependencies) -// } -// } -//} diff --git a/Sources/MenuFeature/Controllers/MenuController.swift b/Sources/MenuFeature/Controllers/MenuController.swift index e02c7e62f37001cbb9780905c5fe475070d98f62..17a9a73505fc73230b5fad387c0dd9c247621ea1 100644 --- a/Sources/MenuFeature/Controllers/MenuController.swift +++ b/Sources/MenuFeature/Controllers/MenuController.swift @@ -196,7 +196,7 @@ public final class MenuController: UIViewController { guard let self, self.currentItem != .share else { return } self.navigator.perform(PresentActivitySheet(items: [ Localized.Menu.shareContent(self.viewModel.referralDeeplink) - ], from: self)) + ], from: self.navController!.topViewController!)) } }.store(in: &cancellables) @@ -247,6 +247,6 @@ public final class MenuController: UIViewController { spacingAfter: 39 ), actionButton - ], isDismissable: true, from: self)) + ], isDismissable: true, from: navController!.topViewController!)) } } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift index 6fa2993c058cd37554b92efd4dea45209a490cfc..92cdda4e7d229e51328343edce602841a14bb529 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift @@ -107,7 +107,10 @@ public final class OnboardingCodeController: UIViewController { if isEmail { navigator.perform(PresentOnboardingPhone(on: navigationController!)) } else { - navigator.perform(PresentChatList(on: navigationController!)) + navigator.perform(PresentSearch( + fromOnboarding: true, + on: navigationController! + )) } }.store(in: &cancellables) diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift index 466a45994a90979bdcbf09e427efad88cef93b24..4eadd53e32ffa9fa633f2fd61824dab9e871c70b 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift @@ -86,7 +86,10 @@ public final class OnboardingPhoneController: UIViewController { .skipButton .publisher(for: .touchUpInside) .sink { [unowned self] in - navigator.perform(PresentChatList(on: navigationController!)) + navigator.perform(PresentSearch( + fromOnboarding: true, + on: navigationController! + )) }.store(in: &cancellables) screenView diff --git a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift index f3bc2af0248b805fae258212d7573d89c9586dc5..701e1359cea59cfd2912d099b7d6fe897aa2de64 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift @@ -45,7 +45,10 @@ public final class OnboardingWelcomeController: UIViewController { .skipButton .publisher(for: .touchUpInside) .sink { [unowned self] in - navigator.perform(PresentChatList(on: navigationController!)) + navigator.perform(PresentSearch( + fromOnboarding: true, + on: navigationController! + )) }.store(in: &cancellables) screenView.didTapInfo = { [weak self] in diff --git a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift index 11a4270c77ca96b452a2bbbcc1fcb2d3f9df560f..49ab70bc1481be2c6797d9af64c2c3162c2026b3 100644 --- a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift @@ -79,11 +79,7 @@ public final class RequestsContainerController: UIViewController { .connectionsPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in - navigator.perform(PresentSearch( - searching: nil, - replacing: false, - on: navigationController! - )) + navigator.perform(PresentSearch(on: navigationController!)) }.store(in: &cancellables) screenView diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift index eb8469c67044a3cb962927a389da2831eb56ee4b..46d4fe29bc99ebb71207264a27663a1f16de7731 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift @@ -20,12 +20,10 @@ struct RequestReceived: Hashable, Equatable { } final class RequestsReceivedViewModel { - @Dependency(\.app.dbManager) var dbManager: DBManager - @Dependency(\.app.messenger) var messenger: Messenger - @Dependency(\.app.hudManager) var hudManager: HUDManager - @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus - - //@Dependency var groupManager: GroupChat + @Dependency(\.app.dbManager) var dbManager + @Dependency(\.app.messenger) var messenger + @Dependency(\.app.hudManager) var hudManager + @Dependency(\.reportingStatus) var reportingStatus @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool @@ -193,8 +191,8 @@ final class RequestsReceivedViewModel { guard let self else { return } do { - //try self.groupManager.joinGroup(serializedGroupData: group.serialized) - + try self.messenger.groupChat()!.joinGroup(serializedGroupData: group.serialized) + var group = group group.authStatus = .participating try self.dbManager.getDB().saveGroup(group) diff --git a/Sources/WebsiteFeature/WebsiteController.swift b/Sources/WebsiteFeature/WebsiteController.swift index 822ebe38b883047de4c3f02157b883a2c0980bb6..dacb0998608f3f43972f01458dcca6e6357c2c8c 100644 --- a/Sources/WebsiteFeature/WebsiteController.swift +++ b/Sources/WebsiteFeature/WebsiteController.swift @@ -11,17 +11,14 @@ public final class WebsiteController: UIViewController { super.init(nibName: nil, bundle: nil) } + public override func loadView() { + view = webView + } + required init?(coder: NSCoder) { nil } public override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .white - view.addSubview(webView) - - webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true DispatchQueue.main.async { [weak self] in guard let self else { return }