diff --git a/PlatformUI/PlatformUI.xcodeproj/project.pbxproj b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj index ab5b72ed3..ef574c43b 100644 --- a/PlatformUI/PlatformUI.xcodeproj/project.pbxproj +++ b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 02F38BF22A9AAE3700969E06 /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F38BF12A9AAE3700969E06 /* CircularProgressView.swift */; }; 1C811E336064517E256D1290 /* Pods_iOS_PlatformUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6793D8074DF2E7FE4592138 /* Pods_iOS_PlatformUITests.framework */; }; 27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27044F872BBB2ADF004C750D /* Text+Ext.swift */; }; + 274C47EA2C0FC6CF000212C3 /* EdgeInsets+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */; }; 277E7AC62BBF3BE8009F95DE /* InlineAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */; }; 27E6A7322AB8D5F600026CB5 /* SwipeActionsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */; }; 6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6488BBDB296F6AEA0096502F /* TabItemViewModel.swift */; }; @@ -135,6 +136,7 @@ 02F16FE128B53A200085DC58 /* SampleStyleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStyleLabel.swift; sourceTree = ""; }; 02F38BF12A9AAE3700969E06 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; 27044F872BBB2ADF004C750D /* Text+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Ext.swift"; sourceTree = ""; }; + 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EdgeInsets+Ext.swift"; sourceTree = ""; }; 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAlert.swift; sourceTree = ""; }; 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsViewModifier.swift; sourceTree = ""; }; 366BD14FE1ED4F2AF21D924E /* Pods-iOS-PlatformUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUI.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUI/Pods-iOS-PlatformUI.release.xcconfig"; sourceTree = ""; }; @@ -375,6 +377,7 @@ isa = PBXGroup; children = ( 27044F872BBB2ADF004C750D /* Text+Ext.swift */, + 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */, ); path = Extensions; sourceTree = ""; @@ -642,6 +645,7 @@ 6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */, 27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */, 0258B9EE2991C9FF0098E1BE /* PlatformWebView.swift in Sources */, + 274C47EA2C0FC6CF000212C3 /* EdgeInsets+Ext.swift in Sources */, 02F38BF22A9AAE3700969E06 /* CircularProgressView.swift in Sources */, 02E2C93028A1C8A400F7C3BE /* PlatformUI.docc in Sources */, 02B120B528A430DF00281498 /* ThemeViewModifiers.swift in Sources */, diff --git a/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift index f2c4d1ced..a0b22a407 100644 --- a/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift +++ b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift @@ -13,7 +13,7 @@ public enum PlatformButtonState { } public enum PlatformButtonType { - case defaultType(fillWidth: Bool), iconType, pill, small + case defaultType(fillWidth: Bool = true, padding: EdgeInsets = .init(all: 14)), iconType, pill, small } public class PlatformButtonViewModel: PlatformViewModel { @@ -21,15 +21,15 @@ public class PlatformButtonViewModel: PlatformVie @Published public var content: Content @Published public var type: PlatformButtonType @Published public var state: PlatformButtonState - + public init(content: Content, - type: PlatformButtonType = .defaultType(fillWidth: true), + type: PlatformButtonType = .defaultType(), state: PlatformButtonState = .primary, action: @escaping () -> ()) { - self.action = action self.content = content self.type = type self.state = state + self.action = action } public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { @@ -40,7 +40,7 @@ public class PlatformButtonViewModel: PlatformVie return AnyView( Group { switch self.type { - case .defaultType(let fillWidth): + case .defaultType(let fillWidth, let padding): let button = Button(action: self.action) { HStack { if fillWidth { @@ -55,7 +55,7 @@ public class PlatformButtonViewModel: PlatformVie } .buttonStyle(BorderlessButtonStyle()) .disabled(disabled) - .padding(.all, 14) + .padding(padding) .if(fillWidth) { view in view.frame(maxWidth: .infinity) } diff --git a/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift b/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift new file mode 100644 index 000000000..25779655b --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift @@ -0,0 +1,18 @@ +// +// EdgeInsets+ext.swift +// PlatformUI +// +// Created by Michael Maguire on 6/4/24. +// + +import SwiftUI + +public extension EdgeInsets { + init(all: CGFloat) { + self.init(top: all, leading: all, bottom: all, trailing: all) + } + + init(horizontal: CGFloat, vertical: CGFloat) { + self.init(top: vertical, leading: horizontal, bottom: vertical, trailing: horizontal) + } +} diff --git a/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift b/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift index 72624e27d..91a4e9f8e 100644 --- a/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift +++ b/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift @@ -212,6 +212,17 @@ open class PlatformTextInputViewModel: PlatformValueInputViewModel { private var inputType: InputType + /// Prefer to set `value` directly if forcing is not needed + /// - Parameters: + /// - value: value to set + /// - shouldForce: whether setting shoudl happen even if input is focused + /// we need to refactor how we do inputs to be more flexible + public final func programmaticallySet(value: String) { + input = value + self.valueChanged(value: self.input) + updateView() + } + override open var value: String? { didSet { if !focused { diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Transfer/TransferOut/dydxTransferOutViewPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Transfer/TransferOut/dydxTransferOutViewPresenter.swift index 630c35659..102c8ba6a 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Transfer/TransferOut/dydxTransferOutViewPresenter.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Transfer/TransferOut/dydxTransferOutViewPresenter.swift @@ -15,6 +15,7 @@ import Abacus import dydxStateManager import dydxFormatter import Combine +import KeyboardObserving protocol dydxTransferOutViewPresenterProtocol: HostedViewPresenterProtocol { var viewModel: dydxTransferOutViewModel? { get } @@ -76,6 +77,10 @@ class dydxTransferOutViewPresenter: HostedViewPresenter Void)? + + public init(onEdited: ((String?) -> Void)?) { + super.init(label: DataLocalizer.localize(path: "APP.GENERAL.MEMO"), + placeHolder: DataLocalizer.localize(path: "APP.DIRECT_TRANSFER_MODAL.REQUIRED_FOR_CEX"), + onEdited: onEdited) + } + + private var memoWarning: InlineAlertViewModel? { + guard value?.isEmpty != false else { return nil } + return InlineAlertViewModel(.init(title: nil, + body: DataLocalizer.localize(path: "ERRORS.TRANSFER_MODAL.TRANSFER_WITHOUT_MEMO"), + level: .warning)) + } + + public override var valueAccessoryView: AnyView? { + set {} + get { memoInputAccessory } + } + + private var memoInputAccessory: AnyView? { + ZStack { + let shouldShowCancel = value?.isEmpty == false + if shouldShowCancel { + let content = Image("icon_cancel", bundle: .dydxView) + .resizable() + .templateColor(.textSecondary) + .frame(width: 9, height: 9) + .padding(.all, 10) + .borderAndClip(style: .circle, borderColor: .layer6) + .wrappedViewModel + + PlatformButtonViewModel(content: content, + type: .iconType, + state: .secondary) {[weak self] in + self?.programmaticallySet(value: "") + } + .createView() + + } + let content = Text(localizerPathKey: "APP.GENERAL.PASTE") + .themeColor(foreground: .textSecondary) + .themeFont(fontType: .base, fontSize: .small) + .wrappedViewModel + + PlatformButtonViewModel(content: content, + type: .defaultType(fillWidth: false, + padding: .init(horizontal: 8, vertical: 6)), + state: .secondary ) {[weak self] in + guard let pastedString = UIPasteboard.general.string else { return } + self?.programmaticallySet(value: pastedString) + } + .createView() + .opacity(shouldShowCancel ? 0 : 1) // hide it with opacity so that it sizes correctly all the timem + } + .wrappedInAnyView() + + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + VStack(spacing: 12) { + super.createView(parentStyle: parentStyle) + .makeInput() + memoWarning?.createView(parentStyle: parentStyle) + } + .wrappedViewModel + .createView(parentStyle: parentStyle) + } + + public static var previewValue: MemoBoxModel = { + let vm = MemoBoxModel(onEdited: nil) + return vm + }() +} + +#if DEBUG +struct MemoBox_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return MemoBoxModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct MemoBox_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return MemoBoxModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/dydx/dydxViews/dydxViews/_v4/Transfer/TransferOut/dydxTransferOutView.swift b/dydx/dydxViews/dydxViews/_v4/Transfer/TransferOut/dydxTransferOutView.swift index b1014c9bf..f884a9e3d 100644 --- a/dydx/dydxViews/dydxViews/_v4/Transfer/TransferOut/dydxTransferOutView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Transfer/TransferOut/dydxTransferOutView.swift @@ -26,6 +26,7 @@ public class dydxTransferOutViewModel: PlatformViewModel { text: DataLocalizer.localize(path: "APP.GENERAL.DYDX_CHAIN")) @Published public var tokensComboBox: TokensComboBoxModel? = TokensComboBoxModel(label: DataLocalizer.localize(path: "APP.GENERAL.ASSET")) + @Published public var memoBox: MemoBoxModel? @Published public var ctaButton: dydxTradeInputCtaButtonViewModel? = dydxTradeInputCtaButtonViewModel() @Published public var validationViewModel: dydxValidationViewModel? = dydxValidationViewModel() @@ -42,16 +43,18 @@ public class dydxTransferOutViewModel: 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) } - return AnyView( VStack { Group { VStack(spacing: 12) { - self.chainsComboBox?.createView(parentStyle: style) - self.addressInput?.createView(parentStyle: style) - .makeInput() + HStack(spacing: 12) { + self.addressInput?.createView(parentStyle: style) + .makeInput() + self.chainsComboBox?.createView(parentStyle: style) + } self.tokensComboBox?.createView(parentStyle: style) self.amountBox?.createView(parentStyle: style) + self.memoBox?.createView(parentStyle: style) } } diff --git a/dydx/dydxViews/dydxViews/_v4/Transfer/dydxTransferView.swift b/dydx/dydxViews/dydxViews/_v4/Transfer/dydxTransferView.swift index 8fe387fda..8a2191428 100644 --- a/dydx/dydxViews/dydxViews/_v4/Transfer/dydxTransferView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Transfer/dydxTransferView.swift @@ -9,6 +9,7 @@ import SwiftUI import PlatformUI import Utilities +import KeyboardObserving public class dydxTransferViewModel: PlatformViewModel { @Published public var sections = dydxTransferSectionsViewModel() @@ -62,6 +63,7 @@ public class dydxTransferViewModel: PlatformViewModel { } .frame(minHeight: UIScreen.main.bounds.size.height - topPadding - (self.safeAreaInsets?.top ?? 0) - (self.safeAreaInsets?.bottom ?? 0)) } + .keyboardObserving() .keyboardAccessory(background: .layer3, parentStyle: style) .frame(minWidth: 0, maxWidth: .infinity) .padding([.leading, .trailing])