Skip to content

Commit 122da44

Browse files
committed
fix: align calculator widget polish with android
1 parent d42ece8 commit 122da44

21 files changed

Lines changed: 164 additions & 314 deletions

Bitkit.xcodeproj/project.pbxproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,11 @@
194194
Models/BlocksWidgetFields.swift,
195195
Models/BlocksWidgetOptions.swift,
196196
Models/BitcoinFacts.swift,
197-
Models/CalculatorWidgetData.swift,
198-
Models/Currency.swift,
199197
Models/NewsWidgetData.swift,
200198
Models/NewsWidgetOptions.swift,
201199
Models/PriceWidgetData.swift,
202200
Models/PriceWidgetOptions.swift,
203201
Services/Widgets/BlocksHomeScreenWidgetOptionsStore.swift,
204-
Services/Widgets/CalculatorHomeScreenWidgetOptionsStore.swift,
205202
Services/Widgets/NewsHomeScreenWidgetOptionsStore.swift,
206203
Services/Widgets/PriceHomeScreenWidgetOptionsStore.swift,
207204
Styles/Colors.swift,

Bitkit/Components/NumberPad.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct NumberPad: View {
1010
let type: NumberPadType
1111
let decimalSeparator: String
1212
let errorKey: String?
13+
let onDeleteLongPress: (() -> Void)?
1314
let onPress: (String) -> Void
1415

1516
static var contentHeight: CGFloat {
@@ -20,10 +21,17 @@ struct NumberPad: View {
2021
UIScreen.main.isSmall ? 65 : 44 + 34
2122
}
2223

23-
init(type: NumberPadType = .simple, decimalSeparator: String = ".", errorKey: String? = nil, onPress: @escaping (String) -> Void) {
24+
init(
25+
type: NumberPadType = .simple,
26+
decimalSeparator: String = ".",
27+
errorKey: String? = nil,
28+
onDeleteLongPress: (() -> Void)? = nil,
29+
onPress: @escaping (String) -> Void
30+
) {
2431
self.type = type
2532
self.decimalSeparator = decimalSeparator
2633
self.errorKey = errorKey
34+
self.onDeleteLongPress = onDeleteLongPress
2735
self.onPress = onPress
2836
}
2937

@@ -110,6 +118,13 @@ struct NumberPad: View {
110118
.buttonStyle(NumberPadButtonStyle())
111119
.accessibilityIdentifier("NRemove")
112120
.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+
)
113128
}
114129
}
115130
}

Bitkit/Components/Widgets/BaseWidget.swift

Lines changed: 83 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -125,104 +125,104 @@ struct BaseWidget<Content: View>: View {
125125
}
126126

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

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

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

172-
Image("burger")
168+
// Action buttons when in edit mode
169+
if isEditing {
170+
HStack(spacing: 8) {
171+
// Delete button
172+
Button {
173+
onDelete()
174+
} label: {
175+
Image("trash")
173176
.resizable()
174177
.foregroundColor(.textPrimary)
175178
.frame(width: 24, height: 24)
176-
.frame(width: 32, height: 32)
177-
.contentShape(Rectangle())
178-
.overlay {
179-
Color.clear
180-
.frame(width: 44, height: 44)
181-
.contentShape(Rectangle())
182-
.trackDragHandle()
183-
}
184-
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
185179
}
186-
}
187-
}
180+
.frame(width: 32, height: 32)
181+
.contentShape(Rectangle())
182+
.accessibilityIdentifier("\(metadata.name)_WidgetActionDelete")
183+
184+
// Edit button
185+
Button {
186+
onEdit()
187+
} label: {
188+
Image("gear-six")
189+
.resizable()
190+
.foregroundColor(.textPrimary)
191+
.frame(width: 24, height: 24)
192+
}
193+
.frame(width: 32, height: 32)
194+
.contentShape(Rectangle())
195+
.accessibilityIdentifier("\(metadata.name)_WidgetActionEdit")
188196

189-
// Add spacer only when showing title and not editing
190-
if settings.showWidgetTitles && !isEditing {
191-
Spacer()
192-
.frame(height: 16)
197+
Image("burger")
198+
.resizable()
199+
.foregroundColor(.textPrimary)
200+
.frame(width: 24, height: 24)
201+
.frame(width: 32, height: 32)
202+
.contentShape(Rectangle())
203+
.overlay {
204+
Color.clear
205+
.frame(width: 44, height: 44)
206+
.contentShape(Rectangle())
207+
.trackDragHandle()
208+
}
209+
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
210+
}
193211
}
194212
}
195213

