diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 3a29e9083b..d169e49a23 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -1912,6 +1912,8 @@ 6BE8A07F2ADE2F950012DE7F /* CurrencyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE8A07D2ADE2F950012DE7F /* CurrencyValue.swift */; }; 6BE8A0812ADE2FAB0012DE7F /* CurrencyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE8A0802ADE2FAB0012DE7F /* CurrencyManager.swift */; }; 6BE8A0822ADE2FAB0012DE7F /* CurrencyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE8A0802ADE2FAB0012DE7F /* CurrencyManager.swift */; }; + 6BFA4E032D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFA4E022D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift */; }; + 6BFA4E042D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFA4E022D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift */; }; ABC9A001F335B695CD066218 /* NftAssetModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD35D41AEEBD38AA08B5 /* NftAssetModule.swift */; }; ABC9A005F31836B4EBAB1C97 /* DonateDescriptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD0848221B0EC25C37F3 /* DonateDescriptionCell.swift */; }; ABC9A0073333D3DEC2797D15 /* BackupCloudPassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A8E4CDD143171A1F9C46 /* BackupCloudPassphraseViewController.swift */; }; @@ -4219,6 +4221,7 @@ 6BE8A07A2ADE2F8D0012DE7F /* Currency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; 6BE8A07D2ADE2F950012DE7F /* CurrencyValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyValue.swift; sourceTree = ""; }; 6BE8A0802ADE2FAB0012DE7F /* CurrencyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyManager.swift; sourceTree = ""; }; + 6BFA4E022D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketAdvancedSearchCategoriesView.swift; sourceTree = ""; }; ABC9A021D71EDD24DFB6BA62 /* CoinProChartModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinProChartModule.swift; sourceTree = ""; }; ABC9A02C808FCE6082E3F97A /* DebounceTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebounceTextField.swift; sourceTree = ""; }; ABC9A03401172C4C65D66764 /* SingleLineFormTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineFormTextView.swift; sourceTree = ""; }; @@ -8896,6 +8899,7 @@ D389BC4B2C0DDCF500724504 /* MarketAdvancedSearchBlockchainsView.swift */, D389BC4E2C0DEF1800724504 /* MarketAdvancedSearchResultsView.swift */, D389BC512C0DEF2200724504 /* MarketAdvancedSearchResultsViewModel.swift */, + 6BFA4E022D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift */, ); path = AdvancedSearch; sourceTree = ""; @@ -9688,6 +9692,7 @@ 11B356476D5E88F21C297B52 /* ManageAccountViewController.swift in Sources */, 11B350860CB79E9C5F032166 /* ManageAccountViewModel.swift in Sources */, 11B3563E71C4AC16DFE8AB76 /* ActiveAccount.swift in Sources */, + 6BFA4E042D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift in Sources */, 6BCD530D2A161F4100993F20 /* ICloudBackupNameViewModel.swift in Sources */, D3833AF92BF2181800ACECFB /* MarketPair.swift in Sources */, 11B35056B69A06C8CFF3CBB6 /* BackupModule.swift in Sources */, @@ -11178,6 +11183,7 @@ 11B359F4651EA254E5B0AD00 /* ManageAccountViewController.swift in Sources */, 11B354D8DCBDAA82A6C51205 /* ManageAccountViewModel.swift in Sources */, 11B356C6E9FC6594917B3FF6 /* ActiveAccount.swift in Sources */, + 6BFA4E032D26B58A0044567A /* MarketAdvancedSearchCategoriesView.swift in Sources */, D3833AF82BF2181800ACECFB /* MarketPair.swift in Sources */, 6BE8A0812ADE2FAB0012DE7F /* CurrencyManager.swift in Sources */, D349031A2BE8DF5F005F147B /* BinancePreSendHandler.swift in Sources */, @@ -13239,7 +13245,7 @@ repositoryURL = "https://github.com/horizontalsystems/MarketKit.Swift"; requirement = { kind = exactVersion; - version = 3.0.13; + version = 3.0.14; }; }; D3604E7D28F03C1D0066C366 /* XCRemoteSwiftPackageReference "Chart" */ = { diff --git a/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift b/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift index a7e48b46ce..80392e46c1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift +++ b/UnstoppableWallet/UnstoppableWallet/Extensions/StatExtensions.swift @@ -125,11 +125,12 @@ extension MarketModule.Top { switch self { case .top100: return .top100 case .top200: return .top200 - case .top250: return .top250 case .top300: return .top300 case .top500: return .top500 case .top1000: return .top1000 case .top1500: return .top1500 + case .top2000: return .top2000 + case .top2500: return .top2500 } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift b/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift index d4549da5b9..f2734ae9c7 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/Stats.swift @@ -460,11 +460,12 @@ enum StatField: String { enum StatMarketTop: String { case top100 case top200 - case top250 case top300 case top500 case top1000 case top1500 + case top2000 + case top2500 } enum StatEntity: String { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/CoinAnalyticsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/CoinAnalyticsViewModel.swift index bf2c6d5750..00c9de8c64 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/CoinAnalyticsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Coin/Analytics/CoinAnalyticsViewModel.swift @@ -5,14 +5,18 @@ import HsExtensions import MarketKit class CoinAnalyticsViewModel: ObservableObject { + private var cancellables = Set() + let coin: Coin private let marketKit = App.shared.marketKit private let currencyManager = App.shared.currencyManager + private let purchaseManager = App.shared.purchaseManager private var tasks = Set() + @Published private(set) var premiumEnabled: Bool = false @Published private(set) var state: State = .loading - private let isPurchased = true + private let isPurchased = false private let ratioFormatter: NumberFormatter = { let formatter = NumberFormatter() @@ -34,6 +38,13 @@ class CoinAnalyticsViewModel: ObservableObject { init(coin: Coin) { self.coin = coin + premiumEnabled = purchaseManager.subscription != nil + purchaseManager.$subscription + .receive(on: DispatchQueue.main) + .sink { [weak self] subscription in + self?.premiumEnabled = subscription != nil + } + .store(in: &cancellables) } private func handle(analytics: Analytics) async { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchBlockchainsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchBlockchainsView.swift index f431132e95..4ee3a8901c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchBlockchainsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchBlockchainsView.swift @@ -7,54 +7,60 @@ struct MarketAdvancedSearchBlockchainsView: View { @Binding var isPresented: Bool var body: some View { - ThemeNavigationView { - ScrollableThemeView { - VStack(spacing: .margin24) { - ListSection { - ClickableRow { - viewModel.blockchains = Set() - } content: { - Text("selector.any".localized).themeBody(color: .themeGray) - - if viewModel.blockchains.isEmpty { - Image("check_1_20").themeIcon(color: .themeJacob) - } - } - - ForEach(viewModel.allBlockchains) { blockchain in + VStack(spacing: 0) { + HStack(spacing: .margin16) { + Image("circle_portfolio_24").themeIcon(color: .themeJacob) + Text("market.advanced_search.blockchains".localized).themeHeadline2() + Button(action: { isPresented = false }) { Image("close_3_24").themeIcon() } + } + .padding(.horizontal, .margin32) + .padding(.vertical, .margin24) + + BottomGradientWrapper(backgroundColor: .themeLawrence) { + ScrollView { + VStack(spacing: .margin24) { + ListSection { ClickableRow { - if viewModel.blockchains.contains(blockchain) { - viewModel.blockchains.remove(blockchain) - } else { - viewModel.blockchains.insert(blockchain) - } + viewModel.blockchains = Set() } content: { - KFImage.url(URL(string: blockchain.type.imageUrl)) - .resizable() - .placeholder { RoundedRectangle(cornerRadius: .cornerRadius8).fill(Color.themeSteel20) } - .clipShape(RoundedRectangle(cornerRadius: .cornerRadius8)) - .frame(width: .iconSize32, height: .iconSize32) + Text("selector.any".localized).themeBody(color: .themeGray) - Text(blockchain.name).themeBody() - - if viewModel.blockchains.contains(blockchain) { + if viewModel.blockchains.isEmpty { Image("check_1_20").themeIcon(color: .themeJacob) } } + + ForEach(viewModel.allBlockchains) { blockchain in + ClickableRow { + if viewModel.blockchains.contains(blockchain) { + viewModel.blockchains.remove(blockchain) + } else { + viewModel.blockchains.insert(blockchain) + } + } content: { + Text(blockchain.name).themeBody() + + if viewModel.blockchains.contains(blockchain) { + Image("check_1_20").themeIcon(color: .themeJacob) + } + } + } } } + .themeListStyle(.bordered) + .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } - .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) - } - .navigationTitle("market.advanced_search.blockchains".localized) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button("button.done".localized) { - isPresented = false - } + } bottomContent: { + Button(buttonTitle()) { + isPresented = false } - } - } + .buttonStyle(PrimaryButtonStyle(style: .yellow)) + } + } + .background(Color.themeLawrence) + } + + func buttonTitle() -> String { + viewModel.blockchains.count > 0 ? ["button.select".localized, viewModel.blockchains.count.description].joined(separator: " ") : "button.done".localized } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchCategoriesView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchCategoriesView.swift new file mode 100644 index 0000000000..3ddcd06314 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchCategoriesView.swift @@ -0,0 +1,80 @@ +import MarketKit +import SwiftUI + +struct MarketAdvancedSearchCategoriesView: View { + @ObservedObject var viewModel: MarketAdvancedSearchViewModel + @Binding var isPresented: Bool + + var body: some View { + VStack(spacing: 0) { + HStack(spacing: .margin16) { + Image("circle_portfolio_24").themeIcon(color: .themeJacob) + Text("market.advanced_search.categories".localized).themeHeadline2() + Button(action: { isPresented = false }) { Image("close_3_24").themeIcon() } + } + .padding(.horizontal, .margin32) + .padding(.vertical, .margin24) + + BottomGradientWrapper(backgroundColor: .themeLawrence) { + ScrollView { + VStack(spacing: .margin24) { + ListSection { + switch viewModel.allCategoriesState { + case .loading: EmptyView() + case .failed: EmptyView() + case let .loaded(categories): + ClickableRow { + viewModel.categories = .any + } content: { + Text("selector.any".localized).themeBody(color: .themeGray) + + if viewModel.categories == .any { + Image("check_1_20").themeIcon(color: .themeJacob) + } + } + + ForEach(categories) { category in + ClickableRow { + switch viewModel.categories { + case .any: viewModel.categories = .list([category.id]) + case var .list(array): + if let index = array.firstIndex(of: category.id) { + array.remove(at: index) + } else { + array.append(category.id) + } + viewModel.categories = array.isEmpty ? .any : .list(array) + } + } content: { + Text(category.name).themeBody() + + if viewModel.categories.include(id: category.id) { + Image("check_1_20").themeIcon(color: .themeJacob) + } + } + } + } + } + } + .themeListStyle(.bordered) + .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin24, trailing: .margin16)) + } + } bottomContent: { + Button(buttonTitle()) { + isPresented = false + } + .buttonStyle(PrimaryButtonStyle(style: .yellow)) + } + } + .background(Color.themeLawrence) + } + + func buttonTitle() -> String { + switch viewModel.categories { + case .any: return "button.done".localized + case let .list(categories): return ["button.select".localized, categories.count.description].joined(separator: " ") + } + } +} + +extension CoinCategory: Identifiable {} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift index 3e613ec910..e11ab0380c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift @@ -7,6 +7,7 @@ struct MarketAdvancedSearchView: View { @State var topPresented = false @State var volumePresented = false + @State var categoriesPresented = false @State var blockchainsPresented = false @State var signalsPresented = false @State var priceCloseToPresented = false @@ -29,6 +30,13 @@ struct MarketAdvancedSearchView: View { VStack(spacing: 0) { PremiumListSectionHeader() + ListSection { + categoriesRow() + } + .modifier(ColoredBorder()) + } + + VStack(spacing: 0) { ListSection { priceChangeRow() pricePeriodRow() @@ -141,7 +149,10 @@ struct MarketAdvancedSearchView: View { viewModel.top = top topPresented = false } content: { - Text(top.title).themeBody() + VStack(spacing: 1) { + Text(top.title).themeBody() + Text(top.description).themeSubhead2() + } if viewModel.top == top { Image("check_1_20").themeIcon(color: .themeJacob) @@ -194,6 +205,20 @@ struct MarketAdvancedSearchView: View { } } + @ViewBuilder private func categoriesRow() -> some View { + ClickableRow(spacing: .margin8) { + categoriesPresented = true + } content: { + Text("market.advanced_search.categories".localized).textBody() + Spacer() + Text(viewModel.categories.title).textSubhead1(color: color(categoriesFilter: viewModel.categories)) + Image("arrow_small_down_20").themeIcon() + } + .sheet(isPresented: $categoriesPresented) { + MarketAdvancedSearchCategoriesView(viewModel: viewModel, isPresented: $categoriesPresented) + } + } + @ViewBuilder private func premiumRow(_ view: some View) -> some View { if viewModel.premiumEnabled { ListRow { @@ -491,6 +516,13 @@ struct MarketAdvancedSearchView: View { } } + private func color(categoriesFilter: MarketAdvancedSearchViewModel.CategoryFilter) -> Color { + switch categoriesFilter { + case .any: return .themeGray + default: return .themeLeah + } + } + private func color(closeToFilter: MarketAdvancedSearchViewModel.PriceCloseToFilter) -> Color { switch closeToFilter { case .none: return .themeGray diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift index d8a5c974b6..c739d0d677 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift @@ -34,6 +34,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { private var cancellables = Set() private var tasks = Set() + private var categoriesTasks = Set() private var internalState: State = .loading { didSet { @@ -44,7 +45,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { @Published private(set) var state: State = .loading @Published private(set) var premiumEnabled: Bool = false - @Published var top: MarketModule.Top = .top250 { + @Published var top: MarketModule.Top = .default { didSet { guard top != oldValue else { return @@ -60,6 +61,13 @@ class MarketAdvancedSearchViewModel: ObservableObject { } } + @Published var allCategoriesState: CategoriesState = .loading + @Published var categories: CategoryFilter = .any { + didSet { + syncState() + } + } + @Published var listedOnTopExchanges = false { didSet { syncState() @@ -165,6 +173,29 @@ class MarketAdvancedSearchViewModel: ObservableObject { .store(in: &cancellables) syncMarketInfos() + syncCategories() + } + + private func syncCategories() { + categoriesTasks = Set() + + Task { [weak self, marketKit, currencyManager] in + await MainActor.run { [weak self] in + self?.allCategoriesState = .loading + } + + do { + let categories = try await marketKit.coinCategories(currencyCode: currencyManager.baseCurrency.code) + + await MainActor.run { [weak self] in + self?.allCategoriesState = .loaded(categories: categories) + } + } catch { + await MainActor.run { [weak self] in + self?.allCategoriesState = .failed(error: error) + } + } + }.store(in: &categoriesTasks) } private func syncState() { @@ -181,8 +212,9 @@ class MarketAdvancedSearchViewModel: ObservableObject { state = .failed(error: error) } - canReset = top != .top250 + canReset = top != .default || volume != .none + || categories != .any || listedOnTopExchanges != false || goodCexVolume != false || goodDexVolume != false @@ -203,6 +235,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { return inBounds(value: marketInfo.totalVolume, lower: volume.lowerBound, upper: volume.upperBound) && + inCategories(marketInfo: marketInfo) && (!listedOnTopExchanges || marketInfo.listedOnTopExchanges == true) && (!goodCexVolume || marketInfo.solidCex == true) && (!goodDexVolume || marketInfo.solidDex == true) && @@ -243,6 +276,14 @@ class MarketAdvancedSearchViewModel: ObservableObject { return false } + private func inCategories(marketInfo: MarketInfo) -> Bool { + switch categories { + case .any: return true + case let .list(array): return !Set(array).intersection(Set(marketInfo.categoryIds)).isEmpty + } + } + + private func filteredBySignal(marketInfo: MarketInfo) -> Bool { guard let signal else { return true @@ -296,7 +337,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { extension MarketAdvancedSearchViewModel { var tops: [MarketModule.Top] { - [.top100, .top250, .top500, .top1000, .top1500] + MarketModule.Top.allCases } var valueFilters: [ValueFilter] { @@ -351,8 +392,9 @@ extension MarketAdvancedSearchViewModel { func reset() { syncStateEnabled = false - top = .top250 + top = .default volume = .none + categories = .any listedOnTopExchanges = false goodDexVolume = false goodCexVolume = false @@ -378,6 +420,46 @@ extension MarketAdvancedSearchViewModel { case failed(error: Error) } + enum CategoriesState { + case loading + case loaded(categories: [CoinCategory]) + case failed(error: Error) + } + + enum CategoryFilter: Identifiable, Equatable { + case any + case list([Int]) + + var id: String { + switch self { + case .any: return "any" + case let .list(array): return array.sorted().map { $0.description }.joined(separator: "|") + } + } + + var title: String { + switch self { + case .any: return "selector.any".localized + case let .list(array): return array.count.description + } + } + + static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.any, .any): return true + case (.list, .list): return lhs.id == rhs.id + default: return false + } + } + + func `include`(id: Int) -> Bool { + if case let .list(array) = self { + return array.firstIndex(of: id) != nil + } + return false + } + } + enum ValueFilter: CaseIterable, Identifiable { case none case lessM5 diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift index d0a055916e..0d92397f39 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsViewModel.swift @@ -27,7 +27,7 @@ class MarketCoinsViewModel: ObservableObject { } } - var top: MarketModule.Top = .top100 { + var top: MarketModule.Top = .default { didSet { stat(page: .markets, event: .switchMarketTop(marketTop: top.statMarketTop)) syncState() diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift index a7ecf2543b..1e39576a81 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketCap/MarketMarketCapViewModel.swift @@ -68,7 +68,7 @@ extension MarketMarketCapViewModel { Task { [weak self, marketKit, currency] in do { - let marketInfos = try await marketKit.marketInfos(top: MarketModule.Top.top100.rawValue, currencyCode: currency.code) + let marketInfos = try await marketKit.marketInfos(top: MarketModule.Top.default.rawValue, currencyCode: currency.code) await MainActor.run { [weak self] in self?.internalState = .loaded(marketInfos: marketInfos) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift index 80957ce408..f1178110a9 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/MarketModule.swift @@ -37,17 +37,34 @@ enum MarketModule { } enum Top: Int, CaseIterable, Identifiable { + static let `default` = Top.top100 + case top100 = 100 case top200 = 200 - case top250 = 250 case top300 = 300 case top500 = 500 case top1000 = 1000 case top1500 = 1500 + case top2000 = 2000 + case top2500 = 2500 var title: String { "market.top_coins".localized("\(rawValue)") } + + var description: String { + let result = "market.advanced_search.top.m_cap".localized + " " + switch self { + case .top100: return result + "market.advanced_search.top.more_1_b".localized + case .top200: return result + "market.advanced_search.top.more_500_m".localized + case .top300: return result + "market.advanced_search.top.more_250_m".localized + case .top500: return result + "market.advanced_search.top.more_100_m".localized + case .top1000: return result + "market.advanced_search.top.more_25_m".localized + case .top1500: return result + "market.advanced_search.top.more_10_m".localized + case .top2000: return result + "market.advanced_search.top.more_5_m".localized + case .top2500: return result + "market.advanced_search.top.more_1_m".localized + } + } var id: Int { rawValue diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift index 5dbaaed754..3c5866038b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Volume/MarketVolumeViewModel.swift @@ -68,7 +68,7 @@ extension MarketVolumeViewModel { Task { [weak self, marketKit, currency] in do { - let marketInfos = try await marketKit.marketInfos(top: MarketModule.Top.top100.rawValue, currencyCode: currency.code) + let marketInfos = try await marketKit.marketInfos(top: MarketModule.Top.default.rawValue, currencyCode: currency.code) await MainActor.run { [weak self] in self?.internalState = .loaded(marketInfos: marketInfos) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Purchases/PurchasesViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Purchases/PurchasesViewModel.swift index bc17aaebb1..6a699e0192 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Purchases/PurchasesViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Purchases/PurchasesViewModel.swift @@ -6,7 +6,6 @@ class PurchasesViewModel: ObservableObject { .init(title: "token_insights", iconName: "circle_portfolio_24"), .init(title: "advanced_search", iconName: "search_discovery_24"), .init(title: "trade_signals", iconName: "bell_ring_24"), - .init(title: "favorable_swaps", iconName: "precent_24"), .init(title: "tx_speed_up", iconName: "outgoing_raw_24"), .init(title: "duress_mode", iconName: "switch_wallet_24"), .init(title: "address_phishing", iconName: "shield_24"), diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BottomGradientWrapper.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BottomGradientWrapper.swift index 3e8d734778..c2c4249ead 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BottomGradientWrapper.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/BottomGradientWrapper.swift @@ -1,9 +1,11 @@ import SwiftUI struct BottomGradientWrapper: View { + var backgroundColor: Color = .themeTyler + @ViewBuilder let content: Content @ViewBuilder let bottomContent: BottomContent - + var body: some View { VStack(spacing: 0) { ZStack { @@ -11,7 +13,7 @@ struct BottomGradientWrapper: View { VStack { Spacer() - LinearGradient(colors: [.themeTyler, Color.themeTyler.opacity(0)], startPoint: .bottom, endPoint: .top) + LinearGradient(colors: [backgroundColor, backgroundColor.opacity(0)], startPoint: .bottom, endPoint: .top) .frame(maxWidth: .infinity) .frame(height: .margin16) } diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index cd045fdd65..9ef7204774 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -29,6 +29,7 @@ "button.i_understand" = "I Understand"; "button.learn_more" = "Learn More"; "button.unlock" = "Unlock"; +"button.select" = "Select"; "alert" = "Alert"; "alert.copied" = "Copied"; @@ -833,6 +834,7 @@ "market.advanced_search.choose_set" = "Choose Set"; "market.advanced_search.market_cap" = "Market Cap"; "market.advanced_search.volume" = "Trading Volume (24h)"; +"market.advanced_search.categories" = "Sectors"; "market.advanced_search.listed_on_top_exchanges" = "Listed on Top Exchanges"; "market.advanced_search.good_cex_volume" = "Good CEX Volume"; @@ -880,6 +882,16 @@ "market.advanced_search.more_50_b" = "> 50B"; "market.advanced_search.more_500_b" = "> 500B"; +"market.advanced_search.top.m_cap" = "M.Cap"; +"market.advanced_search.top.more_1_b" = "> $1B"; +"market.advanced_search.top.more_500_m" = "> $500M"; +"market.advanced_search.top.more_250_m" = "> $250M"; +"market.advanced_search.top.more_100_m" = "> $100M"; +"market.advanced_search.top.more_25_m" = "> $25M"; +"market.advanced_search.top.more_10_m" = "> $10M"; +"market.advanced_search.top.more_5_m" = "> $5M"; +"market.advanced_search.top.more_1_m" = "> $1M"; + "market.advanced_search.day" = "1 Day"; "market.advanced_search.week" = "1 Week"; "market.advanced_search.week2" = "2 Weeks"; @@ -1312,7 +1324,7 @@ "settings.rate_us" = "Rate Us"; "settings.tell_friends" = "Tell Friends"; "settings.contact_us" = "Contact Us"; -"settings.social_networks.label" = "Be Unstoppable"; +"settings.social_networks.label" = "Join Unstoppables"; "settings.social_networks.footer" = "Learn and master crypto via exclusive videos. Get to know us informally. Be the first to see things we work on."; "settings.get_your_tokens" = "Get UWT Tokens"; @@ -2197,10 +2209,6 @@ "purchases.trade_signals.description" = "Signals for confident trading."; "purchases.trade_signals.info" = "Trade Signals provide real-time notifications about potential trading opportunities and market risks. Using advanced algorithms, this feature analyzes market conditions, trends, and indicators to highlight profitable opportunities or warn against potential losses. Ideal for active traders, it ensures users stay informed and ready to act in a fast-moving market."; -"purchases.favorable_swaps" = "Favorable Swaps"; -"purchases.favorable_swaps.description" = "Signals for confident trading."; -"purchases.favorable_swaps.info" = "This feature ensures optimal trade conditions by offering reduced fees and competitive exchange rates. By leveraging favorable liquidity pools and minimizing unnecessary charges, users can maximize their transaction value while saving on costs. It’s designed for those who prioritize efficiency and cost-effectiveness in their trades."; - "purchases.tx_speed_up" = "Tx Speed UP / Cancel "; "purchases.tx_speed_up.description" = "Speed up and cancel transactions."; "purchases.tx_speed_up.info" = "Tx Speed Up / Cancel provides full control over blockchain transactions. With this feature, users can accelerate pending transactions by increasing gas fees or cancel unconfirmed transactions altogether if needed. It adds a layer of flexibility and convenience to transaction management, ensuring users remain in control of their activity.";