From 40ce30b65f7835387ff18e873650b33240865119 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Wed, 27 May 2026 14:08:51 +0800 Subject: [PATCH 1/6] Scroll to bottom after pasting mnemonics --- .../Login/SignInWithMnemonicsViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift b/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift index 453951a75b..6c4b7d1966 100644 --- a/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift @@ -188,6 +188,11 @@ final class SignInWithMnemonicsViewController: InputMnemonicsViewController { return } input(phrases: phrases) + let bottomOffset = CGPoint( + x: scrollView.contentOffset.x, + y: max(0, scrollView.contentSize.height - scrollView.frame.height) + ) + scrollView.setContentOffset(bottomOffset, animated: true) } @objc private func emptyPhrases(_ sender: Any) { From b3ee53b7595466b2f41844d2fb68365a23f3599c Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Thu, 28 May 2026 15:50:47 +0800 Subject: [PATCH 2/6] Report sign up source --- ...CreateAccountIntroductionViewController.swift | 16 ++++++++++++++-- .../Login/OnboardingViewController.swift | 2 +- .../SignInWithMnemonicsViewController.swift | 4 +++- .../SignInWithMobileNumberViewController.swift | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Mixin/UserInterface/Controllers/Login/CreateAccountIntroductionViewController.swift b/Mixin/UserInterface/Controllers/Login/CreateAccountIntroductionViewController.swift index ddc1631908..ca8326a283 100644 --- a/Mixin/UserInterface/Controllers/Login/CreateAccountIntroductionViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/CreateAccountIntroductionViewController.swift @@ -8,6 +8,18 @@ final class CreateAccountIntroductionViewController: UIViewController { @IBOutlet weak var continueButton: UIButton! @IBOutlet weak var footerTextView: IntroTextView! + private let analyticSource: String + + init(analyticSource: String) { + self.analyticSource = analyticSource + let nib = R.nib.createAccountIntroductionView + super.init(nibName: nib.name, bundle: nib.bundle) + } + + required init?(coder: NSCoder) { + fatalError("Storyboard not supported") + } + override func viewDidLoad() { super.viewDidLoad() titleLabel.text = R.string.localizable.create_your_account() @@ -56,7 +68,7 @@ final class CreateAccountIntroductionViewController: UIViewController { guard let navigationController else { return } - presentingViewController.dismiss(animated: true) { + presentingViewController.dismiss(animated: true) { [analyticSource] in let mnemonics: MixinMnemonics? = if let entropy = AppGroupKeychain.mnemonics { try? MixinMnemonics(entropy: entropy) } else { @@ -69,7 +81,7 @@ final class CreateAccountIntroductionViewController: UIViewController { viewControllers.append(next) navigationController.setViewControllers(viewControllers, animated: true) Logger.login.info(category: "CreateAccountIntro", message: "Sign up start") - reporter.report(event: .signUpStart) + reporter.report(event: .signUpStart, tags: ["source": analyticSource]) } } diff --git a/Mixin/UserInterface/Controllers/Login/OnboardingViewController.swift b/Mixin/UserInterface/Controllers/Login/OnboardingViewController.swift index 741136a4d3..403430ce11 100644 --- a/Mixin/UserInterface/Controllers/Login/OnboardingViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/OnboardingViewController.swift @@ -132,7 +132,7 @@ final class OnboardingViewController: UIViewController { } @IBAction func signUp(_ sender: Any) { - let intro = CreateAccountIntroductionViewController() + let intro = CreateAccountIntroductionViewController(analyticSource: "landing") present(intro, animated: true) } diff --git a/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift b/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift index 6c4b7d1966..e43e34d16e 100644 --- a/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/SignInWithMnemonicsViewController.swift @@ -78,7 +78,9 @@ final class SignInWithMnemonicsViewController: InputMnemonicsViewController { override func confirm(_ sender: Any) { if phrasesCount == nil { - let intro = CreateAccountIntroductionViewController() + let intro = CreateAccountIntroductionViewController( + analyticSource: "login_mnemonic_phrase" + ) present(intro, animated: true) } else { do { diff --git a/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift b/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift index ca868869bc..db5891050a 100644 --- a/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift @@ -80,7 +80,7 @@ final class SignInWithMobileNumberViewController: MobileNumberViewController { } @objc private func signup(_ sender: Any) { - let intro = CreateAccountIntroductionViewController() + let intro = CreateAccountIntroductionViewController(analyticSource: "login_start") present(intro, animated: true) } From 30b3f4e03e254499aeee79d3b68a8cfea931fa3c Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Thu, 28 May 2026 16:36:07 +0800 Subject: [PATCH 3/6] Report error when signing in with phone number --- .../Login/SignInWithMobileNumberViewController.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift b/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift index db5891050a..62ac2bc3d5 100644 --- a/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift +++ b/Mixin/UserInterface/Controllers/Login/SignInWithMobileNumberViewController.swift @@ -153,7 +153,16 @@ extension SignInWithMobileNumberViewController { fallthrough } default: - reporter.report(event: .errorSessionVerifications, tags: ["source": "sign_up"]) + var tags = ["type": "phone"] + if error.isServerErrorResponse { + tags["error_type"] = "server_error" + } else if error.isClientErrorResponse { + tags["error_type"] = "client_error" + } + if let statusCode = self.request?.response?.statusCode { + tags["error_code"] = "\(statusCode)" + } + reporter.report(event: .errorSessionVerifications, tags: tags) var userInfo: [String: String] = [:] userInfo["error"] = "\(error)" if let requestId = self.request?.response?.value(forHTTPHeaderField: "x-request-id") { From 959ad3931c03ce0369f4bb40251c842276b7e37f Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Fri, 29 May 2026 16:46:30 +0800 Subject: [PATCH 4/6] Analytic perps operations --- Mixin.xcodeproj/project.pbxproj | 4 ++++ Mixin/Service/UserOperationAnalytics.swift | 20 ++++++++++++++++ .../Home/ExploreViewController.swift | 1 + .../Wallet/CommonWalletViewController.swift | 1 + .../Wallet/Market/MarketViewController.swift | 1 + .../Wallet/MixinTokenViewController.swift | 1 + ...rpetualPositionPreviewViewController.swift | 17 +++++++++++++ .../Wallet/PrivacyWalletViewController.swift | 1 + .../AddPerpsPositionViewController.swift | 2 ++ ...rpetualPositionPreviewViewController.swift | 12 +++++++++- .../OpenPerpetualPositionViewController.swift | 24 +++++++++++++++---- .../PerpetualActivitiesViewController.swift | 2 ++ .../PerpetualMarketViewController.swift | 9 +++++++ .../PerpetualPositionsViewController.swift | 1 + .../PerpsMarginInputViewController.swift | 5 ++++ .../TradePerpetualViewController.swift | 13 ++++++++++ .../Trade/Spot/TradeOrderViewController.swift | 1 + .../Wallet/Trade/TradeViewController.swift | 9 ++++++- .../TransferInputAmountViewController.swift | 8 ++++--- .../WithdrawInputAmountViewController.swift | 8 ++++--- .../Wallet/Web3TokenViewController.swift | 1 + ...eb3TransferInputAmountViewController.swift | 8 ++++--- Mixin/UserInterface/Windows/UrlWindow.swift | 1 + .../MixinServices/Foundation/Reporter.swift | 16 +++++++++++++ 24 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 Mixin/Service/UserOperationAnalytics.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 277ad09405..064d7c4012 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -991,6 +991,7 @@ 944F547F2E45F9A20064A9A2 /* WatchingAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944F547E2E45F99F0064A9A2 /* WatchingAddresses.swift */; }; 94509BC12DDDFC5D00DC6A43 /* RefreshWeb3TokenJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94509BC02DDDFC5800DC6A43 /* RefreshWeb3TokenJob.swift */; }; 94509D772DE086E100DC6A43 /* IAPTransactionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94509D762DE086D800DC6A43 /* IAPTransactionObserver.swift */; }; + 9450EB642FC8964E003534FF /* UserOperationAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9450EB632FC89647003534FF /* UserOperationAnalytics.swift */; }; 94510FBB2C85673C00ACD972 /* ReloadGlobalMarketJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94510FBA2C85673C00ACD972 /* ReloadGlobalMarketJob.swift */; }; 94510FCE2C8742BC00ACD972 /* MarketTokenSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94510FCD2C8742BC00ACD972 /* MarketTokenSelectorViewController.swift */; }; 94510FD42C8743AA00ACD972 /* MarketTokenSelectorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94510FD22C8743AA00ACD972 /* MarketTokenSelectorCell.swift */; }; @@ -2891,6 +2892,7 @@ 944F547E2E45F99F0064A9A2 /* WatchingAddresses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchingAddresses.swift; sourceTree = ""; }; 94509BC02DDDFC5800DC6A43 /* RefreshWeb3TokenJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshWeb3TokenJob.swift; sourceTree = ""; }; 94509D762DE086D800DC6A43 /* IAPTransactionObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPTransactionObserver.swift; sourceTree = ""; }; + 9450EB632FC89647003534FF /* UserOperationAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserOperationAnalytics.swift; sourceTree = ""; }; 94510FBA2C85673C00ACD972 /* ReloadGlobalMarketJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadGlobalMarketJob.swift; sourceTree = ""; }; 94510FCD2C8742BC00ACD972 /* MarketTokenSelectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketTokenSelectorViewController.swift; sourceTree = ""; }; 94510FD22C8743AA00ACD972 /* MarketTokenSelectorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketTokenSelectorCell.swift; sourceTree = ""; }; @@ -6636,6 +6638,7 @@ 948CFAE92F2CB4B200BC34C3 /* QRCodeDetector.swift */, 94EC9CD32F2C7CC000B3CD82 /* VideoCaptureDevice.swift */, 9404D76F2FB1A72600F5563A /* SignPosition.swift */, + 9450EB632FC89647003534FF /* UserOperationAnalytics.swift */, ); path = Service; sourceTree = ""; @@ -8523,6 +8526,7 @@ 7B2AFEE220FE022C00C747BB /* MXMFastURLDetector.m in Sources */, DF1F277C21A53585009A74C6 /* BackupViewController.swift in Sources */, 942FD9342D3A70DB004DCC4C /* TradeOrderHeaderCell.swift in Sources */, + 9450EB642FC8964E003534FF /* UserOperationAnalytics.swift in Sources */, 945FFC7A2C3818630013DC51 /* RouteAPI.swift in Sources */, ABCB87DD23F18A3D00780E60 /* PermissionsViewController.swift in Sources */, 9443A9212CA6BB3D0030A636 /* MarketAlertCoinPickerViewController.swift in Sources */, diff --git a/Mixin/Service/UserOperationAnalytics.swift b/Mixin/Service/UserOperationAnalytics.swift new file mode 100644 index 0000000000..49fdcb79c4 --- /dev/null +++ b/Mixin/Service/UserOperationAnalytics.swift @@ -0,0 +1,20 @@ +import Foundation + +enum UserOperationAnalytics { + + enum Source: String { + case walletHome = "wallet_home" + case moreExplore = "more_explore" + case appCard = "app_card" + case marketDetail = "market_detail" + case tradeDetail = "trade_detail" + case assetDetail = "asset_detail" + case perpsMarginInput = "perps_margin_input" + case transfer = "transfer" + case withdraw = "withdraw" + case scheme = "scheme" + } + + static var tradeSource: Source? + +} diff --git a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift index abb9121ff0..7417bd29c7 100644 --- a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift +++ b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift @@ -125,6 +125,7 @@ final class ExploreViewController: UIViewController, AssetChangeAccountRecoveryC reporter.report(event: .buyStart, tags: ["wallet": "main", "source": "explore"]) case .trade: BadgeManager.shared.setHasViewed(identifier: .trade) + UserOperationAnalytics.tradeSource = .moreExplore let trade = TradeViewController( wallet: .privacy, trading: nil, diff --git a/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift index 2ed086a0ce..73b2da7609 100644 --- a/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift @@ -465,6 +465,7 @@ extension CommonWalletViewController: WalletHeaderView.Delegate { self?.present(selector, animated: true, completion: nil) } case .trade: + UserOperationAnalytics.tradeSource = .walletHome let trade = TradeViewController( wallet: .common(wallet), supportedChainIDs: supportedChainIDs, diff --git a/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift index 1ccde04048..026474d6d8 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift @@ -662,6 +662,7 @@ extension MarketViewController: PillActionView.Delegate { alert(R.string.localizable.swap_not_supported(market.symbol)) } else { pickSingleToken { token in + UserOperationAnalytics.tradeSource = .marketDetail let trade = TradeViewController( wallet: .privacy, trading: nil, diff --git a/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift b/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift index 8e82ba0d94..7c9171a2b3 100644 --- a/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift @@ -192,6 +192,7 @@ extension MixinTokenViewController: TokenActionView.Delegate { self?.send() } case .trade: + UserOperationAnalytics.tradeSource = .assetDetail let trade = TradeViewController( wallet: .privacy, trading: nil, diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/OpenPerpetualPositionPreviewViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/OpenPerpetualPositionPreviewViewController.swift index 850d37020d..d6e5e9207c 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/OpenPerpetualPositionPreviewViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/OpenPerpetualPositionPreviewViewController.swift @@ -188,6 +188,16 @@ final class OpenPerpetualPositionPreviewViewController: WalletIdentifyingAuthent reloadData(with: rows) } + override func confirm(_ sender: Any) { + super.confirm(sender) + reporter.report(event: .tradePerpsPreviewConfirm) + } + + override func close(_ sender: Any) { + super.close(sender) + reporter.report(event: .tradePerpsPreviewCancel) + } + override func performAction(with pin: String) { canDismissInteractively = false tableHeaderView.setIcon(progress: .busy) @@ -226,6 +236,13 @@ final class OpenPerpetualPositionPreviewViewController: WalletIdentifyingAuthent if let callback = context.onDismissAfterSuccess { onDismiss = callback } + reporter.report( + event: .tradePerpsOpenPositionEnd, + tags: [ + "leverage": "\(context.leverageMultiplier)", + "trade_asset_level": operation.amount.reportingAssetLevel, + ] + ) } } catch { let errorDescription = if let error = error as? MixinAPIError, PINVerificationFailureHandler.canHandle(error: error) { diff --git a/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift index fc08c1f136..ff17eaaa5e 100644 --- a/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift @@ -330,6 +330,7 @@ extension PrivacyWalletViewController: WalletHeaderView.Delegate { self?.present(selector, animated: true, completion: nil) } case .trade: + UserOperationAnalytics.tradeSource = .walletHome let trade = TradeViewController( wallet: .privacy, trading: nil, diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/AddPerpsPositionViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/AddPerpsPositionViewController.swift index 78238f9f1b..f7f63c0259 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/AddPerpsPositionViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/AddPerpsPositionViewController.swift @@ -155,11 +155,13 @@ final class AddPerpsPositionViewController: PerpsMarginInputViewController { @IBAction func introduceSize(_ sender: Any) { let manual = PerpsManual.viewController(initialPage: .size) present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_add_position_size"]) } @IBAction func introduceLiquidationPrice(_ sender: Any) { let manual = PerpsManual.viewController(initialPage: .liquidation) present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_add_position_liquidation"]) } @IBAction func cancel(_ sender: Any) { diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/ClosePerpetualPositionPreviewViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/ClosePerpetualPositionPreviewViewController.swift index a673339ea9..0253005277 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/ClosePerpetualPositionPreviewViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/ClosePerpetualPositionPreviewViewController.swift @@ -67,6 +67,16 @@ final class ClosePerpetualPositionPreviewViewController: WalletIdentifyingAuthen reloadData(with: rows) } + override func confirm(_ sender: Any) { + super.confirm(sender) + reporter.report(event: .tradePerpsClosePositionPreviewConfirm) + } + + override func close(_ sender: Any) { + super.close(sender) + reporter.report(event: .tradePerpsClosePositionPreviewCancel) + } + override func performAction(with pin: String) { canDismissInteractively = false tableHeaderView.setIcon(progress: .busy) @@ -76,7 +86,6 @@ final class ClosePerpetualPositionPreviewViewController: WalletIdentifyingAuthen ) replaceTrayView(with: nil, animation: .vertical) let positionID = viewModel.positionID - let walletID = viewModel.wallet.tradingWalletID Task { do { try await AccountAPI.verify(pin: pin) @@ -91,6 +100,7 @@ final class ClosePerpetualPositionPreviewViewController: WalletIdentifyingAuthen ) tableView.setContentOffset(.zero, animated: true) loadFinishedTrayView() + reporter.report(event: .tradePerpsClosePositionEnd) } } catch { let errorDescription = if let error = error as? MixinAPIError, PINVerificationFailureHandler.canHandle(error: error) { diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift index a7bc06ccaa..c88f08ec8f 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift @@ -200,6 +200,12 @@ final class OpenPerpetualPositionViewController: PerpsMarginInputViewController leverageMultiplier: leverageMultiplier, underlyingAsset: viewModel ) + + var tags = ["direction": side.rawValue] + if let source = UserOperationAnalytics.tradeSource { + tags["source"] = source.rawValue + } + reporter.report(event: .tradePerpsOpenPositionStart, tags: tags) } override func editMarginAmount(_ textField: UITextField) { @@ -255,17 +261,20 @@ final class OpenPerpetualPositionViewController: PerpsMarginInputViewController @IBAction func presentAutoClosingManual(_ sender: Any) { let manual = PerpsManual.viewController(initialPage: .autoClosing) present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_open_position_auto_closing"]) } @IBAction func presentOrderValueManual(_ sender: Any) { let manual = PerpsManual.viewController(initialPage: .size) present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_open_position_size"]) } @IBAction func review(_ sender: ConfigurationBasedBusyButton) { guard let marginToken else { return } + reporter.report(event: .tradePerpsPreview) marginAmountTextField.resignFirstResponder() let request = OpenPerpetualOrderRequest( assetID: marginToken.assetID, @@ -328,10 +337,10 @@ final class OpenPerpetualPositionViewController: PerpsMarginInputViewController @objc private func presentCustomerService(_ sender: Any) { let customerService = CustomerServiceViewController() present(customerService, animated: true) - reporter.report(event: .customerServiceDialog, tags: ["source": "open_perps_position"]) + reporter.report(event: .customerServiceDialog, tags: ["source": "perps_open_position"]) } - private func inputCustomeLeverageMultiplier() { + private func inputCustomLeverageMultiplier() { marginAmountTextField.resignFirstResponder() let input = LeverageMultiplierInputViewController( side: side, @@ -484,7 +493,8 @@ extension OpenPerpetualPositionViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { switch multipliers[indexPath.item] { case .custom: - inputCustomeLeverageMultiplier() + reporter.report(event: .tradePerpsLeverageSelect, tags: ["leverage": "custom_tab"]) + inputCustomLeverageMultiplier() return false default: return true @@ -494,7 +504,8 @@ extension OpenPerpetualPositionViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool { switch multipliers[indexPath.item] { case .custom: - inputCustomeLeverageMultiplier() + reporter.report(event: .tradePerpsLeverageSelect, tags: ["leverage": "custom_tab"]) + inputCustomLeverageMultiplier() default: break } @@ -505,8 +516,10 @@ extension OpenPerpetualPositionViewController: UICollectionViewDelegate { marginAmountTextField.resignFirstResponder() switch multipliers[indexPath.item] { case .fixed(let leverage): + reporter.report(event: .tradePerpsLeverageSelect, tags: ["leverage": "\(leverage)x"]) inputLeverageMultiplier(value: leverage) case .max: + reporter.report(event: .tradePerpsLeverageSelect, tags: ["leverage": "max"]) inputLeverageMultiplier(value: viewModel.maxLeverageMultiplier) case .custom: break @@ -518,7 +531,8 @@ extension OpenPerpetualPositionViewController: UICollectionViewDelegate { extension OpenPerpetualPositionViewController: UITextFieldDelegate { func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - inputCustomeLeverageMultiplier() + reporter.report(event: .tradePerpsLeverageSelect, tags: ["leverage": "custom_input"]) + inputCustomLeverageMultiplier() return false } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualActivitiesViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualActivitiesViewController.swift index e071081e82..0cd2455bb9 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualActivitiesViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualActivitiesViewController.swift @@ -177,6 +177,7 @@ extension PerpetualActivitiesViewController: UICollectionViewDataSource { cell.onHelp = { [weak self] in let manual = PerpsManual.viewController() self?.present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_activities"]) } return cell } @@ -202,6 +203,7 @@ extension PerpetualActivitiesViewController: UICollectionViewDelegate { let viewModel = viewModels[indexPath.item] let activity = PerpetualActivityViewController(wallet: wallet, viewModel: viewModel) navigationController?.pushViewController(activity, animated: true) + reporter.report(event: .tradePerpsActivityDetail, tags: ["source": "perps_activity_list"]) } } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualMarketViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualMarketViewController.swift index 22f1b8a38a..6f6f7df30f 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualMarketViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualMarketViewController.swift @@ -253,6 +253,7 @@ final class PerpetualMarketViewController: UIViewController { BadgeManager.shared.setHasViewed(identifier: .perpsManual) let manual = PerpsManual.viewController() present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "first_guide"]) } } @@ -309,6 +310,7 @@ final class PerpetualMarketViewController: UIViewController { } let preview = ClosePerpetualPositionPreviewViewController(viewModel: viewModel) present(preview, animated: true) + reporter.report(event: .tradePerpsClosePositionStart) } @objc private func reloadMarket(_ notification: Notification) { @@ -420,6 +422,10 @@ final class PerpetualMarketViewController: UIViewController { private func viewActivities() { let activities = PerpetualActivitiesViewController(wallet: wallet) navigationController?.pushViewController(activities, animated: true) + reporter.report( + event: .tradePerpsActivity, + tags: ["source": "perps_market"] + ) } private func handleTPSLUpdate(result: MixinAPI.Result) { @@ -761,9 +767,11 @@ extension PerpetualMarketViewController: UICollectionViewDelegate { let viewModel = viewModels[indexPath.item] let activity = PerpetualActivityViewController(wallet: wallet, viewModel: viewModel) navigationController?.pushViewController(activity, animated: true) + reporter.report(event: .tradePerpsActivityDetail, tags: ["source": "perps_market"]) case .introduction: let manual = PerpsManual.viewController() present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_detail_card"]) } } @@ -787,6 +795,7 @@ extension PerpetualMarketViewController: PerpetualMarketOpenPositionCell.Delegat func perpetualMarketOpenPositionCell(_ cell: PerpetualMarketOpenPositionCell, requestManual page: PerpsManual.Page) { let manual = PerpsManual.viewController(initialPage: page) present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_detail_card"]) } func perpetualMarketOpenPositionCellAskToShare(_ cell: PerpetualMarketOpenPositionCell) { diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualPositionsViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualPositionsViewController.swift index 7eb9179a29..f923e93597 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualPositionsViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpetualPositionsViewController.swift @@ -188,6 +188,7 @@ extension PerpetualPositionsViewController: UICollectionViewDataSource { cell.onHelp = { [weak self] in let manual = PerpsManual.viewController() self?.present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_all_positions"]) } return cell } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift index 0897d6a02e..9e8f43ef6a 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift @@ -51,12 +51,15 @@ class PerpsMarginInputViewController: UIViewController { accessoryView.items = [ .init(title: "25%") { [weak self] in self?.inputAmount(withBalanceMultipliedBy: 0.25) + reporter.report(event: .tradePerpsAmountInputPercent, tags: ["percent": "25%"]) }, .init(title: "50%") { [weak self] in self?.inputAmount(withBalanceMultipliedBy: 0.5) + reporter.report(event: .tradePerpsAmountInputPercent, tags: ["percent": "50%"]) }, .init(title: R.string.localizable.max()) { [weak self] in self?.inputAmount(withBalanceMultipliedBy: 1) + reporter.report(event: .tradePerpsAmountInputPercent, tags: ["percent": "max"]) }, ] accessoryView.onDone = { [weak textField=marginAmountTextField] in @@ -112,6 +115,7 @@ class PerpsMarginInputViewController: UIViewController { @IBAction func inputTokenBalance(_ sender: Any) { inputAmount(withBalanceMultipliedBy: 1) + reporter.report(event: .tradePerpsAmountInputBalance) } @IBAction func depositMarginToken(_ sender: Any) { @@ -251,6 +255,7 @@ extension PerpsMarginInputViewController: AddTokenMethodSelectorViewController.D referral: nil ) if let trade { + UserOperationAnalytics.tradeSource = .perpsMarginInput viewControllers.append(trade) navigationController.setViewControllers(viewControllers, animated: true) } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/TradePerpetualViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/TradePerpetualViewController.swift index fb3785cb53..fce0655e71 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/TradePerpetualViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/TradePerpetualViewController.swift @@ -224,6 +224,7 @@ final class TradePerpetualViewController: UIViewController { BadgeManager.shared.setHasViewed(identifier: .perpsManual) let manual = PerpsManual.viewController() present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "first_guide"]) } } @@ -336,6 +337,7 @@ extension TradePerpetualViewController: UICollectionViewDataSource { cell.emptyIndicatorStackView.isHidden = false cell.onHelp = { [weak self] in self?.presentPerpsManual() + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_home_card"]) } } return cell @@ -357,6 +359,7 @@ extension TradePerpetualViewController: UICollectionViewDataSource { cell.emptyIndicatorStackView.isHidden = false cell.onHelp = { [weak self] in self?.presentPerpsManual() + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_home_card"]) } } return cell @@ -394,6 +397,10 @@ extension TradePerpetualViewController: UICollectionViewDataSource { view.label.text = R.string.localizable.perps_activity() view.onShowAll = { [weak self] (sender) in self?.viewActivities() + reporter.report( + event: .tradePerpsActivity, + tags: ["source": "perps_home_card_arrow"] + ) } } return view @@ -416,6 +423,10 @@ extension TradePerpetualViewController: UICollectionViewDataSource { view.viewAllButton.isHidden = (activities?.count ?? 0) <= maxItemCount view.onViewAll = { [weak self] (sender) in self?.viewActivities() + reporter.report( + event: .tradePerpsActivity, + tags: ["source": "perps_home_card_more"] + ) } } return view @@ -460,8 +471,10 @@ extension TradePerpetualViewController: UICollectionViewDelegate { let viewModel = activities[indexPath.item] let activity = PerpetualActivityViewController(wallet: wallet, viewModel: viewModel) navigationController?.pushViewController(activity, animated: true) + reporter.report(event: .tradePerpsActivityDetail, tags: ["source": "perps_home_list"]) case .introduction: presentPerpsManual() + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_home_card"]) } } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift index c569d820b9..7bdcf7bf59 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift @@ -268,6 +268,7 @@ extension TradeOrderViewController: PillActionView.Delegate { referral: nil ) if let trade { + UserOperationAnalytics.tradeSource = .tradeDetail viewControllers.append(trade) navigationController.setViewControllers(viewControllers, animated: true) reporter.report( diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/TradeViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/TradeViewController.swift index 2e1b4bc456..8b063f3c35 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/TradeViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/TradeViewController.swift @@ -191,11 +191,17 @@ final class TradeViewController: UIViewController { } @objc func presentCustomerService(_ sender: Any) { + let trading = self.trading let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) sheet.addAction(UIAlertAction(title: R.string.localizable.contact_support(), style: .default, handler: { _ in let customerService = CustomerServiceViewController() self.present(customerService, animated: true) - reporter.report(event: .customerServiceDialog, tags: ["source": "trade_home"]) + switch trading { + case .simpleSpot, .advancedSpot: + reporter.report(event: .customerServiceDialog, tags: ["source": "trade_home"]) + case .perpetualFutures: + reporter.report(event: .customerServiceDialog, tags: ["source": "perps_home_menu"]) + } })) switch trading { case .simpleSpot, .advancedSpot: @@ -207,6 +213,7 @@ final class TradeViewController: UIViewController { sheet.addAction(UIAlertAction(title: R.string.localizable.perpetual_futures_guide(), style: .default, handler: { _ in let manual = PerpsManual.viewController() self.present(manual, animated: true) + reporter.report(event: .tradePerpsGuide, tags: ["source": "perps_home_menu"]) })) } sheet.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .cancel, handler: nil)) diff --git a/Mixin/UserInterface/Controllers/Wallet/Transfer/TransferInputAmountViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Transfer/TransferInputAmountViewController.swift index 79a5921030..cf2107cf66 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Transfer/TransferInputAmountViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Transfer/TransferInputAmountViewController.swift @@ -260,9 +260,11 @@ extension TransferInputAmountViewController: AddTokenMethodSelectorViewControlle _ viewController: AddTokenMethodSelectorViewController, didPickMethod method: AddTokenMethodSelectorViewController.Method ) { - let next = switch method { + let next: UIViewController? + switch method { case .trade: - TradeViewController( + UserOperationAnalytics.tradeSource = .transfer + next = TradeViewController( wallet: .privacy, trading: .simpleSpot, sendAssetID: nil, @@ -270,7 +272,7 @@ extension TransferInputAmountViewController: AddTokenMethodSelectorViewControlle referral: nil ) case .deposit: - DepositViewController(token: tokenItem, switchingBetweenNetworks: false) + next = DepositViewController(token: tokenItem, switchingBetweenNetworks: false) } if let next { navigationController?.pushViewController(next, animated: true) diff --git a/Mixin/UserInterface/Controllers/Wallet/Transfer/WithdrawInputAmountViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Transfer/WithdrawInputAmountViewController.swift index bc255bcec8..310ac71acf 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Transfer/WithdrawInputAmountViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Transfer/WithdrawInputAmountViewController.swift @@ -288,9 +288,11 @@ extension WithdrawInputAmountViewController: AddTokenMethodSelectorViewControlle guard let feeToken = selectedFeeItem?.token else { return } - let next = switch method { + let next: UIViewController? + switch method { case .trade: - TradeViewController( + UserOperationAnalytics.tradeSource = .withdraw + next = TradeViewController( wallet: .privacy, trading: .simpleSpot, sendAssetID: nil, @@ -298,7 +300,7 @@ extension WithdrawInputAmountViewController: AddTokenMethodSelectorViewControlle referral: nil ) case .deposit: - DepositViewController(token: feeToken, switchingBetweenNetworks: false) + next = DepositViewController(token: feeToken, switchingBetweenNetworks: false) } if let next { navigationController?.pushViewController(next, animated: true) diff --git a/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift index 6c93f5cad2..494f28f897 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift @@ -235,6 +235,7 @@ extension Web3TokenViewController: TokenActionView.Delegate { self?.send() } case .trade: + UserOperationAnalytics.tradeSource = .assetDetail let trade = TradeViewController( wallet: .common(wallet), trading: nil, diff --git a/Mixin/UserInterface/Controllers/Web3/Web3TransferInputAmountViewController.swift b/Mixin/UserInterface/Controllers/Web3/Web3TransferInputAmountViewController.swift index d5b95bedd8..7b0dc1b0a2 100644 --- a/Mixin/UserInterface/Controllers/Web3/Web3TransferInputAmountViewController.swift +++ b/Mixin/UserInterface/Controllers/Web3/Web3TransferInputAmountViewController.swift @@ -292,9 +292,11 @@ extension Web3TransferInputAmountViewController: AddTokenMethodSelectorViewContr guard let feeToken = fee?.selected.token else { return } - let next = switch method { + let next: UIViewController? + switch method { case .trade: - TradeViewController( + UserOperationAnalytics.tradeSource = .transfer + next = TradeViewController( wallet: .common(payment.wallet), trading: .simpleSpot, sendAssetID: nil, @@ -302,7 +304,7 @@ extension Web3TransferInputAmountViewController: AddTokenMethodSelectorViewContr referral: nil ) case .deposit: - DepositViewController( + next = DepositViewController( wallet: payment.wallet, token: feeToken, switchingBetweenNetworks: false diff --git a/Mixin/UserInterface/Windows/UrlWindow.swift b/Mixin/UserInterface/Windows/UrlWindow.swift index 3cf95ab1de..6b8ab79a0d 100644 --- a/Mixin/UserInterface/Windows/UrlWindow.swift +++ b/Mixin/UserInterface/Windows/UrlWindow.swift @@ -104,6 +104,7 @@ class UrlWindow { let mode = AppGroupUserDefaults.Wallet.tradeMode trading = TradeViewController.Trading(rawValue: mode) ?? .simpleSpot } + UserOperationAnalytics.tradeSource = .scheme let trade = TradeViewController( wallet: .privacy, trading: trading, diff --git a/MixinServices/MixinServices/Foundation/Reporter.swift b/MixinServices/MixinServices/Foundation/Reporter.swift index 78b32cab92..aff7d42dad 100644 --- a/MixinServices/MixinServices/Foundation/Reporter.swift +++ b/MixinServices/MixinServices/Foundation/Reporter.swift @@ -29,6 +29,22 @@ open class Reporter { case tradeEnd = "trade_end" case tradeTransactions = "trade_transactions" case tradeDetail = "trade_detail" + case tradePerpsOpenPositionStart = "trade_perps_open_position_start" + case tradePerpsMarginTokenSelect = "trade_perps_margin_token_select" + case tradePerpsAmountInputPercent = "trade_perps_amount_input_percent" + case tradePerpsAmountInputBalance = "trade_perps_amount_input_balance" + case tradePerpsLeverageSelect = "trade_perps_leverage_select" + case tradePerpsPreview = "trade_perps_preview" + case tradePerpsPreviewConfirm = "trade_perps_preview_confirm" + case tradePerpsPreviewCancel = "trade_perps_preview_cancel" + case tradePerpsOpenPositionEnd = "trade_perps_open_position_end" + case tradePerpsClosePositionStart = "trade_perps_close_position_start" + case tradePerpsClosePositionPreviewConfirm = "trade_perps_close_position_preview_confirm" + case tradePerpsClosePositionPreviewCancel = "trade_perps_close_position_preview_cancel" + case tradePerpsClosePositionEnd = "trade_perps_close_position_end" + case tradePerpsActivity = "trade_perps_activity" + case tradePerpsActivityDetail = "trade_perps_activity_detail" + case tradePerpsGuide = "trade_perps_guide" case buyStart = "buy_start" case buyTokenSelect = "buy_token_select" From bcaf594b90543cfe91d316b36263d222115ce8d1 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Fri, 29 May 2026 17:10:50 +0800 Subject: [PATCH 5/6] Report spot trading start and end --- .../Home/ExploreViewController.swift | 1 - .../Wallet/CommonWalletViewController.swift | 9 +------- .../Wallet/Market/MarketViewController.swift | 4 ---- .../Wallet/MixinTokenViewController.swift | 9 +------- .../TradeSpotPreviewViewController.swift | 22 ++++++++++++------- .../Wallet/PrivacyWalletViewController.swift | 4 ---- .../OpenPerpetualPositionViewController.swift | 4 +--- .../Spot/TradeMixinSpotViewController.swift | 7 ++++++ .../Trade/Spot/TradeOrderViewController.swift | 4 ---- .../Trade/Spot/TradeSpotViewController.swift | 2 +- .../Spot/TradeWeb3SpotViewController.swift | 7 ++++++ .../Wallet/Web3TokenViewController.swift | 9 +------- Mixin/UserInterface/Windows/UrlWindow.swift | 8 ------- .../MixinServices/Foundation/Reporter.swift | 4 ++-- 14 files changed, 35 insertions(+), 59 deletions(-) diff --git a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift index 7417bd29c7..ebc452688e 100644 --- a/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift +++ b/Mixin/UserInterface/Controllers/Home/ExploreViewController.swift @@ -138,7 +138,6 @@ final class ExploreViewController: UIViewController, AssetChangeAccountRecoveryC } withAccountRecoveryChecked { [weak self] in self?.navigationController?.pushViewController(trade, animated: true) - reporter.report(event: .tradeStart, tags: ["wallet": "main", "source": "explore"]) } case .membership: if let membership = LoginManager.shared.account?.membership, let plan = membership.plan { diff --git a/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift index 73b2da7609..ed9c998d5a 100644 --- a/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/CommonWalletViewController.swift @@ -478,14 +478,7 @@ extension CommonWalletViewController: WalletHeaderView.Delegate { return } withAccountRecoveryChecked { [weak self] in - guard let self else { - return - } - self.navigationController?.pushViewController(trade, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "web3", "source": "wallet_home"] - ) + self?.navigationController?.pushViewController(trade, animated: true) } } } diff --git a/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift index 026474d6d8..008274d340 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Market/MarketViewController.swift @@ -672,10 +672,6 @@ extension MarketViewController: PillActionView.Delegate { ) if let trade { self.navigationController?.pushViewController(trade, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "market_detail"] - ) } } } diff --git a/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift b/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift index 7c9171a2b3..441d3a8970 100644 --- a/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/MixinTokenViewController.swift @@ -204,14 +204,7 @@ extension MixinTokenViewController: TokenActionView.Delegate { return } withAccountRecoveryChecked { [weak self] in - guard let self else { - return - } - self.navigationController?.pushViewController(trade, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "asset_detail"] - ) + self?.navigationController?.pushViewController(trade, animated: true) } case .buy: break diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/TradeSpotPreviewViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/TradeSpotPreviewViewController.swift index 0cf0546e09..511a4543d0 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/TradeSpotPreviewViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/TradeSpotPreviewViewController.swift @@ -198,19 +198,19 @@ final class TradeSpotPreviewViewController: WalletIdentifyingAuthenticationPrevi subtitle: R.string.localizable.signature_request_from(.mixin) ) replaceTrayView(with: nil, animation: .vertical) - let reportType = switch mode { - case .simple: - "swap" - case .advanced: - "limit" - } let assetIDs = [sendToken.assetID, receiveToken.assetID] Task { do { switch operation { case .mixin(let operation): try await operation.start(pin: pin) - reporter.report(event: .tradeEnd, tags: ["wallet": "main", "type": reportType, "trade_asset_level": sendAmount.reportingAssetLevel]) + reporter.report( + event: .tradeSpotEnd, + tags: [ + "wallet": "main", + "trade_asset_level": sendAmount.reportingAssetLevel + ] + ) let inexistAssetIDs = TokenDAO.shared.inexistAssetIDs(in: assetIDs) for assetID in inexistAssetIDs { let job = RefreshTokenJob(assetID: assetID) @@ -218,7 +218,13 @@ final class TradeSpotPreviewViewController: WalletIdentifyingAuthenticationPrevi } case .web3(let operation): try await operation.start(pin: pin) - reporter.report(event: .tradeEnd, tags: ["wallet": "web3", "type": reportType, "trade_asset_level": sendAmount.reportingAssetLevel]) + reporter.report( + event: .tradeSpotEnd, + tags: [ + "wallet": "web3", + "trade_asset_level": sendAmount.reportingAssetLevel + ] + ) let walletID = operation.wallet.walletID let inexistAssetIDs = Web3TokenDAO.shared.inexistAssetIDs(walletID: walletID, in: assetIDs) for assetID in inexistAssetIDs { diff --git a/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift b/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift index ff17eaaa5e..43f1611bc2 100644 --- a/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/PrivacyWalletViewController.swift @@ -348,10 +348,6 @@ extension PrivacyWalletViewController: WalletHeaderView.Delegate { self.navigationController?.pushViewController(trade, animated: true) self.tableHeaderView.actionView.badgeActions.remove(.trade) BadgeManager.shared.setHasViewed(identifier: .trade) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "wallet_home"] - ) } } } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift index c88f08ec8f..1967a42f2f 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/OpenPerpetualPositionViewController.swift @@ -202,9 +202,7 @@ final class OpenPerpetualPositionViewController: PerpsMarginInputViewController ) var tags = ["direction": side.rawValue] - if let source = UserOperationAnalytics.tradeSource { - tags["source"] = source.rawValue - } + tags["source"] = UserOperationAnalytics.tradeSource?.rawValue reporter.report(event: .tradePerpsOpenPositionStart, tags: tags) } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeMixinSpotViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeMixinSpotViewController.swift index e8832f9dba..459de9b532 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeMixinSpotViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeMixinSpotViewController.swift @@ -38,6 +38,13 @@ final class TradeMixinSpotViewController: TradeSpotViewController { fatalError("Storyboard not supported") } + override func viewDidLoad() { + super.viewDidLoad() + var tags = ["wallet": "main", "type": mode.rawValue] + tags["source"] = UserOperationAnalytics.tradeSource?.rawValue + reporter.report(event: .tradeSpotStart, tags: tags) + } + override func changeSendToken(_ sender: Any) { let selector = TradeMixinTokenSelectorViewController( intent: .send, diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift index 7bdcf7bf59..3c6e55c0e1 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift @@ -271,10 +271,6 @@ extension TradeOrderViewController: PillActionView.Delegate { UserOperationAnalytics.tradeSource = .tradeDetail viewControllers.append(trade) navigationController.setViewControllers(viewControllers, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "trade_detail"] - ) } } case .cancelOrder: diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeSpotViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeSpotViewController.swift index 558ccc42c2..02e1efd615 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeSpotViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeSpotViewController.swift @@ -4,7 +4,7 @@ import MixinServices class TradeSpotViewController: UIViewController { - enum Mode { + enum Mode: String { case simple case advanced } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeWeb3SpotViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeWeb3SpotViewController.swift index 9f85cc3e1e..33262ecca9 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeWeb3SpotViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeWeb3SpotViewController.swift @@ -44,6 +44,13 @@ final class TradeWeb3SpotViewController: TradeSpotViewController { fatalError("Storyboard not supported") } + override func viewDidLoad() { + super.viewDidLoad() + var tags = ["wallet": "web3", "type": mode.rawValue] + tags["source"] = UserOperationAnalytics.tradeSource?.rawValue + reporter.report(event: .tradeSpotStart, tags: tags) + } + override func changeSendToken(_ sender: Any) { let selector = TradeWeb3TokenSelectorViewController( wallet: wallet, diff --git a/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift index 494f28f897..2a372c2ab1 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Web3TokenViewController.swift @@ -247,14 +247,7 @@ extension Web3TokenViewController: TokenActionView.Delegate { return } withAccountRecoveryChecked { [weak self] in - guard let self else { - return - } - self.navigationController?.pushViewController(trade, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "web3", "source": "asset_detail"] - ) + self?.navigationController?.pushViewController(trade, animated: true) } case .buy: break diff --git a/Mixin/UserInterface/Windows/UrlWindow.swift b/Mixin/UserInterface/Windows/UrlWindow.swift index 6b8ab79a0d..1524e0ebcb 100644 --- a/Mixin/UserInterface/Windows/UrlWindow.swift +++ b/Mixin/UserInterface/Windows/UrlWindow.swift @@ -86,10 +86,6 @@ class UrlWindow { viewModel: viewModel, ) navigationController.pushViewController(market, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "schema"] - ) } case .failure(let error): hud.set(style: .error, text: error.localizedDescription) @@ -114,10 +110,6 @@ class UrlWindow { ) if let navigationController = UIApplication.homeNavigationController, let trade { navigationController.pushViewController(trade, animated: true) - reporter.report( - event: .tradeStart, - tags: ["wallet": "main", "source": "schema"] - ) } } return true diff --git a/MixinServices/MixinServices/Foundation/Reporter.swift b/MixinServices/MixinServices/Foundation/Reporter.swift index aff7d42dad..c8ac834801 100644 --- a/MixinServices/MixinServices/Foundation/Reporter.swift +++ b/MixinServices/MixinServices/Foundation/Reporter.swift @@ -22,11 +22,11 @@ open class Reporter { case loginVerifyPIN = "login_pin_verify" case loginEnd = "login_end" - case tradeStart = "trade_start" + case tradeSpotStart = "trade_spot_start" + case tradeSpotEnd = "trade_spot_end" case tradeTokenSelect = "trade_token_select" case tradeQuote = "trade_quote" case tradePreview = "trade_preview" - case tradeEnd = "trade_end" case tradeTransactions = "trade_transactions" case tradeDetail = "trade_detail" case tradePerpsOpenPositionStart = "trade_perps_open_position_start" From de32ec23cc489ff61e74d8b8c5d0541c8a67ce09 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Fri, 29 May 2026 17:39:06 +0800 Subject: [PATCH 6/6] Report add phone number start and end --- Mixin/Service/UserOperationAnalytics.swift | 16 ++++++++++++++-- .../AddMobileNumberViewController.swift | 1 + .../MobileNumberIntroductionViewController.swift | 7 +++++++ ...fyMobileNumberOneTimeCodeViewController.swift | 10 ++++++---- .../Setting/RecoveryKitViewController.swift | 8 +++++--- .../Wallet/Buy/Account+BuyToken.swift | 1 + .../PerpsMarginInputViewController.swift | 2 +- .../Trade/Spot/TradeOrderViewController.swift | 2 +- .../MixinServices/Foundation/Reporter.swift | 3 +++ 9 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Mixin/Service/UserOperationAnalytics.swift b/Mixin/Service/UserOperationAnalytics.swift index 49fdcb79c4..a5419c498b 100644 --- a/Mixin/Service/UserOperationAnalytics.swift +++ b/Mixin/Service/UserOperationAnalytics.swift @@ -2,7 +2,7 @@ import Foundation enum UserOperationAnalytics { - enum Source: String { + enum TradeSource: String { case walletHome = "wallet_home" case moreExplore = "more_explore" case appCard = "app_card" @@ -15,6 +15,18 @@ enum UserOperationAnalytics { case scheme = "scheme" } - static var tradeSource: Source? + static var tradeSource: TradeSource? + +} + +extension UserOperationAnalytics { + + enum AddMobileNumberSource: String { + case recoveryKitGuide = "recovery_kit_guide" + case buyGuide = "buy_guide" + case settings = "settings" + } + + static var addMobileNumberSource: AddMobileNumberSource? } diff --git a/Mixin/UserInterface/Controllers/Setting/Mobile Number/AddMobileNumberViewController.swift b/Mixin/UserInterface/Controllers/Setting/Mobile Number/AddMobileNumberViewController.swift index 15ecf20137..9161219992 100644 --- a/Mixin/UserInterface/Controllers/Setting/Mobile Number/AddMobileNumberViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Mobile Number/AddMobileNumberViewController.swift @@ -44,6 +44,7 @@ extension AddMobileNumberViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + UserOperationAnalytics.addMobileNumberSource = .settings let introduction = MobileNumberIntroductionViewController(action: .add) navigationController?.pushViewController(introduction, animated: true) } diff --git a/Mixin/UserInterface/Controllers/Setting/Mobile Number/MobileNumberIntroductionViewController.swift b/Mixin/UserInterface/Controllers/Setting/Mobile Number/MobileNumberIntroductionViewController.swift index 6501bdd116..338db66537 100644 --- a/Mixin/UserInterface/Controllers/Setting/Mobile Number/MobileNumberIntroductionViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Mobile Number/MobileNumberIntroductionViewController.swift @@ -1,4 +1,5 @@ import UIKit +import MixinServices final class MobileNumberIntroductionViewController: IntroductionViewController { @@ -44,6 +45,12 @@ final class MobileNumberIntroductionViewController: IntroductionViewController { actionButton.setTitle(R.string.localizable.continue(), for: .normal) actionButton.titleLabel?.setFont(scaledFor: .systemFont(ofSize: 16, weight: .medium), adjustForContentSize: true) actionButton.addTarget(self, action: #selector(continueToNext(_:)), for: .touchUpInside) + + if let source = UserOperationAnalytics.tradeSource?.rawValue { + reporter.report(event: .addPhoneStart, tags: ["source": source]) + } else { + reporter.report(event: .addPhoneStart) + } } @objc private func continueToNext(_ sender: Any) { diff --git a/Mixin/UserInterface/Controllers/Setting/Mobile Number/VerifyMobileNumberOneTimeCodeViewController.swift b/Mixin/UserInterface/Controllers/Setting/Mobile Number/VerifyMobileNumberOneTimeCodeViewController.swift index 13abfce0dc..e627efbcbb 100644 --- a/Mixin/UserInterface/Controllers/Setting/Mobile Number/VerifyMobileNumberOneTimeCodeViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Mobile Number/VerifyMobileNumberOneTimeCodeViewController.swift @@ -70,21 +70,23 @@ class VerifyMobileNumberOneTimeCodeViewController: VerificationCodeViewControlle case .success(let account): LoginManager.shared.setAccount(account) self.verificationCodeField.resignFirstResponder() - let alert = switch self.context.intent { + let alert: UIAlertController + switch self.context.intent { case .periodicVerification: - UIAlertController( + alert = UIAlertController( title: R.string.localizable.verification_successful(), message: R.string.localizable.sms_verified_description(), preferredStyle: .alert ) case .addMobileNumber: - UIAlertController( + alert = UIAlertController( title: R.string.localizable.mobile_number_added(), message: R.string.localizable.mobile_number_added_description(), preferredStyle: .alert ) + reporter.report(event: .addPhoneEnd) case .changeMobileNumber: - UIAlertController( + alert = UIAlertController( title: R.string.localizable.mobile_number_changed(), message: R.string.localizable.sms_verified_description(), preferredStyle: .alert diff --git a/Mixin/UserInterface/Controllers/Setting/RecoveryKitViewController.swift b/Mixin/UserInterface/Controllers/Setting/RecoveryKitViewController.swift index 0b6367a0e1..ba57202d7d 100644 --- a/Mixin/UserInterface/Controllers/Setting/RecoveryKitViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/RecoveryKitViewController.swift @@ -87,15 +87,17 @@ extension RecoveryKitViewController: UITableViewDelegate { let next: UIViewController switch indexPath.row { case 0: - next = if let number = account.phone, !account.isAnonymous { - ChangeMobileNumberViewController(phoneNumber: number) + if let number = account.phone, !account.isAnonymous { + next = ChangeMobileNumberViewController(phoneNumber: number) } else { - AddMobileNumberViewController() + UserOperationAnalytics.addMobileNumberSource = .recoveryKitGuide + next = AddMobileNumberViewController() } case 1: next = ExportMnemonicPhrasesViewController() default: if account.isAnonymous { + UserOperationAnalytics.addMobileNumberSource = .recoveryKitGuide let tip = PopupTipViewController(tip: .addMobileNumber(.setRecoveryContact)) present(tip, animated: true) return diff --git a/Mixin/UserInterface/Controllers/Wallet/Buy/Account+BuyToken.swift b/Mixin/UserInterface/Controllers/Wallet/Buy/Account+BuyToken.swift index be7a0c8955..af2d14f474 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Buy/Account+BuyToken.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Buy/Account+BuyToken.swift @@ -18,6 +18,7 @@ extension Account { onVerificationNeeded: (PopupTipViewController) -> Void, ) { if isAnonymous { + UserOperationAnalytics.addMobileNumberSource = .buyGuide let tip = PopupTipViewController(tip: .addMobileNumber(.buyToken)) onVerificationNeeded(tip) } else if isPhoneVerificationValid { diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift index 9e8f43ef6a..1678416b31 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Perpetual/PerpsMarginInputViewController.swift @@ -247,6 +247,7 @@ extension PerpsMarginInputViewController: AddTokenMethodSelectorViewController.D let count = viewControllers.count - index viewControllers.removeLast(count) } + UserOperationAnalytics.tradeSource = .perpsMarginInput let trade = TradeViewController( wallet: .privacy, trading: .simpleSpot, @@ -255,7 +256,6 @@ extension PerpsMarginInputViewController: AddTokenMethodSelectorViewController.D referral: nil ) if let trade { - UserOperationAnalytics.tradeSource = .perpsMarginInput viewControllers.append(trade) navigationController.setViewControllers(viewControllers, animated: true) } diff --git a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift index 3c6e55c0e1..4ceac68f9f 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Trade/Spot/TradeOrderViewController.swift @@ -260,6 +260,7 @@ extension TradeOrderViewController: PillActionView.Delegate { case .swap, .none: trading = .simpleSpot } + UserOperationAnalytics.tradeSource = .tradeDetail let trade = TradeViewController( wallet: viewModel.wallet, trading: trading, @@ -268,7 +269,6 @@ extension TradeOrderViewController: PillActionView.Delegate { referral: nil ) if let trade { - UserOperationAnalytics.tradeSource = .tradeDetail viewControllers.append(trade) navigationController.setViewControllers(viewControllers, animated: true) } diff --git a/MixinServices/MixinServices/Foundation/Reporter.swift b/MixinServices/MixinServices/Foundation/Reporter.swift index c8ac834801..70eace48b8 100644 --- a/MixinServices/MixinServices/Foundation/Reporter.swift +++ b/MixinServices/MixinServices/Foundation/Reporter.swift @@ -22,6 +22,9 @@ open class Reporter { case loginVerifyPIN = "login_pin_verify" case loginEnd = "login_end" + case addPhoneStart = "add_phone_start" + case addPhoneEnd = "add_phone_end" + case tradeSpotStart = "trade_spot_start" case tradeSpotEnd = "trade_spot_end" case tradeTokenSelect = "trade_token_select"