diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 21cd7a3f66..77ee52e641 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -299,7 +299,6 @@ 11B352FC06650CD111076054 /* TextInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35968F5DE9FDA6EC26FCD /* TextInputCell.swift */; }; 11B352FF1C3FA152E2EEFE67 /* EvmMethodLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E3F01D5A5CFE5A4E94B /* EvmMethodLabel.swift */; }; 11B3530088E70831A648EC63 /* CexDepositNetworkRaw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3502198C667A95C21DCF3 /* CexDepositNetworkRaw.swift */; }; - 11B3530307FFDC0AF9D3A8F2 /* CoinPriceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355D1DB2F95F1183FF2F8 /* CoinPriceListView.swift */; }; 11B35307AE70D7996F483DAE /* InputStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353BA87FDCB1BCBA92E61 /* InputStackView.swift */; }; 11B353096900F82EDF084F3B /* SetPasscodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F99E093B7DDB24D39C9 /* SetPasscodeViewModel.swift */; }; 11B35309CE9FBDA200067C4F /* ActiveAccount_v_0_36.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */; }; @@ -317,7 +316,6 @@ 11B35328067C30C80DF244DF /* BackupVerifyWordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3576FCFC9394BA37975FC /* BackupVerifyWordsViewModel.swift */; }; 11B35328EA42C49649B1E3F6 /* SyncMode_v_0_24.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35CB98A27269A510F40EE /* SyncMode_v_0_24.swift */; }; 11B3532D03B0893AB8E46CD9 /* BalanceViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3591AD106DAC0D18FEDD7 /* BalanceViewItem.swift */; }; - 11B3532D56D7D1F8286B39AC /* FavoriteCoinRecordStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C7B8BA65E9AA3BB7AFB /* FavoriteCoinRecordStorage.swift */; }; 11B3532F1BE8F0D1A6B43A33 /* EvmSyncSourceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3564E87C69B2989E6A3D2 /* EvmSyncSourceStorage.swift */; }; 11B35337D37FD03982571DF0 /* ThorChainMultiSwapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B323F0836767BA0565F /* ThorChainMultiSwapProvider.swift */; }; 11B3533941A80D693369E9C0 /* BrandFooterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355DF40EB498107EDAA4A /* BrandFooterCell.swift */; }; @@ -405,7 +403,7 @@ 11B35425857F772B06E7805D /* CexWithdrawViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ABC3E6C990E3BFA0A7B /* CexWithdrawViewController.swift */; }; 11B3542694E183882F9BEBEC /* CoinPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3553967AFF40F6A9A611A /* CoinPageView.swift */; }; 11B3542831EAA647A1D16E8A /* MarkdownVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35DC72F0D8DBBCCE2F988 /* MarkdownVisitor.swift */; }; - 11B354283B8AC609B65AADDF /* FavoriteCoinRecord_v_0_22.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_22.swift */; }; + 11B354283B8AC609B65AADDF /* FavoriteCoinRecord_v_0_38.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_38.swift */; }; 11B3542A74C72C4CE03C727B /* CoinToggleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A3B8FB90E561DE3F22B /* CoinToggleViewController.swift */; }; 11B35434C09F1E3818DC857B /* ReceiveDerivationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357EEC98939F9C7AA3271 /* ReceiveDerivationViewModel.swift */; }; 11B3543546FE55AE0AB91FAF /* MarketOverviewTopPairsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352BDC42A2F717AFAE7BD /* MarketOverviewTopPairsService.swift */; }; @@ -900,7 +898,6 @@ 11B35960AA711C4D5947BFE7 /* RestoreSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3532946EA785A7C65D193 /* RestoreSettingsService.swift */; }; 11B35963BA1215A80E8B26D0 /* CexDepositModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35011026CE084AE40FE6F /* CexDepositModule.swift */; }; 11B35963D09A27ACE74B3A52 /* PublicKeysModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35111F25CE7D0C8E0B29B /* PublicKeysModule.swift */; }; - 11B35968A3A43727ED6FB0B7 /* FavoriteCoinRecordStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C7B8BA65E9AA3BB7AFB /* FavoriteCoinRecordStorage.swift */; }; 11B35968D5BDA7A46C900548 /* AddressInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A382720D6531AE92F72 /* AddressInputView.swift */; }; 11B3596AE38880C5899769D5 /* CoinOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3506BFA73130CA9A1FF71 /* CoinOverviewView.swift */; }; 11B3596F09D52300F7F0067D /* NftCollectionOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BAABF1F6A9EFF769C47 /* NftCollectionOverviewViewController.swift */; }; @@ -964,7 +961,6 @@ 11B359F4651EA254E5B0AD00 /* ManageAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A38C734DF3157C84678 /* ManageAccountViewController.swift */; }; 11B359F73F1D626BF832977F /* BackupModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358967A086CFE9DBB152B /* BackupModule.swift */; }; 11B359F812683F62595AFEE2 /* DateFormatterCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356300F9A6A12C29450E7 /* DateFormatterCache.swift */; }; - 11B359F926F72DF79E9245E3 /* CoinPriceListMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ADBD038830223A8375D /* CoinPriceListMode.swift */; }; 11B359FB94269D9076C396D4 /* NftAssetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350E1584E954D281FA87D /* NftAssetView.swift */; }; 11B359FBC96E5ED356519001 /* WalletTokenListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35136653741E9703E61DE /* WalletTokenListViewModel.swift */; }; 11B35A07ED63F869C0203244 /* CexDepositNetworkSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C227EDC2D4ED188A0FC /* CexDepositNetworkSelectViewController.swift */; }; @@ -1011,7 +1007,6 @@ 11B35A5CD6B04D269E281A6A /* SyncerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B354BDDCAF7AF5A0582CAA /* SyncerState.swift */; }; 11B35A5DA8197D193B7CF8D9 /* AmountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B617A9CE668EEF4978B /* AmountData.swift */; }; 11B35A66D997F86423C2F5A0 /* DonutChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FC207C703EBF63FD56A /* DonutChartView.swift */; }; - 11B35A6F4E6931973277940A /* CoinPriceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355D1DB2F95F1183FF2F8 /* CoinPriceListView.swift */; }; 11B35A71D59757280B126587 /* ReceiveDerivationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357EEC98939F9C7AA3271 /* ReceiveDerivationViewModel.swift */; }; 11B35A79476177D3BE7DE477 /* RestorePrivateKeyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B351EBA5DE11150CE2E3F9 /* RestorePrivateKeyService.swift */; }; 11B35A7DBF1AFB1FDDB01CE0 /* SelfSizedSectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3538D10D6256399A51F61 /* SelfSizedSectionsTableView.swift */; }; @@ -1249,7 +1244,7 @@ 11B35D10BB1FC795924ED987 /* BalanceHiddenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352FBA1B29357E0120055 /* BalanceHiddenManager.swift */; }; 11B35D11809FB5D34D8D6D3F /* BarPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3512EF5B66B852F5E05FB /* BarPageControl.swift */; }; 11B35D11D00301F7B67B0340 /* AppIconManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A309C359456D7DF1A03 /* AppIconManager.swift */; }; - 11B35D16F2F7B8E11A771B18 /* FavoriteCoinRecord_v_0_22.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_22.swift */; }; + 11B35D16F2F7B8E11A771B18 /* FavoriteCoinRecord_v_0_38.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_38.swift */; }; 11B35D19D00091E903B04472 /* MarkdownViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35DF08505C3A7CB1BBBB4 /* MarkdownViewController.swift */; }; 11B35D1F6602B517D19D5C76 /* TopPlatformViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35BB370AE2C896BB9F877 /* TopPlatformViewController.swift */; }; 11B35D24064D9CE75ACCD59A /* CoinAnalyticsIssuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35466E6DC969B551B10D3 /* CoinAnalyticsIssuesView.swift */; }; @@ -1298,7 +1293,6 @@ 11B35D94B4E92789F681E293 /* Coin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F3FBFB1F4BE93B796DF /* Coin.swift */; }; 11B35D95692647EB9F73D9DB /* CoinProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3534997B5CD413DBDB7C7 /* CoinProvider.swift */; }; 11B35D96A814579F37BAD3D0 /* CoinRankService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3513AC6560B9C37C342F3 /* CoinRankService.swift */; }; - 11B35DA145308F888592A7CF /* CoinPriceListMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35ADBD038830223A8375D /* CoinPriceListMode.swift */; }; 11B35DA1A83CE7E402309FE7 /* CreateAccountModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358145A0D9F93ACBC0301 /* CreateAccountModule.swift */; }; 11B35DA4CB435537AD4148D7 /* NftCollectionAssetsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3573B3FE1FD8B476375E6 /* NftCollectionAssetsViewModel.swift */; }; 11B35DA5492B0C4EC7130A19 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3586FDC91E3742847B7E0 /* AppConfig.swift */; }; @@ -1538,8 +1532,6 @@ 11B35FFC8C3E4CF638397650 /* UnlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D36E5D47264AE07D729 /* UnlockView.swift */; }; 11B35FFD159D864F6D914F08 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357511F8F17D8221B64E2 /* AppearanceView.swift */; }; 11B35FFE6FBDA949184E2BF2 /* AmountInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F2BE131B969BBEABDB9 /* AmountInputViewModel.swift */; }; - 179E746F1E3D7BC613BD0AFC /* FavoriteCoinRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179E7048A730489634E27043 /* FavoriteCoinRecord.swift */; }; - 179E7EBD494842280D9F19A4 /* FavoriteCoinRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179E7048A730489634E27043 /* FavoriteCoinRecord.swift */; }; 1A564001701A1E77AF7A651B /* BalanceErrorModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564AB0B646F7A92DD188F2 /* BalanceErrorModule.swift */; }; 1A5640051485E3419FE674F1 /* MarketTopPlatformsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564995DE20E52E8E0F1E6A /* MarketTopPlatformsViewModel.swift */; }; 1A56402D725D6AB0C4149066 /* MarketTopPlatformsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A564995DE20E52E8E0F1E6A /* MarketTopPlatformsViewModel.swift */; }; @@ -1946,7 +1938,6 @@ 58AAA410C9996BA929E3CEEF /* InfoModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD7AC450FEF913E5417F /* InfoModule.swift */; }; 58AAA415B26725FEF4A1128D /* DoubleSpendInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA72BD42697A894C2383C /* DoubleSpendInfoViewController.swift */; }; 58AAA431B5939F7961B65CCF /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB126AA1B83DD40C426F /* CALayer.swift */; }; - 58AAA48687661E27807E9DF1 /* FavoritesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0ED0FCFACF791EC865C /* FavoritesManager.swift */; }; 58AAA488935A7DE6CF7C592D /* MarketGlobalMetricService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7B3EA0C8B9FDEC41837 /* MarketGlobalMetricService.swift */; }; 58AAA48ED47FD19F368385FA /* MetricChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAD2AA132E9B13726D8B /* MetricChartViewController.swift */; }; 58AAA49049C7EA04AABC41E9 /* SwapSlippageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA86B59A4D59F08EB334 /* SwapSlippageViewModel.swift */; }; @@ -2103,7 +2094,6 @@ 58AAAEB0F729B839B9B99A04 /* MarketListDefiDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFE702C2E51EEE209C56 /* MarketListDefiDecorator.swift */; }; 58AAAEDDF791C28174360A15 /* MetricChartFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC7C8A0B2AECDD436A14 /* MetricChartFactory.swift */; }; 58AAAEEA99ACF936862312B2 /* SwapSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9828D8742CD3D9995D9 /* SwapSwitchCell.swift */; }; - 58AAAEF20C684F912ED5D7AE /* FavoritesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0ED0FCFACF791EC865C /* FavoritesManager.swift */; }; 58AAAF011B2E9CDF8455CA7B /* BaseEvmAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA656A81B2C12F618FB44 /* BaseEvmAdapter.swift */; }; 58AAAF041A3FE53A28893E74 /* RecipientAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA19E66FCA0575AE33FAA /* RecipientAddressViewModel.swift */; }; 58AAAF222E553D8DCD123AB2 /* SwapAllowanceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA892221B1283EFC14B9E /* SwapAllowanceService.swift */; }; @@ -2992,12 +2982,18 @@ D3402AEF2BF5D58B003BF6F8 /* FavoritesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */; }; D3402AF12BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */; }; D3402AF22BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */; }; + D3402AF72BF71C11003BF6F8 /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; + D3402AF82BF71C11003BF6F8 /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; D3447DEA25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; }; D3447DEB25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; }; D34903172BE8DF48005F147B /* BinanceSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903162BE8DF48005F147B /* BinanceSendHandler.swift */; }; D34903182BE8DF48005F147B /* BinanceSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903162BE8DF48005F147B /* BinanceSendHandler.swift */; }; D349031A2BE8DF5F005F147B /* BinancePreSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903192BE8DF5F005F147B /* BinancePreSendHandler.swift */; }; D349031B2BE8DF5F005F147B /* BinancePreSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903192BE8DF5F005F147B /* BinancePreSendHandler.swift */; }; + D34A29B62BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */; }; + D34A29B72BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */; }; + D34A29B82BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */; }; + D34A29B92BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */; }; D350DDB02AE2526E00CF1989 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D350DDAF2AE2526E00CF1989 /* Localizable.xcstrings */; }; D350DDB12AE2526E00CF1989 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D350DDAF2AE2526E00CF1989 /* Localizable.xcstrings */; }; D350DDB22AE27E3B00CF1989 /* AppWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D350DDB92AE27E3B00CF1989 /* AppWidget.intentdefinition */; }; @@ -3082,6 +3078,18 @@ D36DE100272FD92F000BC916 /* SwapSelectProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36DE0F5272FD92F000BC916 /* SwapSelectProviderViewModel.swift */; }; D36E0C2A28D084AB00B622B9 /* CollectionViewCenteredFlowLayout in Frameworks */ = {isa = PBXBuildFile; productRef = D36E0C2928D084AB00B622B9 /* CollectionViewCenteredFlowLayout */; }; D36E0C2C28D084CB00B622B9 /* CollectionViewCenteredFlowLayout in Frameworks */ = {isa = PBXBuildFile; productRef = D36E0C2B28D084CB00B622B9 /* CollectionViewCenteredFlowLayout */; }; + D36E50812BF7534700C361BD /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; + D36E50822BF7534900C361BD /* WatchlistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */; }; + D36E50842BF75B6900C361BD /* WatchlistTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */; }; + D36E50852BF75B6900C361BD /* WatchlistTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */; }; + D36E50862BF75B6C00C361BD /* WatchlistTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */; }; + D36E50872BF75B6D00C361BD /* WatchlistTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */; }; + D36E508A2BF76FA700C361BD /* WatchlistEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50892BF76FA700C361BD /* WatchlistEntry.swift */; }; + D36E508B2BF76FA700C361BD /* WatchlistEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50892BF76FA700C361BD /* WatchlistEntry.swift */; }; + D36E508D2BF76FB400C361BD /* WatchlistProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E508C2BF76FB400C361BD /* WatchlistProvider.swift */; }; + D36E508E2BF76FB400C361BD /* WatchlistProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E508C2BF76FB400C361BD /* WatchlistProvider.swift */; }; + D36E50932BF7852D00C361BD /* CoinListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50922BF7852D00C361BD /* CoinListView.swift */; }; + D36E50942BF7852D00C361BD /* CoinListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36E50922BF7852D00C361BD /* CoinListView.swift */; }; D3833AD72BEE1A7900ACECFB /* MarketWatchlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3833AD62BEE1A7900ACECFB /* MarketWatchlistView.swift */; }; D3833AD82BEE1A7900ACECFB /* MarketWatchlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3833AD62BEE1A7900ACECFB /* MarketWatchlistView.swift */; }; D3833ADA2BEE1A8300ACECFB /* MarketWatchlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3833AD92BEE1A8300ACECFB /* MarketWatchlistViewModel.swift */; }; @@ -3650,7 +3658,6 @@ 11B355BEB95969D89B3F8876 /* MarketListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketListViewModel.swift; sourceTree = ""; }; 11B355C1E3C922BAE804AAF9 /* WalletConnectSessionStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionStorage.swift; sourceTree = ""; }; 11B355C615D9FE4290671D5D /* MarketTopPairsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketTopPairsModule.swift; sourceTree = ""; }; - 11B355D1DB2F95F1183FF2F8 /* CoinPriceListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinPriceListView.swift; sourceTree = ""; }; 11B355D5EFD2B74DE15F0C2A /* FaqModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaqModule.swift; sourceTree = ""; }; 11B355DF40EB498107EDAA4A /* BrandFooterCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrandFooterCell.swift; sourceTree = ""; }; 11B355E86612AEE00ED19CFE /* BackupManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupManager.swift; sourceTree = ""; }; @@ -3913,7 +3920,6 @@ 11B35AC2D01DF06DC50EAC6A /* HighlightedTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightedTextView.swift; sourceTree = ""; }; 11B35AD211091A7C8619CEA2 /* CexAssetRecordStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CexAssetRecordStorage.swift; sourceTree = ""; }; 11B35AD24681D0A122E6A3C5 /* SendData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendData.swift; sourceTree = ""; }; - 11B35ADBD038830223A8375D /* CoinPriceListMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinPriceListMode.swift; sourceTree = ""; }; 11B35ADF518A2F98FF673B4B /* CoinAuditsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinAuditsViewModel.swift; sourceTree = ""; }; 11B35ADF9BC4D149F86F23E4 /* MarketFilteredListService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketFilteredListService.swift; sourceTree = ""; }; 11B35AE5785634316A1A5DA8 /* WalletBlockchainElementService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBlockchainElementService.swift; sourceTree = ""; }; @@ -4000,7 +4006,6 @@ 11B35C6498078B1AFF406256 /* CoinMajorHoldersService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinMajorHoldersService.swift; sourceTree = ""; }; 11B35C6DF4DEE25B8B4B2E28 /* TonAddressParserItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TonAddressParserItem.swift; sourceTree = ""; }; 11B35C6E5282F55B88042F8D /* WalletTokenListViewItemFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletTokenListViewItemFactory.swift; sourceTree = ""; }; - 11B35C7B8BA65E9AA3BB7AFB /* FavoriteCoinRecordStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteCoinRecordStorage.swift; sourceTree = ""; }; 11B35C7CCC41913AA8D36CBC /* WalletCexElementService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletCexElementService.swift; sourceTree = ""; }; 11B35C7DCF9B7894F7600623 /* LaunchModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchModule.swift; sourceTree = ""; }; 11B35C7F043B6C41E53D43BC /* SendEvmTransactionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendEvmTransactionService.swift; sourceTree = ""; }; @@ -4073,7 +4078,7 @@ 11B35DFA83DA24A00D73EA7D /* RestoreSettingRecord_v_0_25.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreSettingRecord_v_0_25.swift; sourceTree = ""; }; 11B35DFBFBF34277E7FC3325 /* ActivateSubscriptionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivateSubscriptionViewModel.swift; sourceTree = ""; }; 11B35E1B9C559545FC3E6226 /* ReceiveTokenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveTokenViewModel.swift; sourceTree = ""; }; - 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_22.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteCoinRecord_v_0_22.swift; sourceTree = ""; }; + 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_38.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteCoinRecord_v_0_38.swift; sourceTree = ""; }; 11B35E298D53B8A2C2684119 /* AppWidgetConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppWidgetConstants.swift; sourceTree = ""; }; 11B35E2ACF02E2C35EFAE9FA /* NftMetadataService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftMetadataService.swift; sourceTree = ""; }; 11B35E2D539ACED30C947F2C /* RestoreBinanceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreBinanceService.swift; sourceTree = ""; }; @@ -4150,7 +4155,6 @@ 11B35FF02BBEDAEF446D0610 /* ModuleUnlockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModuleUnlockView.swift; sourceTree = ""; }; 11B35FF3390A7BB8E040620D /* PlaceholderViewNew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderViewNew.swift; sourceTree = ""; }; 11B35FF539B93A4C61AD1D00 /* CoinInvestorsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinInvestorsViewModel.swift; sourceTree = ""; }; - 179E7048A730489634E27043 /* FavoriteCoinRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteCoinRecord.swift; sourceTree = ""; }; 1A56404C1C16B85434117DB7 /* AppStatusModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStatusModule.swift; sourceTree = ""; }; 1A5640528EFD15137E218EA3 /* MainSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSettingsViewModel.swift; sourceTree = ""; }; 1A5640B4F6298D9F326C5EDE /* MarketOverviewTopCoinsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketOverviewTopCoinsViewModel.swift; sourceTree = ""; }; @@ -4347,7 +4351,6 @@ 58AAA0A9EA8A2210522F38EE /* MarketGlobalFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketGlobalFetcher.swift; sourceTree = ""; }; 58AAA0B8ECE5854FAB9362AC /* CoinOverviewViewItemFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinOverviewViewItemFactory.swift; sourceTree = ""; }; 58AAA0D499D632E44F7BE172 /* OneInchSettingsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneInchSettingsDataSource.swift; sourceTree = ""; }; - 58AAA0ED0FCFACF791EC865C /* FavoritesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoritesManager.swift; sourceTree = ""; }; 58AAA11651E3CE29A461BF42 /* MarketPostViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketPostViewModel.swift; sourceTree = ""; }; 58AAA1233617C06AC975285A /* SwapApproveAmountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapApproveAmountView.swift; sourceTree = ""; }; 58AAA13C7C5B258310BA61AF /* CoinChartService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinChartService.swift; sourceTree = ""; }; @@ -4900,8 +4903,11 @@ D3373DB120C52F640082BC4A /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewModel.swift; sourceTree = ""; }; D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModifier.swift; sourceTree = ""; }; + D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistManager.swift; sourceTree = ""; }; D34903162BE8DF48005F147B /* BinanceSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinanceSendHandler.swift; sourceTree = ""; }; D34903192BE8DF5F005F147B /* BinancePreSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinancePreSendHandler.swift; sourceTree = ""; }; + D34A29B42BFB4AE200F63036 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppWidget.strings; sourceTree = ""; }; + D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistSortBy.swift; sourceTree = ""; }; D34E941B21F86C3500AD8E90 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; D34E941C21F86C3500AD8E90 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; D350DDAF2AE2526E00CF1989 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; @@ -4914,7 +4920,6 @@ D350DDC72AE27E4900CF1989 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppWidget.strings; sourceTree = ""; }; D350DDC92AE27E4A00CF1989 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AppWidget.strings; sourceTree = ""; }; D350DDCA2AE2818A00CF1989 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/AppWidget.intentdefinition; sourceTree = ""; }; - D350DDCC2AE2819B00CF1989 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppWidget.strings; sourceTree = ""; }; D35B518821942E7A00504FBA /* TermsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsViewController.swift; sourceTree = ""; }; D368F5662844F2C400F79777 /* AppIconAlternate.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = AppIconAlternate.xcassets; sourceTree = ""; }; D36DE0AA272FD612000BC916 /* SwapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapViewController.swift; sourceTree = ""; }; @@ -4939,6 +4944,10 @@ D36DE0F3272FD92E000BC916 /* SwapSelectProviderModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapSelectProviderModule.swift; sourceTree = ""; }; D36DE0F4272FD92F000BC916 /* SwapSelectProviderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapSelectProviderService.swift; sourceTree = ""; }; D36DE0F5272FD92F000BC916 /* SwapSelectProviderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwapSelectProviderViewModel.swift; sourceTree = ""; }; + D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistTimePeriod.swift; sourceTree = ""; }; + D36E50892BF76FA700C361BD /* WatchlistEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistEntry.swift; sourceTree = ""; }; + D36E508C2BF76FB400C361BD /* WatchlistProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistProvider.swift; sourceTree = ""; }; + D36E50922BF7852D00C361BD /* CoinListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinListView.swift; sourceTree = ""; }; D3833AD62BEE1A7900ACECFB /* MarketWatchlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistView.swift; sourceTree = ""; }; D3833AD92BEE1A8300ACECFB /* MarketWatchlistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistViewModel.swift; sourceTree = ""; }; D3833ADD2BEE3FE000ACECFB /* MarketPlatformsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketPlatformsView.swift; sourceTree = ""; }; @@ -5403,7 +5412,6 @@ 11B3506CB3D780A00F4BBBBE /* AccountRecord_v_0_20.swift */, 11B35B2C6C103AFF4CCC6E91 /* CoinRecord_v19.swift */, 1A5646C5FEE5A60A658B0180 /* AppVersion_v_0_20.swift */, - 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_22.swift */, 11B35CB98A27269A510F40EE /* SyncMode_v_0_24.swift */, 11B3526A40F07F6C8E77BEF9 /* BlockchainSettingRecord_v_0_24.swift */, 11B356E4E27F5C12FC3859D1 /* CustomToken.swift */, @@ -5413,6 +5421,7 @@ 11B3509AC90AEDF72F5989C6 /* EnabledWallet_v_0_34.swift */, 11B3528DDD55DDA1BAC2BADB /* ActiveAccount_v_0_36.swift */, 11B356A734526DECD9606A66 /* AccountRecord_v_0_36.swift */, + 11B35E255F6CA21FFA9E6B42 /* FavoriteCoinRecord_v_0_38.swift */, ); path = Deprecated; sourceTree = ""; @@ -5756,6 +5765,7 @@ 11B3505A43D9C2787B3BD153 /* PasscodeLockManager.swift */, 11B359980AA45D6B44151D7A /* StatManager.swift */, 6BB14F6A2BF49E7100E879B2 /* WalletButtonHiddenManager.swift */, + D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */, ); path = Managers; sourceTree = ""; @@ -5835,7 +5845,6 @@ 11B350F4F8AA86BB84CF5CF2 /* SendEvmTransaction */, 11B35D070AAF3E6A8ED8EC83 /* SendEvm */, D07157D72A2DD7A8006F141F /* SendTron */, - 58AAA119CAEF1B4621D71DF8 /* Favorites */, 58AAAC89F3E7CD9F799B89D7 /* Coin */, 11B3581D97669C8038485FAE /* CreateAccount */, 11B35FEAD9772F1E11F3D767 /* RestoreAccount */, @@ -6036,7 +6045,6 @@ children = ( 11B357F2FAE27C9739CAE5C7 /* CoinPriceListEntry.swift */, 11B35EE1C1F555F4160AC201 /* CoinPriceListProvider.swift */, - 11B355D1DB2F95F1183FF2F8 /* CoinPriceListView.swift */, ); path = CoinPriceList; sourceTree = ""; @@ -6092,7 +6100,6 @@ 11B35450456BE5E3EE8F7391 /* Faq.swift */, 11B35B176A5FDEBBE94D307E /* BitcoinCashCoinType.swift */, 11B352D393EDFE4F015B0DEA /* Address.swift */, - 179E7048A730489634E27043 /* FavoriteCoinRecord.swift */, 11B35F98E89F83A30870F404 /* ActiveAccount.swift */, 11B3546480B733000550BEB6 /* RestoreSettingRecord.swift */, 11B3502637A858E6DDF9471B /* EvmSyncSource.swift */, @@ -6144,6 +6151,8 @@ 11B35B6F5261FF3F9ECBC02E /* PasscodeLockState.swift */, 11B35743A66A4653A3C2FDBF /* Stats.swift */, 11B35B9F4421EE65B8B09370 /* StatRecord.swift */, + D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */, + D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */, ); path = Models; sourceTree = ""; @@ -6200,7 +6209,6 @@ 11B357889F003A0B33D9DF27 /* PriceChangeType.swift */, 11B357A5569EAC7D20CD40B2 /* ValueFormatter.swift */, 11B3558D624AF040E9D102DF /* Extensions.swift */, - 11B35ADBD038830223A8375D /* CoinPriceListMode.swift */, 11B35CBAB0A2DA60C3E9A22B /* WidgetConfig.swift */, ); path = Misc; @@ -6302,7 +6310,6 @@ 11B35763ED14419B9EE4C6F9 /* EnabledWalletStorage.swift */, 11B358556C8FC5368E14D81E /* AccountRecordStorage.swift */, 11B35822E26E7298100CD69D /* LogRecordStorage.swift */, - 11B35C7B8BA65E9AA3BB7AFB /* FavoriteCoinRecordStorage.swift */, 11B355C1E3C922BAE804AAF9 /* WalletConnectSessionStorage.swift */, 11B354C4B46DF1A50103F026 /* ActiveAccountStorage.swift */, 11B350DD8FFDB14904D23AE0 /* RestoreSettingsStorage.swift */, @@ -7553,14 +7560,6 @@ path = MarketPosts; sourceTree = ""; }; - 58AAA119CAEF1B4621D71DF8 /* Favorites */ = { - isa = PBXGroup; - children = ( - 58AAA0ED0FCFACF791EC865C /* FavoritesManager.swift */, - ); - path = Favorites; - sourceTree = ""; - }; 58AAA1A28E8F61C815C86131 /* Info */ = { isa = PBXGroup; children = ( @@ -9324,6 +9323,15 @@ path = Favorites; sourceTree = ""; }; + D36E50882BF7656E00C361BD /* Watchlist */ = { + isa = PBXGroup; + children = ( + D36E50892BF76FA700C361BD /* WatchlistEntry.swift */, + D36E508C2BF76FB400C361BD /* WatchlistProvider.swift */, + ); + path = Watchlist; + sourceTree = ""; + }; D3833AD52BEE1A2900ACECFB /* Watchlist */ = { isa = PBXGroup; children = ( @@ -9371,6 +9379,7 @@ D3948EF52ADA846400FAE566 /* Widget */ = { isa = PBXGroup; children = ( + D36E50882BF7656E00C361BD /* Watchlist */, D3948EF62ADA846400FAE566 /* AppWidgetBundle.swift */, D3948EFA2ADA846800FAE566 /* Assets.xcassets */, D3948EFC2ADA846800FAE566 /* Info.plist */, @@ -9383,6 +9392,7 @@ 11B35B0879F715C0777919AA /* WatchlistWidget.swift */, 11B35E298D53B8A2C2684119 /* AppWidgetConstants.swift */, D350DDAF2AE2526E00CF1989 /* Localizable.xcstrings */, + D36E50922BF7852D00C361BD /* CoinListView.swift */, ); path = Widget; sourceTree = ""; @@ -10064,7 +10074,6 @@ 11B3547B32F2E3458065F2EB /* AmountInputView.swift in Sources */, 11B354B34C4B6471F67F5471 /* InputPrefixWrapperView.swift in Sources */, D3DD67352BC3CC2100EC7F78 /* ThorChainMultiSwapBtcQuote.swift in Sources */, - 179E746F1E3D7BC613BD0AFC /* FavoriteCoinRecord.swift in Sources */, 11B35A42D28B8BC4CDA57D8E /* AccountRecord_v_0_19.swift in Sources */, 58AAAA8975F5B63340672D00 /* MarketWatchlistService.swift in Sources */, 11B35B970E8949F968960796 /* MarketListViewController.swift in Sources */, @@ -10101,7 +10110,6 @@ 1A564DC4E55C1BE1C4D4CDA9 /* HighlightedDescriptionBaseView.swift in Sources */, 1A564977D009E610D8D194AE /* TitledHighlightedDescriptionView.swift in Sources */, 1A5642F3C5C414F65A3CC59D /* TitledHighlightedDescriptionCell.swift in Sources */, - 58AAAEF20C684F912ED5D7AE /* FavoritesManager.swift in Sources */, 58AAAFEB4507E7459BED2F28 /* CoinPageModule.swift in Sources */, 58AAA691DC0544582BABF263 /* CoinChartService.swift in Sources */, 58AAABED534A95A11B332D44 /* CoinChartViewModel.swift in Sources */, @@ -10402,7 +10410,7 @@ 58AAA60557D3A9E3AE7372E0 /* MarketListDefiDecorator.swift in Sources */, 58AAAA07DC05EF7F912EA184 /* MarketListTvlDecorator.swift in Sources */, 58AAAFC5FE754A2286161D16 /* MarketGlobalTvlFetcher.swift in Sources */, - 11B354283B8AC609B65AADDF /* FavoriteCoinRecord_v_0_22.swift in Sources */, + 11B354283B8AC609B65AADDF /* FavoriteCoinRecord_v_0_38.swift in Sources */, 6BA5117E2BCFA06F00CB5A54 /* FirstAppearModifier.swift in Sources */, 58AAAD1BFFE70A777DDF27A9 /* AddressParserChain.swift in Sources */, 58AAAB9E86439B6DE1D22538 /* EvmAddressParserItem.swift in Sources */, @@ -10478,7 +10486,6 @@ D311DA1D2BD114B00013DB8F /* MarketView.swift in Sources */, 11B3503BF015EA47E1061122 /* AccountRecordStorage.swift in Sources */, 11B356A4B22FA16BE27AFAB1 /* LogRecordStorage.swift in Sources */, - 11B35968A3A43727ED6FB0B7 /* FavoriteCoinRecordStorage.swift in Sources */, 11B350354CCA9BDDC05A9CBA /* WalletConnectSessionStorage.swift in Sources */, 11B35890939A3326B352A0FB /* ActiveAccountStorage.swift in Sources */, 11B35AC33360F772120B9562 /* RestoreSettingsStorage.swift in Sources */, @@ -10660,6 +10667,7 @@ D3402AF22BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */, 11B35AC9650545DEBC6C2C90 /* EvmAccountRestoreState.swift in Sources */, 11B35EBC6D5608F23DF8581E /* EvmAccountRestoreStateManager.swift in Sources */, + D3402AF82BF71C11003BF6F8 /* WatchlistManager.swift in Sources */, 11B35C43886D9A0F0C69EF33 /* EvmAccountRestoreStateStorage.swift in Sources */, 11B35B100187D9909A8490A7 /* NftAdapterManager.swift in Sources */, 11B35EC3B9E9C778183E1136 /* EvmNftAdapter.swift in Sources */, @@ -11129,6 +11137,7 @@ ABC9AA802C533F489EB72FDE /* BackupManagerViewModel.swift in Sources */, 11B359F812683F62595AFEE2 /* DateFormatterCache.swift in Sources */, 11B35483AFC1088E56BC7F37 /* LanguageHourFormatter.swift in Sources */, + D36E50852BF75B6900C361BD /* WatchlistTimePeriod.swift in Sources */, 11B35B298DCBAA8AE9DADA34 /* LanguageSettingsModule.swift in Sources */, D3F9B03B2BE3BB36009FFA95 /* WalletConnectSendViewModel.swift in Sources */, 11B35C22C95B08142C13B14D /* LanguageSettingsViewModel.swift in Sources */, @@ -11319,6 +11328,7 @@ ABC9A4DE73BE3055DD97343B /* ShimmerEffect.swift in Sources */, D3833B032BF38A8000ACECFB /* MarketTabViewModel.swift in Sources */, ABC9A6C591067D34C6DF2673 /* SlideButtonStyling.swift in Sources */, + D34A29B82BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */, 11B35A32114022EF422E6602 /* MultiSwapRevokeView.swift in Sources */, 11B357DCC0BA9E888DE64CB1 /* MultiSwapRevokeViewModel.swift in Sources */, 11B35FEC7AA2A8887FCF0AE6 /* StatManager.swift in Sources */, @@ -11627,7 +11637,6 @@ 11B357EA92E2E9C39DEF08B0 /* AmountInputView.swift in Sources */, 11B35FF65FCE441A69822E1C /* InputPrefixWrapperView.swift in Sources */, D3DD67342BC3CC2100EC7F78 /* ThorChainMultiSwapBtcQuote.swift in Sources */, - 179E7EBD494842280D9F19A4 /* FavoriteCoinRecord.swift in Sources */, 11B35C3AFFA5B40481AF15B9 /* AccountRecord_v_0_19.swift in Sources */, 58AAA8D67EB6C19719BD760B /* MarketWatchlistService.swift in Sources */, 11B35C8E09922F59B200E347 /* MarketListViewController.swift in Sources */, @@ -11662,7 +11671,6 @@ 1A56405DB1540DEC70FD5CFA /* HighlightedDescriptionBaseView.swift in Sources */, 1A564058DB366C810AC3C47A /* TitledHighlightedDescriptionView.swift in Sources */, 1A564564359185321F81541D /* TitledHighlightedDescriptionCell.swift in Sources */, - 58AAA48687661E27807E9DF1 /* FavoritesManager.swift in Sources */, 58AAA8D2A6FD519EFC668EC5 /* CoinPageModule.swift in Sources */, 58AAABA864BFEC1F10F57E4B /* CoinChartService.swift in Sources */, D36DE0EF272FD89B000BC916 /* SwapSettingsViewController.swift in Sources */, @@ -11971,7 +11979,7 @@ 58AAAEB0F729B839B9B99A04 /* MarketListDefiDecorator.swift in Sources */, 58AAA331B4A743D9183F8449 /* MarketListTvlDecorator.swift in Sources */, 58AAACA5A8EC9B2A3182395F /* MarketGlobalTvlFetcher.swift in Sources */, - 11B35D16F2F7B8E11A771B18 /* FavoriteCoinRecord_v_0_22.swift in Sources */, + 11B35D16F2F7B8E11A771B18 /* FavoriteCoinRecord_v_0_38.swift in Sources */, 11B35D46B65772A1CC17B099 /* MarketGlobalTvlMetricViewController.swift in Sources */, 58AAADEF6F21AED422D7B569 /* AddressParserChain.swift in Sources */, 58AAAC635552C279592F60F9 /* EvmAddressParserItem.swift in Sources */, @@ -12048,7 +12056,6 @@ 11B356C081AC552CBDA9147B /* AccountRecordStorage.swift in Sources */, D3DB51992BD63D680091BBDB /* MarketSearchViewModel.swift in Sources */, 11B35727A4950C1E066F2244 /* LogRecordStorage.swift in Sources */, - 11B3532D56D7D1F8286B39AC /* FavoriteCoinRecordStorage.swift in Sources */, 11B35262B98EA59CDA12DF97 /* WalletConnectSessionStorage.swift in Sources */, 11B350ACC851B0F8C911AC3E /* ActiveAccountStorage.swift in Sources */, 11B35A2DC1B9521150F81EA6 /* RestoreSettingsStorage.swift in Sources */, @@ -12223,6 +12230,7 @@ D3402AF12BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */, 11B3590E40C88ADD16DEEABB /* BackupMnemonicWordCell.swift in Sources */, 11B35FD18C255E2C6D75F38A /* RestoreMnemonicHintView.swift in Sources */, + D3402AF72BF71C11003BF6F8 /* WatchlistManager.swift in Sources */, 11B356BCDD5E64C6D6F49489 /* RestoreMnemonicHintCell.swift in Sources */, 11B35B152001ADE5E98D1414 /* EvmAccountRestoreState.swift in Sources */, 11B3573F7ED8577EF9F12EF9 /* EvmAccountRestoreStateManager.swift in Sources */, @@ -12692,6 +12700,7 @@ 11B35D5CEB75CD7626D6A612 /* SharedLocalStorage.swift in Sources */, D350DDB22AE27E3B00CF1989 /* AppWidget.intentdefinition in Sources */, 11B35FD593B38EEEE5F18010 /* AppWidgetConstants.swift in Sources */, + D36E50842BF75B6900C361BD /* WatchlistTimePeriod.swift in Sources */, 11B35909DEBFA098976E1D87 /* DateFormatterCache.swift in Sources */, D3F9B03A2BE3BB36009FFA95 /* WalletConnectSendViewModel.swift in Sources */, 11B350938ACDA1EEF888E846 /* LanguageHourFormatter.swift in Sources */, @@ -12882,6 +12891,7 @@ 11B352B9206C86492CDDC9A7 /* EvmFeeData.swift in Sources */, D3833B022BF38A8000ACECFB /* MarketTabViewModel.swift in Sources */, 11B35A920F2DB5784F178BDA /* EvmFeeEstimator.swift in Sources */, + D34A29B62BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */, 11B3545369350E4253688D91 /* MultiSwapRevokeView.swift in Sources */, 11B35CBF59F85C3DAB5FE751 /* MultiSwapRevokeViewModel.swift in Sources */, 11B355FCAF8DC88550CE2DB3 /* StatManager.swift in Sources */, @@ -12915,18 +12925,22 @@ 11B3513A8C5CFB4A4495D935 /* HorizontalDivider.swift in Sources */, 11B350C1B04946C9AA8B3430 /* ListSection.swift in Sources */, 11B35B5EED35DD5F8F8B19A8 /* ThemeListStyle.swift in Sources */, + D36E50942BF7852D00C361BD /* CoinListView.swift in Sources */, + D36E50862BF75B6C00C361BD /* WatchlistTimePeriod.swift in Sources */, 11B357229EFFA602F38D6C2C /* CoinPriceListEntry.swift in Sources */, + D36E508B2BF76FA700C361BD /* WatchlistEntry.swift in Sources */, 11B35E88C40DC151A3BEC0B1 /* CoinPriceListProvider.swift in Sources */, - 11B35A6F4E6931973277940A /* CoinPriceListView.swift in Sources */, 11B35CB0098E9628FE81AC39 /* TopCoinsWidget.swift in Sources */, 11B3538FA5A4953A7C9AC9E6 /* SingleCoinPriceWidget.swift in Sources */, 11B358D519ACFE88A7823C7E /* ApiProvider.swift in Sources */, 11B358587D9C3A1F10EC15A6 /* PriceChangeType.swift in Sources */, + D36E50822BF7534900C361BD /* WatchlistManager.swift in Sources */, 11B35530A9FC0972D8716C31 /* ValueFormatter.swift in Sources */, D350DDB62AE27E3B00CF1989 /* AppWidget.intentdefinition in Sources */, 11B3550548CB49D32EAC1DF5 /* WatchlistWidget.swift in Sources */, + D34A29B92BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */, + D36E508E2BF76FB400C361BD /* WatchlistProvider.swift in Sources */, 11B35287E46AFFBC47162F67 /* Extensions.swift in Sources */, - 11B35DA145308F888592A7CF /* CoinPriceListMode.swift in Sources */, 11B3541E8CB5F0F743E9CDF3 /* AppWidgetConstants.swift in Sources */, 11B354FA80BD8C5512B5ACFE /* WidgetConfig.swift in Sources */, 11B35B31D808ED62EFB3D38B /* BadgeViewNew.swift in Sources */, @@ -12958,18 +12972,22 @@ 11B357AD2632BDF26DCB4BFC /* HorizontalDivider.swift in Sources */, 11B35F728E5BE60FD7C87FA1 /* ListSection.swift in Sources */, 11B35A15391F470849534264 /* ThemeListStyle.swift in Sources */, + D36E50932BF7852D00C361BD /* CoinListView.swift in Sources */, + D36E50872BF75B6D00C361BD /* WatchlistTimePeriod.swift in Sources */, 11B35F6BC7EC8A90FDACD191 /* CoinPriceListEntry.swift in Sources */, + D36E508A2BF76FA700C361BD /* WatchlistEntry.swift in Sources */, 11B35529AB46C98BC35C72E4 /* CoinPriceListProvider.swift in Sources */, - 11B3530307FFDC0AF9D3A8F2 /* CoinPriceListView.swift in Sources */, 11B352F16ADEABF640D2B9FD /* TopCoinsWidget.swift in Sources */, 11B354D628AADF3AFD9123E1 /* SingleCoinPriceWidget.swift in Sources */, 11B353FCD118CAB48511CF12 /* ApiProvider.swift in Sources */, 11B35F0D313E455BCF24C42B /* PriceChangeType.swift in Sources */, + D36E50812BF7534700C361BD /* WatchlistManager.swift in Sources */, 11B3518F3962FEA97AE6C7CD /* ValueFormatter.swift in Sources */, D350DDB32AE27E3B00CF1989 /* AppWidget.intentdefinition in Sources */, 11B359BF322A7B912C778348 /* WatchlistWidget.swift in Sources */, + D34A29B72BFB4E3200F63036 /* WatchlistSortBy.swift in Sources */, + D36E508D2BF76FB400C361BD /* WatchlistProvider.swift in Sources */, 11B35700253CCD66C4CCE354 /* Extensions.swift in Sources */, - 11B359F926F72DF79E9245E3 /* CoinPriceListMode.swift in Sources */, 11B35B2E335C24608DE32B0A /* AppWidgetConstants.swift in Sources */, 11B35BE346FCE9B2BE4096A8 /* WidgetConfig.swift in Sources */, 11B35C8BF55C38F198C3DAE6 /* BadgeViewNew.swift in Sources */, @@ -13041,7 +13059,7 @@ D350DDC72AE27E4900CF1989 /* es */, D350DDC92AE27E4A00CF1989 /* tr */, D350DDCA2AE2818A00CF1989 /* Base */, - D350DDCC2AE2819B00CF1989 /* en */, + D34A29B42BFB4AE200F63036 /* en */, ); name = AppWidget.intentdefinition; sourceTree = ""; diff --git a/UnstoppableWallet/UnstoppableWallet/Core/App.swift b/UnstoppableWallet/UnstoppableWallet/Core/App.swift index 334f9052c3..3b511c43d1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/App.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/App.swift @@ -52,7 +52,7 @@ class App { let networkManager: NetworkManager let guidesManager: GuidesManager let termsManager: TermsManager - let favoritesManager: FavoritesManager + let watchlistManager: WatchlistManager let contactManager: ContactBookManager let subscriptionManager: SubscriptionManager @@ -160,8 +160,7 @@ class App { guidesManager = GuidesManager(networkManager: networkManager) termsManager = TermsManager(userDefaultsStorage: userDefaultsStorage) - let favoriteCoinRecordStorage = FavoriteCoinRecordStorage(dbPool: dbPool) - favoritesManager = FavoritesManager(storage: favoriteCoinRecordStorage, sharedStorage: sharedLocalStorage) + watchlistManager = WatchlistManager(storage: sharedLocalStorage) contactManager = ContactBookManager(localStorage: localStorage, ubiquityContainerIdentifier: AppConfig.privateCloudContainer, helper: ContactBookHelper(), logger: logger) subscriptionManager = SubscriptionManager(userDefaultsStorage: userDefaultsStorage, marketKit: marketKit) @@ -281,7 +280,7 @@ class App { accountManager: accountManager, accountFactory: accountFactory, walletManager: walletManager, - favoritesManager: favoritesManager, + watchlistManager: watchlistManager, evmSyncSourceManager: evmSyncSourceManager, btcBlockchainManager: btcBlockchainManager, restoreSettingsManager: restoreSettingsManager, diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift new file mode 100644 index 0000000000..1f3a85a911 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift @@ -0,0 +1,99 @@ +import Combine +import WidgetKit + +class WatchlistManager { + private let keyCoinUids = "watchlist-coin-uids" + private let keySortBy = "watchlist-sort-by" + private let keyTimePeriod = "watchlist-time-period" + private let keyShowSignals = "watchlist-show-signals" + + private let storage: SharedLocalStorage + + private let coinUidsSubject = PassthroughSubject<[String], Never>() + + var coinUids: [String] { + didSet { + coinUidSet = Set(coinUids) + coinUidsSubject.send(coinUids) + + storage.set(value: coinUids, for: keyCoinUids) + + WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) + } + } + + private var coinUidSet: Set + + var sortBy: WatchlistSortBy { + didSet { + storage.set(value: sortBy.rawValue, for: keySortBy) + WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) + } + } + + var timePeriod: WatchlistTimePeriod { + didSet { + storage.set(value: timePeriod.rawValue, for: keyTimePeriod) + WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) + } + } + + var showSignals: Bool { + didSet { + storage.set(value: showSignals, for: keyShowSignals) + // WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) + } + } + + init(storage: SharedLocalStorage) { + self.storage = storage + + coinUids = storage.value(for: keyCoinUids) ?? [] + coinUidSet = Set(coinUids) + + let sortByRaw: String? = storage.value(for: keySortBy) + sortBy = sortByRaw.flatMap { WatchlistSortBy(rawValue: $0) } ?? .manual + + let timePeriodRaw: String? = storage.value(for: keyTimePeriod) + timePeriod = timePeriodRaw.flatMap { WatchlistTimePeriod(rawValue: $0) } ?? .day1 + + showSignals = storage.value(for: keyShowSignals) ?? true + + WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) + } +} + +extension WatchlistManager { + var coinUidsPublisher: AnyPublisher<[String], Never> { + coinUidsSubject.eraseToAnyPublisher() + } + + func add(coinUid: String) { + guard !coinUids.contains(coinUid) else { + return + } + + coinUids.append(coinUid) + } + + func add(coinUids: [String]) { + let coinUids = coinUids.filter { !self.coinUids.contains($0) } + self.coinUids.append(contentsOf: coinUids) + } + + func removeAll() { + coinUids = [] + } + + func remove(coinUid: String) { + guard let index = coinUids.firstIndex(of: coinUid) else { + return + } + + coinUids.remove(at: index) + } + + func isWatched(coinUid: String) -> Bool { + coinUidSet.contains(coinUid) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/FavoriteCoinRecordStorage.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/FavoriteCoinRecordStorage.swift deleted file mode 100644 index ed1ddde25d..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/FavoriteCoinRecordStorage.swift +++ /dev/null @@ -1,54 +0,0 @@ -import GRDB - -class FavoriteCoinRecordStorage { - private let dbPool: DatabasePool - - init(dbPool: DatabasePool) { - self.dbPool = dbPool - } -} - -extension FavoriteCoinRecordStorage { - func favoriteCoinRecords() throws -> [FavoriteCoinRecord] { - try dbPool.read { db in - try FavoriteCoinRecord.fetchAll(db) - } - } - - func save(favoriteCoinRecord: FavoriteCoinRecord) throws { - _ = try dbPool.write { db in - try favoriteCoinRecord.insert(db) - } - } - - func save(favoriteCoinRecords: [FavoriteCoinRecord]) throws { - _ = try dbPool.write { db in - for record in favoriteCoinRecords { - try record.insert(db) - } - } - } - - func deleteAll() throws { - _ = try dbPool.write { db in - try FavoriteCoinRecord - .deleteAll(db) - } - } - - func deleteFavoriteCoinRecord(coinUid: String) throws { - _ = try dbPool.write { db in - try FavoriteCoinRecord - .filter(FavoriteCoinRecord.Columns.coinUid == coinUid) - .deleteAll(db) - } - } - - func favoriteCoinRecordExists(coinUid: String) throws -> Bool { - try dbPool.read { db in - try FavoriteCoinRecord - .filter(FavoriteCoinRecord.Columns.coinUid == coinUid) - .fetchCount(db) > 0 - } - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift index bb72595ffb..5e7ff27f5b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Storage/StorageMigrator.swift @@ -331,16 +331,6 @@ enum StorageMigrator { try db.drop(table: CoinRecord_v19.databaseTableName) } - migrator.registerMigration("recreateFavoriteCoins") { db in - if try db.tableExists("favorite_coins") { - try db.drop(table: "favorite_coins") - } - - try db.create(table: "favorite_coins_v20") { t in - t.column("coinType", .text).notNull() - } - } - migrator.registerMigration("createActiveAccount") { db in try db.create(table: ActiveAccount_v_0_36.databaseTableName) { t in t.column(ActiveAccount_v_0_36.Columns.uniqueId.name, .text).notNull() @@ -512,12 +502,6 @@ enum StorageMigrator { } } - migrator.registerMigration("newStructureForFavoriteCoins") { db in - try db.create(table: FavoriteCoinRecord.databaseTableName) { t in - t.column(FavoriteCoinRecord.Columns.coinUid.name, .text).primaryKey() - } - } - migrator.registerMigration("createWalletConnectV2Sessions") { db in try db.create(table: WalletConnectSession.databaseTableName) { t in t.column(WalletConnectSession.Columns.accountId.name, .text).notNull() @@ -823,6 +807,20 @@ enum StorageMigrator { } } + migrator.registerMigration("Migrate watchlist coin uids to local storage") { db in + if try db.tableExists(FavoriteCoinRecord_v_0_38.databaseTableName) { + let records = try FavoriteCoinRecord_v_0_38.fetchAll(db) + let coinUids = Array(Set(records.map(\.coinUid))) + + if !coinUids.isEmpty { + let sharedLocalStorage = SharedLocalStorage() + sharedLocalStorage.set(value: coinUids.sorted(), for: "watchlist-coin-uids") + } + + try db.drop(table: FavoriteCoinRecord_v_0_38.databaseTableName) + } + } + try migrator.migrate(dbPool) } diff --git a/UnstoppableWallet/UnstoppableWallet/Info.plist b/UnstoppableWallet/UnstoppableWallet/Info.plist index 4d86b5e55c..5f30b213a3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Info.plist +++ b/UnstoppableWallet/UnstoppableWallet/Info.plist @@ -2,10 +2,6 @@ - OneInchCommissionAddress - ${one_inch_commission_address} - OneInchCommission - ${one_inch_commission} ArbiscanApiKey ${arbiscan_api_key} BscscanApiKey @@ -113,6 +109,12 @@ OfficeMode ${OfficeMode} + OneInchApiKey + ${one_inch_api_key} + OneInchCommission + ${one_inch_commission} + OneInchCommissionAddress + ${one_inch_commission_address} OpenSeaApiKey ${open_sea_api_key} OptimismEtherscanApiKey @@ -162,7 +164,5 @@ ${unstoppable_domains_api_key} WallectConnectV2ProjectKey ${wallet_connect_v2_project_key} - OneInchApiKey - ${one_inch_api_key} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_22.swift b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_22.swift deleted file mode 100644 index f811fd90c3..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_22.swift +++ /dev/null @@ -1,32 +0,0 @@ -// import GRDB -// import MarketKit -// -// class FavoriteCoinRecord_v_0_22: Record { -// let coinType: CoinType -// -// init(coinType: CoinType) { -// self.coinType = coinType -// -// super.init() -// } -// -// -// override class var databaseTableName: String { -// "favorite_coins_v20" -// } -// -// enum Columns: String, ColumnExpression { -// case coinType -// } -// -// required init(row: Row) { -// coinType = CoinType(id: row[Columns.coinType]) -// -// super.init(row: row) -// } -// -// override func encode(to container: inout PersistenceContainer) { -// container[Columns.coinType] = coinType.id -// } -// -// } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/FavoriteCoinRecord.swift b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_38.swift similarity index 92% rename from UnstoppableWallet/UnstoppableWallet/Models/FavoriteCoinRecord.swift rename to UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_38.swift index 79216480b9..65bb7ca4c6 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/FavoriteCoinRecord.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/Deprecated/FavoriteCoinRecord_v_0_38.swift @@ -1,6 +1,6 @@ import GRDB -class FavoriteCoinRecord: Record { +class FavoriteCoinRecord_v_0_38: Record { let coinUid: String init(coinUid: String) { diff --git a/UnstoppableWallet/UnstoppableWallet/Models/WatchlistSortBy.swift b/UnstoppableWallet/UnstoppableWallet/Models/WatchlistSortBy.swift new file mode 100644 index 0000000000..dc3fcfa1db --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/WatchlistSortBy.swift @@ -0,0 +1,7 @@ +enum WatchlistSortBy: String, CaseIterable { + case manual + case highestCap + case lowestCap + case gainers + case losers +} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/WatchlistTimePeriod.swift b/UnstoppableWallet/UnstoppableWallet/Models/WatchlistTimePeriod.swift new file mode 100644 index 0000000000..9c282ba41b --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/WatchlistTimePeriod.swift @@ -0,0 +1,6 @@ +enum WatchlistTimePeriod: String, CaseIterable { + case day1 = "1d" + case week1 = "1w" + case month1 = "1m" + case month3 = "3m" +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift index 3458a90929..3b879ea0d9 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Backup/ICloud/AppBackupProvider.swift @@ -8,7 +8,7 @@ class AppBackupProvider { private let accountManager: AccountManager private let accountFactory: AccountFactory private let walletManager: WalletManager - private let favoritesManager: FavoritesManager + private let watchlistManager: WatchlistManager private let evmSyncSourceManager: EvmSyncSourceManager private let btcBlockchainManager: BtcBlockchainManager private let restoreSettingsManager: RestoreSettingsManager @@ -27,7 +27,7 @@ class AppBackupProvider { init(accountManager: AccountManager, accountFactory: AccountFactory, walletManager: WalletManager, - favoritesManager: FavoritesManager, + watchlistManager: WatchlistManager, evmSyncSourceManager: EvmSyncSourceManager, btcBlockchainManager: BtcBlockchainManager, restoreSettingsManager: RestoreSettingsManager, @@ -46,7 +46,7 @@ class AppBackupProvider { self.accountManager = accountManager self.accountFactory = accountFactory self.walletManager = walletManager - self.favoritesManager = favoritesManager + self.watchlistManager = watchlistManager self.evmSyncSourceManager = evmSyncSourceManager self.btcBlockchainManager = btcBlockchainManager self.restoreSettingsManager = restoreSettingsManager @@ -125,7 +125,7 @@ class AppBackupProvider { let syncSources = EvmSyncSourceManager.SyncSourceBackup(selected: selected, custom: []) return RawFullBackup( accounts: accounts, - watchlistIds: Array(favoritesManager.coinUids), + watchlistIds: watchlistManager.coinUids, contacts: contactManager.backupContactBook?.contacts ?? [], settings: settings(evmSyncSources: syncSources), customSyncSources: custom @@ -186,7 +186,7 @@ extension AppBackupProvider { for wallet in raw.accounts { restore(raws: [wallet]) } - favoritesManager.add(coinUids: raw.watchlistIds) + watchlistManager.add(coinUids: raw.watchlistIds) if !raw.contacts.isEmpty { try? contactManager.restore(contacts: raw.contacts, mergePolitics: .replace) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageModule.swift index d3971fa0fe..f6904bbcd8 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageModule.swift @@ -5,7 +5,7 @@ import UIKit enum CoinPageModule { static func view(fullCoin: FullCoin) -> some View { - let viewModel = CoinPageViewModelNew(fullCoin: fullCoin, favoritesManager: App.shared.favoritesManager) + let viewModel = CoinPageViewModelNew(fullCoin: fullCoin, watchlistManager: App.shared.watchlistManager) let overviewView = CoinOverviewModule.view(coinUid: fullCoin.coin.uid) let analyticsView = CoinAnalyticsModule.view(fullCoin: fullCoin) @@ -26,7 +26,7 @@ enum CoinPageModule { let service = CoinPageService( fullCoin: fullCoin, - favoritesManager: App.shared.favoritesManager + watchlistManager: App.shared.watchlistManager ) let viewModel = CoinPageViewModel(service: service) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageService.swift index d724038b0a..cb45987411 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageService.swift @@ -6,7 +6,7 @@ import RxSwift class CoinPageService { let fullCoin: FullCoin - private let favoritesManager: FavoritesManager + private let watchlistManager: WatchlistManager private var cancellables = Set() private let favoriteRelay = PublishRelay() @@ -18,11 +18,11 @@ class CoinPageService { } } - init(fullCoin: FullCoin, favoritesManager: FavoritesManager) { + init(fullCoin: FullCoin, watchlistManager: WatchlistManager) { self.fullCoin = fullCoin - self.favoritesManager = favoritesManager + self.watchlistManager = watchlistManager - favoritesManager.coinUidsPublisher + watchlistManager.coinUidsPublisher .sink { [weak self] _ in self?.syncFavorite() } .store(in: &cancellables) @@ -30,7 +30,7 @@ class CoinPageService { } private func syncFavorite() { - favorite = favoritesManager.isFavorite(coinUid: fullCoin.coin.uid) + favorite = watchlistManager.isWatched(coinUid: fullCoin.coin.uid) } } @@ -43,10 +43,10 @@ extension CoinPageService { let coinUid = fullCoin.coin.uid if favorite { - favoritesManager.remove(coinUid: coinUid) + watchlistManager.remove(coinUid: coinUid) stat(page: .coinPage, event: .addToWatchlist(coinUid: coinUid)) } else { - favoritesManager.add(coinUid: coinUid) + watchlistManager.add(coinUid: coinUid) stat(page: .coinPage, event: .removeFromWatchlist(coinUid: coinUid)) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageViewModelNew.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageViewModelNew.swift index efdbd0b49a..2e16640f5a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageViewModelNew.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/CoinPageViewModelNew.swift @@ -3,22 +3,22 @@ import MarketKit class CoinPageViewModelNew: ObservableObject { let fullCoin: FullCoin - private let favoritesManager: FavoritesManager + private let watchlistManager: WatchlistManager @Published var isFavorite: Bool { didSet { if isFavorite { - favoritesManager.add(coinUid: fullCoin.coin.uid) + watchlistManager.add(coinUid: fullCoin.coin.uid) } else { - favoritesManager.remove(coinUid: fullCoin.coin.uid) + watchlistManager.remove(coinUid: fullCoin.coin.uid) } } } - init(fullCoin: FullCoin, favoritesManager: FavoritesManager) { + init(fullCoin: FullCoin, watchlistManager: WatchlistManager) { self.fullCoin = fullCoin - self.favoritesManager = favoritesManager + self.watchlistManager = watchlistManager - isFavorite = favoritesManager.isFavorite(coinUid: fullCoin.coin.uid) + isFavorite = watchlistManager.isWatched(coinUid: fullCoin.coin.uid) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Favorites/FavoritesManager.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Favorites/FavoritesManager.swift deleted file mode 100644 index 8995332985..0000000000 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Favorites/FavoritesManager.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Combine -import WidgetKit - -class FavoritesManager { - private let storage: FavoriteCoinRecordStorage - private let sharedStorage: SharedLocalStorage - - private let coinUidsSubject = PassthroughSubject, Never>() - - var coinUids: Set { - didSet { - coinUidsSubject.send(coinUids) - syncSharedStorage() - } - } - - init(storage: FavoriteCoinRecordStorage, sharedStorage: SharedLocalStorage) { - self.storage = storage - self.sharedStorage = sharedStorage - - do { - let records = try storage.favoriteCoinRecords() - coinUids = Set(records.map(\.coinUid)) - } catch { - coinUids = Set() - } - - syncSharedStorage() - } - - private func syncSharedStorage() { - sharedStorage.set(value: Array(coinUids), for: AppWidgetConstants.keyFavoriteCoinUids) - WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind) - } -} - -extension FavoritesManager { - var coinUidsPublisher: AnyPublisher, Never> { - coinUidsSubject.eraseToAnyPublisher() - } - - func add(coinUid: String) { - coinUids.insert(coinUid) - try? storage.save(favoriteCoinRecord: FavoriteCoinRecord(coinUid: coinUid)) - } - - func add(coinUids: [String]) { - self.coinUids.formUnion(coinUids) - try? storage.save(favoriteCoinRecords: coinUids.map { FavoriteCoinRecord(coinUid: $0) }) - } - - func removeAll() { - coinUids = Set() - try? storage.deleteAll() - } - - func remove(coinUid: String) { - coinUids.remove(coinUid) - try? storage.deleteFavoriteCoinRecord(coinUid: coinUid) - } - - func isFavorite(coinUid: String) -> Bool { - coinUids.contains(coinUid) - } -} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift index cfd8eae6ec..c92d9c5213 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift @@ -116,7 +116,7 @@ struct MarketCoinsView: View { itemContent( imageUrl: URL(string: coin.imageUrl), code: coin.code, - name: coin.name, + marketCap: marketInfo.marketCap, price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized, rank: marketInfo.marketCapRank, diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod) @@ -136,7 +136,7 @@ struct MarketCoinsView: View { itemContent( imageUrl: nil, code: "CODE", - name: "Coin Name", + marketCap: 123_456, price: "$123.45", rank: 12, diff: index % 2 == 0 ? 12.34 : -12.34 @@ -148,7 +148,7 @@ struct MarketCoinsView: View { .simultaneousGesture(DragGesture(minimumDistance: 0), including: .all) } - @ViewBuilder private func itemContent(imageUrl: URL?, code: String, name: String, price: String, rank: Int?, diff: Decimal?) -> some View { + @ViewBuilder private func itemContent(imageUrl: URL?, code: String, marketCap: Decimal?, price: String, rank: Int?, diff: Decimal?) -> some View { KFImage.url(imageUrl) .resizable() .placeholder { Circle().fill(Color.themeSteel20) } @@ -168,7 +168,9 @@ struct MarketCoinsView: View { BadgeViewNew(text: "\(rank)") } - Text(name).textSubhead2() + if let marketCap, let formatted = ValueFormatter.instance.formatShort(currency: viewModel.currency, value: marketCap) { + Text(formatted).textSubhead2() + } } Spacer() DiffText(diff) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift index 686ff7fadc..e08dd3817e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift @@ -83,7 +83,7 @@ extension MarketCoinsViewModel { } var sortBys: [MarketModule.SortBy] { - [.highestCap, .lowestCap, .gainers, .losers, .highestVolume, .lowestVolume] + [.highestCap, .lowestCap, .gainers, .losers] } var tops: [MarketModule.Top] { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Favorites/FavoritesViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Favorites/FavoritesViewModel.swift index ac2b9a7fc6..8234a6a71c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Favorites/FavoritesViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Favorites/FavoritesViewModel.swift @@ -1,26 +1,26 @@ import Combine class FavoritesViewModel: ObservableObject { - private let favoritesManager = App.shared.favoritesManager + private let watchlistManager = App.shared.watchlistManager private var cancellables = Set() @Published var coinUids: Set init() { - coinUids = favoritesManager.coinUids + coinUids = Set(watchlistManager.coinUids) - favoritesManager.coinUidsPublisher - .sink { [weak self] in self?.coinUids = $0 } + watchlistManager.coinUidsPublisher + .sink { [weak self] in self?.coinUids = Set($0) } .store(in: &cancellables) } } extension FavoritesViewModel { func add(coinUid: String) { - favoritesManager.add(coinUid: coinUid) + watchlistManager.add(coinUid: coinUid) } func remove(coinUid: String) { - favoritesManager.remove(coinUid: coinUid) + watchlistManager.remove(coinUid: coinUid) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketAdvancedSearchResults/MarketAdvancedSearchResultModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketAdvancedSearchResults/MarketAdvancedSearchResultModule.swift index 096096ed1d..fefb719126 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketAdvancedSearchResults/MarketAdvancedSearchResultModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketAdvancedSearchResults/MarketAdvancedSearchResultModule.swift @@ -4,7 +4,7 @@ import UIKit enum MarketAdvancedSearchResultModule { static func viewController(marketInfos: [MarketInfo], priceChangeType: MarketModule.PriceChangeType) -> UIViewController { let service = MarketAdvancedSearchResultService(marketInfos: marketInfos, currencyManager: App.shared.currencyManager, priceChangeType: priceChangeType) - let watchlistToggleService = MarketWatchlistToggleService(coinUidService: service, favoritesManager: App.shared.favoritesManager, statPage: .advancedSearchResults) + let watchlistToggleService = MarketWatchlistToggleService(coinUidService: service, watchlistManager: App.shared.watchlistManager, statPage: .advancedSearchResults) let decorator = MarketListMarketFieldDecorator(service: service, statPage: .advancedSearchResults) let listViewModel = MarketListWatchViewModel(service: service, watchlistToggleService: watchlistToggleService, decorator: decorator) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCategory/MarketCategoryModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCategory/MarketCategoryModule.swift index 0438959068..afc4ab7413 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCategory/MarketCategoryModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCategory/MarketCategoryModule.swift @@ -12,7 +12,7 @@ enum MarketCategoryModule { ) let listService = MarketFilteredListService(currencyManager: App.shared.currencyManager, provider: service, statPage: .coinCategory) - let watchlistToggleService = MarketWatchlistToggleService(coinUidService: listService, favoritesManager: App.shared.favoritesManager, statPage: .coinCategory) + let watchlistToggleService = MarketWatchlistToggleService(coinUidService: listService, watchlistManager: App.shared.watchlistManager, statPage: .coinCategory) let marketCapFetcher = MarketCategoryMarketCapFetcher(currencyManager: App.shared.currencyManager, marketKit: App.shared.marketKit, category: category.uid) let chartService = MetricChartService(chartFetcher: marketCapFetcher, interval: .byPeriod(.day1), statPage: .coinCategory) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/MarketGlobalMetricModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/MarketGlobalMetricModule.swift index 8b396c2c8b..c4de7a6349 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/MarketGlobalMetricModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketGlobalMetric/MarketGlobalMetricModule.swift @@ -24,7 +24,7 @@ enum MarketGlobalMetricModule { let watchlistToggleService = MarketWatchlistToggleService( coinUidService: service, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, statPage: type.statPage ) @@ -53,7 +53,7 @@ enum MarketGlobalMetricModule { let watchlistToggleService = MarketWatchlistToggleService( coinUidService: service, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, statPage: .globalMetricsDefiCap ) @@ -82,7 +82,7 @@ enum MarketGlobalMetricModule { let watchlistToggleService = MarketWatchlistToggleService( coinUidService: service, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, statPage: .globalMetricsTvlInDefi ) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketList/MarketWatchlistToggleService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketList/MarketWatchlistToggleService.swift index ae81b8083f..6aa7b796ff 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketList/MarketWatchlistToggleService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketList/MarketWatchlistToggleService.swift @@ -2,14 +2,14 @@ import RxSwift class MarketWatchlistToggleService { private let coinUidService: IMarketListCoinUidService - private let favoritesManager: FavoritesManager + private let watchlistManager: WatchlistManager private let statPage: StatPage private let statusSubject = PublishSubject() - init(coinUidService: IMarketListCoinUidService, favoritesManager: FavoritesManager, statPage: StatPage) { + init(coinUidService: IMarketListCoinUidService, watchlistManager: WatchlistManager, statPage: StatPage) { self.coinUidService = coinUidService - self.favoritesManager = favoritesManager + self.watchlistManager = watchlistManager self.statPage = statPage } } @@ -24,7 +24,7 @@ extension MarketWatchlistToggleService { return nil } - return favoritesManager.isFavorite(coinUid: coinUid) + return watchlistManager.isWatched(coinUid: coinUid) } func favorite(index: Int) { @@ -33,7 +33,7 @@ extension MarketWatchlistToggleService { return } - favoritesManager.add(coinUid: coinUid) + watchlistManager.add(coinUid: coinUid) statusSubject.onNext(.favorite) @@ -46,7 +46,7 @@ extension MarketWatchlistToggleService { return } - favoritesManager.remove(coinUid: coinUid) + watchlistManager.remove(coinUid: coinUid) statusSubject.onNext(.unfavorite) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift index 34144a0d61..acc4d2e2af 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift @@ -142,7 +142,6 @@ enum MarketModule { extension MarketModule { enum SortBy: String, CaseIterable { - case manual case highestCap case lowestCap case gainers @@ -152,7 +151,6 @@ extension MarketModule { var title: String { switch self { - case .manual: return "market.sort_by.manual".localized case .highestCap: return "market.sort_by.highest_cap".localized case .lowestCap: return "market.sort_by.lowest_cap".localized case .gainers: return "market.sort_by.gainers".localized @@ -383,26 +381,51 @@ extension MarketKit.MarketInfo { default: return nil } } + + func priceChangeValue(timePeriod: WatchlistTimePeriod) -> Decimal? { + switch timePeriod { + case .day1: return priceChange24h + case .week1: return priceChange7d + case .month1: return priceChange30d + case .month3: return priceChange90d + } + } } extension [MarketKit.MarketInfo] { - func sorted(sortBy: MarketModule.SortBy, timePeriod: HsTimePeriod) -> [MarketKit.MarketInfo] { - sorted { lhsMarketInfo, rhsMarketInfo in - switch sortBy { - case .highestCap: return lhsMarketInfo.marketCap ?? 0 > rhsMarketInfo.marketCap ?? 0 - case .lowestCap: return lhsMarketInfo.marketCap ?? 0 < rhsMarketInfo.marketCap ?? 0 - case .highestVolume: return lhsMarketInfo.totalVolume ?? 0 > rhsMarketInfo.totalVolume ?? 0 - case .lowestVolume: return lhsMarketInfo.totalVolume ?? 0 < rhsMarketInfo.totalVolume ?? 0 - case .gainers, .losers: - guard let rhsPriceChange = rhsMarketInfo.priceChangeValue(timePeriod: timePeriod) else { + func sorted(sortBy: WatchlistSortBy, timePeriod: WatchlistTimePeriod) -> [MarketKit.MarketInfo] { + switch sortBy { + case .manual: return self + case .highestCap: return sorted { $0.marketCap ?? 0 > $1.marketCap ?? 0 } + case .lowestCap: return sorted { $0.marketCap ?? 0 < $1.marketCap ?? 0 } + case .gainers, .losers: return sorted { + guard let lhsPriceChange = $0.priceChangeValue(timePeriod: timePeriod) else { + return false + } + guard let rhsPriceChange = $1.priceChangeValue(timePeriod: timePeriod) else { return true } - guard let lhsPriceChange = lhsMarketInfo.priceChangeValue(timePeriod: timePeriod) else { + + return sortBy == .gainers ? lhsPriceChange > rhsPriceChange : lhsPriceChange < rhsPriceChange + } + } + } + + func sorted(sortBy: MarketModule.SortBy, timePeriod: HsTimePeriod) -> [MarketKit.MarketInfo] { + switch sortBy { + case .highestCap: return sorted { $0.marketCap ?? 0 > $1.marketCap ?? 0 } + case .lowestCap: return sorted { $0.marketCap ?? 0 < $1.marketCap ?? 0 } + case .highestVolume: return sorted { $0.totalVolume ?? 0 > $1.totalVolume ?? 0 } + case .lowestVolume: return sorted { $0.totalVolume ?? 0 < $1.totalVolume ?? 0 } + case .gainers, .losers: return sorted { + guard let lhsPriceChange = $0.priceChangeValue(timePeriod: timePeriod) else { return false } + guard let rhsPriceChange = $1.priceChangeValue(timePeriod: timePeriod) else { + return true + } return sortBy == .gainers ? lhsPriceChange > rhsPriceChange : lhsPriceChange < rhsPriceChange - default: return true } } } @@ -517,3 +540,25 @@ extension HsTimePeriod { "market.time_period.\(rawValue).short".localized } } + +extension WatchlistTimePeriod { + var title: String { + "market.time_period.\(rawValue)".localized + } + + var shortTitle: String { + "market.time_period.\(rawValue).short".localized + } +} + +extension WatchlistSortBy { + var title: String { + switch self { + case .manual: return "market.sort_by.manual".localized + case .highestCap: return "market.sort_by.highest_cap".localized + case .lowestCap: return "market.sort_by.lowest_cap".localized + case .gainers: return "market.sort_by.gainers".localized + case .losers: return "market.sort_by.losers".localized + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTop/MarketTopModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTop/MarketTopModule.swift index b00d8510b7..c747a0b17a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTop/MarketTopModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketTop/MarketTopModule.swift @@ -12,7 +12,7 @@ enum MarketTopModule { ) let watchlistToggleService = MarketWatchlistToggleService( coinUidService: service, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, statPage: .topCoins ) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketViewModel.swift index 678d146baf..d7196e3612 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketViewModel.swift @@ -8,7 +8,7 @@ class MarketViewModel { private let userDefaultsStorage = App.shared.userDefaultsStorage private let launchScreenManager = App.shared.launchScreenManager private let marketKit = App.shared.marketKit - private let favoritesManager = App.shared.favoritesManager + private let watchlistManager = App.shared.watchlistManager @Published var currentTab: MarketModule.TabOld { didSet { @@ -77,18 +77,18 @@ extension MarketViewModel { } func isFavorite(coinUid: String) -> Bool { - favoritesManager.isFavorite(coinUid: coinUid) + watchlistManager.isWatched(coinUid: coinUid) } func favorite(coinUid: String) { - favoritesManager.add(coinUid: coinUid) + watchlistManager.add(coinUid: coinUid) favoritedSubject.send() stat(page: .marketSearch, event: .addToWatchlist(coinUid: coinUid)) } func unfavorite(coinUid: String) { - favoritesManager.remove(coinUid: coinUid) + watchlistManager.remove(coinUid: coinUid) unfavoritedSubject.send() stat(page: .marketSearch, event: .removeFromWatchlist(coinUid: coinUid)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistModule.swift index 0a499f1565..6a10b26871 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistModule.swift @@ -5,13 +5,13 @@ enum MarketWatchlistModule { let service = MarketWatchlistService( marketKit: App.shared.marketKit, currencyManager: App.shared.currencyManager, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, appManager: App.shared.appManager, userDefaultsStorage: App.shared.userDefaultsStorage ) let watchlistToggleService = MarketWatchlistToggleService( coinUidService: service, - favoritesManager: App.shared.favoritesManager, + watchlistManager: App.shared.watchlistManager, statPage: .watchlist ) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistService.swift index 188a3b9e24..714537a6d0 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketWatchlist/MarketWatchlistService.swift @@ -12,7 +12,7 @@ class MarketWatchlistService: IMarketSingleSortHeaderService { private let marketKit: MarketKit.Kit private let currencyManager: CurrencyManager - private let favoritesManager: FavoritesManager + private let watchlistManager: WatchlistManager private let appManager: IAppManager private let userDefaultsStorage: UserDefaultsStorage private let disposeBag = DisposeBag() @@ -32,10 +32,10 @@ class MarketWatchlistService: IMarketSingleSortHeaderService { } } - init(marketKit: MarketKit.Kit, currencyManager: CurrencyManager, favoritesManager: FavoritesManager, appManager: IAppManager, userDefaultsStorage: UserDefaultsStorage) { + init(marketKit: MarketKit.Kit, currencyManager: CurrencyManager, watchlistManager: WatchlistManager, appManager: IAppManager, userDefaultsStorage: UserDefaultsStorage) { self.marketKit = marketKit self.currencyManager = currencyManager - self.favoritesManager = favoritesManager + self.watchlistManager = watchlistManager self.appManager = appManager self.userDefaultsStorage = userDefaultsStorage @@ -43,7 +43,7 @@ class MarketWatchlistService: IMarketSingleSortHeaderService { } private func syncCoinUids() { - coinUids = Array(favoritesManager.coinUids) + coinUids = watchlistManager.coinUids if case let .loaded(marketInfos, _, _) = state { let newMarketInfos = marketInfos.filter { marketInfo in @@ -107,7 +107,7 @@ extension MarketWatchlistService: IMarketListService { } .store(in: &cancellables) - favoritesManager.coinUidsPublisher + watchlistManager.coinUidsPublisher .sink { [weak self] _ in self?.syncCoinUids() } .store(in: &cancellables) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/TopPlatform/TopPlatformModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/TopPlatform/TopPlatformModule.swift index 47618b867d..3103f82a8d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/TopPlatform/TopPlatformModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/TopPlatform/TopPlatformModule.swift @@ -7,7 +7,7 @@ enum TopPlatformModule { static func viewController(topPlatform: TopPlatform) -> UIViewController { let service = TopPlatformService(topPlatform: topPlatform, marketKit: App.shared.marketKit) let listService = MarketFilteredListService(currencyManager: App.shared.currencyManager, provider: service, statPage: .topPlatform) - let watchlistToggleService = MarketWatchlistToggleService(coinUidService: listService, favoritesManager: App.shared.favoritesManager, statPage: .topPlatform) + let watchlistToggleService = MarketWatchlistToggleService(coinUidService: listService, watchlistManager: App.shared.watchlistManager, statPage: .topPlatform) let marketCapFetcher = TopPlatformMarketCapFetcher(marketKit: App.shared.marketKit, currencyManager: App.shared.currencyManager, topPlatform: topPlatform) let chartService = MetricChartService(chartFetcher: marketCapFetcher, interval: .byPeriod(.week1), statPage: .topPlatform) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift index 41722b4ff7..ba8f17d1c5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift @@ -75,25 +75,25 @@ struct MarketWatchlistView: View { .alert( isPresented: $sortBySelectorPresented, title: "market.sort_by.title".localized, - viewItems: viewModel.sortBys.map { .init(text: $0.title, selected: viewModel.sortBy == $0) }, + viewItems: WatchlistSortBy.allCases.map { .init(text: $0.title, selected: viewModel.sortBy == $0) }, onTap: { index in guard let index else { return } - viewModel.sortBy = viewModel.sortBys[index] + viewModel.sortBy = WatchlistSortBy.allCases[index] } ) .alert( isPresented: $timePeriodSelectorPresented, title: "market.time_period.title".localized, - viewItems: viewModel.timePeriods.map { .init(text: $0.title, selected: viewModel.timePeriod == $0) }, + viewItems: WatchlistTimePeriod.allCases.map { .init(text: $0.title, selected: viewModel.timePeriod == $0) }, onTap: { index in guard let index else { return } - viewModel.timePeriod = viewModel.timePeriods[index] + viewModel.timePeriod = WatchlistTimePeriod.allCases[index] } ) } @@ -116,7 +116,7 @@ struct MarketWatchlistView: View { itemContent( imageUrl: URL(string: coin.imageUrl), code: coin.code, - name: coin.name, + marketCap: marketInfo.marketCap, price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized, rank: marketInfo.marketCapRank, diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod), @@ -144,7 +144,7 @@ struct MarketWatchlistView: View { itemContent( imageUrl: nil, code: "CODE", - name: "Coin Name", + marketCap: 123_456, price: "$123.45", rank: 12, diff: index % 2 == 0 ? 12.34 : -12.34, @@ -157,7 +157,7 @@ struct MarketWatchlistView: View { .simultaneousGesture(DragGesture(minimumDistance: 0), including: .all) } - @ViewBuilder private func itemContent(imageUrl: URL?, code: String, name: String, price: String, rank: Int?, diff: Decimal?, signal: TechnicalAdvice.Advice?) -> some View { + @ViewBuilder private func itemContent(imageUrl: URL?, code: String, marketCap: Decimal?, price: String, rank: Int?, diff: Decimal?, signal: TechnicalAdvice.Advice?) -> some View { KFImage.url(imageUrl) .resizable() .placeholder { Circle().fill(Color.themeSteel20) } @@ -190,7 +190,9 @@ struct MarketWatchlistView: View { BadgeViewNew(text: "\(rank)") } - Text(name).textSubhead2() + if let marketCap, let formatted = ValueFormatter.instance.formatShort(currency: viewModel.currency, value: marketCap) { + Text(formatted).textSubhead2() + } } Spacer() DiffText(diff) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift index 579f5d40bb..6fb95c4a70 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift @@ -4,14 +4,9 @@ import HsExtensions import MarketKit class MarketWatchlistViewModel: ObservableObject { - private let keySortBy = "market-watchlist-sort-by" - private let keyTimePeriod = "market-watchlist-time-period" - private let keyShowSignals = "market-watchlist-show-signals" - private let marketKit = App.shared.marketKit private let currencyManager = App.shared.currencyManager - private let favoritesManager = App.shared.favoritesManager - private let userDefaultsStorage = App.shared.userDefaultsStorage + private let watchlistManager = App.shared.watchlistManager private var cancellables = Set() private var tasks = Set() @@ -26,42 +21,35 @@ class MarketWatchlistViewModel: ObservableObject { @Published var state: State = .loading - var sortBy: MarketModule.SortBy { + @Published var sortBy: WatchlistSortBy { didSet { syncState() - - userDefaultsStorage.set(value: sortBy.rawValue, for: keySortBy) + watchlistManager.sortBy = sortBy } } - var timePeriod: HsTimePeriod { + @Published var timePeriod: WatchlistTimePeriod { didSet { syncState() - - userDefaultsStorage.set(value: timePeriod.rawValue, for: keyTimePeriod) + watchlistManager.timePeriod = timePeriod } } - var showSignals: Bool { + @Published var showSignals: Bool { didSet { syncState() - - userDefaultsStorage.set(value: showSignals, for: keyShowSignals) + watchlistManager.showSignals = showSignals } } init() { - let sortByRaw: String? = userDefaultsStorage.value(for: keySortBy) - sortBy = sortByRaw.flatMap { MarketModule.SortBy(rawValue: $0) } ?? .gainers - - let timePeriodRaw: String? = userDefaultsStorage.value(for: keyTimePeriod) - timePeriod = timePeriodRaw.flatMap { HsTimePeriod(rawValue: $0) } ?? .day1 - - showSignals = userDefaultsStorage.value(for: keyShowSignals) ?? true + sortBy = watchlistManager.sortBy + timePeriod = watchlistManager.timePeriod + showSignals = watchlistManager.showSignals } private func syncCoinUids() { - coinUids = Array(favoritesManager.coinUids) + coinUids = watchlistManager.coinUids if case let .loaded(marketInfos, signals) = internalState { let newMarketInfos = marketInfos.filter { marketInfo in @@ -105,8 +93,11 @@ class MarketWatchlistViewModel: ObservableObject { let (marketInfos, signals) = try await (_marketInfos, _signals) + let marketInfoMap = marketInfos.reduce(into: [String: MarketInfo]()) { $0[$1.fullCoin.coin.uid] = $1 } + let orderedMarketInfos = coinUids.compactMap { marketInfoMap[$0] } + await MainActor.run { [weak self] in - self?.internalState = .loaded(marketInfos: marketInfos, signals: signals) + self?.internalState = .loaded(marketInfos: orderedMarketInfos, signals: signals) } } catch { await MainActor.run { [weak self] in @@ -132,14 +123,6 @@ extension MarketWatchlistViewModel { currencyManager.baseCurrency } - var sortBys: [MarketModule.SortBy] { - [.manual, .highestCap, .lowestCap, .gainers, .losers, .highestVolume, .lowestVolume] - } - - var timePeriods: [HsTimePeriod] { - [.day1, .week1, .month1, .month3] - } - func load() { currencyManager.$baseCurrency .sink { [weak self] _ in @@ -147,7 +130,7 @@ extension MarketWatchlistViewModel { } .store(in: &cancellables) - favoritesManager.coinUidsPublisher + watchlistManager.coinUidsPublisher .sink { [weak self] _ in self?.syncCoinUids() } .store(in: &cancellables) @@ -159,7 +142,7 @@ extension MarketWatchlistViewModel { } func remove(coinUid: String) { - favoritesManager.remove(coinUid: coinUid) + watchlistManager.remove(coinUid: coinUid) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift index 4dbf6f4528..0c2b28e978 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BackupApp/Backup/BackupAppViewModel.swift @@ -7,7 +7,7 @@ class BackupAppViewModel: ObservableObject { private let accountManager = App.shared.accountManager private let contactManager = App.shared.contactManager private let cloudBackupManager = App.shared.cloudBackupManager - private let favoritesManager = App.shared.favoritesManager + private let watchlistManager = App.shared.watchlistManager private let evmSyncSourceManager = App.shared.evmSyncSourceManager private var cancellables = Set() @@ -149,7 +149,7 @@ extension BackupAppViewModel { return BackupAppModule.items( watchAccountCount: accounts(watch: true).count, - watchlistCount: favoritesManager.coinUids.count, + watchlistCount: watchlistManager.coinUids.count, contactAddressCount: contacts.count, blockchainSourcesCount: evmSyncSourceManager.customSyncSources(blockchainType: nil).count ) diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BadgeViewNew.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BadgeViewNew.swift index acac34b219..3481dc05bc 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BadgeViewNew.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BadgeViewNew.swift @@ -18,11 +18,11 @@ struct BadgeViewNew: View { if let change, change != 0 { if change > 0 { - Text("↑\(change)") + Text(verbatim: "↑\(change)") .font(.themeMicroSB) .foregroundColor(.themeRemus) } else { - Text("↓\(abs(change))") + Text(verbatim: "↓\(abs(change))") .font(.themeMicroSB) .foregroundColor(.themeLucian) } diff --git a/UnstoppableWallet/Widget/AppWidgetConstants.swift b/UnstoppableWallet/Widget/AppWidgetConstants.swift index b5ad23b417..5a6a1ec87e 100644 --- a/UnstoppableWallet/Widget/AppWidgetConstants.swift +++ b/UnstoppableWallet/Widget/AppWidgetConstants.swift @@ -4,6 +4,4 @@ enum AppWidgetConstants { static let singleCoinPriceWidgetKind: String = "io.horizontalsystems.unstoppable.SingleCoinPriceWidget" static let topCoinsWidgetKind: String = "io.horizontalsystems.unstoppable.TopCoinsWidget" static let watchlistWidgetKind: String = "io.horizontalsystems.unstoppable.WatchlistWidget" - - static let keyFavoriteCoinUids = "favorite_coin_uids" } diff --git a/UnstoppableWallet/Widget/Base.lproj/AppWidget.intentdefinition b/UnstoppableWallet/Widget/Base.lproj/AppWidget.intentdefinition index 47aca06421..3c68ada273 100644 --- a/UnstoppableWallet/Widget/Base.lproj/AppWidget.intentdefinition +++ b/UnstoppableWallet/Widget/Base.lproj/AppWidget.intentdefinition @@ -47,43 +47,23 @@ INEnumValueDisplayName - Highest Volume - INEnumValueDisplayNameID - t6AKQ5 - INEnumValueIndex - 3 - INEnumValueName - highestVolume - - - INEnumValueDisplayName - Lowest Volume - INEnumValueDisplayNameID - C1LmDj - INEnumValueIndex - 4 - INEnumValueName - lowestVolume - - - INEnumValueDisplayName - Top Gainers + Gainers INEnumValueDisplayNameID Rl3a0T INEnumValueIndex - 5 + 3 INEnumValueName - topGainers + gainers INEnumValueDisplayName - Top Losers + Losers INEnumValueDisplayNameID x1kK4T INEnumValueIndex - 6 + 4 INEnumValueName - topLosers + losers @@ -93,11 +73,11 @@ INIntentDefinitionNamespace 5N3KPh INIntentDefinitionSystemVersion - 23B81 + 23E224 INIntentDefinitionToolsBuildVersion - 15A240d + 15E204a INIntentDefinitionToolsVersion - 15.0 + 15.3 INIntents diff --git a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListView.swift b/UnstoppableWallet/Widget/CoinListView.swift similarity index 54% rename from UnstoppableWallet/Widget/CoinPriceList/CoinPriceListView.swift rename to UnstoppableWallet/Widget/CoinListView.swift index 9575d201e6..87227b526b 100644 --- a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListView.swift +++ b/UnstoppableWallet/Widget/CoinListView.swift @@ -2,8 +2,11 @@ import Charts import SwiftUI import WidgetKit -struct CoinPriceListView: View { - var entry: CoinPriceListProvider.Entry +struct CoinListView: View { + let items: [CoinItem] + let maxItemCount: Int + let title: LocalizedStringKey + let subtitle: LocalizedStringKey @Environment(\.widgetFamily) private var family @@ -43,14 +46,14 @@ struct CoinPriceListView: View { @ViewBuilder private func largeView() -> some View { VStack(spacing: 0) { HStack(spacing: .margin16) { - Text(entry.mode.title) + Text(title) .lineLimit(1) .font(.themeSubhead1) .foregroundColor(.themeLeah) Spacer() - Text(title(sortType: entry.sortType)) + Text(subtitle) .lineLimit(1) .font(.themeSubhead2) .foregroundColor(.themeGray) @@ -66,56 +69,30 @@ struct CoinPriceListView: View { .padding(.vertical, .margin4) } - @ViewBuilder private func list(verticalPadding: CGFloat, rowBuilder: @escaping (CoinPriceListEntry.Item) -> some View) -> some View { - if entry.mode.isWatchlist, entry.items.isEmpty { - VStack(spacing: .margin16) { - switch family { - case .systemLarge: - ZStack { - Circle() - .fill(Color.themeRaina) - .frame(width: 100, height: 100) - - Image("rate_48") - .renderingMode(.template) - .foregroundColor(.themeGray) + @ViewBuilder private func list(verticalPadding: CGFloat, rowBuilder: @escaping (CoinItem) -> some View) -> some View { + GeometryReader { proxy in + ListSection { + ForEach(items, id: \.uid) { item in + Link(destination: URL(string: "unstoppable.money://coin/\(item.uid)")!) { + rowBuilder(item) + .padding(.horizontal, .margin16) + .frame(maxHeight: .infinity) + .frame(maxHeight: proxy.size.height / CGFloat(maxItemCount)) } - default: - EmptyView() + .buttonStyle(PlainButtonStyle()) } - Text("watchlist.empty") - .multilineTextAlignment(.center) - .font(.themeSubhead2) - .foregroundColor(.themeGray) - } - .frame(maxHeight: .infinity) - .padding(.margin16) - } else { - GeometryReader { proxy in - ListSection { - ForEach(entry.items, id: \.uid) { item in - Link(destination: URL(string: "unstoppable.money://coin/\(item.uid)")!) { - rowBuilder(item) - .padding(.horizontal, .margin16) - .frame(maxHeight: .infinity) - .frame(maxHeight: proxy.size.height / CGFloat(entry.maxItemCount)) - } - .buttonStyle(PlainButtonStyle()) - } - - if entry.items.count < entry.maxItemCount { - Spacer() - } + if items.count < maxItemCount { + Spacer() } - .themeListStyle(.transparentInline) } - .frame(maxHeight: .infinity) - .padding(.vertical, verticalPadding) + .themeListStyle(.transparentInline) } + .frame(maxHeight: .infinity) + .padding(.vertical, verticalPadding) } - @ViewBuilder private func row(item: CoinPriceListEntry.Item) -> some View { + @ViewBuilder private func row(item: CoinItem) -> some View { HStack(spacing: .margin16) { icon(image: item.icon) @@ -133,9 +110,17 @@ struct CoinPriceListView: View { } HStack(spacing: .margin16) { - Text(item.name) - .font(.themeSubhead2) - .foregroundColor(.themeGray) + HStack(spacing: .margin4) { + if let rank = item.rank { + BadgeViewNew(text: rank) + } + + if let marketCap = item.marketCap { + Text(marketCap) + .font(.themeSubhead2) + .foregroundColor(.themeGray) + } + } Spacer() @@ -159,15 +144,4 @@ struct CoinPriceListView: View { .frame(width: .iconSize32, height: .iconSize32) } } - - private func title(sortType: SortType) -> LocalizedStringKey { - switch sortType { - case .highestCap, .unknown: return "sort_type.highest_cap" - case .lowestCap: return "sort_type.lowest_cap" - case .highestVolume: return "sort_type.highest_volume" - case .lowestVolume: return "sort_type.lowest_volume" - case .topGainers: return "sort_type.top_gainers" - case .topLosers: return "sort_type.top_losers" - } - } } diff --git a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListEntry.swift b/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListEntry.swift index cead1231cd..2652bfb420 100644 --- a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListEntry.swift +++ b/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListEntry.swift @@ -4,18 +4,31 @@ import WidgetKit struct CoinPriceListEntry: TimelineEntry { let date: Date - let mode: CoinPriceListMode let sortType: SortType let maxItemCount: Int - let items: [Item] + let items: [CoinItem] +} + +struct CoinItem { + let uid: String + let icon: Image? + let code: String + let marketCap: String? + let rank: String? + let price: String + let priceChange: String + let priceChangeType: PriceChangeType - struct Item { - let uid: String - let icon: Image? - let code: String - let name: String - let price: String - let priceChange: String - let priceChangeType: PriceChangeType + static func stub(index: Int) -> CoinItem { + CoinItem( + uid: "coin\(index)", + icon: nil, + code: "COD\(index)", + marketCap: "$1.23M", + rank: "\(index)", + price: "$1234", + priceChange: "1.23", + priceChangeType: .unknown + ) } } diff --git a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListProvider.swift b/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListProvider.swift index 35a114280b..6a6a1ea592 100644 --- a/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListProvider.swift +++ b/UnstoppableWallet/Widget/CoinPriceList/CoinPriceListProvider.swift @@ -3,8 +3,6 @@ import SwiftUI import WidgetKit struct CoinPriceListProvider: IntentTimelineProvider { - let mode: CoinPriceListMode - func placeholder(in context: Context) -> CoinPriceListEntry { let count: Int @@ -15,20 +13,9 @@ struct CoinPriceListProvider: IntentTimelineProvider { return CoinPriceListEntry( date: Date(), - mode: mode, sortType: .highestCap, maxItemCount: count, - items: (1 ... count).map { index in - CoinPriceListEntry.Item( - uid: "coin\(index)", - icon: nil, - code: "COD\(index)", - name: "Coin Name \(index)", - price: "$1234", - priceChange: "1.23", - priceChangeType: .unknown - ) - } + items: (1 ... count).map { CoinItem.stub(index: $0) } ) } @@ -61,13 +48,12 @@ struct CoinPriceListProvider: IntentTimelineProvider { switch sortType { case .highestCap, .lowestCap, .unknown: listType = .mcap - case .highestVolume, .lowestVolume: listType = .volume - case .topGainers, .topLosers: listType = .price + case .gainers, .losers: listType = .priceChange24h } switch sortType { - case .highestCap, .highestVolume, .topGainers, .unknown: listOrder = .desc - case .lowestCap, .lowestVolume, .topLosers: listOrder = .asc + case .highestCap, .gainers, .unknown: listOrder = .desc + case .lowestCap, .losers: listOrder = .asc } switch family { @@ -75,35 +61,22 @@ struct CoinPriceListProvider: IntentTimelineProvider { default: limit = 6 } - let coins: [Coin] - - switch mode { - case .topCoins: - coins = try await apiProvider.listCoins(type: listType, order: listOrder, limit: limit, currencyCode: currency.code) - case .watchlist: - let coinUids: [String]? = storage.value(for: AppWidgetConstants.keyFavoriteCoinUids) - - if let coinUids, !coinUids.isEmpty { - coins = try await apiProvider.listCoins(uids: coinUids, type: listType, order: listOrder, limit: limit, currencyCode: currency.code) - } else { - coins = [] - } - } + let coins = try await apiProvider.listCoins(type: listType, order: listOrder, limit: limit, currencyCode: currency.code) return CoinPriceListEntry( date: Date(), - mode: mode, sortType: sortType, maxItemCount: limit, items: coins.map { coin in - CoinPriceListEntry.Item( + CoinItem( uid: coin.uid, icon: coin.image, code: coin.code, - name: coin.name, + marketCap: coin.marketCap.flatMap { ValueFormatter.formatShort(currency: currency, value: $0) }, + rank: coin.rank.map { "\($0)" }, price: coin.formattedPrice(currency: currency), - priceChange: coin.formattedPriceChange, - priceChangeType: coin.priceChangeType + priceChange: coin.formattedPriceChange(), + priceChangeType: coin.priceChangeType() ) } ) diff --git a/UnstoppableWallet/Widget/Localizable.xcstrings b/UnstoppableWallet/Widget/Localizable.xcstrings index e6b5ce1a78..31309dd918 100644 --- a/UnstoppableWallet/Widget/Localizable.xcstrings +++ b/UnstoppableWallet/Widget/Localizable.xcstrings @@ -1,11 +1,55 @@ { "sourceLanguage" : "en", "strings" : { - "↑%lld" : { - + "%@number.billion" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@B" + } + } + } + }, + "%@number.million" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + } + } + }, + "%@number.quadrillion" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@Q" + } + } + } + }, + "%@number.thousand" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + } + } }, - "↓%lld" : { - + "%@number.trillion" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@T" + } + } + } }, "single_coin_price.description" : { "extractionState" : "manual", @@ -125,6 +169,16 @@ } } }, + "sort_type.gainers" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gainers" + } + } + } + }, "sort_type.highest_cap" : { "localizations" : { "de" : { @@ -183,60 +237,12 @@ } } }, - "sort_type.highest_volume" : { + "sort_type.losers" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Höchstes Handelsvolumen" - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Highest Volume" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mayor volumen" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plus grand volume" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "최대 거래량" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Maior volume" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Наибольший объем" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "En yüksek hacim" - } - }, - "zh" : { - "stringUnit" : { - "state" : "translated", - "value" : "最高交易量" + "value" : "Losers" } } } @@ -299,176 +305,12 @@ } } }, - "sort_type.lowest_volume" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geringstes Handelsvolumen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lowest Volume" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Menor volumen" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plus faible volume" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "최소 거래량" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Menor volume" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Наименьший объем" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "En düşük hacim" - } - }, - "zh" : { - "stringUnit" : { - "state" : "translated", - "value" : "最低交易量" - } - } - } - }, - "sort_type.top_gainers" : { + "sort_type.manual" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top-Gewinner" - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Top Gainers" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mayores ganadores" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plus grands gagnants" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "상위 수익 코인" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Maiores ganhadores" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Показывают рост" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "En çok kazananlar" - } - }, - "zh" : { - "stringUnit" : { - "state" : "translated", - "value" : "涨幅最大的股票" - } - } - } - }, - "sort_type.top_losers" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top-Verlierer" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top Losers" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mayores perdedores" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plus grands perdants" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "하위 손실 코인" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Maiores perdedores" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Теряют в цене" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "En çok kaybedenler" - } - }, - "zh" : { - "stringUnit" : { - "state" : "translated", - "value" : "跌幅最大的股票" + "value" : "Manual" } } } diff --git a/UnstoppableWallet/Widget/Misc/ApiProvider.swift b/UnstoppableWallet/Widget/Misc/ApiProvider.swift index 0006b9ba8e..b797326253 100644 --- a/UnstoppableWallet/Widget/Misc/ApiProvider.swift +++ b/UnstoppableWallet/Widget/Misc/ApiProvider.swift @@ -13,12 +13,6 @@ class ApiProvider { var headers = HTTPHeaders() headers.add(name: "widget", value: "true") - headers.add(name: "app_platform", value: "ios") - headers.add(name: "app_version", value: WidgetConfig.appVersion) - - if let appId = WidgetConfig.appId { - headers.add(name: "app_id", value: appId) - } if let apiKey = WidgetConfig.hsProviderApiKey { headers.add(name: "apikey", value: apiKey) @@ -87,25 +81,37 @@ class ApiProvider { } enum ListType: String { - case price - case volume + case priceChange24h = "price_change_24h" + case priceChange1w = "price_change_1w" + case priceChange1m = "price_change_1m" + case priceChange3m = "price_change_3m" case mcap } } struct Coin: ImmutableMappable { let uid: String - let name: String let code: String + let name: String + let marketCap: Decimal? + let rank: Int? let price: Decimal? let priceChange24h: Decimal? + let priceChange1w: Decimal? + let priceChange1m: Decimal? + let priceChange3m: Decimal? init(map: Map) throws { uid = try map.value("uid") - name = try map.value("name") code = try map.value("code") + name = try map.value("name") + marketCap = try? map.value("market_cap", using: Transform.stringToDecimalTransform) + rank = try? map.value("market_cap_rank") price = try? map.value("price", using: Transform.stringToDecimalTransform) priceChange24h = try? map.value("price_change_24h", using: Transform.stringToDecimalTransform) + priceChange1w = try? map.value("price_change_1w", using: Transform.stringToDecimalTransform) + priceChange1m = try? map.value("price_change_1m", using: Transform.stringToDecimalTransform) + priceChange3m = try? map.value("price_change_3m", using: Transform.stringToDecimalTransform) } } diff --git a/UnstoppableWallet/Widget/Misc/CoinPriceListMode.swift b/UnstoppableWallet/Widget/Misc/CoinPriceListMode.swift deleted file mode 100644 index fa326f48de..0000000000 --- a/UnstoppableWallet/Widget/Misc/CoinPriceListMode.swift +++ /dev/null @@ -1,20 +0,0 @@ -import SwiftUI - -enum CoinPriceListMode { - case topCoins - case watchlist - - var title: LocalizedStringKey { - switch self { - case .topCoins: return "top_coins.title" - case .watchlist: return "watchlist.title" - } - } - - var isWatchlist: Bool { - switch self { - case .watchlist: return true - default: return false - } - } -} diff --git a/UnstoppableWallet/Widget/Misc/Extensions.swift b/UnstoppableWallet/Widget/Misc/Extensions.swift index f321cec50b..2a88505d70 100644 --- a/UnstoppableWallet/Widget/Misc/Extensions.swift +++ b/UnstoppableWallet/Widget/Misc/Extensions.swift @@ -15,15 +15,24 @@ extension Coin { price.flatMap { ValueFormatter.format(currency: currency, value: $0) } ?? "n/a" } - var formattedPriceChange: String { - priceChange24h.flatMap { ValueFormatter.format(percentValue: $0) } ?? "n/a" + func formattedPriceChange(timePeriod: WatchlistTimePeriod = .day1) -> String { + priceChange(timePeriod: timePeriod).flatMap { ValueFormatter.format(percentValue: $0) } ?? "n/a" } - var priceChangeType: PriceChangeType { - guard let priceChange24h else { + func priceChangeType(timePeriod: WatchlistTimePeriod = .day1) -> PriceChangeType { + guard let priceChange = priceChange(timePeriod: timePeriod) else { return .unknown } - return priceChange24h >= 0 ? .up : .down + return priceChange >= 0 ? .up : .down + } + + private func priceChange(timePeriod: WatchlistTimePeriod) -> Decimal? { + switch timePeriod { + case .day1: return priceChange24h + case .week1: return priceChange1w + case .month1: return priceChange1m + case .month3: return priceChange3m + } } } diff --git a/UnstoppableWallet/Widget/Misc/ValueFormatter.swift b/UnstoppableWallet/Widget/Misc/ValueFormatter.swift index bf4aab165a..2028df5924 100644 --- a/UnstoppableWallet/Widget/Misc/ValueFormatter.swift +++ b/UnstoppableWallet/Widget/Misc/ValueFormatter.swift @@ -31,6 +31,18 @@ enum ValueFormatter { return maxCount } + private static func digitsAndValue(value: Decimal, basePow: Int) -> (Int, Decimal) { + let digits: Int + + switch value { + case pow(10, basePow) ..< (2 * pow(10, basePow + 1)): digits = 2 + case (2 * pow(10, basePow + 1)) ..< (2 * pow(10, basePow + 2)): digits = 1 + default: digits = 0 + } + + return (digits, value / pow(10, basePow)) + } + private static func transformedFull(value: Decimal, maxDigits: Int, minDigits: Int = 0) -> (value: Decimal, digits: Int) { let value = abs(value) let digits: Int @@ -62,9 +74,75 @@ enum ValueFormatter { return (value: value, digits: max(digits, minDigits)) } - private static func decorated(string: String, symbol: String? = nil, signValue: Decimal? = nil) -> String { + private static func transformedShort(value: Decimal, maxDigits: Int = Int.max) -> (value: Decimal, digits: Int, suffix: ((String) -> String)?, tooSmall: Bool) { + var value = abs(value) + var suffix: ((String) -> String)? + let digits: Int + var tooSmall = false + + switch value { + case 0: + digits = 0 + + case 0 ..< 0.0000_0001: + digits = 8 + value = 0.0000_0001 + tooSmall = true + + case 0.0000_0001 ..< 1: + let zeroCount = fractionZeroCount(value: value, maxCount: 8) + digits = min(maxDigits, zeroCount + 4, 8) + + case 1 ..< 1.01: + digits = 4 + + case 1.01 ..< 1.1: + digits = 3 + + case 1.1 ..< 20: + digits = 2 + + case 20 ..< 200: + digits = 1 + + case 200 ..< 19999.5: + digits = 0 + + case 19999.5 ..< edge(6): + (digits, value) = digitsAndValue(value: value, basePow: 3) + suffix = { String(localized: "\($0)number.thousand") } + + case edge(6) ..< edge(9): + (digits, value) = digitsAndValue(value: value, basePow: 6) + suffix = { String(localized: "\($0)number.million") } + + case edge(9) ..< edge(12): + (digits, value) = digitsAndValue(value: value, basePow: 9) + suffix = { String(localized: "\($0)number.billion") } + + case edge(12) ..< edge(15): + (digits, value) = digitsAndValue(value: value, basePow: 12) + suffix = { String(localized: "\($0)number.trillion") } + + default: + (digits, value) = digitsAndValue(value: value, basePow: 15) + suffix = { String(localized: "\($0)number.quadrillion") } + } + + return (value: value, digits: digits, suffix: suffix, tooSmall: tooSmall) + } + + private static func edge(_ power: Int) -> Decimal { + pow(10, power) - (pow(10, power - 3) / 2) + } + + private static func decorated(string: String, suffix: ((String) -> String)? = nil, symbol: String? = nil, signValue: Decimal? = nil, tooSmall: Bool = false) -> String { var string = string + if let suffix { + string = suffix(string) + } + if let symbol { string = "\(string) \(symbol)" } @@ -77,10 +155,14 @@ enum ValueFormatter { string = "\(sign)\(string)" } + if tooSmall { + string = "< \(string)" + } + return string } - private static func formattedCurrency(value: Decimal, digits: Int, code: String, symbol: String) -> String? { + private static func formattedCurrency(value: Decimal, digits: Int, code: String, symbol: String, suffix: ((String) -> String)? = nil) -> String? { currencyFormatter.currencyCode = code currencyFormatter.currencySymbol = symbol currencyFormatter.internationalCurrencySymbol = symbol @@ -95,7 +177,7 @@ enum ValueFormatter { return nil } - return pattern.replacingOccurrences(of: "1", with: decorated(string: string)) + return pattern.replacingOccurrences(of: "1", with: decorated(string: string, suffix: suffix)) } static func format(percentValue: Decimal, showSign: Bool = true) -> String? { @@ -119,4 +201,14 @@ enum ValueFormatter { return decorated(string: string, signValue: showSign ? value : nil) } + + static func formatShort(currency: Currency, value: Decimal, showSign: Bool = false) -> String? { + let (transformedValue, digits, suffix, tooSmall) = transformedShort(value: value) + + guard let string = formattedCurrency(value: transformedValue, digits: digits, code: currency.code, symbol: currency.symbol, suffix: suffix) else { + return nil + } + + return decorated(string: string, signValue: showSign ? value : nil, tooSmall: tooSmall) + } } diff --git a/UnstoppableWallet/Widget/Misc/WidgetConfig.swift b/UnstoppableWallet/Widget/Misc/WidgetConfig.swift index c2cca92370..d4f77dbc64 100644 --- a/UnstoppableWallet/Widget/Misc/WidgetConfig.swift +++ b/UnstoppableWallet/Widget/Misc/WidgetConfig.swift @@ -2,14 +2,6 @@ import Foundation import UIKit enum WidgetConfig { - static var appVersion: String { - Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String - } - - static var appId: String? { - UIDevice.current.identifierForVendor?.uuidString - } - static var marketApiUrl: String { (Bundle.main.object(forInfoDictionaryKey: "MarketApiUrl") as? String) ?? "" } diff --git a/UnstoppableWallet/Widget/SingleCoinPrice/SingleCoinPriceProvider.swift b/UnstoppableWallet/Widget/SingleCoinPrice/SingleCoinPriceProvider.swift index 8681a32d92..b4e64c538c 100644 --- a/UnstoppableWallet/Widget/SingleCoinPrice/SingleCoinPriceProvider.swift +++ b/UnstoppableWallet/Widget/SingleCoinPrice/SingleCoinPriceProvider.swift @@ -65,8 +65,8 @@ struct SingleCoinPriceProvider: IntentTimelineProvider { icon: coin.image, code: coin.code, price: coin.formattedPrice(currency: currency), - priceChange: coin.formattedPriceChange, - priceChangeType: coin.priceChangeType, + priceChange: coin.formattedPriceChange(), + priceChangeType: coin.priceChangeType(), chartPoints: chartPoints ) } diff --git a/UnstoppableWallet/Widget/TopCoinsWidget.swift b/UnstoppableWallet/Widget/TopCoinsWidget.swift index d20ffd4ae1..cdd531246b 100644 --- a/UnstoppableWallet/Widget/TopCoinsWidget.swift +++ b/UnstoppableWallet/Widget/TopCoinsWidget.swift @@ -7,13 +7,13 @@ struct TopCoinsWidget: Widget { IntentConfiguration( kind: AppWidgetConstants.topCoinsWidgetKind, intent: CoinPriceListIntent.self, - provider: CoinPriceListProvider(mode: .topCoins) + provider: CoinPriceListProvider() ) { entry in if #available(iOS 17.0, *) { - CoinPriceListView(entry: entry) + view(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } else { - CoinPriceListView(entry: entry) + view(entry: entry) .background() } } @@ -26,4 +26,17 @@ struct TopCoinsWidget: Widget { .systemLarge, ]) } + + @ViewBuilder private func view(entry: CoinPriceListEntry) -> some View { + CoinListView(items: entry.items, maxItemCount: entry.maxItemCount, title: "top_coins.title", subtitle: title(sortType: entry.sortType)) + } + + private func title(sortType: SortType) -> LocalizedStringKey { + switch sortType { + case .highestCap, .unknown: return "sort_type.highest_cap" + case .lowestCap: return "sort_type.lowest_cap" + case .gainers: return "sort_type.gainers" + case .losers: return "sort_type.losers" + } + } } diff --git a/UnstoppableWallet/Widget/Watchlist/WatchlistEntry.swift b/UnstoppableWallet/Widget/Watchlist/WatchlistEntry.swift new file mode 100644 index 0000000000..198417fade --- /dev/null +++ b/UnstoppableWallet/Widget/Watchlist/WatchlistEntry.swift @@ -0,0 +1,10 @@ +import Foundation +import SwiftUI +import WidgetKit + +struct WatchlistEntry: TimelineEntry { + let date: Date + let sortBy: WatchlistSortBy + let maxItemCount: Int + let items: [CoinItem] +} diff --git a/UnstoppableWallet/Widget/Watchlist/WatchlistProvider.swift b/UnstoppableWallet/Widget/Watchlist/WatchlistProvider.swift new file mode 100644 index 0000000000..3a4bc2aa13 --- /dev/null +++ b/UnstoppableWallet/Widget/Watchlist/WatchlistProvider.swift @@ -0,0 +1,106 @@ +import Foundation +import SwiftUI +import WidgetKit + +struct WatchlistProvider: TimelineProvider { + func placeholder(in context: Context) -> WatchlistEntry { + let count: Int + + switch context.family { + case .systemSmall, .systemMedium: count = 3 + default: count = 6 + } + + return WatchlistEntry( + date: Date(), + sortBy: .gainers, + maxItemCount: count, + items: (1 ... count).map { CoinItem.stub(index: $0) } + ) + } + + func getSnapshot(in context: Context, completion: @escaping (WatchlistEntry) -> Void) { + Task { + let entry = try await fetch(family: context.family) + completion(entry) + } + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + Task { + let entry = try await fetch(family: context.family) + + if let nextUpdate = Calendar.current.date(byAdding: DateComponents(minute: 15), to: Date()) { + let timeline = Timeline(entries: [entry], policy: .after(nextUpdate)) + completion(timeline) + } + } + } + + private func fetch(family: WidgetFamily) async throws -> WatchlistEntry { + let storage = SharedLocalStorage() + let watchlistManager = WatchlistManager(storage: storage) + let currency = CurrencyManager(storage: storage).baseCurrency + let apiProvider = ApiProvider() + + let listType: ApiProvider.ListType + let listOrder: ApiProvider.ListOrder + let limit: Int + + switch watchlistManager.sortBy { + case .highestCap, .lowestCap: listType = .mcap + case .gainers, .losers, .manual: + switch watchlistManager.timePeriod { + case .day1: listType = .priceChange24h + case .week1: listType = .priceChange1w + case .month1: listType = .priceChange1m + case .month3: listType = .priceChange3m + } + } + + switch watchlistManager.sortBy { + case .highestCap, .gainers, .manual: listOrder = .desc + case .lowestCap, .losers: listOrder = .asc + } + + switch family { + case .systemSmall, .systemMedium: limit = 3 + default: limit = 6 + } + + let coinUids = watchlistManager.coinUids + + let coins: [Coin] + + if !coinUids.isEmpty { + let apiCoins = try await apiProvider.listCoins(uids: coinUids, type: listType, order: listOrder, limit: limit, currencyCode: currency.code) + + switch watchlistManager.sortBy { + case .manual: + coins = coinUids.compactMap { uid in apiCoins.first { $0.uid == uid } } + default: + coins = apiCoins + } + } else { + coins = [] + } + + return WatchlistEntry( + date: Date(), + sortBy: watchlistManager.sortBy, + maxItemCount: limit, + items: coins.map { coin in + CoinItem( + uid: coin.uid, + icon: coin.image, + code: coin.code, + marketCap: coin.marketCap.flatMap { ValueFormatter.formatShort(currency: currency, value: $0) }, + rank: coin.rank.map { "\($0)" }, + price: coin.formattedPrice(currency: currency), + priceChange: coin.formattedPriceChange(timePeriod: watchlistManager.timePeriod), + priceChangeType: coin.priceChangeType(timePeriod: watchlistManager.timePeriod) + ) + } + ) + } +} diff --git a/UnstoppableWallet/Widget/WatchlistWidget.swift b/UnstoppableWallet/Widget/WatchlistWidget.swift index 23c73a4d7b..2dbb96f5f7 100644 --- a/UnstoppableWallet/Widget/WatchlistWidget.swift +++ b/UnstoppableWallet/Widget/WatchlistWidget.swift @@ -4,16 +4,15 @@ import WidgetKit struct WatchlistWidget: Widget { var body: some WidgetConfiguration { - IntentConfiguration( + StaticConfiguration( kind: AppWidgetConstants.watchlistWidgetKind, - intent: CoinPriceListIntent.self, - provider: CoinPriceListProvider(mode: .watchlist) + provider: WatchlistProvider() ) { entry in if #available(iOS 17.0, *) { - CoinPriceListView(entry: entry) + view(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } else { - CoinPriceListView(entry: entry) + view(entry: entry) .background() } } @@ -26,4 +25,18 @@ struct WatchlistWidget: Widget { .systemLarge, ]) } + + @ViewBuilder private func view(entry: WatchlistEntry) -> some View { + CoinListView(items: entry.items, maxItemCount: entry.maxItemCount, title: "watchlist.title", subtitle: title(sortBy: entry.sortBy)) + } + + private func title(sortBy: WatchlistSortBy) -> LocalizedStringKey { + switch sortBy { + case .manual: return "sort_type.manual" + case .highestCap: return "sort_type.highest_cap" + case .lowestCap: return "sort_type.lowest_cap" + case .gainers: return "sort_type.gainers" + case .losers: return "sort_type.losers" + } + } } diff --git a/UnstoppableWallet/Widget/de.lproj/AppWidget.strings b/UnstoppableWallet/Widget/de.lproj/AppWidget.strings index be5a18cd9c..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/de.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/de.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Ausgewählte Münze"; -"8vJheC" = "Sortieren nach"; -"qHGyuo" = "Höchste Obergrenze"; -"tVjZ7W" = "Niedrigste Obergrenze"; -"t6AKQ5" = "Höchste Lautstärke"; -"C1LmDj" = "Geringste Lautstärke"; -"Rl3a0T" = "Top- Gewinner"; -"x1kK4T" = "Top Verlierer"; - -"2puq80" = "Münzpreisliste"; -"Iodq3O-C1LmDj" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du \"geringstes Handelsvolumen\"?"; -"Iodq3O-Rl3a0T" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du 'Top-Gewinner'?"; -"Iodq3O-qHGyuo" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du 'Höchste Kapitalisierung'?"; -"Iodq3O-t6AKQ5" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du 'Höchstes Volumen'?"; -"Iodq3O-tVjZ7W" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du 'Niedrigste Kapitalisierung'?"; -"Iodq3O-x1kK4T" = "Um sicherzustellen, dass ich das richtig verstehe, meintest du 'Top-Verlierer'?"; -"OkcYOj" = "Anzeigen des Münzpreises für ausgewählte Münze"; -"VXTxGF" = "Widget Münze"; -"ZBHf6A" = "Sortiertyp"; -"hrLfmt" = "Einzelpreis der Münze"; -"nj8Co9-C1LmDj" = "Es gibt ${count} Optionen, die 'Niedrigstes Volumen' entsprechen."; -"nj8Co9-Rl3a0T" = "Es gibt ${count} Optionen, die 'Top-Gewinner' entsprechen."; -"nj8Co9-qHGyuo" = "Es gibt ${count} Optionen, die 'Höchste Kapitalisierung' entsprechen."; -"nj8Co9-t6AKQ5" = "Es gibt ${count} Optionen, die 'Höchstes Volumen' entsprechen."; -"nj8Co9-tVjZ7W" = "Es gibt ${count} Optionen, die 'Niedrigste Kapitalisierung' entsprechen."; -"nj8Co9-x1kK4T" = "Es gibt ${count} Optionen, die 'Top-Verlierer' entsprechen."; -"rCV9fg" = "Sehen Sie sich die Münzpreise für die Münzliste an"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/en.lproj/AppWidget.strings b/UnstoppableWallet/Widget/en.lproj/AppWidget.strings index fcad31dce1..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/en.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/en.lproj/AppWidget.strings @@ -1,27 +1,40 @@ +"2puq80" = "Coin Price List"; + "6ebp42" = "Selected Coin"; + "8vJheC" = "Sort By"; -"qHGyuo" = "Highest Cap"; -"tVjZ7W" = "Lowest Cap"; -"t6AKQ5" = "Highest Volume"; -"C1LmDj" = "Lowest Volume"; -"Rl3a0T" = "Top Gainers"; -"x1kK4T" = "Top Losers"; -"2puq80" = "Coin Price List"; -"Iodq3O-C1LmDj" = "Just to confirm, you wanted ‘Lowest Volume’?"; -"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Top Gainers’?"; +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + "Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; -"Iodq3O-t6AKQ5" = "Just to confirm, you wanted ‘Highest Volume’?"; + "Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; -"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Top Losers’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + "OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + "VXTxGF" = "Widget Coin"; + "ZBHf6A" = "Sort Type"; + "hrLfmt" = "Single Coin Price"; -"nj8Co9-C1LmDj" = "There are ${count} options matching ‘Lowest Volume’."; -"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Top Gainers’."; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + "nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; -"nj8Co9-t6AKQ5" = "There are ${count} options matching ‘Highest Volume’."; + "nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; -"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Top Losers’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + "rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/es.lproj/AppWidget.strings b/UnstoppableWallet/Widget/es.lproj/AppWidget.strings index 1831c535af..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/es.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/es.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Moneda seleccionada"; -"8vJheC" = "Ordenar por"; -"qHGyuo" = "Capitalización Mayor"; -"tVjZ7W" = "Capitalización Menor"; -"t6AKQ5" = "Mayor Volumen"; -"C1LmDj" = "Menor Volumen"; -"Rl3a0T" = "Máximos Ganadores"; -"x1kK4T" = "Máximos Perdedores"; - -"2puq80" = "Lista de precios de criptomonedas"; -"Iodq3O-C1LmDj" = "Para confirmar, ¿quieres \"Menor Volumen\"?"; -"Iodq3O-Rl3a0T" = "Para confirmar, ¿quieres 'Principales Ganadores'?"; -"Iodq3O-qHGyuo" = "Para confirmar, ¿querías 'Capitalización Mayor'?"; -"Iodq3O-t6AKQ5" = "Para confirmar, ¿querías 'Mayor Volumen'?"; -"Iodq3O-tVjZ7W" = "Para confirmar, ¿querías 'Capitalización Menor?"; -"Iodq3O-x1kK4T" = "Para confirmar, ¿querías 'Máximos Perdedores'?"; -"OkcYOj" = "Ver el precio de la moneda seleccionada"; -"VXTxGF" = "Widget de moneda"; -"ZBHf6A" = "Tipo de ordenamiento"; -"hrLfmt" = "Precio de una sola moneda"; -"nj8Co9-C1LmDj" = "Hay ${count} opciones que coinciden con 'Menor Volumen'."; -"nj8Co9-Rl3a0T" = "Hay ${count} opciones que coinciden con 'Principales Ganadores'."; -"nj8Co9-qHGyuo" = "Hay ${count} opciones que coinciden con 'Capitalización Mayor'."; -"nj8Co9-t6AKQ5" = "Hay ${count} opciones que coinciden con 'Mayor Volumen'."; -"nj8Co9-tVjZ7W" = "Hay ${count} opciones que coinciden con 'Capitalización Menor'."; -"nj8Co9-x1kK4T" = "Hay ${count} opciones que coinciden con 'Máximos Perdedores'."; -"rCV9fg" = "Ver precios de las monedas en la lista de monedas"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/fr.lproj/AppWidget.strings b/UnstoppableWallet/Widget/fr.lproj/AppWidget.strings index 3cdd05a962..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/fr.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/fr.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Pièce sélectionnée"; -"8vJheC" = "Trier par"; -"qHGyuo" = "Cap. marché la plus forte"; -"tVjZ7W" = "Cap. marché la plus basse"; -"t6AKQ5" = "Volume le plus élevé"; -"C1LmDj" = "Volume le plus bas"; -"Rl3a0T" = "Les grands gagnants"; -"x1kK4T" = "Les gros perdants"; - -"2puq80" = "Liste des prix des cryptomonnaies"; -"Iodq3O-C1LmDj" = "Pour confirmer, vous vouliez 'Volume le plus bas'?"; -"Iodq3O-Rl3a0T" = "Juste pour confirmer, vous vouliez 'Top Gainers'?"; -"Iodq3O-qHGyuo" = "Juste pour confirmer, vous vouliez 'Capitalisation la plus élevée'?"; -"Iodq3O-t6AKQ5" = "Juste pour confirmer, vous vouliez 'Volume le plus élevé'?"; -"Iodq3O-tVjZ7W" = "Juste pour confirmer, vous vouliez 'Capitalisation la plus faible'?"; -"Iodq3O-x1kK4T" = "Juste pour confirmer, vous vouliez 'Les plus grands perdants'?"; -"OkcYOj" = "Afficher le prix de la cryptomonnaie sélectionnée"; -"VXTxGF" = "Widget de la cryptomonnaie"; -"ZBHf6A" = "Type de tri"; -"hrLfmt" = "Prix d'une seule cryptomonnaie"; -"nj8Co9-C1LmDj" = "Il y a ${count} options correspondant à 'Volume le plus bas'."; -"nj8Co9-Rl3a0T" = "Il y a ${count} options correspondant à 'Top Gainers'."; -"nj8Co9-qHGyuo" = "Il y a ${count} options correspondant à 'Capitalisation la plus élevée'."; -"nj8Co9-t6AKQ5" = "Il y a ${count} options correspondant à 'Volume le plus élevé'."; -"nj8Co9-tVjZ7W" = "Il y a ${count} options correspondant à 'Capitalisation la plus faible'."; -"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Top Losers’."; -"rCV9fg" = "Voir les prix des cryptomonnaies de la liste"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/ko.lproj/AppWidget.strings b/UnstoppableWallet/Widget/ko.lproj/AppWidget.strings index 288d607d2a..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/ko.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/ko.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "선택한 코인"; -"8vJheC" = "정렬 기준"; -"qHGyuo" = "가장 높은 시가총액"; -"tVjZ7W" = "가장 낮은 시가총액"; -"t6AKQ5" = "가장 높은 거래량"; -"C1LmDj" = "가장 낮은 거래량"; -"Rl3a0T" = "최고 승자"; -"x1kK4T" = "최고 패자"; - -"2puq80" = "동전 가격표"; -"Iodq3O-C1LmDj" = "확인용으로, '최저 거래량'을 원하셨나요?"; -"Iodq3O-Rl3a0T" = "확인용으로, '최고 승자'을 원하셨나요?"; -"Iodq3O-qHGyuo" = "확인용으로, '가장 높은 시가총액'을 원하셨나요?"; -"Iodq3O-t6AKQ5" = "확인용으로, '가장 높은 거래량'을 원하셨나요?"; -"Iodq3O-tVjZ7W" = "확인용으로, '가장 낮은 시가총액'을 원하셨나요?"; -"Iodq3O-x1kK4T" = "확인용으로, '최고 패자'을 원하셨나요?"; -"OkcYOj" = "선택한 코인의 가격 보기"; -"VXTxGF" = "위젯 코인"; -"ZBHf6A" = "정렬 유형"; -"hrLfmt" = "단일 코인 가격"; -"nj8Co9-C1LmDj" = "'최저 거래량'과 일치하는 옵션은 ${count} 개 있습니다."; -"nj8Co9-Rl3a0T" = "'최고 승자'과 일치하는 옵션은 ${count} 개 있습니다."; -"nj8Co9-qHGyuo" = "'가장 높은 시가총액' 과 일치하는 옵션은 ${count} 개 있습니다."; -"nj8Co9-t6AKQ5" = "'가장 높은 거래량'과 일치하는 옵션은 ${count} 개 있습니다."; -"nj8Co9-tVjZ7W" = "'가장 낮은 시가총액'과 일치하는 옵션은 ${count} 개 있습니다."; -"nj8Co9-x1kK4T" = "'최고 패자'과 일치하는 옵션은 ${count} 개 있습니다."; -"rCV9fg" = "코인 목록의 가격을 확인하세요"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/pt-BR.lproj/AppWidget.strings b/UnstoppableWallet/Widget/pt-BR.lproj/AppWidget.strings index 6901ca0f7a..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/pt-BR.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/pt-BR.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Selecionar moeda"; -"8vJheC" = "Organizar por"; -"qHGyuo" = "Maior capitalização"; -"tVjZ7W" = "Menor capitalização"; -"t6AKQ5" = "Maior volume"; -"C1LmDj" = "Menor volume"; -"Rl3a0T" = "Maiores Altas"; -"x1kK4T" = "Maiores Quedas"; - -"2puq80" = "Lista de Preços de Moedas"; -"Iodq3O-C1LmDj" = "Para confirmar, você queria 'Menor volume'?"; -"Iodq3O-Rl3a0T" = "Para confirmar, você queria 'Maiores Altas'?"; -"Iodq3O-qHGyuo" = "Para confirmar, você queria 'Maior capitalização'?"; -"Iodq3O-t6AKQ5" = "Para confirmar, você queria 'Maior volume'?"; -"Iodq3O-tVjZ7W" = "Para confirmar, você queria 'Menor capitalização'?"; -"Iodq3O-x1kK4T" = "Para confirmar, você queria 'Maiores Quedas'?"; -"OkcYOj" = "Ver el precio de la moneda seleccionada"; -"VXTxGF" = "Widget Moeda"; -"ZBHf6A" = "Tipo de Classificação"; -"hrLfmt" = "Preço de uma Única Moeda"; -"nj8Co9-C1LmDj" = "Há ${count} opções correspondentes a 'Menor volume'."; -"nj8Co9-Rl3a0T" = "Há ${count} opções correspondentes a 'Maiores Altas'."; -"nj8Co9-qHGyuo" = "Há ${count} opções correspondentes a 'Maior capitalização'."; -"nj8Co9-t6AKQ5" = "Há ${count} opções correspondentes a 'Maior volume'."; -"nj8Co9-tVjZ7W" = "Há ${count} opções correspondentes a 'Menor capitalização'."; -"nj8Co9-x1kK4T" = "Há ${count} opções correspondentes a 'Maiores Quedas'."; -"rCV9fg" = "Ver preços das moedas na lista de moedas"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/ru.lproj/AppWidget.strings b/UnstoppableWallet/Widget/ru.lproj/AppWidget.strings index 5bbd0ea01f..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/ru.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/ru.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Выбранный токен"; -"8vJheC" = "Сортировать"; -"qHGyuo" = "Наивысшая кап."; -"tVjZ7W" = "Наименьшая кап."; -"t6AKQ5" = "Наивысший объем"; -"C1LmDj" = "Наименьший объем"; -"Rl3a0T" = "Показывают рост"; -"x1kK4T" = "Теряют в цене"; - -"2puq80" = "Список цен на токены"; -"Iodq3O-C1LmDj" = "Для подтверждения, вы хотели 'Наименьший объем'?"; -"Iodq3O-Rl3a0T" = "Для подтверждения, вы хотели список \"Показывают рост\"?"; -"Iodq3O-qHGyuo" = "Для подтверждения, вы хотели список \"Наивысший капитал\"?"; -"Iodq3O-t6AKQ5" = "Для подтверждения, вы хотели список \"Наивысший объем\"?"; -"Iodq3O-tVjZ7W" = "Для подтверждения, вы хотели список \"Наименьший капитал\"?"; -"Iodq3O-x1kK4T" = "Для подтверждения, вы хотели список \"Теряют в цене\"?"; -"OkcYOj" = "Просмотр цены для выбранного токена"; -"VXTxGF" = "Виджет токена"; -"ZBHf6A" = "Сортировка"; -"hrLfmt" = "Цена за один токен"; -"nj8Co9-C1LmDj" = "Существует ${count} вариантов, соответствующих запросу 'Наименьший объем'."; -"nj8Co9-Rl3a0T" = "Существует ${count} вариантов, соответствующих запросу 'Показывают рост'."; -"nj8Co9-qHGyuo" = "\"Существует ${count} вариантов, соответствующих запросу 'Наивысший капитал'."; -"nj8Co9-t6AKQ5" = "\"Существует ${count} вариантов, соответствующих запросу 'Наивысший объем'."; -"nj8Co9-tVjZ7W" = "Существует ${count} вариантов, соответствующих запросу 'Наименьший капитал'."; -"nj8Co9-x1kK4T" = "Существует ${count} вариантов, соответствующих запросу 'Теряют в цене'."; -"rCV9fg" = "Просмотреть цены токенов из списка"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/tr.lproj/AppWidget.strings b/UnstoppableWallet/Widget/tr.lproj/AppWidget.strings index d968f77023..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/tr.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/tr.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "Seçilen Koin"; -"8vJheC" = "Sırala"; -"qHGyuo" = "En Yüksek"; -"tVjZ7W" = "En Düşük"; -"t6AKQ5" = "En Yüksek HacIm"; -"C1LmDj" = "En Düşük HacIm"; -"Rl3a0T" = "En Kazançlılar"; -"x1kK4T" = "Çok Kaybedenler"; - -"2puq80" = "Para Birimi Fiyat Listesi"; -"Iodq3O-C1LmDj" = "Sadece teyit etmek için, ‘En Düşük Hacim’i mi istediniz?"; -"Iodq3O-Rl3a0T" = "Sadece teyit etmek için, 'En Çok Kazananlar'ı mı istediniz?"; -"Iodq3O-qHGyuo" = "Sadece teyit etmek için, 'En Yüksek Kap'ı mı istediniz?"; -"Iodq3O-t6AKQ5" = "Sadece teyit etmek için, 'En Yüksek Hacim'i mi istediniz?"; -"Iodq3O-tVjZ7W" = "Sadece doğrulamak için, 'En Düşük Kap'ı mı istiyordunuz?"; -"Iodq3O-x1kK4T" = "Sadece teyit etmek için, 'En Çok Kaybedenler'i mi istediniz?"; -"OkcYOj" = "Seçilen para birimi için fiyatı görüntüle"; -"VXTxGF" = "Widget Para Birimi"; -"ZBHf6A" = "Sıralama Türü"; -"hrLfmt" = "Tekil Para Birimi Fiyatı"; -"nj8Co9-C1LmDj" = "'En Düşük Hacim' ile eşleşen ${count} seçenek var."; -"nj8Co9-Rl3a0T" = "‘En Çok Kazananlar’ ile eşleşen ${count} seçenek var."; -"nj8Co9-qHGyuo" = "‘En Yüksek Kap’ ile eşleşen ${count} seçenek var."; -"nj8Co9-t6AKQ5" = "‘En Yüksek Hacim’ ile eşleşen ${count} seçenek var."; -"nj8Co9-tVjZ7W" = "‘En Düşük Kap’ ile eşleşen ${count} seçenek var."; -"nj8Co9-x1kK4T" = "‘En Çok Kaybedenler’ ile eşleşen ${count} seçenek var."; -"rCV9fg" = "Para listesi için para birimi fiyatlarını gör"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; + diff --git a/UnstoppableWallet/Widget/zh.lproj/AppWidget.strings b/UnstoppableWallet/Widget/zh.lproj/AppWidget.strings index 67298a5f19..aef320a2c8 100644 --- a/UnstoppableWallet/Widget/zh.lproj/AppWidget.strings +++ b/UnstoppableWallet/Widget/zh.lproj/AppWidget.strings @@ -1,27 +1,40 @@ -"6ebp42" = "已选硬币"; -"8vJheC" = "排序方式为"; -"qHGyuo" = "市值由高到低"; -"tVjZ7W" = "市值由低到高"; -"t6AKQ5" = "成交量由高到低"; -"C1LmDj" = "成交量由低到高"; -"Rl3a0T" = "涨幅榜"; -"x1kK4T" = "跌幅榜"; - -"2puq80" = "数字货币价格列表"; -"Iodq3O-C1LmDj" = "只是确认一下,你想要'最低交易量'对吗?"; -"Iodq3O-Rl3a0T" = "只是确认一下,您想要‘涨幅最大’对吗?"; -"Iodq3O-qHGyuo" = "只是确认一下,您想要‘最高市值’对吗?"; -"Iodq3O-t6AKQ5" = "只是确认一下,您想要‘最高交易量’对吗?"; -"Iodq3O-tVjZ7W" = "只是确认一下,您想要‘最低市值’对吗?"; -"Iodq3O-x1kK4T" = "只是确认一下,您想要‘最大跌幅’对吗?"; -"OkcYOj" = "查看所选币种的价格"; -"VXTxGF" = "小部件币"; -"ZBHf6A" = "排序类型"; -"hrLfmt" = "单个币种价格"; -"nj8Co9-C1LmDj" = "有 ${count} 个选项符合“最低成交量”。"; -"nj8Co9-Rl3a0T" = "有 ${count} 个选项符合“涨幅最高”。"; -"nj8Co9-qHGyuo" = "有 ${count} 个选项符合“最高市值”。"; -"nj8Co9-t6AKQ5" = "有 ${count} 个选项符合“最高成交量”。"; -"nj8Co9-tVjZ7W" = "有 ${count} 个选项符合“市值最低”。"; -"nj8Co9-x1kK4T" = "有 ${count} 个选项符合“跌幅最大”。"; -"rCV9fg" = "查看币种列表的币价。"; +"2puq80" = "Coin Price List"; + +"6ebp42" = "Selected Coin"; + +"8vJheC" = "Sort By"; + +"Iodq3O-Rl3a0T" = "Just to confirm, you wanted ‘Gainers’?"; + +"Iodq3O-qHGyuo" = "Just to confirm, you wanted ‘Highest Cap’?"; + +"Iodq3O-tVjZ7W" = "Just to confirm, you wanted ‘Lowest Cap’?"; + +"Iodq3O-x1kK4T" = "Just to confirm, you wanted ‘Losers’?"; + +"OkcYOj" = "View coin price for selected coin"; + +"Rl3a0T" = "Gainers"; + +"VXTxGF" = "Widget Coin"; + +"ZBHf6A" = "Sort Type"; + +"hrLfmt" = "Single Coin Price"; + +"nj8Co9-Rl3a0T" = "There are ${count} options matching ‘Gainers’."; + +"nj8Co9-qHGyuo" = "There are ${count} options matching ‘Highest Cap’."; + +"nj8Co9-tVjZ7W" = "There are ${count} options matching ‘Lowest Cap’."; + +"nj8Co9-x1kK4T" = "There are ${count} options matching ‘Losers’."; + +"qHGyuo" = "Highest Cap"; + +"rCV9fg" = "See coin prices for coin list"; + +"tVjZ7W" = "Lowest Cap"; + +"x1kK4T" = "Losers"; +