1- //
1+ //
22// Created by Andrei Ashikhmin
33// Copyright © 2025 Dash Core Group. All rights reserved.
44//
@@ -19,143 +19,205 @@ import SwiftUI
1919import SDWebImageSwiftUI
2020
2121struct DashSpendConfirmationDialog : View {
22- let amount : String
2322 let merchantName : String
2423 let merchantIconUrl : String
2524 let originalPrice : Decimal
2625 let discount : Decimal
26+ let quantities : [ Decimal : Int ] ?
2727 let onConfirm : ( ) -> Void
2828 let onCancel : ( ) -> Void
29-
30- @Environment ( \. presentationMode) private var presentationMode
31-
29+ @Binding var contentHeight : CGFloat
30+
3231 private let fiatFormatter = NumberFormatter . fiatFormatter ( currencyCode: kDefaultCurrencyCode)
33-
34- var body : some View {
35- VStack ( spacing: 40 ) {
36- HStack {
37- Text ( fiatFormatter. currencySymbol + amount)
38- . font ( . system( size: 32 , weight: . medium) )
39- . foregroundColor ( . primaryText)
32+ private var youPayAmount : Decimal { originalPrice * ( 1 - discount) }
33+
34+ private var formattedPayAmount : String {
35+ let hasCents = youPayAmount != Decimal ( Int ( truncating: NSDecimalNumber ( decimal: youPayAmount) ) )
36+ let formatter = NumberFormatter . fiatFormatter ( currencyCode: kDefaultCurrencyCode)
37+ if !hasCents {
38+ formatter. minimumFractionDigits = 0
39+ formatter. maximumFractionDigits = 0
40+ }
41+ return formatter. string ( from: NSDecimalNumber ( decimal: youPayAmount) ) ? . strippingCurrencySymbol ( formatter) ?? " "
42+ }
43+
44+ private var quantityLines : [ String ] {
45+ guard let quantities = quantities else { return [ ] }
46+ return quantities
47+ . filter { $0. value > 0 }
48+ . sorted { $0. key < $1. key }
49+ . map { denomination, count in
50+ let amount = fiatFormatter. string ( from: NSDecimalNumber ( decimal: denomination) ) ?? " $ \( denomination) "
51+ return " \( count) x \( amount) "
4052 }
41-
42- // Details
43- VStack ( spacing: 0 ) {
44- HStack ( spacing: 8 ) {
45- Text ( NSLocalizedString ( " From " , comment: " DashSpend " ) )
46- . font ( . subhead)
47- . fontWeight ( . medium)
48- . foregroundColor ( . tertiaryText)
49-
50- Spacer ( )
51-
52- Image ( " image.explore.dash.wts.dash " )
53- . resizable ( )
54- . frame ( width: 24 , height: 24 )
55-
56- Text ( NSLocalizedString ( " Dash Wallet " , comment: " DashSpend " ) )
57- . font ( . subhead)
58- . foregroundColor ( . primaryText)
59- }
60- . padding ( . horizontal, 12 )
61- . frame ( height: 50 )
62-
63- HStack ( spacing: 8 ) {
64- Text ( NSLocalizedString ( " To " , comment: " DashSpend " ) )
65- . font ( . subhead)
66- . fontWeight ( . medium)
67- . foregroundColor ( . tertiaryText)
68-
69- Spacer ( )
70-
71- WebImage ( url: URL ( string: merchantIconUrl) )
72- . resizable ( )
73- . indicator ( . activity)
74- . transition ( . fade( duration: 0.3 ) )
75- . scaledToFit ( )
76- . frame ( width: 24 , height: 24 )
77- . clipShape ( Circle ( ) )
78-
79- Text ( merchantName)
80- . font ( . subhead)
81- . foregroundColor ( . primaryText)
82- }
83- . padding ( . horizontal, 12 )
84- . frame ( height: 50 )
85-
86- HStack {
87- Text ( NSLocalizedString ( " Gift card total " , comment: " DashSpend " ) )
88- . font ( . subhead)
89- . fontWeight ( . medium)
90- . foregroundColor ( . tertiaryText)
91-
92- Spacer ( )
93-
94- Text ( fiatFormatter. string ( from: NSDecimalNumber ( decimal: originalPrice) ) ?? " " )
95- . font ( . subhead)
96- . foregroundColor ( . primaryText)
97- }
98- . padding ( . horizontal, 12 )
99- . frame ( height: 50 )
100-
101- HStack {
102- Text ( NSLocalizedString ( " Discount " , comment: " DashSpend " ) )
103- . font ( . subhead)
104- . fontWeight ( . medium)
105- . foregroundColor ( . tertiaryText)
106-
107- Spacer ( )
108-
109- Text ( PercentageFormatter . format ( percent: NSDecimalNumber ( decimal: discount * 100 ) . doubleValue) )
110- . font ( . subhead)
111- . foregroundColor ( . primaryText)
53+ }
54+
55+ var body : some View {
56+ VStack ( spacing: 0 ) {
57+ Capsule ( )
58+ . fill ( Color ( red: 0.69 , green: 0.71 , blue: 0.74 ) . opacity ( 0.5 ) )
59+ . frame ( width: 36 , height: 5 )
60+ . padding ( . top, 6 )
61+ . padding ( . bottom, 13 )
62+
63+ Text ( NSLocalizedString ( " Confirm " , comment: " DashSpend " ) )
64+ . font ( . calloutMedium)
65+ . foregroundColor ( . primaryText)
66+ . frame ( height: 44 )
67+
68+ VStack ( spacing: 20 ) {
69+ DashSpendAmountView (
70+ currencySymbol: fiatFormatter. currencySymbol,
71+ amount: formattedPayAmount
72+ )
73+ . frame ( height: 85 )
74+
75+ VStack ( alignment: . leading, spacing: 2 ) {
76+ detailsRow ( title: NSLocalizedString ( " From " , comment: " DashSpend " ) ) {
77+ HStack ( spacing: 8 ) {
78+ Image ( " image.explore.dash.wts.dash " )
79+ . resizable ( )
80+ . frame ( width: 20 , height: 20 )
81+ . clipShape ( RoundedRectangle ( cornerRadius: 7 ) )
82+ Text ( NSLocalizedString ( " Dash Wallet " , comment: " DashSpend " ) )
83+ . font ( . subhead)
84+ . foregroundColor ( . primaryText)
85+ }
86+ }
87+
88+ detailsRow ( title: NSLocalizedString ( " To " , comment: " DashSpend " ) ) {
89+ HStack ( spacing: 8 ) {
90+ WebImage ( url: URL ( string: merchantIconUrl) )
91+ . resizable ( )
92+ . indicator ( . activity)
93+ . transition ( . fade( duration: 0.3 ) )
94+ . scaledToFit ( )
95+ . frame ( width: 20 , height: 20 )
96+ . clipShape ( RoundedRectangle ( cornerRadius: 7 ) )
97+ Text ( merchantName)
98+ . font ( . subhead)
99+ . foregroundColor ( . primaryText)
100+ }
101+ }
102+
103+ detailsRow ( title: NSLocalizedString ( " Gift card " , comment: " DashSpend " ) ) {
104+ Text ( fiatFormatter. string ( from: NSDecimalNumber ( decimal: originalPrice) ) ?? " " )
105+ . font ( . subhead)
106+ . foregroundColor ( . primaryText)
107+ }
108+
109+ if !quantityLines. isEmpty {
110+ detailsRow ( title: NSLocalizedString ( " Quantity " , comment: " DashSpend " ) ) {
111+ VStack ( alignment: . trailing, spacing: 2 ) {
112+ ForEach ( quantityLines, id: \. self) { line in
113+ Text ( line)
114+ . font ( . subhead)
115+ . foregroundColor ( . primaryText)
116+ }
117+ }
118+ }
119+ }
120+
121+ detailsRow ( title: NSLocalizedString ( " Discount " , comment: " DashSpend " ) ) {
122+ Text ( PercentageFormatter . format ( percent: NSDecimalNumber ( decimal: discount * 100 ) . doubleValue) )
123+ . font ( . subhead)
124+ . foregroundColor ( . primaryText)
125+ }
126+
127+ detailsRow ( title: NSLocalizedString ( " You pay " , comment: " DashSpend " ) ) {
128+ Text ( fiatFormatter. string ( from: NSDecimalNumber ( decimal: youPayAmount) ) ?? " " )
129+ . font ( . subhead)
130+ . foregroundColor ( . primaryText)
131+ }
112132 }
113- . padding ( . horizontal, 12 )
114- . frame ( height: 50 )
115-
116- HStack {
117- Text ( NSLocalizedString ( " You pay " , comment: " DashSpend " ) )
118- . font ( . subhead)
119- . fontWeight ( . medium)
120- . foregroundColor ( . tertiaryText)
121-
122- Spacer ( )
123-
124- Text ( fiatFormatter. string ( from: NSDecimalNumber ( decimal: originalPrice * ( 1 - discount) ) ) ?? " " )
125- . font ( . subhead)
126- . foregroundColor ( . primaryText)
133+ . padding ( 6 )
134+ . background ( Color . secondaryBackground)
135+ . cornerRadius ( 20 )
136+ . shadow ( color: Color ( red: 0.72 , green: 0.76 , blue: 0.8 ) . opacity ( 0.1 ) , radius: 20 , x: 0 , y: 5 )
137+
138+ HStack ( spacing: 20 ) {
139+ DashButton (
140+ text: NSLocalizedString ( " Cancel " , comment: " DashSpend " ) ,
141+ action: onCancel
142+ )
143+ . overrideForegroundColor ( . primaryText)
144+ . overrideBackgroundColor ( . gray300Alpha10)
145+
146+ DashButton (
147+ text: NSLocalizedString ( " Confirm " , comment: " DashSpend " ) ,
148+ action: onConfirm
149+ )
127150 }
128- . padding ( . horizontal, 12 )
129- . frame ( height: 50 )
151+ . padding ( . horizontal, 10 )
130152 }
131- . background ( Color . secondaryBackground)
132- . cornerRadius ( 12 )
133- . shadow ( color: Color . shadow, radius: 10 , x: 0 , y: 5 )
134-
135- HStack ( spacing: 20 ) {
136- Button ( action: onCancel) {
137- Text ( NSLocalizedString ( " Cancel " , comment: " DashSpend " ) )
138- . font ( . system( size: 16 , weight: . semibold) )
139- . foregroundColor ( . primaryText)
140- . frame ( maxWidth: . infinity)
141- . frame ( height: 46 )
142- }
143- . background ( Color ( UIColor . systemGray5) )
144- . cornerRadius ( 12 )
145-
146- Button ( action: onConfirm) {
147- Text ( NSLocalizedString ( " Confirm " , comment: " DashSpend " ) )
148- . font ( . system( size: 16 , weight: . semibold) )
149- . foregroundColor ( . white)
150- }
151- . frame ( maxWidth: . infinity)
152- . frame ( height: 46 )
153- . background ( Color . dashBlue)
154- . cornerRadius ( 12 )
153+ . padding ( . horizontal, 20 )
154+ . padding ( . bottom, 20 )
155+ }
156+ . background (
157+ GeometryReader { proxy in
158+ Color . clear
159+ . onAppear { contentHeight = proxy. size. height }
160+ . onChange ( of: proxy. size. height) { contentHeight = $0 }
161+ }
162+ )
163+ }
164+
165+ private func detailsRow( title: String , @ViewBuilder value: ( ) -> some View ) -> some View {
166+ HStack ( alignment: . top) {
167+ Text ( title)
168+ . font ( . subhead)
169+ . fontWeight ( . medium)
170+ . foregroundColor ( . tertiaryText)
171+
172+ Spacer ( )
173+
174+ value ( )
175+ }
176+ . padding ( . horizontal, 14 )
177+ . frame ( minHeight: 46 )
178+ }
179+ }
180+
181+ private extension String {
182+ func strippingCurrencySymbol( _ formatter: NumberFormatter ) -> String {
183+ replacingOccurrences ( of: formatter. currencySymbol, with: " " ) . trimmingCharacters ( in: . whitespaces)
184+ }
185+ }
186+
187+ #Preview {
188+ DashSpendConfirmationDialogPreview ( )
189+ }
190+
191+ private struct DashSpendConfirmationDialogPreview : View {
192+ @State private var isPresented = true
193+ @State private var contentHeight : CGFloat = 0
194+
195+ var body : some View {
196+ VStack {
197+ Text ( " Tap to open " )
198+ . onTapGesture { isPresented = true }
199+ }
200+ . sheet ( isPresented: $isPresented) {
201+ let content = DashSpendConfirmationDialog (
202+ merchantName: " Amazon " ,
203+ merchantIconUrl: " " ,
204+ originalPrice: 75.70 ,
205+ discount: 0.10 ,
206+ quantities: [ 50 : 1 , 25 : 2 ] ,
207+ onConfirm: { } ,
208+ onCancel: { } ,
209+ contentHeight: $contentHeight
210+ )
211+
212+ if #available( iOS 18 . 0 , * ) {
213+ content
214+ . presentationBackground ( Color . primaryBackground)
215+ . presentationDetents ( [ . height( contentHeight > 0 ? contentHeight : 550 ) ] )
216+ . presentationCornerRadius ( 32 )
217+ . presentationDragIndicator ( . hidden)
218+ } else {
219+ content
155220 }
156221 }
157- . padding ( . top, 15 )
158- . padding ( . horizontal, 20 )
159- . edgesIgnoringSafeArea ( . bottom)
160222 }
161223}
0 commit comments