From 62043ef1e3d80405bdd8504d5a147c3f86aa2cee Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Wed, 10 Apr 2024 21:40:31 +0800 Subject: [PATCH 1/2] Handle request in queue --- Mixin.xcodeproj/project.pbxproj | 4 + Mixin/AppDelegate.swift | 1 + Mixin/Service/Web3/WalletConnectService.swift | 86 ++++-------------- Mixin/Service/Web3/WalletConnectSession.swift | 26 +++--- Mixin/Service/Web3/Web3PopupCoordinator.swift | 89 +++++++++++++++++++ .../Home/Web3WalletViewController.swift | 8 +- .../ConnectWalletViewController.swift | 11 ++- .../SignRequestViewController.swift | 17 +++- .../TransactionRequestViewController.swift | 17 +++- .../UnlockWeb3WalletViewController.swift | 16 +--- .../AuthenticationPreviewViewController.swift | 5 +- .../Database/User/DAO/PropertiesDAO.swift | 10 ++- 12 files changed, 183 insertions(+), 107 deletions(-) create mode 100644 Mixin/Service/Web3/Web3PopupCoordinator.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 9e65a9e81e..3f314315e1 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -929,6 +929,7 @@ 94E619B92645BAA000A38049 /* SuspiciousLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94E619B82645BAA000A38049 /* SuspiciousLinkView.xib */; }; 94E82DBC2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94E82DBB2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib */; }; 94E8913925C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 94E8913825C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist */; }; + 94E9C0962BC6978F00D6157C /* Web3PopupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */; }; 94ECCCAB261A029F004E9E2A /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ECCCAA261A029F004E9E2A /* Window.swift */; }; 94ED08B02B5FAFC900493312 /* AppUserSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ED08AF2B5FAFC900493312 /* AppUserSearchResult.swift */; }; 94ED08B62B5FB1C300493312 /* ExploreActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ED08B42B5FB1C300493312 /* ExploreActionCell.swift */; }; @@ -2127,6 +2128,7 @@ 94E619B82645BAA000A38049 /* SuspiciousLinkView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SuspiciousLinkView.xib; sourceTree = ""; }; 94E82DBB2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoadingIndicatorTableFooterView.xib; sourceTree = ""; }; 94E8913825C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = "Pods-Mixin-acknowledgements.plist"; path = "Pods/Target Support Files/Pods-Mixin/Pods-Mixin-acknowledgements.plist"; sourceTree = SOURCE_ROOT; }; + 94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3PopupCoordinator.swift; sourceTree = ""; }; 94ECCCAA261A029F004E9E2A /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; 94ED08AF2B5FAFC900493312 /* AppUserSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUserSearchResult.swift; sourceTree = ""; }; 94ED08B42B5FB1C300493312 /* ExploreActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreActionCell.swift; sourceTree = ""; }; @@ -3108,6 +3110,7 @@ 942F4728299CDF1800528759 /* URLSessionWebSocketFactory.swift */, 942F472A299CDF8100528759 /* Web3SignerFactory.swift */, 94C0D7E829AD348500E372FC /* InPlaceKeyStorage.swift */, + 94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */, 94E35CA2298B836300ADB40D /* WalletConnectService.swift */, 940764C229B2633C00B779A6 /* WalletConnectDecodedSigningRequest.swift */, 947C5E5A29B0FE9B00838458 /* WalletConnectSession.swift */, @@ -5431,6 +5434,7 @@ 7B5A748F230274E200C6107E /* GroupIconMaker.swift in Sources */, DFC0ED0923BB7D990091E7AC /* GiphyImage.swift in Sources */, 7CEB735E29DBC44A006FB5B2 /* DeviceTransferParticipant.swift in Sources */, + 94E9C0962BC6978F00D6157C /* Web3PopupCoordinator.swift in Sources */, 7CEB735C29DBB737006FB5B2 /* DeviceTransferConversation.swift in Sources */, 7B0B01921FEA19BA000EEE4F /* DecryptionFailedMessageViewModel.swift in Sources */, 7B2E3E4B1FA07F4500DDDDEB /* SelectCountryViewController.swift in Sources */, diff --git a/Mixin/AppDelegate.swift b/Mixin/AppDelegate.swift index b924b2bb31..51910e1d6e 100644 --- a/Mixin/AppDelegate.swift +++ b/Mixin/AppDelegate.swift @@ -241,6 +241,7 @@ extension AppDelegate { WKWebsiteDataStore.default().removeAuthenticationRelatedData() BackupJobQueue.shared.cancelAllOperations() WalletConnectService.shared.disconnectAllSessions() + Web3PopupCoordinator.rejectAllPopups() UIApplication.shared.setShortcutItemsEnabled(false) UIApplication.shared.applicationIconBadgeNumber = 1 diff --git a/Mixin/Service/Web3/WalletConnectService.swift b/Mixin/Service/Web3/WalletConnectService.swift index 4b9063c301..4f9306ef89 100644 --- a/Mixin/Service/Web3/WalletConnectService.swift +++ b/Mixin/Service/Web3/WalletConnectService.swift @@ -33,10 +33,6 @@ final class WalletConnectService { private var subscribes = Set() - // Only one request or proposal can be presented at a time - // New incoming requests will be rejected if `presentedViewController` is not nil - private weak var presentedViewController: UIViewController? - private init() { Networking.configure(groupIdentifier: appGroupIdentifier, projectId: MixinKeys.walletConnect, @@ -109,40 +105,6 @@ final class WalletConnectService { } } - func presentRequest(viewController: UIViewController) { - guard let container = UIApplication.homeContainerViewController else { - return - } - let hasPendingRequest: Bool - if let presentedViewController { - // FIXME: Workaround for the issue that requests can't be processed. In some cases, even after the previous - // request has been dismissed, the presentedViewController remains non-nil, preventing the handling of - // subsequent requests. There might be a retain cycle somewhere inside the view controller. - hasPendingRequest = presentedViewController.presentingViewController != nil - if hasPendingRequest { - Logger.web3.warn(category: "Service", message: "Previous request not released") - } - } else { - hasPendingRequest = false - } - guard !hasPendingRequest else { - presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.request_rejected_reason_another_request_in_process()) - return - } - container.presentOnTopMostPresentedController(viewController, animated: true) - presentedViewController = viewController - } - - func presentRejection(title: String, message: String) { - guard let container = UIApplication.homeContainerViewController else { - return - } - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: R.string.localizable.ok(), style: .cancel)) - container.presentOnTopMostPresentedController(alert, animated: true) - } - private func reloadSessions(sessions: [WalletConnectSign.Session]) { self.sessions = sessions.map(WalletConnectSession.init(session:)) let topics = self.sessions.map(\.topic) @@ -300,8 +262,9 @@ extension WalletConnectService { requiredNamespaces = requiredChains.joined(separator: ", ") } DispatchQueue.main.async { - self.presentRejection(title: "Chain not supported", - message: "\(proposal.proposer.name) requires to support \(requiredNamespaces)") + let title = "Chain not supported" + let message = "\(proposal.proposer.name) requires to support \(requiredNamespaces)" + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) } Task { try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .unsupportedChains) @@ -315,8 +278,9 @@ extension WalletConnectService { logger.warn(category: "Service", message: "Requires to support \(proposalEvents)") let events = proposalEvents.joined(separator: ", ") DispatchQueue.main.async { - self.presentRejection(title: "Chain not supported", - message: "\(proposal.proposer.name) requires to support \(events))") + let title = "Chain not supported" + let message = "\(proposal.proposer.name) requires to support \(events)" + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) } Task { try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .upsupportedEvents) @@ -325,32 +289,15 @@ extension WalletConnectService { } let account: String? = PropertiesDAO.shared.value(forKey: .evmAddress) - if account == nil { - DispatchQueue.main.async { - let unlock = UnlockWeb3WalletViewController(chain: chains[0]) - unlock.onDismiss = { isUnlocked in - if isUnlocked { - self.presentedViewController = nil // Value may not released immediately - let connectWallet = ConnectWalletViewController(proposal: proposal, - chains: chains.map(\.caip2), - events: Array(events)) - self.presentRequest(viewController: connectWallet) - } else { - Task { - try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .userRejected) - } - } - } - self.presentRequest(viewController: unlock) - } - } else { - logger.info(category: "Service", message: "Showing: \(proposal))") - DispatchQueue.main.async { - let connectWallet = ConnectWalletViewController(proposal: proposal, - chains: chains.map(\.caip2), - events: Array(events)) - self.presentRequest(viewController: connectWallet) + DispatchQueue.main.async { + if account == nil { + let controller = UnlockWeb3WalletViewController(chain: chains[0]) + Web3PopupCoordinator.enqueue(popup: .unlock(controller)) } + let connectWallet = ConnectWalletViewController(proposal: proposal, + chains: chains.map(\.caip2), + events: Array(events)) + Web3PopupCoordinator.enqueue(popup: .request(connectWallet)) } } } @@ -366,8 +313,9 @@ extension WalletConnectService { let error = JSONRPCError(code: -1, message: "Missing session") try await Web3Wallet.instance.respond(topic: topic, requestId: request.id, response: .error(error)) } - self.presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.session_not_found()) + let title = R.string.localizable.request_rejected() + let message = R.string.localizable.session_not_found() + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) } } } diff --git a/Mixin/Service/Web3/WalletConnectSession.swift b/Mixin/Service/Web3/WalletConnectSession.swift index ac0d279b40..27919936eb 100644 --- a/Mixin/Service/Web3/WalletConnectSession.swift +++ b/Mixin/Service/Web3/WalletConnectSession.swift @@ -65,8 +65,9 @@ final class WalletConnectSession { case .ethSignTypedData, .ethSignTypedDataV4: requestETHSignTypedData(with: request) case .ethSignTransaction: - WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.method_not_supported(request.method)) + let title = R.string.localizable.request_rejected() + let message = R.string.localizable.method_not_supported(request.method) + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) Logger.web3.warn(category: "Session", message: "eth_signTransaction rejected") Task { try await Web3Wallet.instance.respond(topic: request.topic, @@ -76,8 +77,9 @@ final class WalletConnectSession { case .ethSendTransaction: requestSendTransaction(with: request) case .none: - WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.method_not_supported(request.method)) + let title = R.string.localizable.request_rejected() + let message = R.string.localizable.method_not_supported(request.method) + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) Logger.web3.warn(category: "Session", message: "Unknown method: \(request.method)") Task { try await Web3Wallet.instance.respond(topic: request.topic, @@ -103,8 +105,9 @@ extension WalletConnectSession { Task { try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(.methodNotFound)) } - WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.method_not_supported(request.method)) + let title = R.string.localizable.request_rejected() + let message = R.string.localizable.method_not_supported(request.method) + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) } private func requestETHSignTypedData(with request: Request) { @@ -147,13 +150,14 @@ extension WalletConnectSession { transaction: transactionPreview, chain: chain, chainToken: chainToken) - WalletConnectService.shared.presentRequest(viewController: transactionRequest) + Web3PopupCoordinator.enqueue(popup: .request(transactionRequest)) } } catch { Logger.web3.error(category: "Session", message: "Failed to request tx: \(error)") DispatchQueue.main.async { - WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(), - message: R.string.localizable.unable_to_decode_the_request(error.localizedDescription)) + let title = R.string.localizable.request_rejected() + let message = R.string.localizable.unable_to_decode_the_request(error.localizedDescription) + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) } Task { let error = JSONRPCError(code: 0, message: "Local failed") @@ -175,12 +179,12 @@ extension WalletConnectSession { throw Error.noAccount } let signRequest = SignRequestViewController(address: address, session: self, request: decoded) - WalletConnectService.shared.presentRequest(viewController: signRequest) + Web3PopupCoordinator.enqueue(popup: .request(signRequest)) } catch { Logger.web3.error(category: "Session", message: "Failed to sign: \(error)") let title = R.string.localizable.request_rejected() let message = R.string.localizable.unable_to_decode_the_request(error.localizedDescription) - WalletConnectService.shared.presentRejection(title: title, message: message) + Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message)) Task { let error = JSONRPCError(code: 0, message: error.localizedDescription) try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(error)) diff --git a/Mixin/Service/Web3/Web3PopupCoordinator.swift b/Mixin/Service/Web3/Web3PopupCoordinator.swift new file mode 100644 index 0000000000..f247cb3f96 --- /dev/null +++ b/Mixin/Service/Web3/Web3PopupCoordinator.swift @@ -0,0 +1,89 @@ +import UIKit + +protocol Web3PopupViewController: UIViewController { + + var onDismiss: (() -> Void)? { get set } + + func reject() + +} + +struct Web3PopupCoordinator { + + private class AlertController: UIAlertController { + + var onDismiss: (() -> Void)? + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + onDismiss?() + } + + } + + enum Popup { + case unlock(UnlockWeb3WalletViewController) + case request(Web3PopupViewController) + case rejection(title: String, message: String) + } + + private static var popups: [Popup] = [] + + static func enqueue(popup: Popup) { + popups.append(popup) + if popups.count == 1 { + presentNextPopupIfNeeded() + } + } + + static func rejectAllPopups() { + for popup in popups { + switch popup { + case .request(let controller): + controller.reject() + case .unlock, .rejection: + break + } + } + popups = [] + } + + private static func presentNextPopupIfNeeded() { + guard let container = UIApplication.homeContainerViewController else { + return + } + guard let popup = popups.first else { + return + } + let viewController: UIViewController + switch popup { + case .unlock(let controller): + // TODO: Tell user subsequent request will fail if not approved + controller.onDismiss = { + popups.removeFirst() + if controller.isUnlocked { + presentNextPopupIfNeeded() + } else { + rejectAllPopups() + } + } + viewController = controller + case .request(let controller): + controller.onDismiss = { + popups.removeFirst() + presentNextPopupIfNeeded() + } + viewController = controller + case .rejection(let title, let message): + let alert = AlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: R.string.localizable.ok(), style: .cancel)) + alert.onDismiss = { + popups.removeFirst() + presentNextPopupIfNeeded() + } + viewController = alert + } + container.presentOnTopMostPresentedController(viewController, animated: true) + } + +} diff --git a/Mixin/UserInterface/Controllers/Home/Web3WalletViewController.swift b/Mixin/UserInterface/Controllers/Home/Web3WalletViewController.swift index 4fb02b7a9c..ce0c7499b0 100644 --- a/Mixin/UserInterface/Controllers/Home/Web3WalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Home/Web3WalletViewController.swift @@ -51,7 +51,7 @@ final class Web3WalletViewController: UIViewController { selector: #selector(propertiesDidUpdate(_:)), name: PropertiesDAO.propertyDidUpdateNotification, object: nil) - address = PropertiesDAO.shared.value(forKey: .evmAddress) + address = PropertiesDAO.shared.unsafeValue(forKey: .evmAddress) if let address { tableHeaderView.showCopyAddress(chain: chain, address: address) } else { @@ -84,6 +84,12 @@ final class Web3WalletViewController: UIViewController { } private func reloadDapps(web3Chains: [String: Web3Chain]) { +#if DEBUG + if let dapps = web3Chains[chain.internalID]?.dapps { + self.dapps = dapps + tableView.reloadData() + } +#endif tableView.tableFooterView = nil } diff --git a/Mixin/UserInterface/Controllers/Wallet Connect/ConnectWalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet Connect/ConnectWalletViewController.swift index 78577d47e3..ea2951c6aa 100644 --- a/Mixin/UserInterface/Controllers/Wallet Connect/ConnectWalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet Connect/ConnectWalletViewController.swift @@ -42,7 +42,7 @@ final class ConnectWalletViewController: AuthenticationPreviewViewController { var rows: [Row] = [ .proposer(name: proposal.proposer.name, host: host), ] - if let account: String = PropertiesDAO.shared.value(forKey: .evmAddress) { + if let account: String = PropertiesDAO.shared.unsafeValue(forKey: .evmAddress) { // TODO: Get account by `self.request` if blockchain other than EVMs is supported rows.append(.info(caption: .account, content: account)) } @@ -63,6 +63,7 @@ final class ConnectWalletViewController: AuthenticationPreviewViewController { } override func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + super.presentationControllerDidDismiss(presentationController) rejectProposalIfNotApproved() } @@ -122,6 +123,14 @@ final class ConnectWalletViewController: AuthenticationPreviewViewController { return } Logger.web3.info(category: "Connect", message: "Rejected by dismissing") + reject() + } + +} + +extension ConnectWalletViewController: Web3PopupViewController { + + func reject() { Task { try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .userRejected) } diff --git a/Mixin/UserInterface/Controllers/Wallet Connect/SignRequestViewController.swift b/Mixin/UserInterface/Controllers/Wallet Connect/SignRequestViewController.swift index 6fff018083..d3e51aa719 100644 --- a/Mixin/UserInterface/Controllers/Wallet Connect/SignRequestViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet Connect/SignRequestViewController.swift @@ -55,6 +55,7 @@ final class SignRequestViewController: AuthenticationPreviewViewController { } override func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + super.presentationControllerDidDismiss(presentationController) rejectRequestIfSignatureNotSent() } @@ -120,6 +121,17 @@ final class SignRequestViewController: AuthenticationPreviewViewController { } +extension SignRequestViewController: Web3PopupViewController { + + func reject() { + Task { + let error = JSONRPCError(code: 0, message: "User Rejected") + try await Web3Wallet.instance.respond(topic: request.raw.topic, requestId: request.raw.id, response: .error(error)) + } + } + +} + extension SignRequestViewController { @objc private func resendSignature(_ sender: Any) { @@ -178,10 +190,7 @@ extension SignRequestViewController { return } Logger.web3.info(category: "Sign", message: "Rejected by dismissing") - Task { - let error = JSONRPCError(code: 0, message: "User Rejected") - try await Web3Wallet.instance.respond(topic: request.raw.topic, requestId: request.raw.id, response: .error(error)) - } + reject() } } diff --git a/Mixin/UserInterface/Controllers/Wallet Connect/TransactionRequestViewController.swift b/Mixin/UserInterface/Controllers/Wallet Connect/TransactionRequestViewController.swift index 2c1e82ca60..e2b4f28a03 100644 --- a/Mixin/UserInterface/Controllers/Wallet Connect/TransactionRequestViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet Connect/TransactionRequestViewController.swift @@ -99,6 +99,7 @@ final class TransactionRequestViewController: AuthenticationPreviewViewControlle } override func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + super.presentationControllerDidDismiss(presentationController) rejectTransactionIfSignatureNotSent() } @@ -171,6 +172,17 @@ final class TransactionRequestViewController: AuthenticationPreviewViewControlle } +extension TransactionRequestViewController: Web3PopupViewController { + + func reject() { + Task { + let error = JSONRPCError(code: 0, message: "User rejected") + try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(error)) + } + } + +} + extension TransactionRequestViewController { private struct Fee { @@ -322,10 +334,7 @@ extension TransactionRequestViewController { return } Logger.web3.info(category: "TxnRequest", message: "Rejected by dismissing") - Task { - let error = JSONRPCError(code: 0, message: "User rejected") - try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(error)) - } + reject() } private func reloadFeeRow(with selected: Fee) { diff --git a/Mixin/UserInterface/Controllers/Wallet Connect/UnlockWeb3WalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet Connect/UnlockWeb3WalletViewController.swift index 3f3d34c3ce..f88a8fb100 100644 --- a/Mixin/UserInterface/Controllers/Wallet Connect/UnlockWeb3WalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet Connect/UnlockWeb3WalletViewController.swift @@ -8,11 +8,9 @@ final class UnlockWeb3WalletViewController: AuthenticationPreviewViewController case mismatched } - private let chain: WalletConnectService.Chain - - var onDismiss: ((_ isUnlocked: Bool) -> Void)? + private(set) var isUnlocked = false - private var isUnlocked = false + private let chain: WalletConnectService.Chain private var subtitle: String { R.string.localizable.unlock_web3_account_description(chain.name) @@ -65,16 +63,6 @@ final class UnlockWeb3WalletViewController: AuthenticationPreviewViewController animation: animated ? .vertical : nil) } - override func close(_ sender: Any) { - presentingViewController?.dismiss(animated: true) { [onDismiss, isUnlocked] in - onDismiss?(isUnlocked) - } - } - - override func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - onDismiss?(isUnlocked) - } - override func performAction(with pin: String) { Logger.web3.info(category: "Unlock", message: "Will unlock web3") canDismissInteractively = false diff --git a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewViewController.swift index 8c797aae72..7b293e4a5f 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewViewController.swift @@ -8,6 +8,7 @@ class AuthenticationPreviewViewController: UIViewController { let tableHeaderView = R.nib.authenticationPreviewHeaderView(withOwner: nil)! var canDismissInteractively = true + var onDismiss: (() -> Void)? private var rows: [Row] = [] @@ -141,7 +142,7 @@ extension AuthenticationPreviewViewController: UIAdaptivePresentationControllerD } func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - + onDismiss?() } } @@ -369,7 +370,7 @@ extension AuthenticationPreviewViewController { } @objc func close(_ sender: Any) { - presentingViewController?.dismiss(animated: true) + presentingViewController?.dismiss(animated: true, completion: onDismiss) } } diff --git a/MixinServices/MixinServices/Database/User/DAO/PropertiesDAO.swift b/MixinServices/MixinServices/Database/User/DAO/PropertiesDAO.swift index cace2e9414..b670c1256a 100644 --- a/MixinServices/MixinServices/Database/User/DAO/PropertiesDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/PropertiesDAO.swift @@ -18,8 +18,16 @@ public final class PropertiesDAO: UserDatabaseDAO { public static let shared = PropertiesDAO() + // Maybe not the newest + public func unsafeValue(forKey key: Key) -> Value? { + try? db.read { db -> Value? in + try value(forKey: key, db: db) + } + } + public func value(forKey key: Key) -> Value? { - try? db.writeAndReturnError { db -> Value? in + assert(!Thread.isMainThread) // Prevent deadlock + return try? db.writeAndReturnError { db -> Value? in try value(forKey: key, db: db) } } From 6978789d0c6b1b341b5467507a5b999c720ecf95 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Fri, 12 Apr 2024 20:30:24 +0800 Subject: [PATCH 2/2] New explore design --- Mixin.xcodeproj/project.pbxproj | 12 +- .../Home/ExploreAllAppsViewController.swift | 98 -------- .../Home/ExploreBotsViewController.swift | 220 ++++++++++++++++++ .../Home/ExploreFavoriteViewController.swift | 156 ------------- .../Home/ExploreViewController.swift | 29 +-- 5 files changed, 228 insertions(+), 287 deletions(-) delete mode 100644 Mixin/UserInterface/Controllers/Home/ExploreAllAppsViewController.swift create mode 100644 Mixin/UserInterface/Controllers/Home/ExploreBotsViewController.swift delete mode 100644 Mixin/UserInterface/Controllers/Home/ExploreFavoriteViewController.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 3f314315e1..d1fbd5f6c4 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -851,9 +851,8 @@ 94B2CD782B0F88D300D268E1 /* DepositView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B2CD772B0F88D300D268E1 /* DepositView.xib */; }; 94B5776A2B5E554800AE576E /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577692B5E554800AE576E /* ExploreViewController.swift */; }; 94B5776F2B5E555B00AE576E /* ExploreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B5776E2B5E555B00AE576E /* ExploreView.xib */; }; - 94B577772B5E6E3700AE576E /* ExploreFavoriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577752B5E6E3700AE576E /* ExploreFavoriteViewController.swift */; }; 94B5777D2B5E755100AE576E /* SeparatorHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B5777C2B5E755100AE576E /* SeparatorHeaderFooterView.swift */; }; - 94B577822B5E79B800AE576E /* ExploreAllAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577812B5E79B800AE576E /* ExploreAllAppsViewController.swift */; }; + 94B577822B5E79B800AE576E /* ExploreBotsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577812B5E79B800AE576E /* ExploreBotsViewController.swift */; }; 94B577872B5EC11500AE576E /* ExploreAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577862B5EC11500AE576E /* ExploreAction.swift */; }; 94B61EC32B344A7A00BD39AF /* HomeTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B61EC22B344A7A00BD39AF /* HomeTabBarController.swift */; }; 94B6EDFE2B707F15001DCC50 /* WithdrawPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B6EDFD2B707F15001DCC50 /* WithdrawPreviewViewController.swift */; }; @@ -2054,9 +2053,8 @@ 94B2CD772B0F88D300D268E1 /* DepositView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DepositView.xib; sourceTree = ""; }; 94B577692B5E554800AE576E /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = ""; }; 94B5776E2B5E555B00AE576E /* ExploreView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExploreView.xib; sourceTree = ""; }; - 94B577752B5E6E3700AE576E /* ExploreFavoriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreFavoriteViewController.swift; sourceTree = ""; }; 94B5777C2B5E755100AE576E /* SeparatorHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorHeaderFooterView.swift; sourceTree = ""; }; - 94B577812B5E79B800AE576E /* ExploreAllAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreAllAppsViewController.swift; sourceTree = ""; }; + 94B577812B5E79B800AE576E /* ExploreBotsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreBotsViewController.swift; sourceTree = ""; }; 94B577862B5EC11500AE576E /* ExploreAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreAction.swift; sourceTree = ""; }; 94B61EC22B344A7A00BD39AF /* HomeTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTabBarController.swift; sourceTree = ""; }; 94B6EDFD2B707F15001DCC50 /* WithdrawPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawPreviewViewController.swift; sourceTree = ""; }; @@ -3849,8 +3847,7 @@ 9473F8272AE51097004E5739 /* RegisterToSafeViewController.swift */, 94B5776E2B5E555B00AE576E /* ExploreView.xib */, 94B577692B5E554800AE576E /* ExploreViewController.swift */, - 94B577752B5E6E3700AE576E /* ExploreFavoriteViewController.swift */, - 94B577812B5E79B800AE576E /* ExploreAllAppsViewController.swift */, + 94B577812B5E79B800AE576E /* ExploreBotsViewController.swift */, 94B13FC12B5ED5F9001333BE /* ExploreSearchView.xib */, 94B13FC02B5ED5F9001333BE /* ExploreSearchViewController.swift */, 94CF1F262BA1B74A00BC52C1 /* Web3WalletViewController.swift */, @@ -5299,8 +5296,7 @@ 7B5E9B4A243740C4000AE24E /* ConversationCircleEditorFooterView.swift in Sources */, 7BFE47E22284394000FC4379 /* CheckmarkPeerCell.swift in Sources */, 7CDF316C29890FB200421808 /* ConversationFontSet.swift in Sources */, - 94B577772B5E6E3700AE576E /* ExploreFavoriteViewController.swift in Sources */, - 94B577822B5E79B800AE576E /* ExploreAllAppsViewController.swift in Sources */, + 94B577822B5E79B800AE576E /* ExploreBotsViewController.swift in Sources */, 7B369208233A43C1007321A7 /* SegmentedControl.swift in Sources */, 9427D51A25E0D42E00B1EF0E /* MusicInfoView.swift in Sources */, 7C2AC42928F2B384005F369A /* DepositSuspendedView.swift in Sources */, diff --git a/Mixin/UserInterface/Controllers/Home/ExploreAllAppsViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreAllAppsViewController.swift deleted file mode 100644 index a535a045a7..0000000000 --- a/Mixin/UserInterface/Controllers/Home/ExploreAllAppsViewController.swift +++ /dev/null @@ -1,98 +0,0 @@ -import UIKit -import MixinServices - -final class ExploreAllAppsViewController: UITableViewController { - - private let headerReuseID = "header" - - private(set) var allUsers: [User]? = nil - - private var indexTitles: [String]? = nil - private var indexedUsers: [[User]] = [] - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = R.color.background() - tableView.separatorStyle = .none - tableView.register(R.nib.peerCell) - tableView.register(PeerHeaderView.self, forHeaderFooterViewReuseIdentifier: headerReuseID) - tableView.sectionIndexColor = R.color.text_tertiary() - tableView.rowHeight = 70 - reloadData() - NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: UserDAO.usersDidChangeNotification, object: nil) - } - - // MARK: - UITableViewDataSource - override func numberOfSections(in tableView: UITableView) -> Int { - indexTitles?.count ?? 0 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - indexedUsers[section].count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.peer, for: indexPath)! - let user = indexedUsers[indexPath.section][indexPath.row] - cell.peerInfoView.render(user: user, description: .identityNumber) - return cell - } - - override func sectionIndexTitles(for tableView: UITableView) -> [String]? { - indexTitles - } - - // MARK: - UITableViewDelegate - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - 34 - } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerReuseID) as! PeerHeaderView - header.label.text = indexTitles?[section] - return header - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - guard let explore = parent as? ExploreViewController else { - return - } - let user = indexedUsers[indexPath.section][indexPath.row] - explore.presentProfile(user: user) - } - - @objc private func reloadData() { - - class ObjcAccessibleUser: NSObject { - - @objc let fullName: String - let user: User - - init(user: User) { - self.fullName = user.fullName ?? "" - self.user = user - super.init() - } - - } - - DispatchQueue.global().async { - let allUsers = UserDAO.shared.getAppUsers() - let objcAccessibleUsers = allUsers.map(ObjcAccessibleUser.init(user:)) - let (titles, indexedObjcUsers) = UILocalizedIndexedCollation.current() - .catalog(objcAccessibleUsers, usingSelector: #selector(getter: ObjcAccessibleUser.fullName)) - let indexedUsers = indexedObjcUsers.map { $0.map(\.user) } - DispatchQueue.main.async { - self.allUsers = allUsers - self.indexTitles = titles - self.indexedUsers = indexedUsers - self.tableView.reloadData() - self.tableView.checkEmpty(dataCount: allUsers.count, - text: R.string.localizable.no_bots(), - photo: R.image.emptyIndicator.ic_data()!) - } - } - } - -} diff --git a/Mixin/UserInterface/Controllers/Home/ExploreBotsViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreBotsViewController.swift new file mode 100644 index 0000000000..c4f3e4dd7d --- /dev/null +++ b/Mixin/UserInterface/Controllers/Home/ExploreBotsViewController.swift @@ -0,0 +1,220 @@ +import UIKit +import MixinServices + +final class ExploreBotsViewController: UITableViewController { + + private enum FixedSection: Int, CaseIterable { + case actions = 0 + case favorites + } + + private let headerReuseID = "header" + private let actions: [ExploreAction] = [ + .camera, .linkDesktop, .customerService, + ] + + private(set) var allUsers: [User]? = nil + + private var favoriteAppUsers: [User] = [] + private var indexTitles: [String]? = nil + private var indexedUsers: [[User]] = [] + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = R.color.background() + tableView.separatorStyle = .none + tableView.register(R.nib.exploreActionCell) + tableView.register(R.nib.peerCell) + tableView.register(PeerHeaderView.self, forHeaderFooterViewReuseIdentifier: headerReuseID) + tableView.sectionIndexColor = R.color.text_tertiary() + tableView.rowHeight = 70 + tableView.contentInset.bottom = 10 + reloadFavoriteAppsFromLocal() + reloadFavoriteAppsFromRemote() + reloadAllApps() + let center: NotificationCenter = .default + center.addObserver(self, + selector: #selector(reloadAllApps), + name: UserDAO.usersDidChangeNotification, + object: nil) + center.addObserver(self, + selector: #selector(reloadFavoriteAppsFromLocal), + name: UserDAO.usersDidChangeNotification, + object: nil) + center.addObserver(self, + selector: #selector(reloadFavoriteAppsFromLocal), + name: FavoriteAppsDAO.favoriteAppsDidChangeNotification, + object: nil) + center.addObserver(self, + selector: #selector(updateLinkDesktopAction), + name: AppGroupUserDefaults.Account.extensionSessionDidChangeNotification, + object: nil) + } + + // MARK: - UITableViewDataSource + override func numberOfSections(in tableView: UITableView) -> Int { + FixedSection.allCases.count + (indexTitles?.count ?? 0) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch FixedSection(rawValue: section) { + case .actions: + actions.count + case .favorites: + favoriteAppUsers.count + default: + indexedUsers(at: section).count + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch FixedSection(rawValue: indexPath.section) { + case .actions: + let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.explore_action, for: indexPath)! + let action = actions[indexPath.row] + cell.load(action: action) + return cell + case .favorites: + if indexPath.row < favoriteAppUsers.count { + let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.peer, for: indexPath)! + let user = favoriteAppUsers[indexPath.row] + cell.peerInfoView.render(user: user, description: .identityNumber) + cell.peerInfoView.avatarImageView.hasShadow = false + return cell + } else { + let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.explore_action, for: indexPath)! + cell.load(action: .editFavoriteApps) + return cell + } + default: + let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.peer, for: indexPath)! + let user = indexedUser(at: indexPath) + cell.peerInfoView.render(user: user, description: .identityNumber) + return cell + } + } + + override func sectionIndexTitles(for tableView: UITableView) -> [String]? { + indexTitles + } + + override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { + index + FixedSection.allCases.count + } + + // MARK: - UITableViewDelegate + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + switch FixedSection(rawValue: section) { + case .actions: + 0 + default: + 34 + } + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerReuseID) as! PeerHeaderView + switch FixedSection(rawValue: section) { + case .actions: + return nil + case .favorites: + header.label.text = R.string.localizable.favorite() + return header + default: + header.label.text = indexTitles?[section - FixedSection.allCases.count] + return header + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if let explore = parent as? ExploreViewController { + let user = indexedUser(at: indexPath) + explore.presentProfile(user: user) + } + } + + @objc private func reloadAllApps() { + + class ObjcAccessibleUser: NSObject { + + @objc let fullName: String + let user: User + + init(user: User) { + self.fullName = user.fullName ?? "" + self.user = user + super.init() + } + + } + + DispatchQueue.global().async { + let allUsers = UserDAO.shared.getAppUsers() + let objcAccessibleUsers = allUsers.map(ObjcAccessibleUser.init(user:)) + let (titles, indexedObjcUsers) = UILocalizedIndexedCollation.current() + .catalog(objcAccessibleUsers, usingSelector: #selector(getter: ObjcAccessibleUser.fullName)) + let indexedUsers = indexedObjcUsers.map { $0.map(\.user) } + DispatchQueue.main.async { + self.allUsers = allUsers + self.indexTitles = titles + self.indexedUsers = indexedUsers + self.tableView.reloadData() + self.tableView.checkEmpty(dataCount: allUsers.count, + text: R.string.localizable.no_bots(), + photo: R.image.emptyIndicator.ic_data()!) + } + } + } + + @objc private func reloadFavoriteAppsFromLocal() { + DispatchQueue.global().async { [weak self] in + let users = FavoriteAppsDAO.shared.favoriteAppUsersOfUser(withId: myUserId) + DispatchQueue.main.async { + guard let self else { + return + } + self.favoriteAppUsers = users + let favorites = IndexSet(integer: FixedSection.favorites.rawValue) + UIView.performWithoutAnimation { + self.tableView.reloadSections(favorites, with: .none) + } + } + } + } + + private func reloadFavoriteAppsFromRemote() { + UserAPI.getFavoriteApps(ofUserWith: myUserId) { (result) in + guard case let .success(apps) = result else { + return + } + DispatchQueue.global().async { + FavoriteAppsDAO.shared.updateFavoriteApps(apps, forUserWith: myUserId) + let appUserIds = apps.map({ $0.appId }) + UserAPI.showUsers(userIds: appUserIds) { (result) in + guard case let .success(users) = result else { + return + } + UserDAO.shared.updateUsers(users: users) + } + } + } + } + + @objc private func updateLinkDesktopAction() { + guard let row = actions.firstIndex(of: .linkDesktop) else { + return + } + let indexPath = IndexPath(row: row, section: FixedSection.actions.rawValue) + tableView.reloadRows(at: [indexPath], with: .none) + } + + private func indexedUsers(at section: Int) -> [User] { + indexedUsers[section - FixedSection.allCases.count] + } + + private func indexedUser(at indexPath: IndexPath) -> User { + indexedUsers(at: indexPath.section)[indexPath.row] + } + +} diff --git a/Mixin/UserInterface/Controllers/Home/ExploreFavoriteViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreFavoriteViewController.swift deleted file mode 100644 index 539566a432..0000000000 --- a/Mixin/UserInterface/Controllers/Home/ExploreFavoriteViewController.swift +++ /dev/null @@ -1,156 +0,0 @@ -import UIKit -import MixinServices - -final class ExploreFavoriteViewController: UITableViewController { - - private enum Section: Int { - case fixed = 0 - case favorites = 1 - } - - private let fixedActions: [ExploreAction] = [ - .camera, .linkDesktop, .customerService, - ] - - private var favoriteAppUsers: [User] = [] - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = R.color.background() - tableView.separatorStyle = .none - tableView.register(R.nib.exploreActionCell) - tableView.register(R.nib.peerCell) - tableView.register(SeparatorHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: SeparatorHeaderFooterView.reuseIdentifier) - tableView.rowHeight = 70 - tableView.contentInset.bottom = 10 - reloadFavoriteAppsFromLocal() - reloadFavoriteAppsFromRemote() - NotificationCenter.default.addObserver(self, selector: #selector(reloadFavoriteAppsFromLocal), name: UserDAO.usersDidChangeNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(reloadFavoriteAppsFromLocal), name: FavoriteAppsDAO.favoriteAppsDidChangeNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(updateLinkDesktopAction), name: AppGroupUserDefaults.Account.extensionSessionDidChangeNotification, object: nil) - } - - // MARK: - UITableViewDataSource - override func numberOfSections(in tableView: UITableView) -> Int { - 2 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch Section(rawValue: section) { - case .fixed: - return fixedActions.count - case .favorites: - return favoriteAppUsers.count + 1 - case nil: - return 0 - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch Section(rawValue: indexPath.section)! { - case .fixed: - let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.explore_action, for: indexPath)! - let action = fixedActions[indexPath.row] - cell.load(action: action) - return cell - case .favorites: - if indexPath.row < favoriteAppUsers.count { - let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.peer, for: indexPath)! - let user = favoriteAppUsers[indexPath.row] - cell.peerInfoView.render(user: user, description: .identityNumber) - cell.peerInfoView.avatarImageView.hasShadow = false - return cell - } else { - let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.explore_action, for: indexPath)! - cell.load(action: .editFavoriteApps) - return cell - } - } - } - - // MARK: - UITableViewDelegate - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - switch Section(rawValue: section) { - case .favorites: - return 50 - case .fixed, nil: - return .leastNormalMagnitude - } - } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - switch Section(rawValue: section) { - case .favorites: - let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SeparatorHeaderFooterView.reuseIdentifier) - headerView?.backgroundColor = R.color.background() - return headerView - case .fixed, nil: - return nil - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - guard let explore = parent as? ExploreViewController else { - return - } - switch Section(rawValue: indexPath.section) { - case .fixed: - let action = fixedActions[indexPath.row] - explore.perform(action: action) - case .favorites: - if indexPath.row < favoriteAppUsers.count { - let user = favoriteAppUsers[indexPath.row] - explore.openApp(user: user) - } else { - explore.perform(action: .editFavoriteApps) - } - case nil: - break - } - } - - // MARK: - Data Loader - @objc private func updateLinkDesktopAction() { - guard let row = fixedActions.firstIndex(of: .linkDesktop) else { - return - } - let indexPath = IndexPath(row: row, section: Section.fixed.rawValue) - tableView.reloadRows(at: [indexPath], with: .none) - } - - @objc private func reloadFavoriteAppsFromLocal() { - DispatchQueue.global().async { [weak self] in - let users = FavoriteAppsDAO.shared.favoriteAppUsersOfUser(withId: myUserId) - DispatchQueue.main.async { - guard let self else { - return - } - self.favoriteAppUsers = users - let favorites = IndexSet(integer: Section.favorites.rawValue) - UIView.performWithoutAnimation { - self.tableView.reloadSections(favorites, with: .none) - } - } - } - } - - private func reloadFavoriteAppsFromRemote() { - UserAPI.getFavoriteApps(ofUserWith: myUserId) { (result) in - guard case let .success(apps) = result else { - return - } - DispatchQueue.global().async { - FavoriteAppsDAO.shared.updateFavoriteApps(apps, forUserWith: myUserId) - let appUserIds = apps.map({ $0.appId }) - UserAPI.showUsers(userIds: appUserIds) { (result) in - guard case let .success(users) = result else { - return - } - UserDAO.shared.updateUsers(users: users) - } - } - } - } - -} diff --git a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift index 3ce0c60f77..bb5b4e3251 100644 --- a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift +++ b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift @@ -6,8 +6,7 @@ final class ExploreViewController: UIViewController { @IBOutlet weak var segmentsCollectionView: UICollectionView! @IBOutlet weak var contentContainerView: UIView! - private let favoriteViewController = ExploreFavoriteViewController() - private let allAppsViewController = ExploreAllAppsViewController() + private let exploreBotsViewController = ExploreBotsViewController() private let hiddenSearchTopMargin: CGFloat = -28 private weak var web3WalletViewController: Web3WalletViewController? @@ -18,8 +17,7 @@ final class ExploreViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - addContentViewController(allAppsViewController) - addContentViewController(favoriteViewController) + addContentViewController(exploreBotsViewController) segmentsCollectionView.register(R.nib.exploreSegmentCell) if let layout = segmentsCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { layout.scrollDirection = .horizontal @@ -36,7 +34,7 @@ final class ExploreViewController: UIViewController { } @IBAction func searchApps(_ sender: Any) { - let searchViewController = ExploreSearchViewController(users: allAppsViewController.allUsers) + let searchViewController = ExploreSearchViewController(users: exploreBotsViewController.allUsers) addChild(searchViewController) searchViewController.view.alpha = 0 view.addSubview(searchViewController.view) @@ -152,22 +150,12 @@ extension ExploreViewController: UICollectionViewDelegate { removeWeb3WalletViewController() let segment = Segment(rawValue: indexPath.item)! switch segment { - case .favorite: - contentContainerView.bringSubviewToFront(favoriteViewController.view) case .bots: - contentContainerView.bringSubviewToFront(allAppsViewController.view) + contentContainerView.bringSubviewToFront(exploreBotsViewController.view) case .ethereum: let wallet = Web3WalletViewController(chain: .ethereum) addContentViewController(wallet) web3WalletViewController = wallet - case .polygon: - let wallet = Web3WalletViewController(chain: .polygon) - addContentViewController(wallet) - web3WalletViewController = wallet - case .bsc: - let wallet = Web3WalletViewController(chain: .bnbSmartChain) - addContentViewController(wallet) - web3WalletViewController = wallet } } @@ -177,24 +165,15 @@ extension ExploreViewController { private enum Segment: Int, CaseIterable { - case favorite case bots case ethereum - case polygon - case bsc var name: String { switch self { - case .favorite: - R.string.localizable.favorite() case .bots: R.string.localizable.bots_title() case .ethereum: "Ethereum" - case .polygon: - "Polygon" - case .bsc: - "BSC" } }