Skip to content

Commit

Permalink
CLI-638: prediction markets UI (#222)
Browse files Browse the repository at this point in the history
* add feature flag

* support filtering to prediction markets

* add banner

* comment

* add feature flag to debug menu

---------

Co-authored-by: Mike <[email protected]>
  • Loading branch information
mike-dydx and mike-dydx committed Aug 21, 2024
1 parent 9ae2919 commit 2878489
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 16 deletions.
41 changes: 36 additions & 5 deletions PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@ public class TabItemViewModel: PlatformViewModel, Equatable {
}

public enum TabItemContent: Equatable {
public struct PillConfig {
var text: String
var textColor: ThemeColor.SemanticColor
var backgroundColor: ThemeColor.SemanticColor

public init(text: String, textColor: ThemeColor.SemanticColor, backgroundColor: ThemeColor.SemanticColor) {
self.text = text
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}

case text(String, EdgeInsets = EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
case textWithPillAccessory(text: String, pillConfig: PillConfig)
case icon(UIImage)
case bar(PlatformViewModel)

Expand Down Expand Up @@ -51,23 +64,41 @@ public class TabItemViewModel: PlatformViewModel, Equatable {

let styleKey = self.isSelected ? "pill_tab_group_selected_item" : "pill_tab_group_unselected_item"
let templateColor: ThemeColor.SemanticColor = self.isSelected ? .textPrimary: .textTertiary
let textFontSize = ThemeFont.FontSize.small
let borderWidth: CGFloat = 1
switch value {
case .text(let value, let edgeInsets):
return Text(value)
.frame(maxHeight: .infinity)
.themeFont(fontSize: .small)
.themeFont(fontSize: textFontSize)
.padding(edgeInsets)
.themeStyle(styleKey: styleKey, parentStyle: style)
.borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth)
.wrappedInAnyView()
case .textWithPillAccessory(let text, let pillConfig):
return HStack(alignment: .center, spacing: 4) {
Text(text)
.themeFont(fontSize: textFontSize)
.themeColor(foreground: .textSecondary)
Text(pillConfig.text)
.themeFont(fontSize: .smaller)
.padding(.horizontal, 5)
.padding(.vertical, 2)
.themeColor(foreground: pillConfig.textColor)
.themeColor(background: pillConfig.backgroundColor)
.clipShape(.rect(cornerRadius: 6))
}
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
.themeStyle(styleKey: styleKey, parentStyle: style)
.borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth)
.wrappedInAnyView()
case .icon(let image):
let height = ThemeSettings.shared.themeConfig.themeFont.uiFont(of: .base, fontSize: textFontSize)?.lineHeight ?? 14
return PlatformIconViewModel(type: .uiImage(image: image),
size: CGSize(width: 18, height: 18),
size: CGSize(width: height, height: height),
templateColor: templateColor)
.createView(parentStyle: parentStyle)
.padding([.bottom, .top], 8)
.padding([.leading, .trailing], 12)
.padding(.vertical, 6)
.padding(.horizontal, 8)
.themeStyle(styleKey: styleKey, parentStyle: style)
.borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth)
.wrappedInAnyView()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ private struct BorderAndClipModifier: ViewModifier {

case .cornerRadius(let cornerRadius):
content
.clipShape(RoundedRectangle(cornerSize: .init(width: cornerRadius, height: cornerRadius)))
.clipShape(.rect(cornerRadius: cornerRadius))
.overlay(RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(borderColor.color, lineWidth: lineWidth))

Expand All @@ -343,7 +343,7 @@ private struct BorderModifier: ViewModifier {

func body(content: Content) -> some View {
content
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.clipShape(.rect(cornerRadius: cornerRadius))
.overlay(
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(borderColor ?? .clear, lineWidth: borderWidth)
Expand Down
3 changes: 3 additions & 0 deletions dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum dydxBoolFeatureFlag: String, CaseIterable {
case enable_app_rating
case shouldUseSkip = "ff_skip_migration"
case isVaultEnabled = "ff_vault_enabled"
case showPredictionMarketsUI = "ff_show_prediction_markets_ui"

var defaultValue: Bool {
switch self {
Expand All @@ -25,6 +26,8 @@ public enum dydxBoolFeatureFlag: String, CaseIterable {
return true
case .isVaultEnabled:
return false
case .showPredictionMarketsUI:
return false
}
}

Expand Down
20 changes: 20 additions & 0 deletions dydx/dydxPresenters/dydxPresenters/_Features/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@
}
]
}
},
{
"title":{
"text":"Prediction Marktes"
},
"field":{
"field":"ff_show_prediction_markets_ui",
"optional":true,
"type" : "bool",
"options" : [
{
"text": "yes",
"value" : 1
},
{
"text": "no",
"value" : 0
}
]
}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import PlatformUI
import Abacus
import dydxStateManager
import Combine
import dydxFormatter

// MARK: AssetList

Expand Down Expand Up @@ -217,7 +218,7 @@ struct SortAction {

struct FilterAction {
static var actions: [FilterAction] {
[
var actions = [
FilterAction(type: .all,
content: .text(DataLocalizer.localize(path: "APP.GENERAL.ALL")),
action: { _, _ in
Expand All @@ -242,6 +243,21 @@ struct FilterAction {
assetMap[market.assetId]?.tags?.contains("Defi") ?? false
})
]
if dydxBoolFeatureFlag.showPredictionMarketsUI.isEnabled {
let predictionMarketText = DataLocalizer.localize(path: "APP.GENERAL.PREDICTION_MARKET")
let newPillConfig = TabItemViewModel.TabItemContent.PillConfig(text: DataLocalizer.localize(path: "APP.GENERAL.NEW"),
textColor: .colorPurple,
backgroundColor: .colorFadedPurple)
let content = TabItemViewModel.TabItemContent.textWithPillAccessory(text: predictionMarketText,
pillConfig: newPillConfig)
let predictionMarketsAction = FilterAction(type: .predictionMarkets,
content: content,
action: { market, assetMap in
assetMap[market.assetId]?.tags?.contains("Prediction Market") ?? false
})
actions.insert(predictionMarketsAction, at: 2)
}
return actions
}

let type: MarketFiltering
Expand All @@ -264,6 +280,7 @@ enum MarketSorting {
enum MarketFiltering {
case all
case favorited
case predictionMarkets
case layer1
case layer2
case defi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ private class dydxMarketsViewPresenter: HostedViewPresenter<dydxMarketsViewModel
viewModel?.header = dydxMarketsHeaderViewModel(searchAction: {
Router.shared?.navigate(to: RoutingRequest(path: "/markets/search"), animated: true, completion: nil)
})

// TODO: remove after election day
// logic here turns this banner display off after election day
// Nov 6 12am ET https://currentmillis.com/?1730869200010
let electionDate = Date(timeIntervalSince1970: 1730869200)
if Date.now <= electionDate && dydxBoolFeatureFlag.showPredictionMarketsUI.isEnabled {
viewModel?.banner = dydxMarketsBannerViewModel(navigationAction: {
Router.shared?.navigate(to: RoutingRequest(path: "/trade/TRUMP-USD"), animated: true, completion: nil)
})
}

viewModel?.summary = dydxMarketSummaryViewModel()
viewModel?.filter = dydxMarketAssetFilterViewModel(contents: FilterAction.actions.map(\.content),
onSelectionChanged: { [weak self] selectedIdx in
Expand Down
4 changes: 4 additions & 0 deletions dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
2728CE1B2BBCD2AB004C9323 /* dydxGainLossInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2728CE1A2BBCD2AB004C9323 /* dydxGainLossInputViewModel.swift */; };
2729123E2C06A775003F3EA0 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 2729123D2C06A775003F3EA0 /* Introspect */; };
272912402C06A780003F3EA0 /* KeyboardObserving in Frameworks */ = {isa = PBXBuildFile; productRef = 2729123F2C06A780003F3EA0 /* KeyboardObserving */; };
2729F9E92C64093700769BA4 /* dydxMarketsBannerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2729F9E82C64093700769BA4 /* dydxMarketsBannerViewModel.swift */; };
273C2F382C496F4F00F8391F /* dydxSliderInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 273C2F372C496F4F00F8391F /* dydxSliderInputView.swift */; };
273F50162B7C3F120034792A /* SignedAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 273F50152B7C3F120034792A /* SignedAmountView.swift */; };
274C47F02C0FC9A4000212C3 /* MemoBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C47EF2C0FC9A4000212C3 /* MemoBox.swift */; };
Expand Down Expand Up @@ -545,6 +546,7 @@
271010462BC7454B0037091A /* dydxCustomAmountViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxCustomAmountViewModel.swift; sourceTree = "<group>"; };
272030172A7812B900D233B9 /* UINavigationController+SwipeBackNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+SwipeBackNavigation.swift"; sourceTree = "<group>"; };
2728CE1A2BBCD2AB004C9323 /* dydxGainLossInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxGainLossInputViewModel.swift; sourceTree = "<group>"; };
2729F9E82C64093700769BA4 /* dydxMarketsBannerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMarketsBannerViewModel.swift; sourceTree = "<group>"; };
273C2F372C496F4F00F8391F /* dydxSliderInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSliderInputView.swift; sourceTree = "<group>"; };
273F50152B7C3F120034792A /* SignedAmountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedAmountView.swift; sourceTree = "<group>"; };
2742C04D2BF6897A00E13C09 /* dydxAnalytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = dydxAnalytics.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -753,6 +755,7 @@
02A8975F28E6A962006F1658 /* dydxMarketAssetListView.swift */,
02D1344028EBA86200B46941 /* dydxMarketAssetSortView.swift */,
02D1344E28ECA75C00B46941 /* dydxMarketAssetFilterView.swift */,
2729F9E82C64093700769BA4 /* dydxMarketsBannerViewModel.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -2035,6 +2038,7 @@
024F48902965CE9200E40247 /* dydxPortfolioDetailsView.swift in Sources */,
02A8976028E6A962006F1658 /* dydxMarketAssetListView.swift in Sources */,
02FD2B8B292307A200A5609E /* LeverageRiskChange.swift in Sources */,
2729F9E92C64093700769BA4 /* dydxMarketsBannerViewModel.swift in Sources */,
024F465929646BF000E40247 /* dydxMarketDepthHightlightView.swift in Sources */,
02D1342228EB564900B46941 /* SharedMarketView.swift in Sources */,
270BA8CF2A6F1470009212EA /* dydxDebugThemeViewerView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// dydxMarketsHeaderView.swift
// dydxViews
//
// Created by Rui Huang on 9/1/22.
// Copyright © 2022 dYdX Trading Inc. All rights reserved.
//

import SwiftUI
import PlatformUI
import Utilities

public class dydxMarketsBannerViewModel: PlatformViewModel {
public var navigationAction: (() -> Void)

static var previewValue: dydxMarketsBannerViewModel = {
let vm = dydxMarketsBannerViewModel(navigationAction: {})
return vm
}()

public init(navigationAction: @escaping (() -> Void)) {
self.navigationAction = navigationAction
}

public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView {
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in
guard let self = self else { return AnyView(PlatformView.nilView) }
return dydxMarketsBannerView(viewModel: self)
.wrappedInAnyView()
}
}
}

private struct dydxMarketsBannerView: View {
var viewModel: dydxMarketsBannerViewModel

var textStack: some View {
HStack(alignment: .top, spacing: 6) {
Text("🇺🇸")
.themeFont(fontType: .base, fontSize: .medium)
VStack(alignment: .leading, spacing: 4) {
Text(localizerPathKey: "APP.PREDICTION_MARKET.LEVERAGE_TRADE_US_ELECTION_SHORT")
.themeFont(fontType: .base, fontSize: .medium)
.themeColor(foreground: .textPrimary)
Text(localizerPathKey: "APP.PREDICTION_MARKET.WITH_PREDICTION_MARKETS")
.themeFont(fontType: .base, fontSize: .small)
.themeColor(foreground: .textSecondary)
}
}
}

var navButton: some View {
Button(action: viewModel.navigationAction) {
Text("")
.themeFont(fontType: .base, fontSize: .large)
.themeColor(foreground: .textSecondary)
.centerAligned()
}
.frame(width: 32, height: 32)
.themeColor(background: .layer6)
.borderAndClip(style: .circle, borderColor: .layer6)
}

var body: some View {
HStack(spacing: 0) {
textStack
Spacer(minLength: 8)
navButton
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.themeColor(background: .layer1)
.clipShape(.rect(cornerRadius: 16))
}
}
27 changes: 19 additions & 8 deletions dydx/dydxViews/dydxViews/_v4/Markets/dydxMarketsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class dydxMarketsViewModel: PlatformViewModel {
private static let topId = UUID().uuidString

@Published public var header = dydxMarketsHeaderViewModel()
@Published public var banner: dydxMarketsBannerViewModel?
@Published public var summary = dydxMarketSummaryViewModel()
@Published public var filter = dydxMarketAssetFilterViewModel()
@Published public var sort = dydxMarketAssetSortViewModel()
Expand All @@ -30,6 +31,7 @@ public class dydxMarketsViewModel: PlatformViewModel {
public static var previewValue: dydxMarketsViewModel = {
let vm = dydxMarketsViewModel()
vm.header = .previewValue
vm.banner = .previewValue
vm.summary = .previewValue
vm.filter = .previewValue
vm.sort = .previewValue
Expand All @@ -39,24 +41,33 @@ public class dydxMarketsViewModel: PlatformViewModel {

public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView {
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in
guard let self = self else { return AnyView(PlatformView.nilView) }
let view = VStack(spacing: 0) {
self?.header.createView(parentStyle: style)
self.header.createView(parentStyle: style)
.padding(.horizontal, 16)

ScrollViewReader { proxy in
ScrollView(showsIndicators: false) {
LazyVStack(pinnedViews: [.sectionHeaders]) {
self?.summary.createView(parentStyle: style)

if let banner = self.banner {
banner
.createView()
.padding(.horizontal, 16)
.padding(.top, 12)
}

self.summary.createView(parentStyle: style)
.themeColor(background: .layer2)
.zIndex(.greatestFiniteMagnitude)
.padding(.horizontal, 16)

let header =
VStack(spacing: 0) {
self?.filter.createView(parentStyle: style)
self.filter.createView(parentStyle: style)
.padding(.horizontal, 16)
Spacer()
self?.sort.createView(parentStyle: style)
self.sort.createView(parentStyle: style)
.padding(.leading, 16)
Spacer(minLength: 12)
}
Expand All @@ -66,23 +77,23 @@ public class dydxMarketsViewModel: PlatformViewModel {

Section(header: header) {
VStack(spacing: 12) {
self?.assetList?
self.assetList?
.createView(parentStyle: style)
Spacer(minLength: 64)
}
.padding(.horizontal, 16)
.animation(.default)
}
.onChange(of: self?.scrollAction) { newValue in
.onChange(of: self.scrollAction) { newValue in
if newValue == .toTop {
withAnimation {
proxy.scrollTo(Self.topId)
}
}
self?.scrollAction = .none
self.scrollAction = .none
}
.onAppear {
self?.scrollAction = .none
self.scrollAction = .none
}

// account for scrolling behind tab bar
Expand Down

0 comments on commit 2878489

Please sign in to comment.