Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI-638: prediction markets UI #222

Merged
merged 5 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public struct FontTypeDetail: Codable, Equatable {

public extension ThemeFont {

private func uiFont(of fontType: FontType, fontSize: FontSize) -> UIFont? {
func uiFont(of fontType: FontType, fontSize: FontSize) -> UIFont? {
let sizeValue: Float
switch fontSize {
case .custom(size: let size):
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 @@ -168,6 +168,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 @@ -542,6 +543,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 @@ -749,6 +751,7 @@
02A8975F28E6A962006F1658 /* dydxMarketAssetListView.swift */,
02D1344028EBA86200B46941 /* dydxMarketAssetSortView.swift */,
02D1344E28ECA75C00B46941 /* dydxMarketAssetFilterView.swift */,
2729F9E82C64093700769BA4 /* dydxMarketsBannerViewModel.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -2020,6 +2023,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
Loading