196-
// Widget content (only shown when not editing)
197-
if !isEditing {
198-
content
214+
// Add spacer only when showing title and not editing
215+
if settings.showWidgetTitles && !isEditing {
216+
Spacer()
217+
.frame(height: 16)
199218
}
200219
}
201-
.contentShape(Rectangle())
202-
}
203-
.accessibilityIdentifier("\(type.rawValue.capitalized)Widget")
204-
.buttonStyle(WidgetButtonStyle())
205-
.frame(maxWidth: .infinity)
206-
.padding((hasBackground || isEditing) ? 16 : 0)
207-
.background((hasBackground || isEditing) ? Color.gray6 : Color.clear)
208-
.cornerRadius(hasBackground || isEditing ? 16 : 0)
209-
.alert(
210-
t("widgets__delete__title"),
211-
isPresented: $showDeleteDialog,
212-
actions: {
213-
Button(t("common__cancel"), role: .cancel) {
214-
showDeleteDialog = false
215-
}
216220

217-
Button(t("common__delete_yes"), role: .destructive) {
218-
widgets.deleteWidget(type)
219-
showDeleteDialog = false
220-
}
221-
},
222-
message: {
223-
Text(t("widgets__delete__description", variables: ["name": metadata.name]))
221+
// Widget content (only shown when not editing)
222+
if !isEditing {
223+
content
224224
}
225-
)
225+
}
226226
}
227227

228228
/// Truncate a string to a maximum length
@@ -236,14 +236,6 @@ struct BaseWidget<Content: View>: View {
236236
}
237237
}
238238

239-
/// Custom button style for widgets
240-
struct WidgetButtonStyle: ButtonStyle {
241-
func makeBody(configuration: Configuration) -> some View {
242-
configuration.label
243-
.opacity(configuration.isPressed ? 0.9 : 1.0)
244-
}
245-
}
246-
247239
// Preview for the BaseWidget
248240
#Preview {
249241
VStack {

Bitkit/Components/Widgets/CalculatorWidget.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ struct CalculatorWidget: View {
5959
guard let key = calculatorInput.submittedKey?.value else { return }
6060
handleNumberPadInput(key)
6161
}
62-
.onDisappear {
63-
calculatorInput.dismiss()
64-
}
6562
}
6663

