From 937606f59ee061ee8e9e50ae24cc18ef5258299d Mon Sep 17 00:00:00 2001 From: mike-dydx <149746839+mike-dydx@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:36:39 -0500 Subject: [PATCH] TRCL-3319 : Trading Rewards Details Screen History Card (#41) * show help card in trading rewards * add padding * set up trading rewards title card * add list items to the reward history card * add view more button and header * add more periods * fix CD compilation * address comments --- .../TabGroup/TabItemViewModel.swift | 28 ++-- .../Theme/ThemeViewModifiers.swift | 53 +++++-- .../PlatformUI/PlatformListViewModel.swift | 58 +++---- .../dydxPresenters.xcodeproj/project.pbxproj | 12 +- ...ift => dydxRewardsHelpViewPresenter.swift} | 2 +- .../dydxRewardsHistoryViewPresenter.swift | 83 ++++++++++ .../dydxTradingRewardsViewPresenter.swift | 6 + .../dydxViews.xcodeproj/project.pbxproj | 8 + .../Components/dydxTitledCardView.swift | 12 +- .../Components/dydxRewardsHistoryView.swift | 147 ++++++++++++++++++ .../Components/dydxRewardsRewardView.swift | 98 ++++++++++++ .../dydxTradingRewardsView.swift | 4 +- 12 files changed, 444 insertions(+), 67 deletions(-) rename dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/{dydxRewardsHelpViewBuilder.swift => dydxRewardsHelpViewPresenter.swift} (97%) create mode 100644 dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryViewPresenter.swift create mode 100644 dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryView.swift create mode 100644 dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsRewardView.swift diff --git a/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift b/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift index 075175d95..d19e73800 100644 --- a/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift +++ b/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift @@ -54,33 +54,23 @@ public class TabItemViewModel: PlatformViewModel, Equatable { let borderWidth: CGFloat = 1 switch value { case .text(let value): - let content = Text(value) - .themeFont(fontSize: .medium) - .padding([.bottom, .top], 6) - .padding([.leading, .trailing], 12) + return Text(value) + .themeFont(fontSize: .small) + .padding(.vertical, 6) + .padding(.horizontal, 8) .themeStyle(styleKey: styleKey, parentStyle: style) - .clipShape(Capsule()) - .overlay( - Capsule(style: .circular) - .stroke(ThemeColor.SemanticColor.layer6.color, lineWidth: borderWidth) - ) - .padding(borderWidth * 2) - return AnyView(content) + .borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth) + .wrappedInAnyView() case .icon(let image): - let content = PlatformIconViewModel(type: .uiImage(image: image), + return PlatformIconViewModel(type: .uiImage(image: image), size: CGSize(width: 18, height: 18), templateColor: templateColor) .createView(parentStyle: parentStyle) .padding([.bottom, .top], 6) .padding([.leading, .trailing], 12) .themeStyle(styleKey: styleKey, parentStyle: style) - .clipShape(Capsule()) - .overlay( - Capsule(style: .circular) - .stroke(ThemeColor.SemanticColor.layer6.color, lineWidth: borderWidth) - ) - .padding(borderWidth * 2) - return AnyView(content) + .borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth) + .wrappedInAnyView() case .bar(let value): let content = VStack { value.createView(parentStyle: style) diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift index c7889ad8a..70026c71f 100644 --- a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift @@ -499,16 +499,32 @@ public extension View { // MARK: AttributedString public extension AttributedString { - func themeFont(fontType: ThemeFont.FontType = .text, fontSize: ThemeFont.FontSize = .medium) -> Self { - var copy = self - copy.font = ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize) - return copy + /// Applies a font to the attributed string. + /// - Parameters: + /// - foreground: the font to apply + /// - range: the range to modify, `nil` if the entire string should be modified + func themeFont(fontType: ThemeFont.FontType = .text, fontSize: ThemeFont.FontSize = .medium, to range: Range? = nil) -> Self { + var string = self + if let range = range { + string[range].font = ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize) + } else { + string.font = ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize) + } + return string } - - func themeColor(foreground: ThemeColor.SemanticColor) -> Self { - var copy = self - copy.foregroundColor = ThemeSettings.shared.themeConfig.themeColor.color(of: foreground) - return copy + + /// Applies a foreground color to the attributed string. + /// - Parameters: + /// - foreground: the color to apply + /// - range: the range to modify, `nil` if the entire string should be modified + func themeColor(foreground: ThemeColor.SemanticColor, to range: Range? = nil) -> Self { + var string = self + if let range = range { + string[range].foregroundColor = ThemeSettings.shared.themeConfig.themeColor.color(of: foreground) + } else { + string.foregroundColor = ThemeSettings.shared.themeConfig.themeColor.color(of: foreground) + } + return string } } @@ -608,3 +624,22 @@ public extension View { } } +// MARK: ScrollView + +public extension View { + func disableBounces() -> some View { + modifier(DisableBouncesModifier()) + } +} + +struct DisableBouncesModifier: ViewModifier { + func body(content: Content) -> some View { + content + .onAppear { + UIScrollView.appearance().bounces = false + } + .onDisappear { + UIScrollView.appearance().bounces = true + } + } +} diff --git a/PlatformUI/PlatformUI/PlatformListViewModel.swift b/PlatformUI/PlatformUI/PlatformListViewModel.swift index 614cf2f88..cb4615bc8 100644 --- a/PlatformUI/PlatformUI/PlatformListViewModel.swift +++ b/PlatformUI/PlatformUI/PlatformListViewModel.swift @@ -76,39 +76,41 @@ open class PlatformListViewModel: PlatformViewModeling { } return AnyView( - ForEach(list, id: \.id) { [weak self] item in - Group { - let cell = + VStack(spacing: intraItemSeparator ? 0 : 10) { + ForEach(list, id: \.id) { [weak self] item in Group { - if item === list.first, let header = self?.header { - header.createView(parentStyle: parentStyle) - } else { - VStack(alignment: .leading, spacing: 0) { - if self?.intraItemSeparator == true { - let shouldDisplayTopSeparator = self?.intraItemSeparator == true && (self?.firstListItemTopSeparator == true && item === list.first) - let shouldDisplayBottomSeparator = self?.intraItemSeparator == true || (item !== list.last || self?.lastListItemBottomSeparator == true) - if shouldDisplayTopSeparator { - DividerModel().createView(parentStyle: parentStyle) - } - - Spacer() - item.createView(parentStyle: parentStyle) - Spacer() - - if shouldDisplayBottomSeparator { - DividerModel().createView(parentStyle: parentStyle) + let cell = + Group { + if item === list.first, let header = self?.header { + header.createView(parentStyle: parentStyle) + } else { + VStack(alignment: .leading, spacing: 0) { + if self?.intraItemSeparator == true { + let shouldDisplayTopSeparator = self?.intraItemSeparator == true && (self?.firstListItemTopSeparator == true && item === list.first) + let shouldDisplayBottomSeparator = self?.intraItemSeparator == true || (item !== list.last || self?.lastListItemBottomSeparator == true) + if shouldDisplayTopSeparator { + DividerModel().createView(parentStyle: parentStyle) + } + + Spacer() + item.createView(parentStyle: parentStyle) + Spacer() + + if shouldDisplayBottomSeparator { + DividerModel().createView(parentStyle: parentStyle) + } + } else { + item.createView(parentStyle: parentStyle) } - } else { - item.createView(parentStyle: parentStyle) } } } - } - - if let width = self?.width { - cell.frame(width: width) - } else { - cell + + if let width = self?.width { + cell.frame(width: width) + } else { + cell + } } } } diff --git a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj index 4c7231c1c..20559fdb0 100644 --- a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj +++ b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj @@ -133,7 +133,8 @@ 277E8FC92B1E576B005CCBCB /* dydxProfileRewardsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E8FC82B1E576B005CCBCB /* dydxProfileRewardsViewPresenter.swift */; }; 277E90152B1EA0E3005CCBCB /* dydxTradingRewardsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90142B1EA0E3005CCBCB /* dydxTradingRewardsViewPresenter.swift */; }; 277E90192B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90182B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift */; }; - 277E90332B1FAE9A005CCBCB /* dydxRewardsHelpViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewBuilder.swift */; }; + 277E90332B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift */; }; + 277E908B2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E908A2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift */; }; 27C027532AFD761300E92CCB /* dydxSettingsHelpRowViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C027522AFD761300E92CCB /* dydxSettingsHelpRowViewPresenter.swift */; }; 27DB2EA32AC1E7B20047BC39 /* dydxTradeRestrictedViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DB2EA22AC1E7B20047BC39 /* dydxTradeRestrictedViewPresenter.swift */; }; 314BBDE9F332ECA910BC414E /* Pods_iOS_dydxPresenters.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1551C00FFF41C29CFC5BD94 /* Pods_iOS_dydxPresenters.framework */; }; @@ -475,7 +476,8 @@ 277E8FC82B1E576B005CCBCB /* dydxProfileRewardsViewPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxProfileRewardsViewPresenter.swift; sourceTree = ""; }; 277E90142B1EA0E3005CCBCB /* dydxTradingRewardsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTradingRewardsViewPresenter.swift; sourceTree = ""; }; 277E90182B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsSummaryPresenter.swift; sourceTree = ""; }; - 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsHelpViewBuilder.swift; sourceTree = ""; }; + 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsHelpViewPresenter.swift; sourceTree = ""; }; + 277E908A2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsHistoryViewPresenter.swift; sourceTree = ""; }; 27C027522AFD761300E92CCB /* dydxSettingsHelpRowViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSettingsHelpRowViewPresenter.swift; sourceTree = ""; }; 27DB2EA22AC1E7B20047BC39 /* dydxTradeRestrictedViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTradeRestrictedViewPresenter.swift; sourceTree = ""; }; 64487FFE2AA248340068DD87 /* dydxAlertsWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxAlertsWorker.swift; sourceTree = ""; }; @@ -1297,7 +1299,8 @@ isa = PBXGroup; children = ( 277E90182B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift */, - 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewBuilder.swift */, + 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift */, + 277E908A2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift */, ); path = Components; sourceTree = ""; @@ -1772,6 +1775,7 @@ 0262F2D529DB4891009889E2 /* WalletAction.swift in Sources */, 023789ED28BD381C00F212E1 /* dydxPresenters.docc in Sources */, 0258B9E72991BC900098E1BE /* dydxNewsAlertsViewBuilder.swift in Sources */, + 277E908B2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift in Sources */, 0230376F28C11BE600412B72 /* dydxMarketsViewBuilder.swift in Sources */, 270BA8F32A6F278F009212EA /* dydxDebugThemeViewerBuilder.swift in Sources */, 02669B952AD87A9D00A756AA /* dydxGlobalWorkers.swift in Sources */, @@ -1812,7 +1816,7 @@ 02A565AF2A5E310B0035469F /* dydxAlertsProvider.swift in Sources */, 025841F428EE9DE8007338D3 /* dydxAssetItemChartViewPresenter.swift in Sources */, 02860A9F29C15E760079E644 /* dydxOnboardScanViewBuilder.swift in Sources */, - 277E90332B1FAE9A005CCBCB /* dydxRewardsHelpViewBuilder.swift in Sources */, + 277E90332B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift in Sources */, 02FAFA5C29D4E08E001A0903 /* dydxDebugViewBuilder.swift in Sources */, 02F700FE29EA0FD9004DEB5E /* dydxReceiptPresenter.swift in Sources */, 027E1EF829CA27CD0098666F /* dydxSettingsLandingViewBuilder.swift in Sources */, diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewPresenter.swift similarity index 97% rename from dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewBuilder.swift rename to dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewPresenter.swift index bbc1de439..0175a40ec 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHelpViewPresenter.swift @@ -1,5 +1,5 @@ // -// dydxRewardsHelpViewBuilder.swift +// dydxRewardsHelpViewPresenter.swift // dydxPresenters // // Created by Michael Maguire on 12/5/23. diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryViewPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryViewPresenter.swift new file mode 100644 index 000000000..e08d9aed1 --- /dev/null +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryViewPresenter.swift @@ -0,0 +1,83 @@ +// +// dydxRewardsHistoryViewPresenter.swift +// dydxPresenters +// +// Created by Michael Maguire on 12/6/23. +// + +import dydxViews +import PlatformParticles +import ParticlesKit +import RoutingKit +import Utilities + +public protocol dydxRewardsHistoryViewPresenterProtocol: HostedViewPresenterProtocol { + var viewModel: dydxRewardsHistoryViewModel? { get } +} + +public class dydxRewardsHistoryViewPresenter: HostedViewPresenter, dydxRewardsHistoryViewPresenterProtocol { + + enum Period: CaseIterable { + case monthly + case weekly + case daily + + var text: String? { + switch self { + case .monthly: return DataLocalizer.shared?.localize(path: "APP.GENERAL.TIME_STRINGS.MONTHLY", params: nil) + case .weekly: return DataLocalizer.shared?.localize(path: "APP.GENERAL.TIME_STRINGS.WEEKLY", params: nil) + case .daily: return DataLocalizer.shared?.localize(path: "APP.GENERAL.TIME_STRINGS.DAILY", params: nil) + } + } + } + + override init() { + super.init() + + viewModel = dydxRewardsHistoryViewModel() + + viewModel?.filters = Period.allCases.compactMap { period in + guard let text = period.text else { return nil } + return .text(text) + } + + viewModel?.onSelectionChanged = { index in + let period = Period.allCases[index] + // TODO filter abacus results by period +// Router.shared?.navigate(to: .init(url: ...), animated: true, completion: nil) + } + + // TODO get todos from abacus + viewModel?.items = [ + .init(period: "period 1 -> period 2", amount: "1.000"), + .init(period: "period 2 -> period 3", amount: "2.000"), + .init(period: "period 3 -> period 4", amount: "3.000"), + .init(period: "period 4 -> period 5", amount: "4.000"), + .init(period: "period 5 -> period 6", amount: "5.000"), + .init(period: "period 6 -> period 7", amount: "6.000"), + .init(period: "period 7 -> period 8", amount: "7.000"), + .init(period: "period 8 -> period 9", amount: "8.000"), + .init(period: "period 9 -> period 10", amount: "9.000"), + .init(period: "period 10 -> period 11", amount: "10.000"), + .init(period: "period 11 -> period 12", amount: "11.000"), + .init(period: "period 12 -> period 2", amount: "1.000"), + .init(period: "period 13 -> period 3", amount: "2.000"), + .init(period: "period 14 -> period 4", amount: "3.000"), + .init(period: "period 15 -> period 5", amount: "4.000"), + .init(period: "period 16 -> period 6", amount: "5.000"), + .init(period: "period 17 -> period 7", amount: "6.000"), + .init(period: "period 18 -> period 8", amount: "7.000"), + .init(period: "period 19 -> period 9", amount: "8.000"), + .init(period: "period 20 -> period 10", amount: "9.000"), + .init(period: "period 21 -> period 11", amount: "10.000"), + .init(period: "period 22 -> period 3", amount: "2.000"), + .init(period: "period 23 -> period 4", amount: "3.000"), + .init(period: "period 24 -> period 5", amount: "4.000"), + .init(period: "period 25 -> period 6", amount: "5.000"), + .init(period: "period 26 -> period 7", amount: "6.000"), + .init(period: "period 27 -> period 8", amount: "7.000"), + .init(period: "period 28 -> period 9", amount: "8.000"), + .init(period: "period 29 -> period 10", amount: "9.000") + ] + } +} diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/dydxTradingRewardsViewPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/dydxTradingRewardsViewPresenter.swift index a0840d741..275f6444e 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/dydxTradingRewardsViewPresenter.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/dydxTradingRewardsViewPresenter.swift @@ -34,6 +34,7 @@ private protocol dydxTradingRewardsViewPresenterProtocol: HostedViewPresenterPro private class dydxTradingRewardsViewPresenter: HostedViewPresenter, dydxTradingRewardsViewPresenterProtocol { private let helpPresenter = dydxRewardsHelpViewPresenter() + private let historyPresenter = dydxRewardsHistoryViewPresenter() override init() { super.init() @@ -46,6 +47,11 @@ private class dydxTradingRewardsViewPresenter: HostedViewPresenter Void)? public init(title: String, - verticalContentPadding: CGFloat = 10, - horizontalContentPadding: CGFloat = 18) { + verticalContentPadding: CGFloat = 16, + horizontalContentPadding: CGFloat = 16) { self.title = title self.verticalContentPadding = verticalContentPadding self.horizontalContentPadding = horizontalContentPadding @@ -48,14 +48,16 @@ public class dydxTitledCardViewModel: PlatformViewModel { guard let self = self else { return AnyView(PlatformView.nilView) } let view = VStack(spacing: 0) { - HStack { + HStack(spacing: 0) { Text(self.title) .themeFont(fontSize: .small) .themeColor(foreground: .textSecondary) - Spacer() + .fixedSize() + Spacer(minLength: 16) self.createTitleAccessoryView(parentStyle: parentStyle) } - .padding() + .padding(.horizontal, 16) + .padding(.vertical, 12) DividerModel() .createView(parentStyle: style) diff --git a/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryView.swift b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryView.swift new file mode 100644 index 000000000..6cc20840d --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsHistoryView.swift @@ -0,0 +1,147 @@ +// +// dydxRewardsHistoryView.swift +// dydxViews +// +// Created by Michael Maguire on 12/6/23. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class dydxRewardsHistoryViewModel: dydxTitledCardViewModel { + // MARK: public properties + @Published public var filters: [TabItemViewModel.TabItemContent] = [] + @Published public var onSelectionChanged: ((Int) -> Void)? + @Published public var items: [dydxRewardsRewardViewModel] = [] + public var contentChanged: (() -> Void)? + + // MARK: private properties + private static let numItemsStepSize: Int = 10 + private var visibleItems: [dydxRewardsRewardViewModel] { + Array(items.prefix(maxItemsToDisplay)) + } + private var hasMoreItemsToDisplay: Bool { items.count > maxItemsToDisplay } + @Published private var maxItemsToDisplay: Int = dydxRewardsHistoryViewModel.numItemsStepSize + @Published private var filtersViewWidth: CGFloat = .zero + + // MARK: impl + + public init() { + super.init(title: DataLocalizer.shared?.localize(path: "APP.GENERAL.REWARD_HISTORY", params: nil) ?? "") + } + + public override func createTitleAccessoryView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> AnyView? { + let items = filters.compactMap { TabItemViewModel(value: $0, isSelected: false) } + let selectedItems = filters.compactMap {TabItemViewModel(value: $0, isSelected: true) } + return ScrollView(.horizontal, showsIndicators: false) { + TabGroupModel(items: items, + selectedItems: selectedItems, + currentSelection: 0, + onSelectionChanged: { [weak self] index in + self?.onSelectionChanged?(index) + }, + spacing: 8) + .createView(parentStyle: parentStyle) + .sizeReader {[weak self] size in + self?.filtersViewWidth = size.width + } + } + .disableBounces() + .frame(maxWidth: filtersViewWidth, alignment: .trailing) + .wrappedInAnyView() + } + + private let headerViewModel: PlatformViewModel = { + PlatformViewModel { _ in + HStack(spacing: 0) { + Text(DataLocalizer.shared?.localize(path: "APP.TRADING_REWARDS.EVENT", params: nil) ?? "") + .themeFont(fontType: .text, fontSize: .smaller) + .themeColor(foreground: .textTertiary) + Spacer() + Text(DataLocalizer.shared?.localize(path: "APP.TRADING_REWARDS.TRADING_REWARD", params: nil) ?? "") + .themeFont(fontType: .text, fontSize: .smaller) + .themeColor(foreground: .textTertiary) + } + .wrappedInAnyView() + } + }() + + private var itemsListViewModel: PlatformListViewModel { + PlatformListViewModel(items: visibleItems, + intraItemSeparator: false, + contentChanged: contentChanged) + } + + private var viewMoreButtonViewModel: PlatformButtonViewModel { + PlatformButtonViewModel(content: PlatformViewModel { _ in + HStack(spacing: 8) { + Text("View More") + .themeFont(fontType: .text, fontSize: .small) + .themeColor(foreground: .textSecondary) + PlatformIconViewModel(type: .asset(name: "icon_dropdown", bundle: Bundle.dydxView), + clip: .noClip, + size: .init(width: 14, height: 8), + templateColor: .textSecondary) + .createView() + } + .wrappedInAnyView() + }, + type: .defaultType, + state: .secondary, + action: { + withAnimation { [weak self] in + self?.maxItemsToDisplay += dydxRewardsHistoryViewModel.numItemsStepSize + } + }) + } + + public override func createContentView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> AnyView? { + VStack(spacing: 16) { + VStack(spacing: 10) { + headerViewModel.createView(parentStyle: parentStyle) + itemsListViewModel.createView(parentStyle: parentStyle) + } + if self.hasMoreItemsToDisplay { + viewMoreButtonViewModel.createView(parentStyle: parentStyle) + } + } + .wrappedInAnyView() + } + + public static var previewValue: dydxRewardsHistoryViewModel { + let vm = dydxRewardsHistoryViewModel() + vm.items = [.previewValue] + return vm + } +} + +#if DEBUG +struct dydxRewardsHistoryViewModel_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return dydxRewardsHistoryViewModel.previewValue + .createView() + .environmentObject(themeSettings) + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct dydxRewardsHistoryViewModel_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return dydxRewardsHistoryViewModel.previewValue + .createView() + .environmentObject(themeSettings) + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsRewardView.swift b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsRewardView.swift new file mode 100644 index 000000000..354af9df9 --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsRewardView.swift @@ -0,0 +1,98 @@ +// +// dydxRewardsRewardViewModel.swift +// dydxViews +// +// Created by Michael Maguire on 12/7/23. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class dydxRewardsRewardViewModel: PlatformViewModel { + + public var id: String { period + amount } + private let period: String + private let amount: String + + public init(period: String, amount: String) { + self.period = period + self.amount = amount + super.init() + } + + private var periodFormatted: AttributedString { + let localizedString = DataLocalizer.shared?.localize(path: "APP.TRADING_REWARDS.FOR_TRADING", params: ["PERIOD": period]) ?? "" + + var attributedString = AttributedString(localizedString) + .themeFont(fontType: .text, fontSize: .smaller) + + attributedString = attributedString.themeColor(foreground: .textTertiary, to: nil) + if let periodTextRange = attributedString.range(of: period) { + attributedString = attributedString.themeColor(foreground: .textSecondary, to: periodTextRange) + } + + return attributedString + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] _ in + guard let self = self else { return AnyView(PlatformView.nilView) } + return HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 2) { + Text(DataLocalizer.shared?.localize(path: "APP.GENERAL.REWARDED", params: nil) ?? "") + .themeFont(fontType: .text, fontSize: .smaller) + .themeColor(foreground: .textSecondary) + Text(self.periodFormatted) + } + Spacer(minLength: 16) + HStack(spacing: 4) { + Text(self.amount) + .themeFont(fontType: .text, fontSize: .smaller) + .themeColor(foreground: .textSecondary) + PlatformIconViewModel(type: .asset(name: "icon_dydx", bundle: .dydxView), clip: .noClip, size: .init(width: 24, height: 24), templateColor: nil) + .createView() + } + } + .wrappedInAnyView() + } + } + + public static var previewValue: dydxRewardsRewardViewModel { + let vm = dydxRewardsRewardViewModel(period: "test period", amount: "15") + return vm + } + +} + +#if DEBUG +struct dydxRewardsRewardViewModel_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return dydxRewardsRewardViewModel.previewValue + .createView() + .themeColor(background: .layer0) + .environmentObject(themeSettings) + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct dydxRewardsRewardViewModel_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return dydxRewardsRewardViewModel.previewValue + .createView() + .themeColor(background: .layer0) + .environmentObject(themeSettings) + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/dydxTradingRewardsView.swift b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/dydxTradingRewardsView.swift index aa453cf30..05a17afc2 100644 --- a/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/dydxTradingRewardsView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/dydxTradingRewardsView.swift @@ -14,6 +14,7 @@ public class dydxTradingRewardsViewModel: PlatformViewModel { @Published public var headerViewModel: NavHeaderModel = NavHeaderModel() @Published public var rewardsSummary: dydxRewardsSummaryViewModel = dydxRewardsSummaryViewModel() @Published public var help: dydxRewardsHelpViewModel? = dydxRewardsHelpViewModel() + @Published public var history: dydxRewardsHistoryViewModel? = dydxRewardsHistoryViewModel() public init() { } @@ -31,7 +32,8 @@ public class dydxTradingRewardsViewModel: PlatformViewModel { VStack(spacing: 16) { self.rewardsSummary.createView(parentStyle: style) self.help?.createView(parentStyle: style) - Spacer(minLength: 68) + self.history?.createView(parentStyle: style) + Spacer(minLength: 80) } } }