Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bitkit/AppScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct AppScene: View {
@StateObject private var contactsManager = ContactsManager()
@State private var keyboardManager = KeyboardManager()
@State private var trezorViewModel = TrezorViewModel()
@State private var calculatorInputManager = CalculatorInputManager()

@State private var hideSplash = false
@State private var removeSplash = false
Expand Down Expand Up @@ -147,6 +148,7 @@ struct AppScene: View {
.environmentObject(contactsManager)
.environment(keyboardManager)
.environment(trezorViewModel)
.environment(calculatorInputManager)
.onChange(of: pubkyProfile.authState, initial: true) { _, authState in
if authState == .authenticated, let pk = pubkyProfile.publicKey {
Task {
Expand Down
14 changes: 14 additions & 0 deletions Bitkit/Components/Header.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import SwiftUI

struct Header: View {
@Environment(CalculatorInputManager.self) private var calculatorInput

@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false

@EnvironmentObject var app: AppViewModel
Expand Down Expand Up @@ -33,12 +35,14 @@ struct Header: View {
AppStatus(
testID: "HeaderAppStatus",
onPress: {
if dismissCalculatorIfNeeded() { return }
navigation.navigate(.appStatus)
}
)

if showWidgetEditButton {
Button(action: {
if dismissCalculatorIfNeeded() { return }
isEditingWidgets.toggle()
}) {
Image(isEditingWidgets ? "check-mark" : "pencil")
Expand All @@ -53,6 +57,8 @@ struct Header: View {
}

Button {
if dismissCalculatorIfNeeded() { return }

withAnimation {
app.showDrawer = true
}
Expand All @@ -75,6 +81,8 @@ struct Header: View {

private var profileButton: some View {
Button {
if dismissCalculatorIfNeeded() { return }

if pubkyProfile.isAuthenticated || pubkyProfile.cachedName != nil {
navigation.navigate(.profile)
} else if pubkyProfile.initializationErrorMessage != nil {
Expand Down Expand Up @@ -103,6 +111,12 @@ struct Header: View {
.accessibilityIdentifier("ProfileButton")
}

private func dismissCalculatorIfNeeded() -> Bool {
guard calculatorInput.isPresented else { return false }
calculatorInput.dismiss()
return true
}

@ViewBuilder
private var profileAvatar: some View {
if let imageUri = pubkyProfile.displayImageUri {
Expand Down
33 changes: 30 additions & 3 deletions Bitkit/Components/NumberPad.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,38 @@ enum NumberPadType {

struct NumberPad: View {
let type: NumberPadType
let decimalSeparator: String
let errorKey: String?
let onDeleteLongPress: (() -> Void)?
let onPress: (String) -> Void

init(type: NumberPadType = .simple, errorKey: String? = nil, onPress: @escaping (String) -> Void) {
static var contentHeight: CGFloat {
buttonHeight * 4
}

private static var buttonHeight: CGFloat {
UIScreen.main.isSmall ? 65 : 44 + 34
}

init(
type: NumberPadType = .simple,
decimalSeparator: String = ".",
errorKey: String? = nil,
onDeleteLongPress: (() -> Void)? = nil,
onPress: @escaping (String) -> Void
) {
self.type = type
self.decimalSeparator = decimalSeparator
self.errorKey = errorKey
self.onDeleteLongPress = onDeleteLongPress
self.onPress = onPress
}

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

var body: some View {
VStack(spacing: 0) {
Expand Down Expand Up @@ -59,7 +79,7 @@ struct NumberPad: View {
}
case .decimal:
NumberPadButton(
text: ".",
text: decimalSeparator,
height: buttonHeight,
hasError: errorKey == ".",
testID: "NDecimal"
Expand Down Expand Up @@ -98,6 +118,13 @@ struct NumberPad: View {
.buttonStyle(NumberPadButtonStyle())
.accessibilityIdentifier("NRemove")
.frame(maxWidth: .infinity)
.simultaneousGesture(
LongPressGesture(minimumDuration: 0.45).onEnded { _ in
guard let onDeleteLongPress else { return }
Haptics.play(.buttonTap)
onDeleteLongPress()
}
)
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion Bitkit/Components/TabBar/TabBar.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import SwiftUI

struct TabBar: View {
@Environment(CalculatorInputManager.self) private var calculatorInput
@EnvironmentObject var navigation: NavigationViewModel
@EnvironmentObject var sheets: SheetViewModel
@EnvironmentObject var wallet: WalletViewModel

var shouldShow: Bool {
if calculatorInput.isPresented { return false }

let routesWithTabBar = Set<Route>([.activityList, .savingsWallet, .spendingWallet])
if navigation.path.isEmpty { return true }
return navigation.currentRoute.map { routesWithTabBar.contains($0) } ?? false
Expand Down Expand Up @@ -34,7 +37,7 @@ struct TabBar: View {
.transition(.move(edge: .bottom))
}
}
.animation(.easeInOut, value: shouldShow)
.animation(.easeOut(duration: 0.14), value: shouldShow)
.bottomSafeAreaPadding()
}

Expand Down Expand Up @@ -66,6 +69,7 @@ struct TabBar: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
TabBar()
.environment(CalculatorInputManager())
.environmentObject(NavigationViewModel())
.environmentObject(SheetViewModel())
}
Expand All @@ -79,6 +83,7 @@ struct TabBar: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
TabBar()
.environment(CalculatorInputManager())
.environmentObject(NavigationViewModel())
.environmentObject(SheetViewModel())
}
Expand Down
174 changes: 83 additions & 91 deletions Bitkit/Components/Widgets/BaseWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,104 +125,104 @@ struct BaseWidget<Content: View>: View {
}

var body: some View {
Button {} label: {
VStack(spacing: 0) {
if type == .suggestions ? isEditing : (settings.showWidgetTitles || isEditing) {
HStack {
HStack(spacing: 16) {
Image(metadata.icon)
.resizable()
.frame(width: 32, height: 32)
widgetContent
.accessibilityIdentifierIfPresent(isEditing ? nil : "\(type.rawValue.capitalized)Widget")
.frame(maxWidth: .infinity)
.padding((hasBackground || isEditing) ? 16 : 0)
.background((hasBackground || isEditing) ? Color.gray6 : Color.clear)
.cornerRadius(hasBackground || isEditing ? 16 : 0)
.alert(
t("widgets__delete__title"),
isPresented: $showDeleteDialog,
actions: {
Button(t("common__cancel"), role: .cancel) {
showDeleteDialog = false
}

BodyMSBText(truncate(metadata.name, 18))
.lineLimit(1)
}
Button(t("common__delete_yes"), role: .destructive) {
widgets.deleteWidget(type)
showDeleteDialog = false
}
},
message: {
Text(t("widgets__delete__description", variables: ["name": metadata.name]))
}
)
}

Spacer()

// Action buttons when in edit mode
if isEditing {
HStack(spacing: 8) {
// Delete button
Button {
onDelete()
} label: {
Image("trash")
.resizable()
.foregroundColor(.textPrimary)
.frame(width: 24, height: 24)
}
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.accessibilityIdentifier("\(metadata.name)_WidgetActionDelete")

// Edit button
Button {
onEdit()
} label: {
Image("gear-six")
.resizable()
.foregroundColor(.textPrimary)
.frame(width: 24, height: 24)
}
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.accessibilityIdentifier("\(metadata.name)_WidgetActionEdit")
private var widgetContent: some View {
VStack(spacing: 0) {
if type == .suggestions ? isEditing : (settings.showWidgetTitles || isEditing) {
HStack {
HStack(spacing: 16) {
Image(metadata.icon)
.resizable()
.frame(width: 32, height: 32)

BodyMSBText(truncate(metadata.name, 18))
.lineLimit(1)
}

Spacer()

Image("burger")
// Action buttons when in edit mode
if isEditing {
HStack(spacing: 8) {
// Delete button
Button {
onDelete()
} label: {
Image("trash")
.resizable()
.foregroundColor(.textPrimary)
.frame(width: 24, height: 24)
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.overlay {
Color.clear
.frame(width: 44, height: 44)
.contentShape(Rectangle())
.trackDragHandle()
}
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
}
}
}
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.accessibilityIdentifier("\(metadata.name)_WidgetActionDelete")

// Edit button
Button {
onEdit()
} label: {
Image("gear-six")
.resizable()
.foregroundColor(.textPrimary)
.frame(width: 24, height: 24)
}
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.accessibilityIdentifier("\(metadata.name)_WidgetActionEdit")

// Add spacer only when showing title and not editing
if settings.showWidgetTitles && !isEditing {
Spacer()
.frame(height: 16)
Image("burger")
.resizable()
.foregroundColor(.textPrimary)
.frame(width: 24, height: 24)
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.overlay {
Color.clear
.frame(width: 44, height: 44)
.contentShape(Rectangle())
.trackDragHandle()
}
.accessibilityIdentifier("\(metadata.name)_WidgetActionReorder")
}
}
}

// Widget content (only shown when not editing)
if !isEditing {
content
// Add spacer only when showing title and not editing
if settings.showWidgetTitles && !isEditing {
Spacer()
.frame(height: 16)
}
}
.contentShape(Rectangle())
}
.accessibilityIdentifier("\(type.rawValue.capitalized)Widget")
.buttonStyle(WidgetButtonStyle())
.frame(maxWidth: .infinity)
.padding((hasBackground || isEditing) ? 16 : 0)
.background((hasBackground || isEditing) ? Color.gray6 : Color.clear)
.cornerRadius(hasBackground || isEditing ? 16 : 0)
.alert(
t("widgets__delete__title"),
isPresented: $showDeleteDialog,
actions: {
Button(t("common__cancel"), role: .cancel) {
showDeleteDialog = false
}

Button(t("common__delete_yes"), role: .destructive) {
widgets.deleteWidget(type)
showDeleteDialog = false
}
},
message: {
Text(t("widgets__delete__description", variables: ["name": metadata.name]))
// Widget content (only shown when not editing)
if !isEditing {
content
}
)
}
}

/// Truncate a string to a maximum length
Expand All @@ -236,14 +236,6 @@ struct BaseWidget<Content: View>: View {
}
}

/// Custom button style for widgets
struct WidgetButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.opacity(configuration.isPressed ? 0.9 : 1.0)
}
}

// Preview for the BaseWidget
#Preview {
VStack {
Expand Down
Loading
Loading