diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/Contents.json b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/Contents.json new file mode 100644 index 0000000000..afbb31081e --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "crown@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "crown@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@2x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@2x.png new file mode 100644 index 0000000000..ab84f90bfb Binary files /dev/null and b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@2x.png differ diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@3x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@3x.png new file mode 100644 index 0000000000..8d855c5f9f Binary files /dev/null and b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/crown_20.imageset/crown@3x.png differ diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsView.swift index 5a29d48610..a2c9cf0413 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsView.swift @@ -9,6 +9,8 @@ struct MarketAdvancedSearchResultsView: View { @State private var sortBySelectorPresented = false @State private var presentedCoin: Coin? + @State private var signalsPresented = false + @State private var subscriptionPresented = false init(marketInfos: [MarketInfo], timePeriod: HsTimePeriod, isParentPresented: Binding) { _viewModel = StateObject(wrappedValue: MarketAdvancedSearchResultsViewModel(marketInfos: marketInfos, timePeriod: timePeriod)) @@ -30,6 +32,7 @@ struct MarketAdvancedSearchResultsView: View { }) { itemContent( coin: coin, + indicatorResult: marketInfo.indicatorsResult, marketCap: marketInfo.marketCap, price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized, rank: marketInfo.marketCapRank, @@ -67,6 +70,14 @@ struct MarketAdvancedSearchResultsView: View { viewModel.sortBy = viewModel.sortBys[index] } ) + .sheet(isPresented: $signalsPresented) { + MarketWatchlistSignalsView(setShowSignals: { [weak viewModel] in + viewModel?.set(showSignals: $0) + }, isPresented: $signalsPresented) + } + .sheet(isPresented: $subscriptionPresented) { + PurchasesView() + } } @ViewBuilder private func header() -> some View { @@ -78,6 +89,19 @@ struct MarketAdvancedSearchResultsView: View { Text(viewModel.sortBy.title) } .buttonStyle(SecondaryButtonStyle(style: .default, rightAccessory: .dropDown)) + + if viewModel.showSignals { + signalsButton() + .buttonStyle(SecondaryActiveButtonStyle(leftAccessory: + .custom(icon: "crown_20", enabledColor: .themeDark, disabledColor: .themeDark) + )) + } else { + signalsButton() + .buttonStyle( + SecondaryButtonStyle(leftAccessory: + .custom(image: Image("crown_20"), pressedColor: .themeJacob, activeColor: .themeJacob, disabledColor: .themeJacob) + )) + } } .padding(.horizontal, .margin16) .padding(.vertical, .margin8) @@ -96,12 +120,34 @@ struct MarketAdvancedSearchResultsView: View { ) } - @ViewBuilder private func itemContent(coin: Coin?, marketCap: Decimal?, price: String, rank: Int?, diff: Decimal?) -> some View { + @ViewBuilder private func signalsButton() -> some View { + Button(action: { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } + + if viewModel.showSignals { + viewModel.set(showSignals: false) + } else { + signalsPresented = true + } + }) { + Text("market.watchlist.signals".localized) + } + } + + @ViewBuilder private func itemContent(coin: Coin?, indicatorResult: TechnicalAdvice.Advice?, marketCap: Decimal?, price: String, rank: Int?, diff: Decimal?) -> some View { CoinIconView(coin: coin) VStack(spacing: 1) { HStack(spacing: .margin8) { Text(coin?.code ?? "CODE").textBody() + + if viewModel.showSignals, let signal = indicatorResult { + MarketWatchlistSignalBadge(signal: signal) + } + Spacer() Text(price).textBody() } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsViewModel.swift index 70fb2438ce..b89fa1da1b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchResultsViewModel.swift @@ -3,13 +3,28 @@ import Foundation import MarketKit class MarketAdvancedSearchResultsViewModel: ObservableObject { + private static let showSignalKey = "advanced_search_show_signal_key" + private let currencyManager = App.shared.currencyManager + private let purchaseManager = App.shared.purchaseManager + private let userDefaultsStorage = App.shared.userDefaultsStorage private var cancellables = Set() private let internalMarketInfos: [MarketInfo] let timePeriod: HsTimePeriod + private var showSignalsVar: Bool { + get { + userDefaultsStorage.value(for: Self.showSignalKey) ?? false + } + set { + userDefaultsStorage.set(value: newValue, for: Self.showSignalKey) + } + } + + @Published private(set) var premiumEnabled: Bool = false @Published var marketInfos: [MarketInfo] = [] + @Published var showSignals: Bool = false var sortBy: MarketModule.SortBy = .highestCap { didSet { @@ -18,12 +33,29 @@ class MarketAdvancedSearchResultsViewModel: ObservableObject { } init(marketInfos: [MarketInfo], timePeriod: HsTimePeriod) { + let premiumEnabled = purchaseManager.subscription != nil + internalMarketInfos = marketInfos self.timePeriod = timePeriod + showSignals = premiumEnabled && showSignalsVar + self.premiumEnabled = premiumEnabled + + purchaseManager.$subscription + .receive(on: DispatchQueue.main) + .sink { [weak self] subscription in + self?.premiumEnabled = subscription != nil + self?.syncShowSignals() + } + .store(in: &cancellables) + syncState() } + private func syncShowSignals() { + showSignals = premiumEnabled && showSignalsVar + } + private func syncState() { marketInfos = internalMarketInfos.sorted(sortBy: sortBy, timePeriod: timePeriod) } @@ -37,4 +69,12 @@ extension MarketAdvancedSearchResultsViewModel { var sortBys: [MarketModule.SortBy] { [.highestCap, .lowestCap, .gainers, .losers] } + + func set(showSignals: Bool) { + stat(page: .markets, section: .searchResults, event: .showSignals(shown: showSignals)) + syncState() + showSignalsVar = showSignals + + self.showSignals = premiumEnabled && showSignals + } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift index 1e6517facc..3e613ec910 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchView.swift @@ -12,7 +12,7 @@ struct MarketAdvancedSearchView: View { @State var priceCloseToPresented = false @State var priceChangePresented = false @State var pricePeriodPresented = false - @State private var subscriptionPresented = false + @State var subscriptionPresented = false @State var resultsPresented = false var body: some View { @@ -116,10 +116,6 @@ struct MarketAdvancedSearchView: View { } } - private func openPremiumSubscription() { - subscriptionPresented = true - } - @ViewBuilder private func topRow() -> some View { ClickableRow(spacing: .margin8) { topPresented = true diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift index bb92508aed..d8a5c974b6 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/AdvancedSearch/MarketAdvancedSearchViewModel.swift @@ -158,6 +158,7 @@ class MarketAdvancedSearchViewModel: ObservableObject { premiumEnabled = purchaseManager.subscription != nil purchaseManager.$subscription + .receive(on: DispatchQueue.main) .sink { [weak self] subscription in self?.premiumEnabled = subscription != nil } @@ -328,9 +329,11 @@ extension MarketAdvancedSearchViewModel { func syncMarketInfos() { tasks = Set() - internalState = .loading - Task { [weak self, marketKit, top, currencyManager] in + await MainActor.run { [weak self] in + self?.internalState = .loading + } + do { let marketInfos = try await marketKit.advancedMarketInfos(top: top.rawValue, currencyCode: currencyManager.baseCurrency.code) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalsView.swift index 6439a7c018..1a261e34ac 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalsView.swift @@ -2,7 +2,7 @@ import MarketKit import SwiftUI struct MarketWatchlistSignalsView: View { - @ObservedObject var viewModel: MarketWatchlistViewModel + var setShowSignals: (Bool) -> Void @Binding var isPresented: Bool @State private var maxBadgeWidth: CGFloat = .zero @@ -36,7 +36,7 @@ struct MarketWatchlistSignalsView: View { } } bottomContent: { Button(action: { - viewModel.showSignals = true + setShowSignals(true) isPresented = false }) { Text("market.watchlist.signals.turn_on".localized) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift index f2f6941a5b..d8f25ad302 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift @@ -9,6 +9,7 @@ struct MarketWatchlistView: View { @State private var timePeriodSelectorPresented = false @State private var presentedCoin: Coin? @State private var signalsPresented = false + @State private var subscriptionPresented = false @State private var editMode: EditMode = .inactive @@ -78,11 +79,16 @@ struct MarketWatchlistView: View { if viewModel.showSignals { signalsButton() - .buttonStyle(SecondaryActiveButtonStyle()) + .buttonStyle(SecondaryActiveButtonStyle(leftAccessory: + .custom(icon: "crown_20", enabledColor: .themeDark, disabledColor: .themeDark) + )) .disabled(disabled) } else { signalsButton() - .buttonStyle(SecondaryButtonStyle()) + .buttonStyle( + SecondaryButtonStyle(leftAccessory: + .custom(image: Image("crown_20"), pressedColor: .themeJacob, activeColor: .themeJacob, disabledColor: .themeJacob) + )) .disabled(disabled) } } @@ -114,14 +120,24 @@ struct MarketWatchlistView: View { } ) .sheet(isPresented: $signalsPresented) { - MarketWatchlistSignalsView(viewModel: viewModel, isPresented: $signalsPresented) + MarketWatchlistSignalsView(setShowSignals: { [weak viewModel] in + viewModel?.set(showSignals: $0) + }, isPresented: $signalsPresented) + } + .sheet(isPresented: $subscriptionPresented) { + PurchasesView() } } @ViewBuilder private func signalsButton() -> some View { Button(action: { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } + if viewModel.showSignals { - viewModel.showSignals = false + viewModel.set(showSignals: false) } else { signalsPresented = true } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift index 577a0282f8..79ca0e3ceb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift @@ -9,6 +9,7 @@ class MarketWatchlistViewModel: ObservableObject { private let watchlistManager = App.shared.watchlistManager private let userDefaultsStorage = App.shared.userDefaultsStorage private let appManager = App.shared.appManager + private let purchaseManager = App.shared.purchaseManager private var cancellables = Set() private var tasks = Set() @@ -20,6 +21,7 @@ class MarketWatchlistViewModel: ObservableObject { } } + @Published private(set) var premiumEnabled: Bool = false @Published var state: State = .loading @Published var sortBy: WatchlistSortBy { @@ -41,24 +43,33 @@ class MarketWatchlistViewModel: ObservableObject { } } - @Published var showSignals: Bool { - didSet { - stat(page: .markets, section: .watchlist, event: .showSignals(shown: showSignals)) - syncState() - watchlistManager.showSignals = showSignals - } - } + @Published var showSignals: Bool init() { + let premiumEnabled = purchaseManager.subscription != nil + sortBy = watchlistManager.sortBy timePeriod = watchlistManager.timePeriod - showSignals = watchlistManager.showSignals + showSignals = premiumEnabled && watchlistManager.showSignals + self.premiumEnabled = premiumEnabled watchlistManager.$timePeriod .sink { [weak self] timePeriod in self?.timePeriod = timePeriod } .store(in: &cancellables) + + purchaseManager.$subscription + .receive(on: DispatchQueue.main) + .sink { [weak self] subscription in + self?.premiumEnabled = subscription != nil + self?.syncShowSignals() + } + .store(in: &cancellables) + } + + private func syncShowSignals() { + showSignals = premiumEnabled && watchlistManager.showSignals } private func syncCoinUids() { @@ -168,6 +179,14 @@ extension MarketWatchlistViewModel { await _syncMarketInfos() } + func set(showSignals: Bool) { + stat(page: .markets, section: .watchlist, event: .showSignals(shown: showSignals)) + syncState() + watchlistManager.showSignals = showSignals + + syncShowSignals() + } + func remove(coinUid: String) { watchlistManager.remove(coinUid: coinUid) stat(page: .markets, section: .watchlist, event: .removeFromWatchlist(coinUid: coinUid)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsPremiumCell.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsPremiumCell.swift index 133d69035a..90aa463a38 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsPremiumCell.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsPremiumCell.swift @@ -13,7 +13,7 @@ class MainSettingsPremiumCell: UITableViewCell { private let tryForFreeLabel = UILabel() private let boxImageView = UIImageView() - private let radialBackgroundView = RadialBackgroundView() + private let radialBackgroundView = RadialBackgroundView(background: .themeHelsing) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift index d651b9b957..8b9664c70c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift @@ -57,7 +57,6 @@ class MainSettingsViewController: ThemeViewController { navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil) tableView.registerHeaderFooter(forClass: HighlightedSubtitleHeaderFooterView.self) - tableView.registerHeaderFooter(forClass: PremiumHeaderFooterView.self) tableView.registerCell(forClass: MainSettingsPremiumCell.self) tableView.sectionDataSource = self @@ -648,18 +647,14 @@ extension MainSettingsViewController: SectionsDataSource { var sections: [SectionProtocol] = [ Section(id: "token", headerState: .margin(height: .margin32), rows: tokenRows), Section(id: "account", headerState: .margin(height: .margin32), rows: accountRows), - Section(id: "appearance_settings", headerState: .margin(height: .margin32), footerState: .margin(height: .margin24), rows: appearanceRows), + Section(id: "appearance_settings", headerState: .margin(height: .margin32), rows: appearanceRows), ] if viewModel.hasSubscription, viewModel.premiumSubscription { sections.append( Section( id: "premium", - headerState: .cellType( - hash: "subscription.premium.label".localized, - binder: { _ in }, - dynamicHeight: { _ in .margin32 } - ), + headerState: .static(view: PremiumHeaderFooterView(), height: .margin32 + .margin24), rows: premiumSupportRows ) ) @@ -778,7 +773,7 @@ class PremiumHeaderFooterView: UITableViewHeaderFooterView { addSubview(iconView) iconView.snp.makeConstraints { maker in maker.leading.equalToSuperview().inset(CGFloat.margin32) - maker.top.equalToSuperview().inset(CGFloat.margin6) + maker.bottom.equalToSuperview().inset(9) maker.size.equalTo(CGFloat.iconSize16) } @@ -786,7 +781,7 @@ class PremiumHeaderFooterView: UITableViewHeaderFooterView { label.snp.makeConstraints { maker in maker.leading.equalTo(iconView.snp.trailing).offset(CGFloat.margin6) maker.trailing.equalToSuperview().inset(CGFloat.margin32) - maker.top.equalToSuperview().inset(CGFloat.margin6) + maker.bottom.equalToSuperview().inset(9) } label.font = .subhead1 diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift index 8913762030..92cf107861 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsView.swift @@ -10,6 +10,7 @@ struct SecuritySettingsView: View { @State var editPasscodePresented = false @State var createDuressPasscodePresented = false @State var editDuressPasscodePresented = false + @State var subscriptionPresented = false var body: some View { ScrollableThemeView { @@ -110,13 +111,19 @@ struct SecuritySettingsView: View { } VStack(spacing: 0) { + PremiumListSectionHeader() + ListSection { if viewModel.isDuressPasscodeSet { ClickableRow(action: { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } unlockReason = .changeDuressPasscode }) { Image("switch_wallet_24").themeIcon(color: .themeJacob) - Text("settings_security.edit_duress_passcode".localized).themeBody(color: .themeJacob) + Text("settings_security.edit_duress_passcode".localized).themeBody() } ClickableRow(action: { @@ -125,8 +132,14 @@ struct SecuritySettingsView: View { Image("trash_24").themeIcon(color: .themeLucian) Text("settings_security.disable_duress_mode".localized).themeBody(color: .themeLucian) } + .modifier(ColoredBorder()) } else { ClickableRow(action: { + guard viewModel.premiumEnabled else { + subscriptionPresented = true + return + } + if viewModel.isPasscodeSet { unlockReason = .enableDuressMode } else { @@ -134,8 +147,9 @@ struct SecuritySettingsView: View { } }) { Image("switch_wallet_24").themeIcon(color: .themeJacob) - Text("settings_security.enable_duress_mode".localized).themeBody(color: .themeJacob) + Text("settings_security.enable_duress_mode".localized).themeBody() } + .modifier(ColoredBorder()) } } @@ -201,6 +215,9 @@ struct SecuritySettingsView: View { .sheet(isPresented: $editDuressPasscodePresented) { ThemeNavigationView { EditPasscodeModule.editDuressPasscodeView(showParentSheet: $editDuressPasscodePresented) } } + .sheet(isPresented: $subscriptionPresented) { + PurchasesView() + } .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } .navigationTitle("settings_security.title".localized) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift index c01e2621c5..58a87c4d73 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Security/SecuritySettingsViewModel.swift @@ -5,12 +5,14 @@ class SecuritySettingsViewModel: ObservableObject { private let biometryManager: BiometryManager private let lockManager: LockManager private let balanceHiddenManager: BalanceHiddenManager + private let purchaseManager = App.shared.purchaseManager private var cancellables = Set() @Published var currentPasscodeLevel: Int @Published var isPasscodeSet: Bool @Published var isDuressPasscodeSet: Bool @Published var biometryType: BiometryType? + @Published private(set) var premiumEnabled: Bool @Published var autoLockPeriod: AutoLockPeriod { didSet { @@ -47,6 +49,8 @@ class SecuritySettingsViewModel: ObservableObject { biometryEnabledType = biometryManager.biometryEnabledType balanceAutoHide = balanceHiddenManager.balanceAutoHide + premiumEnabled = purchaseManager.subscription != nil + passcodeManager.$currentPasscodeLevel .sink { [weak self] in self?.currentPasscodeLevel = $0 } .store(in: &cancellables) @@ -62,6 +66,11 @@ class SecuritySettingsViewModel: ObservableObject { biometryManager.$biometryEnabledType .sink { [weak self] in self?.biometryEnabledType = $0 } .store(in: &cancellables) + purchaseManager.$subscription + .sink { [weak self] subscription in + self?.premiumEnabled = subscription != nil + } + .store(in: &cancellables) } func removePasscode() { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoModule.swift index 19d72356c1..34c2cbe3c0 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoModule.swift @@ -52,11 +52,17 @@ extension TransactionInfoModule { struct SectionViewItem { let viewItems: [ViewItem] + let header: SectionHeaderViewItem? let footer: String? - init(_ viewItems: [ViewItem], footer: String? = nil) { + init(_ viewItems: [ViewItem], header: SectionHeaderViewItem? = nil, footer: String? = nil) { self.viewItems = viewItems + self.header = header self.footer = footer } } + + enum SectionHeaderViewItem { + case premium + } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift index 2aca97a3ab..26e386a263 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoService.swift @@ -10,6 +10,7 @@ class TransactionInfoService { private let rateService: HistoricalRateService private let nftMetadataService: NftMetadataService private let balanceHiddenManager: BalanceHiddenManager + private let purchaseManager = App.shared.purchaseManager private var transactionRecord: TransactionRecord private var rates = [RateKey: CurrencyValue]() @@ -150,6 +151,10 @@ extension TransactionInfoService { balanceHiddenManager.balanceHidden } + var subscription: PurchaseManager.Subscription? { + purchaseManager.subscription + } + var item: Item { Item( record: transactionRecord, diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewController.swift index a7a38e511c..6118de595d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewController.swift @@ -82,6 +82,11 @@ class TransactionInfoViewController: ThemeViewController { } private func openResend(type: ResendTransactionType) { + guard viewModel.hasSubscription else { + present(PurchasesView().toViewController(), animated: true) + return + } + do { if let evmAdapter = adapter as? BaseEvmAdapter { let viewController = try SendEvmConfirmationModule.resendViewController(adapter: adapter, type: type, transactionHash: viewModel.transactionHash) @@ -194,14 +199,14 @@ class TransactionInfoViewController: ThemeViewController { return CellBuilderNew.row( rootElement: .hStack([ .imageElement(image: .local(image?.withTintColor(color)), size: .image24), - .textElement(text: .body(title, color: color)), + .textElement(text: .body(title)), ]), tableView: tableView, id: "option-\(rowInfo.index)", height: .heightCell48, autoDeselect: true, bind: { cell in - cell.set(backgroundStyle: .lawrence, isFirst: rowInfo.isFirst, isLast: rowInfo.isLast) + cell.set(backgroundStyle: .borderedLawrence(.themeJacob), isFirst: rowInfo.isFirst, isLast: rowInfo.isLast) }, action: action ) @@ -557,9 +562,19 @@ extension TransactionInfoViewController: SectionsDataSource { footerState = .margin(height: index == viewItems.count - 1 ? .margin32 : 0) } + let headerState: ViewState + if let header = sectionViewItem.header { + switch header { + case .premium: + headerState = .static(view: PremiumHeaderFooterView(), height: .margin48) + } + } else { + headerState = .margin(height: .margin12) + } + return Section( id: "section_\(index)", - headerState: .margin(height: .margin12), + headerState: headerState, footerState: footerState, rows: sectionViewItem.viewItems.enumerated().map { index, viewItem in row(viewItem: viewItem, rowInfo: RowInfo(index: index, isFirst: index == 0, isLast: index == sectionViewItem.viewItems.count - 1)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift index 17793eb3b7..e27070d03f 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewItemFactory.swift @@ -531,7 +531,7 @@ class TransactionInfoViewItemFactory { sections.append(.init([ .option(option: .resend(type: .speedUp)), .option(option: .resend(type: .cancel)), - ], footer: "tx_info.resend_description".localized)) + ], header: .premium, footer: "tx_info.resend_description".localized)) } case let record as BinanceChainIncomingTransactionRecord: @@ -674,7 +674,7 @@ class TransactionInfoViewItemFactory { sections.append(.init([ .option(option: .resend(type: .speedUp)), .option(option: .resend(type: .cancel)), - ], footer: "tx_info.resend_description".localized)) + ], header: .premium, footer: "tx_info.resend_description".localized)) } sections.append(.init([ diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewModel.swift index 74a45b6904..bb2690e360 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/TransactionInfo/TransactionInfoViewModel.swift @@ -1,3 +1,4 @@ +import Combine import MarketKit import RxCocoa import RxSwift @@ -51,6 +52,10 @@ extension TransactionInfoViewModel { service.item.record } + var hasSubscription: Bool { + service.subscription != nil + } + func togglePrice() { factory.priceReversed.toggle() reSyncServiceItem() diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/RadialGradientView.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/RadialGradientView.swift index 12bb9f7638..cc2c83d30d 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/RadialGradientView.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/RadialGradientView.swift @@ -4,15 +4,22 @@ import UIKit final class RadialBackgroundView: UIView { private let hostingController: UIHostingController + init(background: UIColor) { + hostingController = UIHostingController(rootView: MainSettingsPremiumBackgroundView(backgroundColor: Color(uiColor: background))) + + super.init(frame: .zero) + setupView() + } + override init(frame: CGRect) { - hostingController = UIHostingController(rootView: MainSettingsPremiumBackgroundView()) + hostingController = UIHostingController(rootView: MainSettingsPremiumBackgroundView(backgroundColor: .themeTyler)) super.init(frame: frame) setupView() } required init?(coder: NSCoder) { - hostingController = UIHostingController(rootView: MainSettingsPremiumBackgroundView()) + hostingController = UIHostingController(rootView: MainSettingsPremiumBackgroundView(backgroundColor: .themeTyler)) super.init(coder: coder) setupView() @@ -31,10 +38,12 @@ final class RadialBackgroundView: UIView { } struct MainSettingsPremiumBackgroundView: View { + let backgroundColor: Color + var body: some View { GeometryReader { proxy in ZStack { - Color.themeTyler.ignoresSafeArea() + backgroundColor.ignoresSafeArea() ZStack { Circle()