From 590db8e8e6ebd2cea1cd6474f377cb2c77968bfc Mon Sep 17 00:00:00 2001 From: mike-dydx <149746839+mike-dydx@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:47:34 -0400 Subject: [PATCH] CLI-638: iOS prediction markets UI (#226) * add footer text when prediction markets is selected filter * presenting prediction markets notice * new `present_over_full_screen` presentation style * speed up animation, improve timing for trade input presentation * add checkbox * remove test code * touchups * change logic to display prediction markets notice --------- Co-authored-by: Mike --- .../_iOS/_App/MappedUIKitRouter.swift | 16 +++ .../Theme/ThemeViewModifiers.swift | 54 +++++--- .../RoutingKit/_Router/MappedRouter.swift | 4 + .../dydxPresenters.xcodeproj/project.pbxproj | 4 + .../_Features/routing_swiftui.json | 6 +- .../_v4/KeyValueStoreProtocolStore+Ext.swift | 2 + .../dydxMarketInfoViewBuilder.swift | 45 +++++-- ...dxPredictionMarketsNoticeViewBuilder.swift | 56 ++++++++ .../_v4/Markets/dydxMarketsViewBuilder.swift | 5 + .../dydxTradeInputViewBuilder.swift | 9 +- .../dydxViews.xcodeproj/project.pbxproj | 4 + .../Contents.json | 12 ++ .../icon_prediction_event.pdf | Bin 0 -> 4607 bytes .../Contents.json | 12 ++ .../icon_settlement_cash.pdf | Bin 0 -> 3089 bytes .../dydxPredictionMarketsNoticeView.swift | 121 ++++++++++++++++++ .../_v4/Markets/dydxMarketsView.swift | 14 +- 17 files changed, 330 insertions(+), 34 deletions(-) create mode 100644 dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxPredictionMarketsNoticeViewBuilder.swift create mode 100644 dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/Contents.json create mode 100644 dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/icon_prediction_event.pdf create mode 100644 dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/Contents.json create mode 100644 dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/icon_settlement_cash.pdf create mode 100644 dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxPredictionMarketsNoticeView.swift diff --git a/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift index ad31939e1..e15c7576b 100644 --- a/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift +++ b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift @@ -256,6 +256,8 @@ open class MappedUIKitRouter: MappedRouter { case .popup: popup(viewController, animated: animated, completion: completion) + case .presentOverFullScreen: + presentOverFullScreen(viewController, animated: animated, completion: completion) } } @@ -372,6 +374,20 @@ open class MappedUIKitRouter: MappedRouter { completion?(nil, false) } } + + private func presentOverFullScreen(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topParent() { + let navigationController = UIViewController.navigation(with: viewController) + navigationController.modalPresentationStyle = .overFullScreen + //speeds up the animation by 2x + navigationController.view.layer.speed = 2 + topmost.present(navigationController, animated: animated) { + } + completion?(viewController, true) + } else { + completion?(nil, false) + } + } private func float(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { if let topmost = ViewControllerStack.shared?.topParent() { diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift index 91d93b0e4..91a02aa4b 100644 --- a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift @@ -192,6 +192,10 @@ private struct StyleKeyModifier: ViewModifier { public enum MakeSheetStyle { case fullScreen, fitSize + /// note this style behaves significantly different. It takes content and rounds the corners, adds insets and safe area ignore. + /// only use on views presented with presentOverFullSreen presentation style + /// these sheets are not drag-to-dismiss + case forPresentedOverCurrentScreen } public extension View { @@ -213,7 +217,8 @@ private struct SheetViewModifier: ViewModifier { .clipShape(Capsule()) .padding(.top, topPadding) - if sheetStyle == .fullScreen { + switch sheetStyle { + case .fullScreen: return AnyView( ZStack(alignment: .top) { content @@ -223,26 +228,41 @@ private struct SheetViewModifier: ViewModifier { Spacer() } } - .environmentObject(themeSettings) + .environmentObject(themeSettings) ) - } else { + case .fitSize: return AnyView( - VStack(spacing: 0) { - Spacer() - ZStack(alignment: .top) { - content - .cornerRadius(36, corners: [.topLeft, .topRight]) - VStack { - dragIndicator + ZStack(alignment: .bottom) { + VStack(spacing: 0) { + Spacer() + ZStack(alignment: .top) { + content + .cornerRadius(36, corners: [.topLeft, .topRight]) + VStack { + dragIndicator + } } } + .environmentObject(themeSettings) } - .environmentObject(themeSettings) ) + + case .forPresentedOverCurrentScreen: + return ZStack(alignment: .bottom) { + ThemeColor.SemanticColor.layer0.color + .opacity(0.8) + content + .padding(.top, 24) + .padding(.bottom, max((content.safeAreaInsets?.bottom ?? 0), 16)) + .padding(.horizontal, 24) + .themeColor(background: .layer3) + .cornerRadius(36, corners: [.topLeft, .topRight]) + } + .ignoresSafeArea(edges: [.all]) + .wrappedInAnyView() } } } - // MARK: Make any view a button public extension View { @@ -387,7 +407,7 @@ private struct LeftAlignedModifier: ViewModifier { func body(content: Content) -> some View { HStack(spacing: 0) { content - Spacer() + Spacer(minLength: 0) } } } @@ -419,9 +439,9 @@ public extension View { private struct TopAlignedModifier: ViewModifier { func body(content: Content) -> some View { - VStack { + VStack(spacing: 0) { content - Spacer() + Spacer(minLength: 0) } } } @@ -436,8 +456,8 @@ public extension View { private struct BottomAlignedModifier: ViewModifier { func body(content: Content) -> some View { - VStack { - Spacer() + VStack(spacing: 0) { + Spacer(minLength: 0) content } } diff --git a/RoutingKit/RoutingKit/_Router/MappedRouter.swift b/RoutingKit/RoutingKit/_Router/MappedRouter.swift index 1f7a5518d..1c2189f1e 100644 --- a/RoutingKit/RoutingKit/_Router/MappedRouter.swift +++ b/RoutingKit/RoutingKit/_Router/MappedRouter.swift @@ -22,6 +22,7 @@ public enum RoutingPresentation: Int { case drawer /// center-screen popup which dims the background case popup + case presentOverFullScreen } private struct PathTuple { @@ -77,6 +78,9 @@ public class RoutingMap: NSObject, ParsingProtocol { case "popup": presentation = .popup + + case "present_over_full_screen": + presentation = .presentOverFullScreen default: presentation = nil diff --git a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj index 18c2cdc59..f4c5b82ee 100644 --- a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj +++ b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj @@ -145,6 +145,7 @@ 276908FF2AAFB22F0075B2D6 /* dydxPortfolioTransfersViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 276908FE2AAFB22F0075B2D6 /* dydxPortfolioTransfersViewPresenter.swift */; }; 277754352C069F8600E3E985 /* OnboardingAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277754282C069F8600E3E985 /* OnboardingAnalytics.swift */; }; 277987512BA33F15006DC5CD /* dydxSelectedMarketStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277987502BA33F15006DC5CD /* dydxSelectedMarketStore.swift */; }; + 277DB22F2C669A6800964F9B /* dydxPredictionMarketsNoticeViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277DB22E2C669A6800964F9B /* dydxPredictionMarketsNoticeViewBuilder.swift */; }; 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 */; }; @@ -533,6 +534,7 @@ 276908FE2AAFB22F0075B2D6 /* dydxPortfolioTransfersViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPortfolioTransfersViewPresenter.swift; sourceTree = ""; }; 277754282C069F8600E3E985 /* OnboardingAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingAnalytics.swift; sourceTree = ""; }; 277987502BA33F15006DC5CD /* dydxSelectedMarketStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSelectedMarketStore.swift; sourceTree = ""; }; + 277DB22E2C669A6800964F9B /* dydxPredictionMarketsNoticeViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPredictionMarketsNoticeViewBuilder.swift; sourceTree = ""; }; 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 = ""; }; @@ -1222,6 +1224,7 @@ 277987502BA33F15006DC5CD /* dydxSelectedMarketStore.swift */, 0274B34328F113FD005AF69E /* Components */, 02B841B128EF6C6400C4D25B /* dydxMarketInfoViewBuilder.swift */, + 277DB22E2C669A6800964F9B /* dydxPredictionMarketsNoticeViewBuilder.swift */, ); path = MarketInfo; sourceTree = ""; @@ -2066,6 +2069,7 @@ 647D0F152A9FB1C600DA7815 /* dydxFrontendAlertsProvider.swift in Sources */, 02D1345828ECF30000B46941 /* dydxMarketsSearchViewBuilder.swift in Sources */, 27044F702BBB1CDF004C750D /* dydxTakeProfitStopLossViewPresenter.swift in Sources */, + 277DB22F2C669A6800964F9B /* dydxPredictionMarketsNoticeViewBuilder.swift in Sources */, 64529A4C2AE8705E000810E6 /* dydxUpdateWorker.swift in Sources */, 278A4DA42B8FDD9D003898EB /* dydxRatingsWorker.swift in Sources */, 02DDAD55292587C600CC7531 /* QRCodeDisplayBuilder.swift in Sources */, diff --git a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json index bfe3b805e..48a4283c8 100644 --- a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json +++ b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json @@ -293,7 +293,7 @@ "destination":"Share.storyboard", "presentation":"half" }, - "/trade/:market":{ + "/trade/:market": { "destination":"dydxPresenters.dydxMarketInfoViewBuilder", "presentation":"push" }, @@ -301,6 +301,10 @@ "destination":"dydxPresenters.dydxTradeInputViewBuilder", "presentation":"float" }, + "/trade/prediction_markets_notice": { + "destination":"dydxPresenters.dydxPredictionMarketsNoticeViewBuilder", + "presentation":"present_over_full_screen" + }, "/trade/status":{ "destination":"dydxPresenters.dydxTradeStatusViewBuilder", "presentation":"prompt" diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/KeyValueStoreProtocolStore+Ext.swift b/dydx/dydxPresenters/dydxPresenters/_v4/KeyValueStoreProtocolStore+Ext.swift index 51ace937b..e0161b66a 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/KeyValueStoreProtocolStore+Ext.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/KeyValueStoreProtocolStore+Ext.swift @@ -15,6 +15,7 @@ public enum dydxSettingsStoreKey: String, CaseIterable { case directionColorPreference = "direction_color_preference" case shouldDisplayInAppNotifications = "should_display_in_app_notifications" case gasToken = "gas_token" + case hidePredictionMarketsNoticeKey = "hide_prediction_markets_notice" public var defaultValue: Any? { switch self { @@ -23,6 +24,7 @@ public enum dydxSettingsStoreKey: String, CaseIterable { case .directionColorPreference: return "green_is_up" case .shouldDisplayInAppNotifications: return true case .gasToken: return "USDC" + case .hidePredictionMarketsNoticeKey: return false } } } diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift index 4dbaf5891..89daab598 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift @@ -26,23 +26,21 @@ public class dydxMarketInfoViewBuilder: NSObject, ObjectBuilderProtocol { } private class dydxMarketInfoViewController: HostingViewController { + private var hidePredictionMarketsNotice: Bool { + SettingsStore.shared?.value(forKey: dydxSettingsStoreKey.hidePredictionMarketsNoticeKey.rawValue) as? Bool ?? false + } + override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { if request?.path == "/trade" || request?.path == "/market", let presenter = presenter as? dydxMarketInfoViewPresenter { let selectedMarketId = request?.params?["market"] as? String ?? dydxSelectedMarketsStore.shared.lastSelectedMarket dydxSelectedMarketsStore.shared.lastSelectedMarket = selectedMarketId presenter.marketId = selectedMarketId + presenter.shouldDisplayFullTradeInputOnAppear = request?.path == "/trade" if let sectionRaw = request?.params?["currentSection"] as? String { let section = PortfolioSection(rawValue: sectionRaw) ?? .positions let preselectedSection = Section.allSections.map(\.key).firstIndex(of: section) ?? 0 presenter.viewModel?.sections.onSelectionChanged?(preselectedSection) } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if request?.path == "/trade" { - Router.shared?.navigate(to: RoutingRequest(path: "/trade/input", params: ["full": "true"]), animated: true, completion: nil) - } else { - Router.shared?.navigate(to: RoutingRequest(path: "/trade/input"), animated: true, completion: nil) - } - } return true } return false @@ -55,6 +53,7 @@ private protocol dydxMarketInfoViewPresenterProtocol: HostedViewPresenterProtoco private class dydxMarketInfoViewPresenter: HostedViewPresenter, dydxMarketInfoViewPresenterProtocol { @Published var marketId: String? + var shouldDisplayFullTradeInputOnAppear: Bool = false private let pagingPresenter = dydxMarketInfoPagingViewPresenter() private let statsPresenter = dydxMarketStatsViewPresenter() @@ -80,6 +79,10 @@ private class dydxMarketInfoViewPresenter: HostedViewPresenter() -> T? { + let presenter = dydxPredictionMarketsNoticeViewPresenter() + let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView() + return dydxPredictionMarketsNoticeViewController(presenter: presenter, view: view, configuration: .default) as? T + } +} + +private class dydxPredictionMarketsNoticeViewController: HostingViewController { + override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if request?.path == "/trade/prediction_markets_notice" { + return true + } + return false + } +} + +private protocol dydxPredictionMarketsNoticeViewPresenterProtocol: HostedViewPresenterProtocol { + var viewModel: dydxPredictionMarketsNoticeViewModel? { get } +} + +private class dydxPredictionMarketsNoticeViewPresenter: HostedViewPresenter, dydxPredictionMarketsNoticeViewPresenterProtocol { + + fileprivate static var hidePredictionMarketsNotice: Bool { + get { SettingsStore.shared?.value(forKey: dydxSettingsStoreKey.hidePredictionMarketsNoticeKey.rawValue) as? Bool ?? false } + set { SettingsStore.shared?.setValue(newValue, forKey: dydxSettingsStoreKey.hidePredictionMarketsNoticeKey.rawValue) } + } + + override init() { + super.init() + + viewModel = dydxPredictionMarketsNoticeViewModel() + viewModel?.continueAction = { + Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss"), animated: true, completion: nil) + } + + viewModel?.hidePredictionMarketsNotice = Self.hidePredictionMarketsNotice + viewModel?.$hidePredictionMarketsNotice + .sink { Self.hidePredictionMarketsNotice = $0 } + .store(in: &subscriptions) + } +} diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Markets/dydxMarketsViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Markets/dydxMarketsViewBuilder.swift index af6d168f8..2fde7bb07 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Markets/dydxMarketsViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Markets/dydxMarketsViewBuilder.swift @@ -62,6 +62,11 @@ private class dydxMarketsViewPresenter: HostedViewPresenter, FloatingInsetProvider, FloatedDelegate, dydxTradeInputViewPresenterDelegate { override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { - if request?.path == "/trade/input" { + if request?.path == "/trade/input", let presenter = presenter as? dydxTradeInputViewPresenter { AbacusStateManager.shared.startTrade() if request?.params?["full"] as? String == "true" { - (presenter as? dydxTradeInputViewPresenterProtocol)?.updateViewControllerPosition(position: .half) + presenter.updateViewControllerPosition(position: .half) move(to: .half) } else { - (presenter as? dydxTradeInputViewPresenterProtocol)?.updateViewControllerPosition(position: .tip) + presenter.updateViewControllerPosition(position: .tip) move(to: .tip) } - presenter?.viewModel?.editViewModel?.onScrollViewCreated = { [weak self] scrollView in + presenter.viewModel?.editViewModel?.onScrollViewCreated = { [weak self] scrollView in self?.floatTracking = scrollView } return true @@ -205,5 +205,6 @@ private class dydxTradeInputViewPresenter: HostedViewPresenter 0 ? .draft : .buySell } .store(in: &subscriptions) + } } diff --git a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj index a60e15d32..c4522891f 100644 --- a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj +++ b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj @@ -181,6 +181,7 @@ 277442972AD88C4900C91357 /* Satoshi-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 277442952AD88C4900C91357 /* Satoshi-Bold.otf */; }; 277442982AD88C4900C91357 /* Satoshi-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 277442962AD88C4900C91357 /* Satoshi-Regular.otf */; }; 27759F5C2B89125F002865A9 /* dydxInlineShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27759F5B2B89125F002865A9 /* dydxInlineShareView.swift */; }; + 277DB23E2C669AB700964F9B /* dydxPredictionMarketsNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277DB23D2C669AB700964F9B /* dydxPredictionMarketsNoticeView.swift */; }; 277E8F9F2B1A847D005CCBCB /* dydxTitledCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E8F9E2B1A847D005CCBCB /* dydxTitledCardView.swift */; }; 277E8FCB2B1E5798005CCBCB /* dydxProfileRewardsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E8FCA2B1E5798005CCBCB /* dydxProfileRewardsViewModel.swift */; }; 277E90132B1EA0D3005CCBCB /* dydxTradingRewardsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90122B1EA0D3005CCBCB /* dydxTradingRewardsView.swift */; }; @@ -559,6 +560,7 @@ 277442952AD88C4900C91357 /* Satoshi-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Satoshi-Bold.otf"; sourceTree = ""; }; 277442962AD88C4900C91357 /* Satoshi-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Satoshi-Regular.otf"; sourceTree = ""; }; 27759F5B2B89125F002865A9 /* dydxInlineShareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxInlineShareView.swift; sourceTree = ""; }; + 277DB23D2C669AB700964F9B /* dydxPredictionMarketsNoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPredictionMarketsNoticeView.swift; sourceTree = ""; }; 277E8F9E2B1A847D005CCBCB /* dydxTitledCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTitledCardView.swift; sourceTree = ""; }; 277E8FCA2B1E5798005CCBCB /* dydxProfileRewardsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxProfileRewardsViewModel.swift; sourceTree = ""; }; 277E90122B1EA0D3005CCBCB /* dydxTradingRewardsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTradingRewardsView.swift; sourceTree = ""; }; @@ -1320,6 +1322,7 @@ children = ( 02B8419E28EF68CE00C4D25B /* Components */, 02B8419C28EF68C300C4D25B /* dydxMarketInfoView.swift */, + 277DB23D2C669AB700964F9B /* dydxPredictionMarketsNoticeView.swift */, ); path = MarketInfo; sourceTree = ""; @@ -2032,6 +2035,7 @@ 02084B2D297FC2CD00CF9522 /* dydxTransferFaucetView.swift in Sources */, 022EDC8E29A048B3003D59A7 /* dydxClosePositionHeaderView.swift in Sources */, 27E0736B2C20D27F0034B963 /* dydxCancelPendingIsolatedOrdersView.swift in Sources */, + 277DB23E2C669AB700964F9B /* dydxPredictionMarketsNoticeView.swift in Sources */, 02F1D3882BEAA6CA00A9376C /* dydxTradeInputMarginView.swift in Sources */, 2700A3172C5D72BB00880AFA /* dydxVaultChartViewModel.swift in Sources */, 0256F53629AFFC9800A083C0 /* dydxOnboardConnectView.swift in Sources */, diff --git a/dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/Contents.json b/dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/Contents.json new file mode 100644 index 000000000..8b66b4baa --- /dev/null +++ b/dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_prediction_event.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/icon_prediction_event.pdf b/dydx/dydxViews/dydxViews/Media.xcassets/icon_prediction_event.imageset/icon_prediction_event.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d755cd14f9205ad1cee0f2f72e1b87bbbc010e54 GIT binary patch literal 4607 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 literal 0 HcmV?d00001 diff --git a/dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/Contents.json b/dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/Contents.json new file mode 100644 index 000000000..8892b1d88 --- /dev/null +++ b/dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_settlement_cash.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/icon_settlement_cash.pdf b/dydx/dydxViews/dydxViews/Media.xcassets/icon_settlement_cash.imageset/icon_settlement_cash.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1dafeb293169c702b9abd73ad0a5f841a594cdcf GIT binary patch literal 3089 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 E0j9xfod5s; literal 0 HcmV?d00001 diff --git a/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxPredictionMarketsNoticeView.swift b/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxPredictionMarketsNoticeView.swift new file mode 100644 index 000000000..3bd46e708 --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxPredictionMarketsNoticeView.swift @@ -0,0 +1,121 @@ +// +// dydxPredictionMarketsNoticeView.swift +// dydxViews +// +// Created by Michael Maguire on 8/9/24. +// + +import SwiftUI +import PlatformUI +import Utilities +import Popovers + +public class dydxPredictionMarketsNoticeViewModel: PlatformViewModel { + @Published public var hidePredictionMarketsNotice = false + @Published public var continueAction: (() -> Void)? + + public init() { } + + public static var previewValue: dydxPredictionMarketsNoticeViewModel { + let vm = dydxPredictionMarketsNoticeViewModel() + return vm + } + + 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 dydxPredictionMarketsNoticeView(viewModel: self) + .wrappedInAnyView() + } + } +} + +private struct dydxPredictionMarketsNoticeView: View { + @ObservedObject var viewModel: dydxPredictionMarketsNoticeViewModel + + var title: some View { + HStack(alignment: .center, spacing: 4) { + Text(localizerPathKey: "APP.PREDICTION_MARKET.PREDICTION_MARKETS") + .themeFont(fontSize: .larger) + .themeColor(foreground: .textPrimary) + Text(localizerPathKey: "APP.GENERAL.NEW") + .themeFont(fontSize: .smaller) + .padding(.horizontal, 4) + .padding(.vertical, 2.5) + .themeColor(foreground: .colorPurple) + .themeColor(background: .colorFadedPurple) + .clipShape(.rect(cornerRadius: 4)) + } + .leftAligned() + .wrappedInAnyView() + } + + var checkboxRow: some View { + HStack(spacing: 8) { + ZStack(alignment: .center) { + ThemeColor.SemanticColor.layer0.color + PlatformIconViewModel(type: .asset(name: "icon_checked", bundle: .dydxView), + clip: .noClip, + size: .init(width: 15, height: 15), + templateColor: .textPrimary) + .createView() + .opacity(viewModel.hidePredictionMarketsNotice ? 1 : 0) + } + .frame(width: 20, height: 20) + .borderAndClip(style: .cornerRadius(6), borderColor: .borderDefault) + .onTapGesture { + viewModel.hidePredictionMarketsNotice.toggle() + } + Text(localizerPathKey: "APP.GENERAL.DONT_SHOW_AGAIN") + .themeFont(fontSize: .medium) + .themeColor(foreground: .textSecondary) + } + .leftAligned() + } + + func infoRow(imageName: String, titlePathKey: String, descriptionPathKey: String) -> some View { + HStack(alignment: .center, spacing: 8) { + PlatformIconViewModel(type: .asset(name: imageName, bundle: .dydxView), + clip: .circle(background: .layer5, spacing: 10.5, borderColor: nil), + size: CGSize(width: 48, height: 48), + templateColor: nil) + .createView() + VStack(alignment: .leading, spacing: 2) { + Text(localizerPathKey: titlePathKey) + .themeFont(fontSize: .large) + .themeColor(foreground: .textSecondary) + Text(localizerPathKey: descriptionPathKey) + .themeFont(fontSize: .medium) + .themeColor(foreground: .textTertiary) + } + } + .leftAligned() + } + + var continueButton: some View { + let buttonContent = + Text(DataLocalizer.localize(path: "APP.COMPLIANCE_MODAL.CONTINUE")) + .wrappedViewModel + return PlatformButtonViewModel(content: buttonContent) { + viewModel.continueAction?() + } + .createView() + } + + var body: some View { + VStack(spacing: 16) { + title + VStack(spacing: 24) { + infoRow(imageName: "icon_settlement_cash", + titlePathKey: "APP.PREDICTION_MARKET.LEVERAGE_TRADE_EVENT_OUTCOMES_TITLE", + descriptionPathKey: "APP.PREDICTION_MARKET.LEVERAGE_TRADE_EVENT_OUTCOMES_DESCRIPTION") + infoRow(imageName: "icon_prediction_event", + titlePathKey: "APP.PREDICTION_MARKET.SETTLEMENT_OUTCOMES_TITLE", + descriptionPathKey: "APP.PREDICTION_MARKET.SETTLEMENT_OUTCOMES_DESCRIPTION") + checkboxRow + continueButton + } + } + .makeSheet(sheetStyle: .forPresentedOverCurrentScreen) + } +} diff --git a/dydx/dydxViews/dydxViews/_v4/Markets/dydxMarketsView.swift b/dydx/dydxViews/dydxViews/_v4/Markets/dydxMarketsView.swift index d8671e107..1c4ab6fa4 100644 --- a/dydx/dydxViews/dydxViews/_v4/Markets/dydxMarketsView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Markets/dydxMarketsView.swift @@ -22,6 +22,7 @@ public class dydxMarketsViewModel: PlatformViewModel { @Published public var banner: dydxMarketsBannerViewModel? @Published public var summary = dydxMarketSummaryViewModel() @Published public var filter = dydxMarketAssetFilterViewModel() + @Published public var filterFooterText: String? @Published public var sort = dydxMarketAssetSortViewModel() @Published public var assetList: dydxMarketAssetListViewModel? = dydxMarketAssetListViewModel() @Published public var scrollAction: ScrollAction = .none @@ -64,9 +65,16 @@ public class dydxMarketsViewModel: PlatformViewModel { let header = VStack(spacing: 0) { - self.filter.createView(parentStyle: style) - .padding(.horizontal, 16) - Spacer() + VStack(spacing: 8) { + self.filter.createView(parentStyle: style) + .padding(.horizontal, 16) + if let filterFooterText = self.filterFooterText { + Text(filterFooterText) + .themeFont(fontType: .base, fontSize: .small) + .themeColor(foreground: .textTertiary) + } + } + Spacer(minLength: 16) self.sort.createView(parentStyle: style) .padding(.leading, 16) Spacer(minLength: 12)