diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index e03408eed7..193a8285dc 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -1844,6 +1844,8 @@ 6B9032C92D1D6FD400F3F5AC /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9032C72D1D6FD400F3F5AC /* PurchaseListView.swift */; }; 6B9032CB2D1D72F300F3F5AC /* PurchaseListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9032CA2D1D72F300F3F5AC /* PurchaseListViewModel.swift */; }; 6B9032CC2D1D72F300F3F5AC /* PurchaseListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9032CA2D1D72F300F3F5AC /* PurchaseListViewModel.swift */; }; + 6B9032CE2D2125E800F3F5AC /* PremiumListSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9032CD2D2125E800F3F5AC /* PremiumListSectionHeader.swift */; }; + 6B9032CF2D2126E800F3F5AC /* PremiumListSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9032CD2D2125E800F3F5AC /* PremiumListSectionHeader.swift */; }; 6BA5117D2BCFA06F00CB5A54 /* FirstAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */; }; 6BA5117E2BCFA06F00CB5A54 /* FirstAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */; }; 6BAAF3472B9B245C00EFE5B2 /* ShimmerEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAAF3442B9B245C00EFE5B2 /* ShimmerEffect.swift */; }; @@ -4170,6 +4172,7 @@ 6B8BD39D2C11B959003ADE10 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = ""; }; 6B9032C72D1D6FD400F3F5AC /* PurchaseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListView.swift; sourceTree = ""; }; 6B9032CA2D1D72F300F3F5AC /* PurchaseListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListViewModel.swift; sourceTree = ""; }; + 6B9032CD2D2125E800F3F5AC /* PremiumListSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumListSectionHeader.swift; sourceTree = ""; }; 6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAppearModifier.swift; sourceTree = ""; }; 6BAAF3442B9B245C00EFE5B2 /* ShimmerEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShimmerEffect.swift; sourceTree = ""; }; 6BAAF3452B9B245C00EFE5B2 /* SlideButtonStyling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlideButtonStyling.swift; sourceTree = ""; }; @@ -5372,6 +5375,7 @@ D3C5986D2C1315AF00789D69 /* AttributedStringView.swift */, D3C598762C16FE6400789D69 /* TappablePadding.swift */, 6B7EB8252D0D11E900783F66 /* RoundedCorner.swift */, + 6B9032CD2D2125E800F3F5AC /* PremiumListSectionHeader.swift */, ); path = SwiftUI; sourceTree = ""; @@ -10621,6 +10625,7 @@ 11B35076F57EA6103BBE3D63 /* WCSendEthereumTransactionRequestViewController.swift in Sources */, 11B35BD102629037A4348B3C /* WCSignEthereumTransactionRequestViewController.swift in Sources */, 11B353FF811AF49F4BC7BD43 /* CoinMarketsViewModel.swift in Sources */, + 6B9032CF2D2126E800F3F5AC /* PremiumListSectionHeader.swift in Sources */, D311DA232BD23C230013DB8F /* MarketAdvancedSearchView.swift in Sources */, 11B3507AB99A089727463C16 /* CoinMarketsView.swift in Sources */, 11B350473A42FF70E806AF96 /* ThemeList.swift in Sources */, @@ -12102,6 +12107,7 @@ 11B35B2B8B3093530144F877 /* WCSignEthereumTransactionRequestModule.swift in Sources */, 11B35E60B65A327311D8CAB1 /* WCSignEthereumTransactionRequestViewModel.swift in Sources */, 11B354B551E301C652D67319 /* WCSendEthereumTransactionRequestViewController.swift in Sources */, + 6B9032CE2D2125E800F3F5AC /* PremiumListSectionHeader.swift in Sources */, D311DA222BD23C230013DB8F /* MarketAdvancedSearchView.swift in Sources */, 11B35B99BA314135CB139D02 /* WCSignEthereumTransactionRequestViewController.swift in Sources */, 11B3530E911B6F8B121BF174 /* CoinMarketsViewModel.swift in Sources */, @@ -13329,7 +13335,7 @@ repositoryURL = "https://github.com/horizontalsystems/ComponentKit.Swift"; requirement = { kind = exactVersion; - version = 2.0.14; + version = 2.0.15; }; }; D3C187D3290FCF7D00FE1900 /* XCRemoteSwiftPackageReference "HUD" */ = { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift index d1ec39c9f1..eb21ed9ecb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift @@ -6,12 +6,13 @@ struct MarketAdvancedSearchView: View { @Binding var isPresented: Bool @State var topPresented = false - @State var marketCapPresented = false @State var volumePresented = false @State var blockchainsPresented = false @State var signalsPresented = false + @State var priceCloseToPresented = false @State var priceChangePresented = false @State var pricePeriodPresented = false + @State private var subscriptionPresented = false @State var resultsPresented = false var body: some View { @@ -22,45 +23,38 @@ struct MarketAdvancedSearchView: View { VStack(spacing: .margin24) { ListSection { topRow() + volumeRow() + blockchainsRow() } VStack(spacing: 0) { - ListSectionHeader(text: "market.advanced_search.market_parameters".localized) + PremiumListSectionHeader() ListSection { - marketCapRow() - volumeRow() - listedOnTopExchangesRow() - goodCexVolumeRow() - goodDexVolumeRow() - goodDistributionRow() - } - } - - VStack(spacing: 0) { - ListSectionHeader(text: "market.advanced_search.network_parameters".localized) - ListSection { - blockchainsRow() + priceChangeRow() + pricePeriodRow() + signalRow() + priceCloseToRow() } + .modifier(ColoredBorder()) } VStack(spacing: 0) { - ListSectionHeader(text: "market.advanced_search.indicators".localized) ListSection { - signalRow() + premiumRow(outperformedBtcRow()) + premiumRow(outperformedEthRow()) + premiumRow(outperformedBnbRow()) } + .modifier(ColoredBorder()) } VStack(spacing: 0) { - ListSectionHeader(text: "market.advanced_search.price_parameters".localized) ListSection { - priceChangeRow() - pricePeriodRow() - outperformedBtcRow() - outperformedEthRow() - outperformedBnbRow() - priceCloseToAthRow() - priceCloseToAtlRow() + premiumRow(goodCexVolumeRow()) + premiumRow(goodDexVolumeRow()) + premiumRow(goodDistributionRow()) + premiumRow(listedOnTopExchangesRow()) } + .modifier(ColoredBorder()) } } .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) @@ -116,8 +110,15 @@ struct MarketAdvancedSearchView: View { } } } + .sheet(isPresented: $subscriptionPresented) { + PurchasesView() + } } } + + private func openPremiumSubscription() { + subscriptionPresented = true + } @ViewBuilder private func topRow() -> some View { ClickableRow(spacing: .margin8) { @@ -158,45 +159,6 @@ struct MarketAdvancedSearchView: View { } } - @ViewBuilder private func marketCapRow() -> some View { - ClickableRow(spacing: .margin8) { - marketCapPresented = true - } content: { - Text("market.advanced_search.market_cap".localized).textBody() - Spacer() - Text(viewModel.marketCap.title).textSubhead1(color: color(valueFilter: viewModel.marketCap)) - Image("arrow_small_down_20").themeIcon() - } - .bottomSheet(isPresented: $marketCapPresented) { - VStack(spacing: 0) { - HStack(spacing: .margin16) { - Image("usd_24").themeIcon(color: .themeJacob) - Text("market.advanced_search.market_cap".localized).themeHeadline2() - Button(action: { marketCapPresented = false }) { Image("close_3_24").themeIcon() } - } - .padding(.horizontal, .margin32) - .padding(.vertical, .margin24) - - ListSection { - ForEach(viewModel.valueFilters) { filter in - ClickableRow { - viewModel.marketCap = filter - marketCapPresented = false - } content: { - Text(filter.title).themeBody(color: color(valueFilter: filter)) - - if viewModel.marketCap == filter { - Image("check_1_20").themeIcon(color: .themeJacob) - } - } - } - } - .themeListStyle(.bordered) - .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin24, trailing: .margin16)) - } - } - } - @ViewBuilder private func volumeRow() -> some View { ClickableRow(spacing: .margin8) { volumePresented = true @@ -236,49 +198,60 @@ struct MarketAdvancedSearchView: View { } } - @ViewBuilder private func listedOnTopExchangesRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.listedOnTopExchanges) { - Text("market.advanced_search.listed_on_top_exchanges".localized).themeBody() + + @ViewBuilder private func premiumRow(_ view: some View) -> some View { + if viewModel.premiumEnabled { + ListRow { + view + } + } else { + ClickableRow { + subscriptionPresented = true + } content: { + view } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } } + + @ViewBuilder private func listedOnTopExchangesRow() -> some View { + Toggle(isOn: $viewModel.listedOnTopExchanges) { + Text("market.advanced_search.listed_on_top_exchanges".localized).themeBody() + } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) + } @ViewBuilder private func goodCexVolumeRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.goodCexVolume) { - VStack(spacing: 1) { - Text("market.advanced_search.good_cex_volume".localized).themeBody() - Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() - } + Toggle(isOn: $viewModel.goodCexVolume) { + VStack(spacing: 1) { + Text("market.advanced_search.good_cex_volume".localized).themeBody() + Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } @ViewBuilder private func goodDexVolumeRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.goodDexVolume) { - VStack(spacing: 1) { - Text("market.advanced_search.good_dex_volume".localized).themeBody() - Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() - } + Toggle(isOn: $viewModel.goodDexVolume) { + VStack(spacing: 1) { + Text("market.advanced_search.good_dex_volume".localized).themeBody() + Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } @ViewBuilder private func goodDistributionRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.goodDistribution) { - VStack(spacing: 1) { - Text("market.advanced_search.good_distribution".localized).themeBody() - Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() - } + Toggle(isOn: $viewModel.goodDistribution) { + VStack(spacing: 1) { + Text("market.advanced_search.good_distribution".localized).themeBody() + Text("market.advanced_search.overall_score_is_good_or_excellent".localized).themeSubhead2() } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } @ViewBuilder private func blockchainsRow() -> some View { @@ -305,6 +278,10 @@ struct MarketAdvancedSearchView: View { @ViewBuilder private func signalRow() -> some View { ClickableRow(spacing: .margin8) { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } signalsPresented = true } content: { Text("market.advanced_search.signal".localized).textBody() @@ -361,6 +338,10 @@ struct MarketAdvancedSearchView: View { @ViewBuilder private func priceChangeRow() -> some View { ClickableRow(spacing: .margin8) { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } priceChangePresented = true } content: { Text("market.advanced_search.price_change".localized).textBody() @@ -400,6 +381,10 @@ struct MarketAdvancedSearchView: View { @ViewBuilder private func pricePeriodRow() -> some View { ClickableRow(spacing: .margin8) { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } pricePeriodPresented = true } content: { Text("market.advanced_search.price_period".localized).textBody() @@ -438,47 +423,69 @@ struct MarketAdvancedSearchView: View { } @ViewBuilder private func outperformedBtcRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.outperformedBtc) { - Text("market.advanced_search.outperformed_btc".localized).themeBody() - } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) + Toggle(isOn: $viewModel.outperformedBtc) { + Text("market.advanced_search.outperformed_btc".localized).themeBody() } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } @ViewBuilder private func outperformedEthRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.outperformedEth) { - Text("market.advanced_search.outperformed_eth".localized).themeBody() - } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) + Toggle(isOn: $viewModel.outperformedEth) { + Text("market.advanced_search.outperformed_eth".localized).themeBody() } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } @ViewBuilder private func outperformedBnbRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.outperformedBnb) { - Text("market.advanced_search.outperformed_bnb".localized).themeBody() - } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) + Toggle(isOn: $viewModel.outperformedBnb) { + Text("market.advanced_search.outperformed_bnb".localized).themeBody() } + .disabled(!viewModel.premiumEnabled) + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } - @ViewBuilder private func priceCloseToAthRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.priceCloseToAth) { - Text("market.advanced_search.price_close_to_ath".localized).themeBody() + @ViewBuilder private func priceCloseToRow() -> some View { + ClickableRow(spacing: .margin8) { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) + priceCloseToPresented = true + } content: { + Text("market.advanced_search.price_close_to".localized).textBody() + Spacer() + Text(viewModel.priceCloseTo.title).textSubhead1(color: color(closeToFilter: viewModel.priceCloseTo)) + Image("arrow_small_down_20").themeIcon() } - } + .bottomSheet(isPresented: $priceCloseToPresented) { + VStack(spacing: 0) { + HStack(spacing: .margin16) { + Image("arrow_swap_24").themeIcon(color: .themeJacob) + Text("market.advanced_search.price_close_to".localized).themeHeadline2() + Button(action: { priceCloseToPresented = false }) { Image("close_3_24").themeIcon() } + } + .padding(.horizontal, .margin32) + .padding(.vertical, .margin24) + + ListSection { + ForEach(MarketAdvancedSearchViewModel.PriceCloseToFilter.allCases) { closeTo in + ClickableRow { + viewModel.priceCloseTo = closeTo + priceCloseToPresented = false + } content: { + Text(closeTo.title).themeBody(color: color(closeToFilter: closeTo)) - @ViewBuilder private func priceCloseToAtlRow() -> some View { - ListRow { - Toggle(isOn: $viewModel.priceCloseToAtl) { - Text("market.advanced_search.price_close_to_atl".localized).themeBody() + if viewModel.priceCloseTo == closeTo { + Image("check_1_20").themeIcon(color: .themeJacob) + } + } + } + } + .themeListStyle(.bordered) + .padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin24, trailing: .margin16)) } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } } @@ -489,6 +496,13 @@ struct MarketAdvancedSearchView: View { } } + private func color(closeToFilter: MarketAdvancedSearchViewModel.PriceCloseToFilter) -> Color { + switch closeToFilter { + case .none: return .themeGray + default: return .themeLeah + } + } + private func color(priceChangeFilter: MarketAdvancedSearchViewModel.PriceChangeFilter) -> Color { switch priceChangeFilter { case .none: return .themeGray diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift index a116c863c7..335fac369d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift @@ -30,6 +30,8 @@ class MarketAdvancedSearchViewModel: ObservableObject { private let marketKit = App.shared.marketKit private let currencyManager = App.shared.currencyManager private let priceChangeModeManager = App.shared.priceChangeModeManager + private let purchaseManager = App.shared.purchaseManager + private var cancellables = Set() private var tasks = Set() @@ -40,6 +42,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { } @Published private(set) var state: State = .loading + @Published private(set) var premiumEnabled: Bool = false @Published var top: MarketModule.Top = .top250 { didSet { @@ -51,12 +54,6 @@ class MarketAdvancedSearchViewModel: ObservableObject { } } - @Published var marketCap: ValueFilter = .none { - didSet { - syncState() - } - } - @Published var volume: ValueFilter = .none { didSet { syncState() @@ -99,43 +96,37 @@ class MarketAdvancedSearchViewModel: ObservableObject { } } - @Published var priceChange: PriceChangeFilter = .none { - didSet { - syncState() - } - } - - @Published var priceChangePeriod: HsTimePeriod { + @Published var priceCloseTo: PriceCloseToFilter = .none { didSet { syncState() } } - @Published var outperformedBtc = false { + @Published var priceChange: PriceChangeFilter = .none { didSet { syncState() } } - @Published var outperformedEth = false { + @Published var priceChangePeriod: HsTimePeriod { didSet { syncState() } } - @Published var outperformedBnb = false { + @Published var outperformedBtc = false { didSet { syncState() } } - @Published var priceCloseToAth = false { + @Published var outperformedEth = false { didSet { syncState() } } - @Published var priceCloseToAtl = false { + @Published var outperformedBnb = false { didSet { syncState() } @@ -165,6 +156,13 @@ class MarketAdvancedSearchViewModel: ObservableObject { } .store(in: &cancellables) + premiumEnabled = purchaseManager.subscription != nil + purchaseManager.$subscription + .sink { [weak self] subscription in + self?.premiumEnabled = subscription != nil + } + .store(in: &cancellables) + syncMarketInfos() } @@ -183,7 +181,6 @@ class MarketAdvancedSearchViewModel: ObservableObject { } canReset = top != .top250 - || marketCap != .none || volume != .none || listedOnTopExchanges != false || goodCexVolume != false @@ -196,15 +193,14 @@ class MarketAdvancedSearchViewModel: ObservableObject { || outperformedBtc != false || outperformedEth != false || outperformedBnb != false - || priceCloseToAtl != false - || priceCloseToAth != false + || priceCloseTo != .none } private func filtered(marketInfos: [MarketInfo]) -> [MarketInfo] { marketInfos.filter { marketInfo in let priceChangeValue = marketInfo.priceChangeValue(timePeriod: priceChangePeriod) - return inBounds(value: marketInfo.marketCap, lower: marketCap.lowerBound, upper: marketCap.upperBound) && + return inBounds(value: marketInfo.totalVolume, lower: volume.lowerBound, upper: volume.upperBound) && (!listedOnTopExchanges || marketInfo.listedOnTopExchanges == true) && (!goodCexVolume || marketInfo.solidCex == true) && @@ -216,8 +212,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { (!outperformedBtc || outperformed(value: priceChangeValue, coinUid: "bitcoin")) && (!outperformedEth || outperformed(value: priceChangeValue, coinUid: "ethereum")) && (!outperformedBnb || outperformed(value: priceChangeValue, coinUid: "binancecoin")) && - (!priceCloseToAth || closedToAllTime(value: marketInfo.athPercentage)) && - (!priceCloseToAtl || closedToAllTime(value: marketInfo.atlPercentage)) + (closedToAllTime(closedTo: priceCloseTo, ath: marketInfo.athPercentage, atl: marketInfo.atlPercentage)) } } @@ -282,7 +277,14 @@ class MarketAdvancedSearchViewModel: ObservableObject { return marketInfos.first { $0.fullCoin.coin.uid == coinUid } } - private func closedToAllTime(value: Decimal?) -> Bool { + private func closedToAllTime(closedTo: PriceCloseToFilter, ath: Decimal?, atl: Decimal?) -> Bool { + var value: Decimal? + switch closedTo { + case .none: return true + case .ath: value = ath + case .atl: value = atl + } + guard let value else { return false } @@ -347,7 +349,6 @@ extension MarketAdvancedSearchViewModel { syncStateEnabled = false top = .top250 - marketCap = .none volume = .none listedOnTopExchanges = false goodDexVolume = false @@ -360,8 +361,7 @@ extension MarketAdvancedSearchViewModel { outperformedBtc = false outperformedEth = false outperformedBnb = false - priceCloseToAtl = false - priceCloseToAth = false + priceCloseTo = .none syncStateEnabled = true syncState() @@ -478,6 +478,24 @@ extension MarketAdvancedSearchViewModel { } } + enum PriceCloseToFilter: CaseIterable, Identifiable { + case none + case ath + case atl + + var id: Self { + self + } + + var title: String { + switch self { + case .none: return "selector.none".localized + case .ath: return "market.advanced_search.price_close_to_ath".localized + case .atl: return "market.advanced_search.price_close_to_atl".localized + } + } + } + enum PriceChangeFilter: CaseIterable, Identifiable { case none case plus10 @@ -536,3 +554,4 @@ extension MarketAdvancedSearchViewModel { } } } + diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift index 878802e051..67c060dd3b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift @@ -415,6 +415,7 @@ class MainSettingsViewController: ThemeViewController { image: .local(UIImage(named: "support_2_24")?.withTintColor(.themeJacob)), title: .body("purchases.vip_support".localized), accessoryType: .disclosure, + backgroundStyle: .borderedLawrence(.themeJacob), autoDeselect: true, isFirst: true, action: { @@ -428,6 +429,7 @@ class MainSettingsViewController: ThemeViewController { image: .local(UIImage(named: "support_24")?.withTintColor(.themeJacob)), title: .body("purchases.vip_club".localized), accessoryType: .disclosure, + backgroundStyle: .borderedLawrence(.themeJacob), autoDeselect: true, isLast: true, action: { @@ -655,9 +657,7 @@ extension MainSettingsViewController: SectionsDataSource { id: "premium", headerState: .cellType( hash: "subscription.premium.label".localized, - binder: { (view: PremiumHeaderFooterView) in - view.bind(iconName: "crown_16", text: "subscription.premium.label".localized, color: .themeJacob, backgroundColor: UIColor.clear) - }, + binder: { _ in }, dynamicHeight: { _ in .margin32 } ), rows: premiumSupportRows @@ -790,7 +790,8 @@ class PremiumHeaderFooterView: UITableViewHeaderFooterView { } label.font = .subhead1 - label.textColor = .themeJacob + + bind() } @available(*, unavailable) @@ -798,7 +799,7 @@ class PremiumHeaderFooterView: UITableViewHeaderFooterView { fatalError("init(coder:) has not been implemented") } - func bind(iconName: String, text: String?, color: UIColor = .themeJacob, backgroundColor: UIColor = .clear) { + func bind(iconName: String = "crown_16", text: String? = "subscription.premium.label".localized, color: UIColor = .themeJacob, backgroundColor: UIColor = .clear) { label.text = text label.textColor = color diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/InputTextView.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/InputTextView.swift index bd2ef62d1e..8fc2ec679c 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/InputTextView.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/InputTextView.swift @@ -78,6 +78,24 @@ extension InputTextView { } } +struct ColoredBorder: ViewModifier { + let cornerRadius: CGFloat + let color: Color + + init(cornerRadius: CGFloat = InputView.cornerRadius, color: Color = .themeJacob) { + self.cornerRadius = cornerRadius + self.color = color + } + + func body(content: Content) -> some View { + content + .overlay( + RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + .stroke(color, lineWidth: .heightOneDp) + ) + } +} + struct CautionBorder: ViewModifier { let cornerRadius: CGFloat @Binding var cautionState: CautionState diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PremiumListSectionHeader.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PremiumListSectionHeader.swift new file mode 100644 index 0000000000..afdca7fc77 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/PremiumListSectionHeader.swift @@ -0,0 +1,16 @@ +import SwiftUI + +struct PremiumListSectionHeader: View { + var body: some View { + HStack(spacing: .margin6) { + Image("crown_16") + .renderingMode(.template) + .foregroundColor(.themeJacob) + + Text("subscription.premium.label".localized) + .themeSubhead1(color: .themeJacob) + } + .padding(EdgeInsets(top: .margin6, leading: .margin16, bottom: 0, trailing: .margin16)) + .frame(height: .margin32, alignment: .top) + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 38804bf992..cd045fdd65 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -103,6 +103,7 @@ "number.quadrillion" = "%@Q"; "selector.any" = "Any"; +"selector.none" = "None"; "face_id" = "Face ID"; "touch_id" = "Touch ID"; @@ -848,8 +849,9 @@ "market.advanced_search.outperformed_btc" = "Outperformed BTC"; "market.advanced_search.outperformed_eth" = "Outperformed ETH"; "market.advanced_search.outperformed_bnb" = "Outperformed BNB"; -"market.advanced_search.price_close_to_ath" = "Price Close To ATH"; -"market.advanced_search.price_close_to_atl" = "Price Close To ATL"; +"market.advanced_search.price_close_to" = "Price Close To"; +"market.advanced_search.price_close_to_ath" = "ATH(All Time High)"; +"market.advanced_search.price_close_to_atl" = "ATL(All Time Low)"; "market.advanced_search.top" = "Top %d"; "market.advanced_search.reset_all" = "Reset";