From 2efa768ada4655675bcb05f9b737666a98d04094 Mon Sep 17 00:00:00 2001 From: Anton Stavnichiy Date: Fri, 31 May 2024 12:38:00 +0400 Subject: [PATCH] Add new market and appearance statistics --- .../Extensions/StatExtensions.swift | 122 ++++++++++++++++++ .../Models/LaunchScreen.swift | 9 ++ .../Models/PriceChangeMode.swift | 9 ++ .../UnstoppableWallet/Models/Stats.swift | 102 ++++++++++++++- .../Modules/AppStatus/AppStatusView.swift | 2 + .../AddEvmSyncSourceService.swift | 2 +- .../AddEvmSyncSourceViewModel.swift | 2 +- .../Modules/Faq/FaqViewController.swift | 1 + .../Modules/Guides/GuidesViewController.swift | 2 + .../LanguageSettingsViewModel.swift | 1 + .../Modules/Main/MainViewModel.swift | 2 +- .../Modules/Main/Workers/EventHandler.swift | 6 +- .../AddressAppShowModule.swift | 28 ++-- .../WalletConnectAppShowView.swift | 5 +- .../WidgetCoinAppShowModule.swift | 2 +- .../Market/Coins/MarketCoinsView.swift | 1 + .../Market/Coins/MarketCoinsViewModel.swift | 3 + .../Market/Etf/MarketEtfViewModel.swift | 2 + .../MarketCap/MarketMarketCapView.swift | 3 +- .../MarketCap/MarketMarketCapViewModel.swift | 1 + .../MarketGlobalTvlMetricService.swift | 2 +- .../Modules/Market/MarketGlobalView.swift | 4 + .../Modules/Market/MarketTabView.swift | 1 + .../Modules/Market/MarketView.swift | 4 +- .../Modules/Market/News/MarketNewsView.swift | 1 + .../Market/News/MarketNewsViewModel.swift | 1 + .../Market/Pairs/MarketPairsView.swift | 1 + .../Market/Pairs/MarketPairsViewModel.swift | 1 + .../Platforms/MarketPlatformsView.swift | 1 + .../Platforms/MarketPlatformsViewModel.swift | 2 + .../Modules/Market/Tvl/MarketTvlView.swift | 3 +- .../Market/Tvl/MarketTvlViewModel.swift | 10 +- .../Market/Volume/MarketVolumeView.swift | 3 +- .../Market/Volume/MarketVolumeViewModel.swift | 1 + .../Watchlist/MarketWatchlistView.swift | 1 + .../Watchlist/MarketWatchlistViewModel.swift | 4 + .../Market/Watchlist/WatchlistViewModel.swift | 9 +- .../Modules/Settings/About/AboutView.swift | 6 + .../Appearance/AppearanceViewModel.swift | 34 +++++ .../BaseCurrencySettingsViewModel.swift | 1 + .../Modules/Wallet/WalletViewModel.swift | 2 +- .../WalletConnectListViewController.swift | 3 + .../List/WalletConnectListViewModel.swift | 3 +- .../WalletConnectMainViewController.swift | 6 + .../WalletConnectPairingViewModel.swift | 2 + .../WalletConnect/WalletConnectRequest.swift | 4 + .../WalletConnect/WalletConnectService.swift | 2 + .../UserInterface/ScanQr/ScanQrView.swift | 2 +- 48 files changed, 385 insertions(+), 34 deletions(-) diff --git a/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift b/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift index 9f1a1fd123..50515abcba 100644 --- a/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift +++ b/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift @@ -16,6 +16,15 @@ extension HsTimePeriod { } } +extension MarketEtfViewModel.TimePeriod { + var statPeriod: StatPeriod { + switch self { + case .all: return .all + case let .period(timePeriod): return timePeriod.statPeriod + } + } +} + extension HsPeriodType { var statPeriod: StatPeriod { switch self { @@ -37,6 +46,18 @@ extension MainModule.Tab { } } +extension MarketModule.Tab { + var statTab: StatTab { + switch self { + case .coins: return .coins + case .news: return .news + case .pairs: return .pairs + case .platforms: return .platforms + case .watchlist: return .watchlist + } + } +} + extension MarketModule.TabOld { var statTab: StatTab { switch self { @@ -103,6 +124,17 @@ extension CoinProChartModule.ProChartType { } } +extension MarketModule.Top { + var statMarketTop: StatMarketTop { + switch self { + case .top100: return .top100 + case .top200: return .top200 + case .top300: return .top300 + case .top500: return .top500 + } + } +} + extension MarketModule.MarketTop { var statMarketTop: StatMarketTop { switch self { @@ -145,6 +177,96 @@ extension MarketModule.MarketPlatformField { } } +extension MarketModule.SortBy { + var statSortType: StatSortType { + switch self { + case .highestCap: return .highestCap + case .lowestCap: return .lowestCap + case .highestVolume: return .highestVolume + case .lowestVolume: return .lowestVolume + case .gainers: return .topGainers + case .losers: return .topLosers + } + } +} + +extension WatchlistSortBy { + var statSortType: StatSortType { + switch self { + case .manual: return .manual + case .highestCap: return .highestCap + case .lowestCap: return .lowestCap + case .gainers: return .topGainers + case .losers: return .topLosers + } + } +} + +extension MarketModule.SortOrder { + var statVolumeSortType: StatSortType { + switch self { + case .asc: return .lowestVolume + case .desc: return .highestVolume + } + } +} + +extension MarketModule.MarketTvlField { + var statField: String { + switch self { + case .value: return "currency" + case .diff: return "percent" + } + } +} + +extension MarketTvlViewModel.DiffType { + var statField: String { + switch self { + case .percent: return "percent" + case .currencyValue: return "currency" + } + } +} + +extension MarketTvlViewModel.Platforms { + var statPlatform: String { + switch self { + case .all: return "all" + case .ethereum: return "Ethereum" + case .solana: return "Solana" + case .binance: return "Binance" + case .avalanche: return "Avalanche" + case .terra: return "Terra" + case .fantom: return "Fantom" + case .arbitrum: return "Arbitrum" + case .polygon: return "Polygon" + } + } +} + +extension MarketEtfViewModel.SortBy { + var statSortBy: StatSortType { + switch self { + case .highestAssets: return .highestAssets + case .lowestAssets: return .lowestAssets + case .inflow: return .inflow + case .outflow: return .outflow + } + } +} + +extension WatchlistTimePeriod { + var statPeriod: StatPeriod { + switch self { + case .day1: return .day1 + case .week1: return .week1 + case .month1: return .month1 + case .month3: return .month3 + } + } +} + extension MarketModule.SortingField { var statSortType: StatSortType { switch self { diff --git a/UnstoppableWallet/UnstoppableWallet/Models/LaunchScreen.swift b/UnstoppableWallet/UnstoppableWallet/Models/LaunchScreen.swift index 875c86d711..b9e272b02b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/LaunchScreen.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/LaunchScreen.swift @@ -30,4 +30,13 @@ extension LaunchScreen: Codable { case marketOverview = "market_overview" case watchlist } + + var statType: String { + switch self { + case .auto: return "auto" + case .balance: return "balance" + case .marketOverview: return "market_overview" + case .watchlist: return "watchlist" + } + } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift b/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift index e45428f426..1f74c0aeb3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift @@ -2,3 +2,12 @@ enum PriceChangeMode: String, CaseIterable, Codable { case hour24 case midnightUtc } + +extension PriceChangeMode { + var statName: String { + switch self { + case .hour24: return "hour_24" + case .midnightUtc: return "midnight_utc" + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift b/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift index b679f245c0..f307ab10fb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift @@ -5,10 +5,10 @@ enum StatPage: String { case academy case accountExtendedPrivateKey = "account_extended_private_key" case accountExtendedPublicKey = "account_extended_public_key" - case addEvmSyncSource = "add_evm_sync_source" case addToken = "add_token" case advancedSearch = "advanced_search" case advancedSearchResults = "advanced_search_results" + case appStatus = "app_status" case appearance case backupManager = "backup_manager" case backupPromptAfterCreate = "backup_prompt_after_create" @@ -65,6 +65,7 @@ enum StatPage: String { case externalReddit = "external_reddit" case externalTelegram = "external_telegram" case externalTwitter = "external_twitter" + case externalWebsite = "external_website" case faq case globalMetricsDefiCap = "global_metrics_defi_cap" case globalMetricsMarketCap = "global_metrics_market_cap" @@ -95,6 +96,7 @@ enum StatPage: String { case news case newWallet = "new_wallet" case newWalletAdvanced = "new_wallet_advanced" + case privacy case privateKeys = "private_keys" case publicKeys = "public_keys" case rateUs = "rate_us" @@ -112,6 +114,7 @@ enum StatPage: String { case swap case switchWallet = "switch_wallet" case tellFriends = "tell_friends" + case terms case tokenPage = "token_page" case topCoins = "top_coins" case topMarketPairs = "top_market_pairs" @@ -123,8 +126,12 @@ enum StatPage: String { case transactions case unlinkWallet = "unlink_wallet" case walletConnect = "wallet_connect" + case walletConnectPairings = "wallet_connect_pairings" + case walletConnectRequest = "wallet_connect_request" + case walletConnectSession = "wallet_connect_session" case watchlist case watchWallet = "watch_wallet" + case whatsNews = "whats_news" case widget } @@ -133,7 +140,12 @@ enum StatSection: String { case addressRecipient = "address_recipient" case addressSpender = "address_spender" case addressTo = "address_to" + case coins + case deepLink = "deep_link" case input + case news + case pairs + case platforms case popular case recent case searchResults = "search_results" @@ -142,15 +154,20 @@ enum StatSection: String { case topGainers = "top_gainers" case topLosers = "top_losers" case topPlatforms = "top_platforms" + case qrScan = "qr_scan" + case watchlist } -enum StatEvent { +enum StatEvent { case add(entity: StatEntity) case addEvmSource(chainUid: String) case addToken(token: Token) case addToWallet case addToWatchlist(coinUid: String) + case approveRequest(chainUid: String) + case cancel case clear(entity: StatEntity) + case connect, reconnect, disconnect, reject case copy(entity: StatEntity) case copyAddress(chainUid: String) case createWallet(walletType: String) @@ -160,6 +177,7 @@ enum StatEvent { case edit(entity: StatEntity) case enableToken(token: Token) case exportFull + case hideBalanceButtons(hide: Bool) case importWallet(walletType: String) case importFull case open(page: StatPage) @@ -172,24 +190,37 @@ enum StatEvent { case openReceive(token: Token) case openResend(chainUid: String, type: String) case openSend(token: Token) + case openSendTokenList(coinUid: String?, chainUid: String?) case openTokenInfo(token: Token) case openTokenPage(element: WalletModule.Element) case paste(entity: StatEntity) case refresh + case rejectRequest(chainUid: String) case removeAmount case removeFromWallet case removeFromWatchlist(coinUid: String) case scanQr(entity: StatEntity) case select(entity: StatEntity) + case selectAppIcon(iconUid: String) + case openArticle(relativeUrl: String) + case selectBalanceConversion(coinUid: String) + case selectBalanceValue(type: String) + case selectLaunchScreen(type: String) + case selectTheme(type: String) case setAmount case share(entity: StatEntity) + case showMarketsTab(shown: Bool) + case showSignals(shown: Bool) + case switchBaseCurrency(code: String) case switchBtcSource(chainUid: String, type: BtcRestoreMode) case switchChartPeriod(period: StatPeriod) case switchEvmSource(chainUid: String, name: String) case switchField(field: StatField) case switchFilterType(type: String) + case switchLanguage(language: String) case switchMarketTop(marketTop: StatMarketTop) case switchPeriod(period: StatPeriod) + case switchPriceChangeMode(mode: PriceChangeMode) case switchSortType(sortType: StatSortType) case switchTab(tab: StatTab) case switchTvlChain(chain: String) @@ -199,7 +230,8 @@ enum StatEvent { case toggleIndicators(shown: Bool) case togglePrice case toggleSortDirection - case toggleTvlField + case toggleTvlField(field: String) + case walletConnectPair case watchWallet(walletType: String) var name: String { @@ -208,39 +240,58 @@ enum StatEvent { case .addEvmSource: return "add_evm_source" case .addToWallet: return "add_to_wallet" case .addToWatchlist: return "add_to_watchlist" + case .approveRequest: return "approve_request" + case .cancel: return "cancel" case .clear: return "clear" + case .connect: return "connect" case .copy, .copyAddress: return "copy" case .createWallet: return "create_wallet" case .delete: return "delete" case .deleteCustomEvmSource: return "delete_custom_evm_source" case .disableToken: return "disable_token" + case .disconnect: return "disconnect" case .edit: return "edit" case .enableToken: return "enable_token" case .exportFull: return "export_full" + case .hideBalanceButtons: return "hide_balance_buttons" case .importFull: return "import_full" case .importWallet: return "import_wallet" - case .open, .openCategory, .openCoin, .openPlatform, .openReceive, .openResend, .openSend, .openTokenPage, + case .open, .openCategory, .openCoin, .openPlatform, .openReceive, .openResend, .openSend, .openSendTokenList, .openTokenPage, .openBlockchainSettingsBtc, .openBlockchainSettingsEvm, .openBlockchainSettingsEvmAdd: return "open_page" case .openTokenInfo: return "open_token_info" case .paste: return "paste" + case .reconnect: return "reconnect" case .refresh: return "refresh" + case .reject: return "disconnect" + case .rejectRequest: return "reject_request" case .removeAmount: return "remove_amount" case .removeFromWallet: return "remove_from_wallet" case .removeFromWatchlist: return "remove_from_watchlist" case .scanQr: return "scan_qr" case .select: return "select" + case .selectAppIcon: return "select_app_icon" + case .openArticle: return "open_article" + case .selectBalanceConversion: return "select_balance_conversion" + case .selectBalanceValue: return "select_balance_value" + case .selectLaunchScreen: return "select_launch_screen" + case .selectTheme: return "select_theme" case .setAmount: return "set_amount" case .share: return "share" + case .showMarketsTab: return "show_markets_tab" + case .showSignals: return "show_signals" + case .switchBaseCurrency: return "switch_base_currency" case .switchBtcSource: return "switch_btc_source" case .switchChartPeriod: return "switch_chart_period" case .switchEvmSource: return "switch_evm_source" case .switchField: return "switch_field" case .switchFilterType: return "switch_filter_type" + case .switchLanguage: return "switch_language" case .switchMarketTop: return "switch_market_top" case .switchPeriod: return "switch_period" + case .switchPriceChangeMode: return "switch_price_change_mode" case .switchSortType: return "switch_sort_type" case .switchTab: return "switch_tab" - case .switchTvlChain: return "switch_tvl_platform" + case .switchTvlChain: return "switch_tvl_chain" case .toggleBalanceHidden: return "toggle_balance_hidden" case .toggleConversionCoin: return "toggle_conversion_coin" case .toggleHidden: return "toggle_hidden" @@ -248,6 +299,7 @@ enum StatEvent { case .togglePrice: return "toggle_price" case .toggleSortDirection: return "toggle_sort_direction" case .toggleTvlField: return "toggle_tvl_field" + case .walletConnectPair: return "wallet_connect_pair" case .watchWallet: return "watch_wallet" } } @@ -258,6 +310,7 @@ enum StatEvent { case let .addEvmSource(chainUid): return [.chainUid: chainUid] case let .addToken(token): return params(token: token).merging([.entity: StatEntity.token.rawValue]) { $1 } case let .addToWatchlist(coinUid): return [.coinUid: coinUid] + case let .approveRequest(chainUid): return [.chainUid: chainUid] case let .clear(entity): return [.entity: entity.rawValue] case let .copy(entity): return [.entity: entity.rawValue] case let .copyAddress(chainUid): return [.entity: StatEntity.address.rawValue, .chainUid: chainUid] @@ -267,6 +320,7 @@ enum StatEvent { case let .disableToken(token): return params(token: token) case let .edit(entity): return [.entity: entity.rawValue] case let .enableToken(token): return params(token: token) + case let .hideBalanceButtons(hide): return [.shown: hide] case let .importWallet(walletType): return [.walletType: walletType] case let .open(page): return [.page: page.rawValue] case let .openBlockchainSettingsBtc(chainUid: chainUid): return [.page: StatPage.blockchainSettingsBtc.rawValue, .chainUid: chainUid] @@ -274,6 +328,18 @@ enum StatEvent { case let .openBlockchainSettingsEvmAdd(chainUid: chainUid): return [.page: StatPage.blockchainSettingsEvmAdd.rawValue, .chainUid: chainUid] case let .openCategory(categoryUid): return [.page: StatPage.coinCategory.rawValue, .categoryUid: categoryUid] case let .openCoin(coinUid): return [.page: StatPage.coinPage.rawValue, .coinUid: coinUid] + case let .openSendTokenList(coinUid, chainUid): + var params: [StatParam: Any] = [.page: StatPage.sendTokenList.rawValue] + params[.coinUid] = coinUid + params[.chainUid] = chainUid + return params + case let .selectAppIcon(iconUid): return [.iconUid: iconUid] + case let .openArticle(relativeUrl): return [.relativeUrl: relativeUrl] + case let .rejectRequest(chainUid): return [.chainUid: chainUid] + case let .selectBalanceConversion(coinUid): return [.coinUid: coinUid] + case let .selectBalanceValue(type): return [.type: type] + case let .selectLaunchScreen(type): return [.type: type] + case let .selectTheme(type): return [.type: type] case let .openPlatform(chainUid): return [.page: StatPage.topPlatform.rawValue, .chainUid: chainUid] case let .openReceive(token): return params(token: token).merging([.page: StatPage.receive.rawValue]) { $1 } case let .openResend(chainUid, type): return [.page: StatPage.resend.rawValue, .chainUid: chainUid, .type: type] @@ -291,17 +357,23 @@ enum StatEvent { case let .scanQr(entity): return [.entity: entity.rawValue] case let .select(entity): return [.entity: entity.rawValue] case let .share(entity): return [.entity: entity.rawValue] + case let .showMarketsTab(shown): return [.shown: shown] + case let .showSignals(shown): return [.shown: shown] + case let .switchBaseCurrency(code: code): return [.currencyCode: code] case let .switchBtcSource(chainUid, type): return [.chainUid: chainUid, .type: type.rawValue] case let .switchChartPeriod(period): return [.period: period.rawValue] case let .switchEvmSource(chainUid, name): return [.chainUid: chainUid, .type: name] case let .switchField(field): return [.field: field.rawValue] case let .switchFilterType(type): return [.type: type] + case let .switchLanguage(language): return [.language: language] case let .switchMarketTop(marketTop): return [.marketTop: marketTop.rawValue] case let .switchPeriod(period): return [.period: period.rawValue] + case let .switchPriceChangeMode(priceChangeMode): return [.changeMode: priceChangeMode.statName] case let .switchSortType(sortType): return [.type: sortType.rawValue] case let .switchTab(tab): return [.tab: tab.rawValue] case let .switchTvlChain(chain): return [.tvlChain: chain] case let .toggleIndicators(shown): return [.shown: shown] + case let .toggleTvlField(field): return [.field: field] case let .watchWallet(walletType): return [.walletType: walletType] default: return nil } @@ -320,13 +392,19 @@ enum StatParam: String { case bitcoinCashCoinType = "bitcoin_cash_coin_type" case categoryUid = "category_uid" case chainUid = "chain_uid" + case currencyCode = "currency_code" case coinUid = "coin_uid" case derivation case entity case field + case hide + case iconUid = "icon_uid" + case language case marketTop = "market_top" case page case period + case changeMode = "change_mode" + case relativeUrl = "relative_url" case shown case tab case tvlChain = "tvl_chain" @@ -336,7 +414,7 @@ enum StatParam: String { enum StatTab: String { case markets, balance, transactions, settings - case overview, news, watchlist + case coins, overview, news, pairs, platforms, watchlist case analytics case all, incoming, outgoing, swap, approve } @@ -346,12 +424,18 @@ enum StatSortType: String { case name case priceChange = "price_change" + case manual case highestCap = "highest_cap" case lowestCap = "lowest_cap" case highestVolume = "highest_volume" case lowestVolume = "lowest_volume" case topGainers = "top_gainers" case topLosers = "top_losers" + + case highestAssets = "highest_assets" + case lowestAssets = "lowest_assets" + case inflow + case outflow } enum StatPeriod: String { @@ -377,24 +461,28 @@ enum StatMarketTop: String { case top100 case top200 case top300 + case top500 } enum StatEntity: String { case account case address + case all case blockchain case cloudBackup = "cloud_backup" case contractAddress = "contract_address" case derivation case evmAddress = "evm_address" case evmPrivateKey = "evm_private_key" - case evmSyncSource = "evm_sync_source" case key case passphrase case receiveAddress = "receive_address" case recoveryPhrase = "recovery_phrase" + case session + case status case token case transactionId = "transaction_id" case wallet + case walletConnectPair = "wallet_connect_pair" case walletName = "wallet_name" } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/AppStatus/AppStatusView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/AppStatus/AppStatusView.swift index 178f520e14..8b1c70910b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/AppStatus/AppStatusView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/AppStatus/AppStatusView.swift @@ -10,6 +10,7 @@ struct AppStatusView: View { VStack(spacing: .margin24) { HStack(spacing: .margin8) { Button(action: { + stat(page: .appStatus, event: .copy(entity: .status)) CopyHelper.copyAndNotify(value: viewModel.rawStatus) }) { Text("button.copy".localized) @@ -17,6 +18,7 @@ struct AppStatusView: View { .buttonStyle(PrimaryButtonStyle(style: .yellow)) Button(action: { + stat(page: .appStatus, event: .share(entity: .status)) shareText = viewModel.rawStatus }) { Text("button.share".localized) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceService.swift index fa6523c95a..6192db4f4e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceService.swift @@ -5,7 +5,7 @@ import RxRelay import RxSwift class AddEvmSyncSourceService { - private let blockchainType: BlockchainType + let blockchainType: BlockchainType private let evmSyncSourceManager: EvmSyncSourceManager private var disposeBag = DisposeBag() diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceViewModel.swift index 773061b287..0421dd6b7f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/EvmNetwork/AddEvmSyncSource/AddEvmSyncSourceViewModel.swift @@ -35,7 +35,7 @@ extension AddEvmSyncSourceViewModel { func onTapAdd() { do { try service.save() - stat(page: .addEvmSyncSource, event: .add(entity: .evmSyncSource)) + stat(page: .blockchainSettingsEvmAdd, event: .addEvmSource(chainUid: service.blockchainType.uid)) finishRelay.accept(()) } catch AddEvmSyncSourceService.UrlError.alreadyExists { urlCautionRelay.accept(Caution(text: "add_evm_sync_source.warning.url_exists".localized, type: .warning)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Faq/FaqViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Faq/FaqViewController.swift index b820b8858c..fd56f8060a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Faq/FaqViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Faq/FaqViewController.swift @@ -147,6 +147,7 @@ extension FaqViewController: SectionsDataSource { return } + stat(page: .faq, event: .openArticle(relativeUrl: url.relativePath)) let module = MarkdownModule.viewController(url: url, handleRelativeUrl: false) self?.navigationController?.pushViewController(module, animated: true) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Guides/GuidesViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Guides/GuidesViewController.swift index 517bd17325..92e3585675 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Guides/GuidesViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Guides/GuidesViewController.swift @@ -134,6 +134,8 @@ extension GuidesViewController: UITableViewDataSource, UITableViewDelegate { return } + stat(page: .academy, event: .openArticle(relativeUrl: url.relativePath)) + let module = MarkdownModule.viewController(url: url) navigationController?.pushViewController(module, animated: true) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/LanguageSettings/LanguageSettingsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/LanguageSettings/LanguageSettingsViewModel.swift index 7b0b29a755..a5eec142d7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/LanguageSettings/LanguageSettingsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/LanguageSettings/LanguageSettingsViewModel.swift @@ -5,6 +5,7 @@ class LanguageSettingsViewModel: ObservableObject { @Published var currentLanguage: String { didSet { + stat(page: .language, event: .switchLanguage(language: currentLanguage)) LanguageManager.shared.currentLanguage = currentLanguage } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainViewModel.swift index 2e4cb1106a..8e1dbec10f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainViewModel.swift @@ -57,7 +57,7 @@ class MainViewModel { deepLinkService.setDeepLinkShown() Task { do { - try await eventHandler.handle(event: deepLink, eventType: .deepLink) + try await eventHandler.handle(source: .main, event: deepLink, eventType: .deepLink) } catch { print("Can't handle Deep Link \(error)") } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/EventHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/EventHandler.swift index 129d3882d3..5c7b751542 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/EventHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/EventHandler.swift @@ -2,7 +2,7 @@ import ComponentKit import Foundation protocol IEventHandler { - func handle(event: Any, eventType: EventHandler.EventType) async throws + func handle(source: StatPage, event: Any, eventType: EventHandler.EventType) async throws } class EventHandler { @@ -26,11 +26,11 @@ class EventHandler { } extension EventHandler: IEventHandler { - func handle(event: Any, eventType: EventHandler.EventType = .all) async throws { + func handle(source: StatPage, event: Any, eventType: EventHandler.EventType = .all) async throws { var lastError: Error? for handler in eventHandlers { do { - try await handler.handle(event: event, eventType: eventType) + try await handler.handle(source: source, event: event, eventType: eventType) return } catch { lastError = error diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/SendAppShowWorker/AddressAppShowModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/SendAppShowWorker/AddressAppShowModule.swift index 5316569566..1c0fb27956 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/SendAppShowWorker/AddressAppShowModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/SendAppShowWorker/AddressAppShowModule.swift @@ -5,6 +5,7 @@ import UIKit class AddressAppShowModule { private let disposeBag = DisposeBag() private let parentViewController: UIViewController? + private let marketKit = App.shared.marketKit init(parentViewController: UIViewController?) { self.parentViewController = parentViewController @@ -27,19 +28,31 @@ class AddressAppShowModule { } } - private func showSendTokenList(uri: AddressUri, allowedBlockchainTypes: [BlockchainType]? = nil) { + private func showSendTokenList(source: StatPage, eventType: EventHandler.EventType, uri: AddressUri, allowedBlockchainTypes: [BlockchainType]? = nil) { let allowedBlockchainTypes = allowedBlockchainTypes ?? uri.allowedBlockchainTypes - var allowedTokenTypes: [TokenType]? + var allowedTokenType: TokenType? if let tokenUid: String = uri.value(field: .tokenUid), let tokenType = TokenType(id: tokenUid) { - allowedTokenTypes = [tokenType] + allowedTokenType = tokenType } + var token: Token? + if let allowedTokenType, + let blockchainUid: String = uri.value(field: .blockchainUid), + let blockchain = try? marketKit.blockchain(uid: blockchainUid), + let selectedToken = try? marketKit.token(query: .init(blockchainType: blockchain.type, tokenType: allowedTokenType)) + { + token = selectedToken + } + + let event = StatEvent.openSendTokenList(coinUid: token?.coin.uid, chainUid: token?.blockchain.uid) + stat(page: source, section: eventType.contains(.address) ? .qrScan : .deepLink, event: event) + guard let viewController = WalletModule.sendTokenListViewController( allowedBlockchainTypes: allowedBlockchainTypes, - allowedTokenTypes: allowedTokenTypes, + allowedTokenTypes: allowedTokenType.map { [$0] }, mode: .prefilled(address: uri.address, amount: uri.amount) ) else { return @@ -50,14 +63,14 @@ class AddressAppShowModule { extension AddressAppShowModule: IEventHandler { @MainActor - func handle(event: Any, eventType: EventHandler.EventType) async throws { + func handle(source: StatPage, event: Any, eventType: EventHandler.EventType) async throws { // check if we parse deeplink with transfer address if eventType.contains(.deepLink) { if let event = event as? DeepLinkManager.DeepLink { guard case let .transfer(parsed) = event else { throw EventHandler.HandleError.noSuitableHandler } - showSendTokenList(uri: parsed) + showSendTokenList(source: source, eventType: eventType, uri: parsed) } else { return } @@ -70,7 +83,7 @@ extension AddressAppShowModule: IEventHandler { } if let parsed = uri(text: text.trimmingCharacters(in: .whitespacesAndNewlines)) { - showSendTokenList(uri: parsed) + showSendTokenList(source: source, eventType: eventType, uri: parsed) } else { let disposeBag = DisposeBag() let chain = AddressParserFactory.parserChain(blockchainType: nil, withEns: false) @@ -90,7 +103,6 @@ extension AddressAppShowModule: IEventHandler { } var uri = AddressUri(scheme: "") uri.address = text - showSendTokenList(uri: uri, allowedBlockchainTypes: types) return } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WalletConnectAppShowWorker/WalletConnectAppShowView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WalletConnectAppShowWorker/WalletConnectAppShowView.swift index e09926c322..9e97889fd1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WalletConnectAppShowWorker/WalletConnectAppShowView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WalletConnectAppShowWorker/WalletConnectAppShowView.swift @@ -96,6 +96,7 @@ class WalletConnectAppShowView { return case let .controller(controller): guard let controller else { return } + stat(page: .main, event: .open(page: .walletConnectRequest)) parentViewController?.visibleController.present(controller, animated: true) } } @@ -152,8 +153,9 @@ extension WalletConnectAppShowView { extension WalletConnectAppShowView: IEventHandler { var eventType: EventHandler.EventType { [.deepLink, .walletConnectUri] } - func handle(event: Any, eventType _: EventHandler.EventType) async throws { + func handle(source: StatPage, event: Any, eventType _: EventHandler.EventType) async throws { var uri: String? + switch event { case let event as String: uri = event @@ -174,6 +176,7 @@ extension WalletConnectAppShowView: IEventHandler { throw EventHandler.HandleError.noSuitableHandler } + stat(page: source, event: .walletConnectPair) try viewModel.handleWalletConnect(url: uri) isWaitingForSession = true diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WidgetCoinAppShowWorker/WidgetCoinAppShowModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WidgetCoinAppShowWorker/WidgetCoinAppShowModule.swift index e2cccc6c24..4000453215 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WidgetCoinAppShowWorker/WidgetCoinAppShowModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/WidgetCoinAppShowWorker/WidgetCoinAppShowModule.swift @@ -10,7 +10,7 @@ class WidgetCoinAppShowModule { extension WidgetCoinAppShowModule: IEventHandler { @MainActor - func handle(event: Any, eventType: EventHandler.EventType) async throws { + func handle(source _: StatPage, event: Any, eventType: EventHandler.EventType) async throws { guard eventType.contains(.deepLink) else { throw EventHandler.HandleError.noSuitableHandler } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift index 04dd5cdac4..1a5e647b51 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift @@ -35,6 +35,7 @@ struct MarketCoinsView: View { } .sheet(item: $presentedFullCoin) { fullCoin in CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() + .onFirstAppear { stat(page: .markets, section: .coins, event: .openCoin(coinUid: fullCoin.coin.uid)) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift index e08dd3817e..8a7df447a1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift @@ -20,18 +20,21 @@ class MarketCoinsViewModel: ObservableObject { var sortBy: MarketModule.SortBy = .gainers { didSet { + stat(page: .markets, section: .coins, event: .switchSortType(sortType: sortBy.statSortType)) syncState() } } var top: MarketModule.Top = .top100 { didSet { + stat(page: .markets, event: .switchMarketTop(marketTop: top.statMarketTop)) syncState() } } var timePeriod: HsTimePeriod = .day1 { didSet { + stat(page: .markets, section: .coins, event: .switchPeriod(period: timePeriod.statPeriod)) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Etf/MarketEtfViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Etf/MarketEtfViewModel.swift index 8492bd9df3..f6db03a373 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Etf/MarketEtfViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Etf/MarketEtfViewModel.swift @@ -20,12 +20,14 @@ class MarketEtfViewModel: ObservableObject { var sortBy: SortBy = .highestAssets { didSet { + stat(page: .globalMetricsEtf, event: .switchSortType(sortType: sortBy.statSortBy)) syncState() } } var timePeriod: TimePeriod = .period(timePeriod: .day1) { didSet { + stat(page: .globalMetricsEtf, event: .switchPeriod(period: timePeriod.statPeriod)) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapView.swift index 2ff99c77bf..9d26027873 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapView.swift @@ -13,7 +13,7 @@ struct MarketMarketCapView: View { init(isPresented: Binding) { _viewModel = StateObject(wrappedValue: MarketMarketCapViewModel()) _chartViewModel = StateObject(wrappedValue: MetricChartViewModel.instance(type: .totalMarketCap)) - _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel()) + _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel(page: .globalMetricsMarketCap)) _isPresented = isPresented } @@ -62,6 +62,7 @@ struct MarketMarketCapView: View { } .sheet(item: $presentedFullCoin) { fullCoin in CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() + .onFirstAppear { stat(page: .globalMetricsMarketCap, event: .openCoin(coinUid: fullCoin.coin.uid)) } } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift index b379561b91..a7ecf2543b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift @@ -20,6 +20,7 @@ class MarketMarketCapViewModel: ObservableObject { var sortOrder: MarketModule.SortOrder = .desc { didSet { + stat(page: .globalMetricsMarketCap, event: .toggleSortDirection) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/TvlInDefi/MarketGlobalTvlMetricService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/TvlInDefi/MarketGlobalTvlMetricService.swift index e663efd6c4..cc8d9bf605 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/TvlInDefi/MarketGlobalTvlMetricService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/TvlInDefi/MarketGlobalTvlMetricService.swift @@ -46,7 +46,7 @@ class MarketGlobalTvlMetricService { didSet { syncIfPossible() - stat(page: .globalMetricsTvlInDefi, event: .toggleTvlField) + stat(page: .globalMetricsTvlInDefi, event: .toggleTvlField(field: marketTvlField.statField)) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalView.swift index c3b89e4774..92f7e596df 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalView.swift @@ -35,15 +35,19 @@ struct MarketGlobalView: View { .animation(.default, value: viewModel.marketGlobal == nil) .sheet(isPresented: $tvlPresented) { MarketTvlView(isPresented: $tvlPresented) + .onFirstAppear { stat(page: .markets, event: .open(page: .globalMetricsTvlInDefi)) } } .sheet(isPresented: $marketCapPresented) { MarketMarketCapView(isPresented: $marketCapPresented) + .onFirstAppear { stat(page: .markets, event: .open(page: .globalMetricsMarketCap)) } } .sheet(isPresented: $volumePresented) { MarketVolumeView(isPresented: $volumePresented) + .onFirstAppear { stat(page: .markets, event: .open(page: .globalMetricsVolume)) } } .sheet(isPresented: $etfPresented) { MarketEtfView(isPresented: $etfPresented) + .onFirstAppear { stat(page: .markets, event: .open(page: .globalMetricsEtf)) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTabView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTabView.swift index a79f31dfcd..f35e84b0cf 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTabView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTabView.swift @@ -49,6 +49,7 @@ struct MarketTabView: View { } .frame(maxHeight: .infinity) .onChange(of: viewModel.currentTab) { tab in + stat(page: .markets, event: .switchTab(tab: tab.statTab)) load(tab: tab) } .onFirstAppear { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketView.swift index 145209516f..8a6f66a86f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketView.swift @@ -14,7 +14,7 @@ struct MarketView: View { init() { _searchViewModel = StateObject(wrappedValue: MarketSearchViewModel()) _globalViewModel = StateObject(wrappedValue: MarketGlobalViewModel()) - _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel()) + _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel(page: .markets, section: .coins)) } var body: some View { @@ -30,6 +30,7 @@ struct MarketView: View { if searchFocused { MarketSearchView(viewModel: searchViewModel, watchlistViewModel: watchlistViewModel) + .onFirstAppear { stat(page: .markets, event: .open(page: .marketSearch)) } } } } @@ -39,6 +40,7 @@ struct MarketView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { + stat(page: .markets, event: .open(page: .advancedSearch)) advancedSearchPresented = true }) { Image("manage_2_24") diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsView.swift index 9f107138ff..1a62817382 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsView.swift @@ -32,6 +32,7 @@ struct MarketNewsView: View { padding: EdgeInsets(top: .margin16, leading: .margin16, bottom: .margin16, trailing: .margin16), action: { UrlManager.open(url: post.url) + stat(page: .markets, section: .news, event: .open(page: .externalNews)) } ) { itemContent( diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsViewModel.swift index 3e2ea8a97d..026ce28137 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/News/MarketNewsViewModel.swift @@ -46,6 +46,7 @@ extension MarketNewsViewModel { func refresh() async { await _sync() + stat(page: .markets, section: .news, event: .refresh) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsView.swift index ab8e54bef1..ef2b4104f7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsView.swift @@ -49,6 +49,7 @@ struct MarketPairsView: View { ClickableRow(action: { if let tradeUrl = pair.tradeUrl { UrlManager.open(url: tradeUrl) + stat(page: .markets, section: .pairs, event: .open(page: .externalMarketPair)) } }) { itemContent( diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsViewModel.swift index d46f9f488a..2f02f0c6b2 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Pairs/MarketPairsViewModel.swift @@ -20,6 +20,7 @@ class MarketPairsViewModel: ObservableObject { var volumeSortOrder: MarketModule.SortOrder = .desc { didSet { + stat(page: .markets, section: .pairs, event: .switchSortType(sortType: volumeSortOrder.statVolumeSortType)) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsView.swift index 15c65d558b..05015ce967 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsView.swift @@ -33,6 +33,7 @@ struct MarketPlatformsView: View { } .sheet(item: $presentedPlatform) { platform in MarketPlatformView(platform: platform).ignoresSafeArea() + .onFirstAppear { stat(page: .markets, section: .platforms, event: .openPlatform(chainUid: platform.blockchain.uid)) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsViewModel.swift index c7882d82ae..aefd7d35f5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Platforms/MarketPlatformsViewModel.swift @@ -20,12 +20,14 @@ class MarketPlatformsViewModel: ObservableObject { var sortBy: MarketModule.SortBy = .gainers { didSet { + stat(page: .markets, section: .platforms, event: .switchSortType(sortType: sortBy.statSortType)) syncState() } } var timePeriod: HsTimePeriod = .week1 { didSet { + stat(page: .markets, section: .platforms, event: .switchPeriod(period: timePeriod.statPeriod)) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlView.swift index 35c5247504..df027c3c6c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlView.swift @@ -14,7 +14,7 @@ struct MarketTvlView: View { init(isPresented: Binding) { _viewModel = StateObject(wrappedValue: MarketTvlViewModel()) _chartViewModel = StateObject(wrappedValue: MetricChartViewModel.instance(type: .tvlInDefi)) - _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel()) + _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel(page: .globalMetricsTvlInDefi)) _isPresented = isPresented } @@ -65,6 +65,7 @@ struct MarketTvlView: View { } .sheet(item: $presentedFullCoin) { fullCoin in CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() + .onFirstAppear { stat(page: .globalMetricsTvlInDefi, event: .openCoin(coinUid: fullCoin.coin.uid)) } } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlViewModel.swift index 9b233f7e3a..7571010d77 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Tvl/MarketTvlViewModel.swift @@ -20,18 +20,24 @@ class MarketTvlViewModel: ObservableObject { var platforms: MarketTvlViewModel.Platforms = .all { didSet { + stat(page: .globalMetricsTvlInDefi, event: .switchTvlChain(chain: platforms.statPlatform)) syncState() } } var sortOrder: MarketModule.SortOrder = .desc { didSet { + stat(page: .globalMetricsTvlInDefi, event: .toggleSortDirection) syncState() } } @Published var timePeriod: HsTimePeriod = .day1 - @Published var diffType: DiffType = .percent + @Published var diffType: DiffType = .percent { + didSet { + stat(page: .globalMetricsTvlInDefi, event: .toggleTvlField(field: diffType.statField)) + } + } init() { currencyManager.$baseCurrency @@ -104,7 +110,7 @@ extension MarketTvlViewModel { case .all: tvl = defiCoin.tvl - var tvlChange: Decimal? = defiCoin.tvlChangeValue(timePeriod: timePeriod) + let tvlChange: Decimal? = defiCoin.tvlChangeValue(timePeriod: timePeriod) switch diffType { case .percent: diff = tvlChange.map { .percent(value: $0) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeView.swift index 375b12e5aa..2c8538e078 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeView.swift @@ -13,7 +13,7 @@ struct MarketVolumeView: View { init(isPresented: Binding) { _viewModel = StateObject(wrappedValue: MarketVolumeViewModel()) _chartViewModel = StateObject(wrappedValue: MetricChartViewModel.instance(type: .volume24h)) - _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel()) + _watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel(page: .globalMetricsVolume)) _isPresented = isPresented } @@ -62,6 +62,7 @@ struct MarketVolumeView: View { } .sheet(item: $presentedFullCoin) { fullCoin in CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() + .onFirstAppear { stat(page: .globalMetricsVolume, event: .openCoin(coinUid: fullCoin.coin.uid)) } } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift index 382f1c49eb..5dbaaed754 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift @@ -20,6 +20,7 @@ class MarketVolumeViewModel: ObservableObject { var sortOrder: MarketModule.SortOrder = .desc { didSet { + stat(page: .globalMetricsVolume, event: .toggleSortDirection) syncState() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift index 435071f468..f55511a47e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift @@ -39,6 +39,7 @@ struct MarketWatchlistView: View { } .sheet(item: $presentedFullCoin) { fullCoin in CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() + .onFirstAppear { stat(page: .markets, section: .watchlist, event: .openCoin(coinUid: fullCoin.coin.uid)) } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift index 2bf8bb7646..f25243f9b0 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift @@ -24,6 +24,7 @@ class MarketWatchlistViewModel: ObservableObject { @Published var sortBy: WatchlistSortBy { didSet { + stat(page: .markets, section: .watchlist, event: .switchSortType(sortType: sortBy.statSortType)) syncState() watchlistManager.sortBy = sortBy } @@ -31,6 +32,7 @@ class MarketWatchlistViewModel: ObservableObject { @Published var timePeriod: WatchlistTimePeriod { didSet { + stat(page: .markets, section: .watchlist, event: .switchPeriod(period: timePeriod.statPeriod)) syncState() watchlistManager.timePeriod = timePeriod } @@ -38,6 +40,7 @@ class MarketWatchlistViewModel: ObservableObject { @Published var showSignals: Bool { didSet { + stat(page: .markets, section: .watchlist, event: .showSignals(shown: showSignals)) syncState() watchlistManager.showSignals = showSignals } @@ -150,6 +153,7 @@ extension MarketWatchlistViewModel { func remove(coinUid: String) { watchlistManager.remove(coinUid: coinUid) + stat(page: .markets, section: .watchlist, event: .removeFromWatchlist(coinUid: coinUid)) } func move(source: IndexSet, destination: Int) { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/WatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/WatchlistViewModel.swift index d468d4d12f..3cba730f4d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/WatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/WatchlistViewModel.swift @@ -2,13 +2,18 @@ import Combine class WatchlistViewModel: ObservableObject { private let watchlistManager = App.shared.watchlistManager + private let page: StatPage + private let section: StatSection? private var cancellables = Set() @Published var coinUids: Set - init() { + init(page: StatPage, section: StatSection? = nil) { coinUids = Set(watchlistManager.coinUids) + self.page = page + self.section = section + watchlistManager.coinUidsPublisher .sink { [weak self] in self?.coinUids = Set($0) } .store(in: &cancellables) @@ -18,9 +23,11 @@ class WatchlistViewModel: ObservableObject { extension WatchlistViewModel { func add(coinUid: String) { watchlistManager.add(coinUid: coinUid) + stat(page: page, section: section, event: .addToWatchlist(coinUid: coinUid)) } func remove(coinUid: String) { watchlistManager.remove(coinUid: coinUid) + stat(page: page, section: section, event: .removeFromWatchlist(coinUid: coinUid)) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/About/AboutView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/About/AboutView.swift index 8f11754c78..656ff8982c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/About/AboutView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/About/AboutView.swift @@ -13,6 +13,7 @@ struct AboutView: View { ListSection { NavigationRow(spacing: .margin8, destination: { MarkdownModule.gitReleaseNotesMarkdownView(url: releaseNotesUrl, presented: false) + .onFirstAppear { stat(page: .aboutApp, event: .open(page: .whatsNews)) } .ignoresSafeArea() }) { HStack(spacing: .margin16) { @@ -29,6 +30,7 @@ struct AboutView: View { ListSection { NavigationRow(destination: { AppStatusModule.view() + .onFirstAppear { stat(page: .aboutApp, event: .open(page: .appStatus)) } }) { Image("app_status_24").themeIcon() Text("app_status.title".localized).themeBody() @@ -36,6 +38,7 @@ struct AboutView: View { } ClickableRow(action: { + stat(page: .aboutApp, event: .open(page: .terms)) termsPresented = true }) { Image("unordered_24").themeIcon() @@ -51,6 +54,7 @@ struct AboutView: View { NavigationRow(destination: { PrivacyPolicyView(config: .privacy) .navigationTitle(PrivacyPolicyViewController.Config.privacy.title) + .onFirstAppear { stat(page: .aboutApp, event: .open(page: .privacy)) } .ignoresSafeArea() }) { Image("user_24").themeIcon() @@ -61,6 +65,7 @@ struct AboutView: View { ListSection { ClickableRow(action: { + stat(page: .aboutApp, event: .open(page: .externalGithub)) linkUrl = URL(string: "https://github.com/\(AppConfig.appGitHubAccount)/\(AppConfig.appGitHubRepository)") }) { Image("github_24").themeIcon() @@ -69,6 +74,7 @@ struct AboutView: View { } ClickableRow(action: { + stat(page: .aboutApp, event: .open(page: .externalWebsite)) linkUrl = URL(string: AppConfig.appWebPageLink) }) { Image("globe_24").themeIcon() diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift index 381c7bdf26..4f3c39b4d1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift @@ -19,48 +19,82 @@ class AppearanceViewModel: ObservableObject { @Published var themeMode: ThemeMode { didSet { + guard themeManager.themeMode != themeMode else { + return + } + stat(page: .appearance, event: .selectTheme(type: themeMode.rawValue)) themeManager.themeMode = themeMode } } @Published var hideMarkets: Bool { didSet { + guard launchScreenManager.showMarket == hideMarkets else { + return + } + stat(page: .appearance, event: .showMarketsTab(shown: !hideMarkets)) launchScreenManager.showMarket = !hideMarkets } } @Published var priceChangeMode: PriceChangeMode { didSet { + guard priceChangeModeManager.priceChangeMode != priceChangeMode else { + return + } + stat(page: .appearance, event: .showMarketsTab(shown: !hideMarkets)) priceChangeModeManager.priceChangeMode = priceChangeMode } } @Published var launchScreen: LaunchScreen { didSet { + guard launchScreenManager.launchScreen != launchScreen else { + return + } + stat(page: .appearance, event: .selectLaunchScreen(type: launchScreen.statType)) launchScreenManager.launchScreen = launchScreen } } @Published var hideBalanceButtons: Bool { didSet { + guard walletButtonHiddenManager.buttonHidden != hideBalanceButtons else { + return + } + stat(page: .appearance, event: .hideBalanceButtons(hide: hideBalanceButtons)) walletButtonHiddenManager.buttonHidden = hideBalanceButtons } } @Published var balancePrimaryValue: BalancePrimaryValue { didSet { + guard balancePrimaryValueManager.balancePrimaryValue != balancePrimaryValue else { + return + } + stat(page: .appearance, event: .selectBalanceValue(type: balancePrimaryValue.rawValue)) balancePrimaryValueManager.balancePrimaryValue = balancePrimaryValue } } @Published var conversionToken: Token? { didSet { + guard balanceConversionManager.conversionToken != conversionToken else { + return + } + if let conversionToken { + stat(page: .appearance, event: .selectBalanceConversion(coinUid: conversionToken.coin.uid)) + } balanceConversionManager.set(conversionToken: conversionToken) } } @Published var appIcon: AppIcon { didSet { + guard appIconManager.appIcon != appIcon else { + return + } + stat(page: .appearance, event: .selectAppIcon(iconUid: appIcon.title.lowercased())) appIconManager.appIcon = appIcon } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BaseCurrency/BaseCurrencySettingsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BaseCurrency/BaseCurrencySettingsViewModel.swift index 19e260bf3d..77170b1a5c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BaseCurrency/BaseCurrencySettingsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BaseCurrency/BaseCurrencySettingsViewModel.swift @@ -8,6 +8,7 @@ class BaseCurrencySettingsViewModel: ObservableObject { var baseCurrency: Currency { didSet { + stat(page: .baseCurrency, event: .switchBaseCurrency(code: baseCurrency.code)) currencyManager.baseCurrency = baseCurrency } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewModel.swift index c99dcc1b43..e9ad9b44f5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletViewModel.swift @@ -303,7 +303,7 @@ extension WalletViewModel { do { self?.qrScanningRelay.accept(true) - try await eventHandler.handle(event: scanned.trimmingCharacters(in: .whitespacesAndNewlines), eventType: [.walletConnectUri, .address]) + try await eventHandler.handle(source: StatPage.balance, event: scanned.trimmingCharacters(in: .whitespacesAndNewlines), eventType: [.walletConnectUri, .address]) } catch {} } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewController.swift index b90ddc2e52..9a5c880e69 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewController.swift @@ -97,6 +97,7 @@ class WalletConnectListViewController: ThemeViewController { let scanQrViewController = ScanQrViewController(reportAfterDismiss: true, pasteEnabled: true) scanQrViewController.didFetch = { [weak self] in self?.viewModel.didScan(string: $0) } + stat(page: .walletConnect, event: .open(page: .scanQrCode)) present(scanQrViewController, animated: true) } @@ -105,12 +106,14 @@ class WalletConnectListViewController: ThemeViewController { return } + stat(page: .walletConnect, event: .open(page: .walletConnectSession)) navigationController?.present(viewController, animated: true) } private func showPairings() { let viewController = WalletConnectPairingModule.viewController() + stat(page: .walletConnect, event: .open(page: .walletConnectPairings)) navigationController?.pushViewController(viewController, animated: true) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewModel.swift index 621ac9b7fb..5843e3f354 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/List/WalletConnectListViewModel.swift @@ -118,7 +118,7 @@ extension WalletConnectListViewModel { do { self?.disableNewConnectionRelay.accept(true) - try await eventHandler.handle(event: string, eventType: .walletConnectUri) + try await eventHandler.handle(source: .walletConnect, event: string, eventType: .walletConnectUri) } catch {} } } @@ -129,6 +129,7 @@ extension WalletConnectListViewModel { } func kill(id: Int) { + stat(page: .walletConnect, event: .delete(entity: .session)) service.kill(id: id) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainViewController.swift index a00a870ed9..866fba87ca 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainViewController.swift @@ -165,22 +165,27 @@ class WalletConnectMainViewController: ThemeViewController { } @objc private func onTapCancel() { + stat(page: .walletConnectSession, event: .cancel) viewModel.cancel() } @objc private func onTapConnect() { + stat(page: .walletConnectSession, event: .connect) viewModel.connect() } @objc private func onTapReject() { + stat(page: .walletConnectSession, event: .reject) viewModel.reject() } @objc private func onTapDisconnect() { + stat(page: .walletConnectSession, event: .disconnect) viewModel.disconnect() } @objc private func onTapReconnect() { + stat(page: .walletConnectSession, event: .reconnect) viewModel.reconnect() } @@ -224,6 +229,7 @@ class WalletConnectMainViewController: ThemeViewController { return case let .controller(controller): guard let controller else { return } + stat(page: .walletConnectSession, event: .open(page: .walletConnectRequest)) present(controller, animated: true) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Pairings/WalletConnectPairingViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Pairings/WalletConnectPairingViewModel.swift index 04790eff8d..2edc41a7ec 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Pairings/WalletConnectPairingViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Pairings/WalletConnectPairingViewModel.swift @@ -50,10 +50,12 @@ extension WalletConnectPairingViewModel { } func onDisconnect(topic: String) { + stat(page: .walletConnectPairings, event: .delete(entity: .walletConnectPair)) service.disconnect(topic: topic) } func onDisconnectAll() { + stat(page: .walletConnectPairings, event: .delete(entity: .all)) service.disconnectAll() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectRequest.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectRequest.swift index 8cd5a67cf0..4edbc9fdbe 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectRequest.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectRequest.swift @@ -28,6 +28,10 @@ class WalletConnectRequest { self.chainName = chainName self.address = address } + + var description: String { + chainName ?? id.description + } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift index 90ef65476f..0483ce02f1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift @@ -309,6 +309,7 @@ extension WalletConnectService { Task { [weak self] in do { try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .response(result)) + stat(page: .walletConnectRequest, event: .approveRequest(chainUid: request.chainId.absoluteString)) self?.pendingRequestsUpdatedRelay.accept(()) } } @@ -318,6 +319,7 @@ extension WalletConnectService { Task { [weak self] in do { try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(.init(code: 5000, message: "Reject by User"))) + stat(page: .walletConnectRequest, event: .rejectRequest(chainUid: request.chainId.absoluteString)) self?.pendingRequestsUpdatedRelay.accept(()) } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/ScanQr/ScanQrView.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/ScanQr/ScanQrView.swift index 54e48b4b64..07bbe13b44 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/ScanQr/ScanQrView.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/ScanQr/ScanQrView.swift @@ -156,7 +156,7 @@ class ScanQrView: UIView { func startCaptureSession() { if let captureSession, !captureSession.isRunning { - DispatchQueue.main.async { + scanQueue.async { captureSession.startRunning() } }