Skip to content

Commit 3045c46

Browse files
committed
feat: calculator v61 redesign
1 parent 9b59db3 commit 3045c46

17 files changed

Lines changed: 1641 additions & 380 deletions

Bitkit/AppScene.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct AppScene: View {
3232
@StateObject private var contactsManager = ContactsManager()
3333
@State private var keyboardManager = KeyboardManager()
3434
@State private var trezorViewModel = TrezorViewModel()
35+
@State private var calculatorInputManager = CalculatorInputManager()
3536

3637
@State private var hideSplash = false
3738
@State private var removeSplash = false
@@ -148,6 +149,7 @@ struct AppScene: View {
148149
.environmentObject(contactsManager)
149150
.environment(keyboardManager)
150151
.environment(trezorViewModel)
152+
.environment(calculatorInputManager)
151153
.onChange(of: pubkyProfile.authState, initial: true) { _, authState in
152154
if authState == .authenticated, let pk = pubkyProfile.publicKey {
153155
Task {

Bitkit/Components/Header.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SwiftUI
22

33
struct Header: View {
4+
@Environment(CalculatorInputManager.self) private var calculatorInput
5+
46
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
57

68
@EnvironmentObject var app: AppViewModel
@@ -33,12 +35,14 @@ struct Header: View {
3335
AppStatus(
3436
testID: "HeaderAppStatus",
3537
onPress: {
38+
if dismissCalculatorIfNeeded() { return }
3639
navigation.navigate(.appStatus)
3740
}
3841
)
3942

4043
if showWidgetEditButton {
4144
Button(action: {
45+
if dismissCalculatorIfNeeded() { return }
4246
isEditingWidgets.toggle()
4347
}) {
4448
Image(isEditingWidgets ? "check-mark" : "pencil")
@@ -53,6 +57,8 @@ struct Header: View {
5357
}
5458

5559
Button {
60+
if dismissCalculatorIfNeeded() { return }
61+
5662
withAnimation {
5763
app.showDrawer = true
5864
}
@@ -75,6 +81,8 @@ struct Header: View {
7581

7682
private var profileButton: some View {
7783
Button {
84+
if dismissCalculatorIfNeeded() { return }
85+
7886
if pubkyProfile.isAuthenticated || pubkyProfile.cachedName != nil {
7987
navigation.navigate(.profile)
8088
} else if pubkyProfile.initializationErrorMessage != nil {
@@ -103,6 +111,12 @@ struct Header: View {
103111
.accessibilityIdentifier("ProfileButton")
104112
}
105113

114+
private func dismissCalculatorIfNeeded() -> Bool {
115+
guard calculatorInput.isPresented else { return false }
116+
calculatorInput.dismiss()
117+
return true
118+
}
119+
106120
@ViewBuilder
107121
private var profileAvatar: some View {
108122
if let imageUri = pubkyProfile.displayImageUri {

Bitkit/Components/NumberPad.swift

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,38 @@ enum NumberPadType {
88

99
struct NumberPad: View {
1010
let type: NumberPadType
11+
let decimalSeparator: String
1112
let errorKey: String?
13+
let onDeleteLongPress: (() -> Void)?
1214
let onPress: (String) -> Void
1315

14-
init(type: NumberPadType = .simple, errorKey: String? = nil, onPress: @escaping (String) -> Void) {
16+
static var contentHeight: CGFloat {
17+
buttonHeight * 4
18+
}
19+
20+
private static var buttonHeight: CGFloat {
21+
UIScreen.main.isSmall ? 65 : 44 + 34
22+
}
23+
24+
init(
25+
type: NumberPadType = .simple,
26+
decimalSeparator: String = ".",
27+
errorKey: String? = nil,
28+
onDeleteLongPress: (() -> Void)? = nil,
29+
onPress: @escaping (String) -> Void
30+
) {
1531
self.type = type
32+
self.decimalSeparator = decimalSeparator
1633
self.errorKey = errorKey
34+
self.onDeleteLongPress = onDeleteLongPress
1735
self.onPress = onPress
1836
}
1937

20-
private let buttonHeight: CGFloat = UIScreen.main.isSmall ? 65 : 44 + 34
2138
private let gridItems = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3)
2239
private let numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
40+
private var buttonHeight: CGFloat {
41+
Self.buttonHeight
42+
}
2343

2444
var body: some View {
2545
VStack(spacing: 0) {
@@ -59,7 +79,7 @@ struct NumberPad: View {
5979
}
6080
case .decimal:
6181
NumberPadButton(
62-
text: ".",
82+
text: decimalSeparator,
6383
height: buttonHeight,
6484
hasError: errorKey == ".",
6585
testID: "NDecimal"
@@ -98,6 +118,13 @@ struct NumberPad: View {
98118
.buttonStyle(NumberPadButtonStyle())
99119
.accessibilityIdentifier("NRemove")
100120
.frame(maxWidth: .infinity)
121+
.simultaneousGesture(
122+
LongPressGesture(minimumDuration: 0.45).onEnded { _ in
123+
guard let onDeleteLongPress else { return }
124+
Haptics.play(.buttonTap)
125+
onDeleteLongPress()
126+
}
127+
)
101128
}
102129
}
103130
}

Bitkit/Components/TabBar/TabBar.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import SwiftUI
22

33
struct TabBar: View {
4+
@Environment(CalculatorInputManager.self) private var calculatorInput
45
@EnvironmentObject var navigation: NavigationViewModel
56
@EnvironmentObject var sheets: SheetViewModel
67
@EnvironmentObject var wallet: WalletViewModel
78

89
var shouldShow: Bool {
10+
if calculatorInput.isPresented { return false }
11+
912
let routesWithTabBar = Set<Route>([.activityList, .savingsWallet, .spendingWallet])
1013
if navigation.path.isEmpty { return true }
1114
return navigation.currentRoute.map { routesWithTabBar.contains($0) } ?? false
@@ -34,7 +37,7 @@ struct TabBar: View {
3437
.transition(.move(edge: .bottom))
3538
}
3639
}
37-
.animation(.easeInOut, value: shouldShow)
40+
.animation(.easeOut(duration: 0.14), value: shouldShow)
3841
.bottomSafeAreaPadding()
3942
}
4043

@@ -66,6 +69,7 @@ struct TabBar: View {
6669
.frame(maxWidth: .infinity, maxHeight: .infinity)
6770
.overlay {
6871
TabBar()
72+
.environment(CalculatorInputManager())
6973
.environmentObject(NavigationViewModel())
7074
.environmentObject(SheetViewModel())
7175
}
@@ -79,6 +83,7 @@ struct TabBar: View {
7983
.frame(maxWidth: .infinity, maxHeight: .infinity)
8084
.overlay {
8185
TabBar()
86+
.environment(CalculatorInputManager())
8287
.environmentObject(NavigationViewModel())
8388
.environmentObject(SheetViewModel())
8489
}

Bitkit/Components/Widgets/BaseWidget.swift

Lines changed: 79 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -124,98 +124,98 @@ struct BaseWidget<Content: View>: View {
124124
}
125125

126126
var body: some View {
127-
Button {} label: {
128-
VStack(spacing: 0) {
129-
if isEditing {
130-
HStack {
131-
HStack(spacing: 16) {
132-
Image(metadata.icon)
133-
.resizable()
134-
.frame(width: 32, height: 32)
127+
widgetContent
128+
.accessibilityIdentifierIfPresent(isEditing ? nil : "\(type.rawValue.capitalized)Widget")
129+
.frame(maxWidth: .infinity)
130+
.padding((hasBackground || isEditing) ? 16 : 0)
131+
.background((hasBackground || isEditing) ? Color.gray6 : Color.clear)
132+
.cornerRadius(hasBackground || isEditing ? 16 : 0)
133+
.alert(
134+
t("widgets__delete__title"),
135+
isPresented: $showDeleteDialog,
136+
actions: {
137+
Button(t("common__cancel"), role: .cancel) {
138+
showDeleteDialog = false
139+
}
135140

136-
BodyMSBText(truncate(metadata.name, 18))
137-
.lineLimit(1)
138-
}
141+
Button(t("common__delete_yes"), role: .destructive) {
142+
widgets.deleteWidget(type)
143+
showDeleteDialog = false
144+
}
145+
},
146+
message: {
147+
Text(t("widgets__delete__description", variables: ["name": metadata.name]))
148+
}
149+
)
150+
}
139151

140-
Spacer()
141-
142-
// Action buttons when in edit mode
143-
if isEditing {
144-
HStack(spacing: 8) {
145-
// Delete button
146-
Button {
147-
onDelete()
148-
} label: {
149-
Image("trash")
150-
.resizable()
151-
.foregroundColor(.textPrimary)
152-
.frame(width: 24, height: 24)
153-
}
154-
.frame(width: 32, height: 32)
155-
.contentShape(Rectangle())
156-
.accessibilityIdentifier("\(metadata.name)_WidgetActionDelete")
157-
158-
// Edit button
159-
Button {
160-
onEdit()
161-
} label: {
162-
Image("gear-six")
163-
.resizable()
164-
.foregroundColor(.textPrimary)
165-
.frame(width: 24, height: 24)
166-
}
167-
.frame(width: 32, height: 32)
168-
.contentShape(Rectangle())
169-
.accessibilityIdentifier("\(metadata.name)_WidgetActionEdit")
152+
private var widgetContent: some View {
153+
VStack(spacing: 0) {
154+
if isEditing {
155+
HStack {
156+
HStack(spacing: 16) {
157+
Image(metadata.icon)
158+
.resizable()
159+
.frame(width: 32, height: 32)
160+
161+
BodyMSBText(truncate(metadata.name, 18))
162+
.lineLimit(1)
163+
}
164+
165+
Spacer()
170166

171-
Image("burger")
167+
// Action buttons when in edit mode
168+
if isEditing {
169+
HStack(spacing: 8) {
170+
// Delete button
171+
Button {
172+
onDelete()
173+
} label: {
174+
Image("trash")
172175
.resizable()
173176
.foregroundColor(.textPrimary)
174177
.frame(width: 24, height: 24)
175-
.frame(width: 32, height: 32)
176-
.contentShape(Rectangle())
177-
.overlay {
178-
Color.clear
179-
.frame(width: 44, height: 44)
180-
.contentShape(Rectangle())
181-
.trackDragHandle()
182-
}
183-
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
184178
}
179+
.frame(width: 32, height: 32)
180+
.contentShape(Rectangle())
181+
.accessibilityIdentifier("\(metadata.name)_WidgetActionDelete")
182+
183+
// Edit button
184+
Button {
185+
onEdit()
186+
} label: {
187+
Image("gear-six")
188+
.resizable()
189+
.foregroundColor(.textPrimary)
190+
.frame(width: 24, height: 24)
191+
}
192+
.frame(width: 32, height: 32)
193+
.contentShape(Rectangle())
194+
.accessibilityIdentifier("\(metadata.name)_WidgetActionEdit")
195+
196+
Image("burger")
197+
.resizable()
198+
.foregroundColor(.textPrimary)
199+
.frame(width: 24, height: 24)
200+
.frame(width: 32, height: 32)
201+
.contentShape(Rectangle())
202+
.overlay {
203+
Color.clear
204+
.frame(width: 44, height: 44)
205+
.contentShape(Rectangle())
206+
.trackDragHandle()
207+
}
208+
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
185209
}
186210
}
187211
}
188-
189-
// Widget content (only shown when not editing)
190-
if !isEditing {
191-
content
192-
}
193212
}
194-
.contentShape(Rectangle())
195-
}
196-
.accessibilityIdentifier("\(type.rawValue.capitalized)Widget")
197-
.buttonStyle(WidgetButtonStyle())
198-
.frame(maxWidth: .infinity)
199-
.padding((hasBackground || isEditing) ? 16 : 0)
200-
.background((hasBackground || isEditing) ? Color.gray6 : Color.clear)
201-
.cornerRadius(hasBackground || isEditing ? 16 : 0)
202-
.alert(
203-
t("widgets__delete__title"),
204-
isPresented: $showDeleteDialog,
205-
actions: {
206-
Button(t("common__cancel"), role: .cancel) {
207-
showDeleteDialog = false
208-
}
209213

210-
Button(t("common__delete_yes"), role: .destructive) {
211-
widgets.deleteWidget(type)
212-
showDeleteDialog = false
213-
}
214-
},
215-
message: {
216-
Text(t("widgets__delete__description", variables: ["name": metadata.name]))
214+
// Widget content (only shown when not editing)
215+
if !isEditing {
216+
content
217217
}
218-
)
218+
}
219219
}
220220

221221
/// Truncate a string to a maximum length
@@ -229,14 +229,6 @@ struct BaseWidget<Content: View>: View {
229229
}
230230
}
231231

232-
/// Custom button style for widgets
233-
struct WidgetButtonStyle: ButtonStyle {
234-
func makeBody(configuration: Configuration) -> some View {
235-
configuration.label
236-
.opacity(configuration.isPressed ? 0.9 : 1.0)
237-
}
238-
}
239-
240232
// Preview for the BaseWidget
241233
#Preview {
242234
VStack {
@@ -269,6 +261,5 @@ struct WidgetButtonStyle: ButtonStyle {
269261
.environmentObject(WidgetsViewModel())
270262
.environmentObject(NavigationViewModel())
271263
.environmentObject(CurrencyViewModel())
272-
.environmentObject(SettingsViewModel.shared)
273264
.preferredColorScheme(.dark)
274265
}

0 commit comments

Comments
 (0)