6764
private var currentValues: CalculatorWidgetValues {
@@ -114,7 +111,7 @@ struct CalculatorWidget: View {
114111
maxDecimalPlaces: maxDecimalPlaces(for: activeInput)
115112
)
116113

117-
guard nextValue != currentValue || key == "delete" else {
114+
guard nextValue != currentValue || key == "delete" || key == "clear" else {
118115
showInputError(for: key)
119116
return
120117
}
@@ -233,7 +230,6 @@ struct CalculatorWidget: View {
233230

234231
private func persistValues() {
235232
CalculatorHomeScreenWidgetOptionsStore.save(currentValues)
236-
CalculatorHomeScreenWidgetOptionsStore.reloadHomeScreenWidgetIfNeeded()
237233
}
238234

239235
private func showInputError(for key: String) {
@@ -247,7 +243,7 @@ struct CalculatorWidget: View {
247243
}
248244
}
249245

250-
// MARK: - Wide layout (in-app + carousel page + .systemMedium OS widget)
246+
// MARK: - Wide layout (in-app + carousel page)
251247

252248
struct CalculatorWidgetWideContent: View {
253249
let values: CalculatorWidgetValues
@@ -263,11 +259,11 @@ struct CalculatorWidgetWideContent: View {
263259
iconSize: 32,
264260
rowPadding: 16,
265261
showsLabel: true,
266-
isActive: activeInput == .bitcoin
262+
isActive: activeInput == .bitcoin,
263+
accessibilityIdentifier: "CalculatorBtcInput"
267264
) {
268265
onSelectInput?(.bitcoin)
269266
}
270-
.accessibilityIdentifier("CalculatorBtcInput")
271267

272268
CalculatorWidgetRow(
273269
currencySymbol: values.currencySymbol,
@@ -277,16 +273,16 @@ struct CalculatorWidgetWideContent: View {
277273
iconSize: 32,
278274
rowPadding: 16,
279275
showsLabel: true,
280-
isActive: activeInput == .fiat
276+
isActive: activeInput == .fiat,
277+
accessibilityIdentifier: "CalculatorFiatInput"
281278
) {
282279
onSelectInput?(.fiat)
283280
}
284-
.accessibilityIdentifier("CalculatorFiatInput")
285281
}
286282
}
287283
}
288284

289-
// MARK: - Compact layout (small carousel page + .systemSmall OS widget)
285+
// MARK: - Compact layout (small carousel page)
290286

291287
struct CalculatorWidgetCompactContent: View {
292288
let values: CalculatorWidgetValues
@@ -327,9 +323,22 @@ private struct CalculatorWidgetRow: View {
327323
let rowPadding: CGFloat
328324
let showsLabel: Bool
329325
let isActive: Bool
326+
var accessibilityIdentifier: String?
330327
var onTap: (() -> Void)?
331328

332329
var body: some View {
330+
if let onTap {
331+
Button(action: onTap) {
332+
rowContent
333+
}
334+
.buttonStyle(.plain)
335+
.accessibilityIdentifier(accessibilityIdentifier ?? "")
336+
} else {
337+
rowContent
338+
}
339+
}
340+
341+
private var rowContent: some View {
333342
HStack(alignment: .center, spacing: 8) {
334343
ZStack {
335344
Circle()
@@ -377,9 +386,6 @@ private struct CalculatorWidgetRow: View {
377386
.background(Color.black)
378387
.cornerRadius(8)
379388
.contentShape(Rectangle())
380-
.onTapGesture {
381-
onTap?()
382-
}
383389
}
384390

385391
private var displayValue: String {

Bitkit/Managers/CalculatorInputManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ final class CalculatorInputManager {
3333
submittedKey = SubmittedKey(value: key)
3434
}
3535

36+
func clear() {
37+
submittedKey = SubmittedKey(value: "clear")
38+
}
39+
3640
func dismiss() {
3741
activeInput = nil
3842
errorKey = nil

Bitkit/Models/CalculatorWidgetData.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ enum CalculatorWidgetFormatter {
112112
}
113113

114114
let nextValue: String = switch key {
115+
case "clear":
116+
""
115117
case "delete":
116118
String(normalizedRawValue.dropLast())
117119
case ".":

Bitkit/Resources/Localization/cs.lproj/Localizable.strings

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1161,7 +1161,6 @@
11611161
"widgets__facts__description" = "Pokaždé, když otevřete peněženku, objevte zábavná fakta o bitcoinu.";
11621162
"widgets__calculator__name" = "Bitcoinová kalkulačka";
11631163
"widgets__calculator__description" = "Převeďte ₿ částky na {fiatSymbol} nebo naopak.";
1164-
"widgets__calculator__gallery_description" = "Převeďte ₿ částky na fiat měnu nebo naopak.";
11651164
"widgets__weather__name" = "Počasí v bitcoinu";
11661165
"widgets__weather__description" = "Zjistěte, kdy je vhodná doba pro transakce v blockchainu bitcoinu.";
11671166
"widgets__weather__condition__good__title" = "Příznivé podmínky";

Bitkit/Resources/Localization/de.lproj/Localizable.strings

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,6 @@
11581158
"widgets__facts__description" = "Entdecke Fun Facts über Bitcoin, jedes Mal wenn du dein Wallet öffnest.";
11591159
"widgets__calculator__name" = "Bitcoin Rechner";
11601160
"widgets__calculator__description" = "Wandle ₿-Beträge in {fiatSymbol} und umgekehrt.";
1161-
"widgets__calculator__gallery_description" = "Wandle ₿-Beträge in Fiatwährung und umgekehrt.";
11621161
"widgets__weather__name" = "Bitcoin Wetter";
11631162
"widgets__weather__description" = "Finde heraus, wann es eine gute Zeit ist, um auf der Bitcoin blockchain Überweisungen zu tätigen.";
11641163
"widgets__weather__condition__good__title" = "Günstige Bedingungen";

0 commit comments

Comments
 (0)