Skip to content

Commit

Permalink
Simple UI Portfolio design feedback (#331)
Browse files Browse the repository at this point in the history
* Add feature flag and mode selector for simple UI

* WIP

* Search

* Onboarding and pnls

* Move the swiftui chart logic to dydxLineChartViewModel for reuse

* Chart

* Add delay to reduce loading state updates

* Default to show 90D PNL chart

* Nav

* Header

* Chart Control

* Market details

* Position info

* TradeSheet and buy/sell tips

* WIP

* Size input

* Validation error

* Keyboard

* keyboard optimization

* CTA button

* background color

* Clean up

* Menu

* History bottom tabbar area

* Mode switcher

* Keyboard/UI optimizations

* Design review feedbacks

* light logo

* Clean up

* Switch default theme to Dark

* Add gradient to the market list bottom
  • Loading branch information
ruixhuang authored Jan 9, 2025
1 parent 7ec519a commit b6e783a
Show file tree
Hide file tree
Showing 32 changed files with 338 additions and 118 deletions.
12 changes: 12 additions & 0 deletions PlatformUI/PlatformUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
0207FCA62D2BCEDB004C2C9F /* KeyboardObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0207FCA52D2BCEDA004C2C9F /* KeyboardObserving.swift */; };
0230377828C15C0E00412B72 /* PlatformViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0230377728C15C0E00412B72 /* PlatformViewModel.swift */; };
023788F528B9924D00F212E1 /* PlatformButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023788F428B9924D00F212E1 /* PlatformButton.swift */; };
0242E3E92A9B1AE1007605F9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0242E3E82A9B1AE1007605F9 /* Media.xcassets */; };
Expand Down Expand Up @@ -104,6 +105,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
0207FCA52D2BCEDA004C2C9F /* KeyboardObserving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardObserving.swift; sourceTree = "<group>"; };
0230377728C15C0E00412B72 /* PlatformViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewModel.swift; sourceTree = "<group>"; };
023788F428B9924D00F212E1 /* PlatformButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformButton.swift; sourceTree = "<group>"; };
0242E3E82A9B1AE1007605F9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -175,6 +177,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
0207FCA42D2BCECD004C2C9F /* KeyboardObserving */ = {
isa = PBXGroup;
children = (
0207FCA52D2BCEDA004C2C9F /* KeyboardObserving.swift */,
);
path = KeyboardObserving;
sourceTree = "<group>";
};
023788EF28B991F100F212E1 /* Buttons */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -316,6 +326,7 @@
02E2C92D28A1C8A400F7C3BE /* PlatformUI */ = {
isa = PBXGroup;
children = (
0207FCA42D2BCECD004C2C9F /* KeyboardObserving */,
02F16FDA28B491EE0085DC58 /* PlatformUI.swift */,
0230377728C15C0E00412B72 /* PlatformViewModel.swift */,
0278DD0D2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift */,
Expand Down Expand Up @@ -655,6 +666,7 @@
files = (
0273A32D2ACDFFAC001B89F5 /* ThemeFontCache.swift in Sources */,
02714C8C29E0C3AD00CC1C44 /* ComboBox.swift in Sources */,
0207FCA62D2BCEDB004C2C9F /* KeyboardObserving.swift in Sources */,
02F16FE228B53A200085DC58 /* SampleStyleLabel.swift in Sources */,
6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */,
27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */,
Expand Down
27 changes: 19 additions & 8 deletions PlatformUI/PlatformUI/Components/Input/PlatformInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ private struct PlatformInputView: View {
self.model = model
self.parentStyle = parentStyle
self.styleKey = styleKey
self.isFocused = model.isFocused
}

var body: some View {
Expand All @@ -44,9 +45,12 @@ private struct PlatformInputView: View {
.onChange(of: model.isFocused) {
isFocused = $0
}
// .onChange(of: isFocused) {
// model.isFocused = $0
// }
.if(model.twoWayBinding, transform: { content in
content
.onChange(of: isFocused) {
model.isFocused = $0
}
})
.onTapGesture {
isFocused = true
}
Expand Down Expand Up @@ -113,6 +117,7 @@ public class PlatformInputModel: PlatformViewModel {
@Published public var truncateMode: Text.TruncationMode = .tail
@Published public var focusedOnAppear: Bool = false
@Published public var isFocused: Bool = false
@Published public var twoWayBinding: Bool = false

public init(label: String? = nil,
labelAccessory: AnyView? = nil,
Expand All @@ -125,7 +130,8 @@ public class PlatformInputModel: PlatformViewModel {
onEditingChanged: ((Bool) -> Void)? = nil,
truncateMode: Text.TruncationMode = .tail,
focusedOnAppear: Bool = false,
isFocused: Bool = false) {
isFocused: Bool = false,
twoWayBinding: Bool = false) {
self.label = label
self.labelAccessory = labelAccessory
self.value = value
Expand All @@ -138,6 +144,7 @@ public class PlatformInputModel: PlatformViewModel {
self.truncateMode = truncateMode
self.focusedOnAppear = focusedOnAppear
self.isFocused = isFocused
self.twoWayBinding = twoWayBinding
}

public static var previewValue: PlatformInputModel = {
Expand Down Expand Up @@ -288,7 +295,8 @@ open class PlatformTextInputViewModel: PlatformValueInputViewModel {

private let truncateMode: Text.TruncationMode
private let focusedOnAppear: Bool

private let twoWayBinding: Bool

@Published public var isFocused: Bool = false

public init(label: String? = nil,
Expand All @@ -300,17 +308,19 @@ open class PlatformTextInputViewModel: PlatformValueInputViewModel {
contentType: UITextContentType? = nil,
onEdited: ((String?) -> Void)? = nil,
truncateMode: Text.TruncationMode = .middle,
focusedOnAppear: Bool = false) {
focusedOnAppear: Bool = false,
twoWayBinding: Bool = false) {
self.inputType = inputType
self.truncateMode = truncateMode
self.focusedOnAppear = focusedOnAppear
self.twoWayBinding = twoWayBinding
super.init(label: label, labelAccessory: labelAccessory, valueAccessoryView: valueAccessoryView, onEdited: onEdited)
self.value = value
input = value ?? ""
self.placeHolder = placeHolder
self.contentType = contentType
}

override open 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) }
Expand All @@ -328,7 +338,8 @@ open class PlatformTextInputViewModel: PlatformValueInputViewModel {
},
truncateMode: truncateMode,
focusedOnAppear: focusedOnAppear,
isFocused: isFocused
isFocused: isFocused,
twoWayBinding: twoWayBinding
)

return AnyView(PlatformInputView(model: model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ private struct SheetViewModifier: ViewModifier {
Spacer()
}
}
.frame(maxWidth: .infinity)
.environmentObject(themeSettings)
)
case .fitSize:
Expand All @@ -243,6 +244,7 @@ private struct SheetViewModifier: ViewModifier {
}
}
}
.frame(maxWidth: .infinity)
.environmentObject(themeSettings)
}
)
Expand All @@ -258,6 +260,7 @@ private struct SheetViewModifier: ViewModifier {
.themeColor(background: .layer3)
.cornerRadius(36, corners: [.topLeft, .topRight])
}
.frame(maxWidth: .infinity)
.ignoresSafeArea(edges: [.all])
.wrappedInAnyView()
}
Expand Down
63 changes: 63 additions & 0 deletions PlatformUI/PlatformUI/KeyboardObserving/KeyboardObserving.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// KeyboardObserving.swift
// PlatformUI
//
// Created by Rui Huang on 06/01/2025.
//

import Foundation
import SwiftUI
import UIKit

public enum KeyboardObservingMode {
case yPadding, yOffset
}

extension View {
public func keyboardObserving(offset: CGFloat = 0.0, mode: KeyboardObservingMode = .yPadding) -> some View {
self.modifier(KeyboardObserving(offset: offset, mode: mode))
}
}

struct KeyboardObserving: ViewModifier {

let offset: CGFloat
let mode: KeyboardObservingMode

@State var keyboardHeight: CGFloat = 0
@State var keyboardAnimationDuration: Double = 0

func body(content: Content) -> some View {
content
.if(mode == .yPadding, transform: { content in
content.padding([.bottom], keyboardHeight)
})
.if(mode == .yOffset, transform: { content in
content.offset(y: -keyboardHeight)
})
.edgesIgnoringSafeArea((keyboardHeight > 0) ? [.bottom] : [])
.animation(.easeOut(duration: keyboardAnimationDuration), value: keyboardHeight)
.onReceive(
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
.receive(on: RunLoop.main),
perform: updateKeyboardHeight
)
}

func updateKeyboardHeight(_ notification: Notification) {
guard let info = notification.userInfo else { return }
// Get the duration of the keyboard animation
keyboardAnimationDuration = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double)
?? 0.25

guard let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
else { return }
// If the top of the frame is at the bottom of the screen, set the height to 0.
if keyboardFrame.origin.y == UIScreen.main.bounds.height {
keyboardHeight = 0
} else {
// IMPORTANT: This height will _include_ the SafeAreaInset height.
keyboardHeight = keyboardFrame.height + offset
}
}
}
11 changes: 1 addition & 10 deletions dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "4abc8b6e37ddb299948f224ccf7a7ccf9ed6d5156db6de917ea842dc596c3bbf",
"originHash" : "9ced47665f0ca83fddbd77fc554301f96d0ede1667b9538b0706d8d08115275f",
"pins" : [
{
"identity" : "bigint",
Expand Down Expand Up @@ -37,15 +37,6 @@
"revision" : "748a85b1dfe9a2fa592bd9266c5a926e4e1d3f44"
}
},
{
"identity" : "keyboardobserving",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nickffox/KeyboardObserving",
"state" : {
"branch" : "master",
"revision" : "48134b5460435cc9d048223ad7560ee2e40f3d4a"
}
},
{
"identity" : "percy-xcui-swift",
"kind" : "remoteSourceControl",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum dydxSettingsStoreKey: String, CaseIterable {
public var defaultValue: Any? {
switch self {
case .language: return DataLocalizer.shared?.language
case .v4Theme: return dydxThemeType.classicDark.rawValue
case .v4Theme: return dydxThemeType.dark.rawValue
case .directionColorPreference: return "green_is_up"
case .shouldDisplayInAppNotifications: return true
case .gasToken: return "USDC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public class dydxHistoryViewBuilder: NSObject, ObjectBuilderProtocol {
private class dydxHistoryViewController: HostingViewController<PlatformView, dydxHistoryViewModel> {
override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool {
if request?.path == "/portfolio/history" {
let inTabBar = parser.asBoolean(request?.params?["inTabBar"])?.boolValue ?? true
if inTabBar {
configuration = .tabbarItemView
} else {
configuration = .default
}
return true
}
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ class dydxSimpleUIMarketListViewPresenter: HostedViewPresenter<dydxSimpleUIMarke
return dydxSimpleUIMarketViewModel.createFrom(market: market, asset: asset, position: position)
}
.sorted { lhs, rhs in
if lhs.leverage != nil && rhs.leverage != nil {
let lhsLeverage = lhs.leverage ?? 0
let rhsLeverage = rhs.leverage ?? 0
if lhsLeverage != 0 && rhsLeverage != 0 {
return (lhs.volumn ?? 0) > (rhs.volumn ?? 0)
} else if lhs.leverage != nil {
} else if lhsLeverage != 0 {
return true
} else if rhs.leverage != nil {
} else if rhsLeverage != 0 {
return false
}
return (lhs.volumn ?? 0) > (rhs.volumn ?? 0)
Expand All @@ -94,14 +96,13 @@ private extension dydxSimpleUIMarketViewModel {
side = SideTextViewModel(side: .short)
}
}
let leverage = dydxFormatter.shared.raw(number: position?.leverage.current?.doubleValue, digits: 3)
return dydxSimpleUIMarketViewModel(marketId: market.id,
assetName: asset?.displayableAssetId ?? market.assetId,
iconUrl: asset?.resources?.imageUrl,
price: price,
change: change,
sideText: side,
leverage: leverage,
leverage: position?.leverage.current?.doubleValue,
volumn: market.perpetual?.volume24HUSDC?.doubleValue,
onMarketSelected: {
Router.shared?.navigate(to: RoutingRequest(path: "/market", params: ["market": market.id]), animated: true, completion: nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ private extension dydxSimpleUIMarketsHeaderViewModel.MenuItem {
static let history = dydxSimpleUIMarketsHeaderViewModel.MenuItem(
icon: "icon_clock",
title: DataLocalizer.localize(path: "APP.GENERAL.HISTORY")) {
Router.shared?.navigate(to: RoutingRequest(path: "/portfolio/history"), animated: true, completion: nil)
Router.shared?.navigate(to: RoutingRequest(path: "/portfolio/history",
params: ["inTabBar": "false"]),
animated: true, completion: nil)
}

static let transfers = dydxSimpleUIMarketsHeaderViewModel.MenuItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ class dydxSimpleUITradeInputSizeViewPresenter: HostedViewPresenter<dydxSimpleUIT
viewModel = dydxSimpleUITradeInputSizeViewModel()
viewModel?.sizeItem = sizeItem
viewModel?.usdSizeItem = usdSizeItem
viewModel?.showingUsdc = true
viewModel?.focusState = .none
}

func updateFocusState(_ focusState: dydxSimpleUITradeInputSizeViewModel.FocusState) {
viewModel?.focusState = focusState
}

override func start() {
Expand All @@ -68,8 +72,8 @@ class dydxSimpleUITradeInputSizeViewPresenter: HostedViewPresenter<dydxSimpleUIT
let marketConfigs = configsAndAsset?.configs
let asset = configsAndAsset?.asset

viewModel?.sizeItem.placeHolder = dydxFormatter.shared.raw(number: .zero, digits: marketConfigs?.displayStepSizeDecimals?.intValue ?? 0)
viewModel?.sizeItem.tokenSymbol = configsAndAsset?.asset?.displayableAssetId ?? asset?.id
viewModel?.sizeItem?.placeHolder = dydxFormatter.shared.raw(number: .zero, digits: marketConfigs?.displayStepSizeDecimals?.intValue ?? 0)
viewModel?.sizeItem?.tokenSymbol = configsAndAsset?.asset?.displayableAssetId ?? asset?.id

for itemViewModel in [viewModel?.sizeItem, viewModel?.usdSizeItem] {
if tradeInput.options?.needsSize ?? false {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,25 @@ private class dydxSimpleUITradeInputViewPresenter: HostedViewPresenter<dydxSimpl

weak var delegate: dydxSimpeUITradeInputViewPresenterDelegate?

private var lastSizeFocusState: dydxSimpleUITradeInputSizeViewModel.FocusState?

// MARK: dydxTradeInputViewPresenterProtocol

func updateViewControllerPosition(position: FloatingPanel.FloatingPanelState) {
switch position {
case .tip:
viewModel?.displayState = .tip
if sizeViewPresenter.viewModel?.focusState != dydxSimpleUITradeInputSizeViewModel.FocusState.none {
lastSizeFocusState = sizeViewPresenter.viewModel?.focusState
}
sizeViewPresenter.updateFocusState(.none)
default:
viewModel?.displayState = .full
if let lastSizeFocusState, lastSizeFocusState != dydxSimpleUITradeInputSizeViewModel.FocusState.none {
sizeViewPresenter.updateFocusState(lastSizeFocusState)
} else {
sizeViewPresenter.updateFocusState(.atUsdcSize)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Abacus
import dydxStateManager
import dydxFormatter
import Combine
import KeyboardObserving

protocol dydxTransferOutViewPresenterProtocol: HostedViewPresenterProtocol {
var viewModel: dydxTransferOutViewModel? { get }
Expand Down
Loading

0 comments on commit b6e783a

Please sign in to comment.