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
24 changes: 22 additions & 2 deletions Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.2;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
);
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.notification;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
Expand Down Expand Up @@ -543,6 +547,10 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.2;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
);
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.notification;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
Expand Down Expand Up @@ -675,6 +683,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 184;
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
Expand Down Expand Up @@ -702,8 +711,13 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 2.1.2;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
);
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -718,6 +732,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 184;
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
Expand Down Expand Up @@ -745,8 +760,13 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 2.1.2;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
);
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down Expand Up @@ -937,8 +957,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/synonymdev/bitkit-core";
requirement = {
branch = master;
kind = branch;
kind = exactVersion;
version = 0.1.48;
};
};
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Bitkit/AppScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct AppScene: View {
@StateObject private var transferTracking: TransferTrackingManager
@StateObject private var channelDetails = ChannelDetailsViewModel.shared
@StateObject private var migrations = MigrationsService.shared
@State private var trezorViewModel = TrezorViewModel()

@State private var hideSplash = false
@State private var removeSplash = false
Expand Down Expand Up @@ -133,6 +134,7 @@ struct AppScene: View {
.environmentObject(tagManager)
.environmentObject(transferTracking)
.environmentObject(channelDetails)
.environment(trezorViewModel)
.onAppear {
if !settings.pinEnabled {
isPinVerified = true
Expand Down
212 changes: 212 additions & 0 deletions Bitkit/Components/Trezor/TrezorDeviceRow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import BitkitCore
import SwiftUI

/// Row displaying a discovered Trezor device
struct TrezorDeviceRow: View {
let device: TrezorDeviceInfo
let isConnecting: Bool
let onConnect: () -> Void

var body: some View {
Button(action: {
if !isConnecting {
onConnect()
}
}) {
HStack(spacing: 16) {
// Device icon
Image(systemName: transportIcon)
.font(.system(size: 24))
.foregroundColor(.white)
.frame(width: 48, height: 48)
.background(Color.white.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 12))

// Device info
VStack(alignment: .leading, spacing: 4) {
Text(displayName)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)

Text(transportLabel)
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.6))
}

Spacer()

// Connect indicator or chevron
if isConnecting {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
} else {
Text("Connect")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.white)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(Color.white.opacity(0.15))
.clipShape(Capsule())
}
}
.padding(16)
.background(Color.white.opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 16))
}
.buttonStyle(.plain)
.disabled(isConnecting)
}

private var displayName: String {
if let label = device.label, !label.isEmpty {
return label
}
return modelName
}

private var modelName: String {
if let model = device.model {
return "Trezor \(model)"
}
return "Trezor"
}

private var transportIcon: String {
switch device.transportType {
case .bluetooth:
return "wave.3.right"
case .usb:
return "cable.connector"
}
}

private var transportLabel: String {
switch device.transportType {
case .bluetooth:
return "Bluetooth"
case .usb:
return "USB"
}
}
}

// MARK: - Known Device Row

/// Row displaying a previously connected (known) Trezor device
struct KnownDeviceRow: View {
let device: TrezorKnownDevice
let isConnecting: Bool
let onConnect: () -> Void
let onForget: () -> Void

var body: some View {
HStack(spacing: 16) {
// Tap area for connect
Button(action: {
if !isConnecting {
onConnect()
}
}) {
HStack(spacing: 16) {
// Device icon
Image(systemName: device.transportType == "bluetooth" ? "wave.3.right" : "cable.connector")
.font(.system(size: 24))
.foregroundColor(.white)
.frame(width: 48, height: 48)
.background(Color.white.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 12))

// Device info
VStack(alignment: .leading, spacing: 4) {
Text(device.label ?? device.name)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)

Text(device.lastConnectedAt.relativeDescription)
.font(.system(size: 12))
.foregroundColor(.white.opacity(0.4))
}

Spacer()

if isConnecting {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
}
}
}
.buttonStyle(.plain)
.disabled(isConnecting)

// Forget button
Button(action: onForget) {
Image(systemName: "trash")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.4))
.padding(10)
}
.buttonStyle(.plain)
}
.padding(16)
.background(Color.white.opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}

// MARK: - Date Helper

extension Date {
private static let relativeDateFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter
}()

/// Returns a relative description like "2 minutes ago"
var relativeDescription: String {
Self.relativeDateFormatter.localizedString(for: self, relativeTo: Date())
}
}

// MARK: - Preview

#if DEBUG
struct TrezorDeviceRow_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.black.ignoresSafeArea()

VStack(spacing: 16) {
TrezorDeviceRow(
device: TrezorDeviceInfo(
id: "ble:12345",
transportType: .bluetooth,
name: "Trezor Safe 5",
path: "ble:12345",
label: "My Trezor",
model: "Safe 5",
isBootloader: false
),
isConnecting: false,
onConnect: {}
)

TrezorDeviceRow(
device: TrezorDeviceInfo(
id: "usb:001",
transportType: .usb,
name: "Trezor Model T",
path: "usb:001",
label: nil,
model: "Model T",
isBootloader: false
),
isConnecting: true,
onConnect: {}
)
}
.padding()
}
}
}
#endif
63 changes: 63 additions & 0 deletions Bitkit/Components/Trezor/TrezorExpandableSection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import SwiftUI

/// Reusable expandable section for the Trezor dashboard.
/// Provides a tappable header with animated expand/collapse of content.
struct TrezorExpandableSection<Content: View>: View {
let title: String
let icon: String
let description: String
@Binding var isExpanded: Bool
@ViewBuilder let content: () -> Content

var body: some View {
VStack(spacing: 0) {
// Tappable header
Button(action: {
withAnimation(.easeInOut(duration: 0.25)) {
isExpanded.toggle()
}
}) {
HStack(spacing: 16) {
Image(systemName: icon)
.font(.system(size: 20))
.foregroundColor(.white)
.frame(width: 40, height: 40)
.background(Color.white.opacity(0.1))
.clipShape(Circle())

VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)

Text(description)
.font(.system(size: 12))
.foregroundColor(.white.opacity(0.6))
}

Spacer()

Image(systemName: "chevron.down")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.4))
.rotationEffect(.degrees(isExpanded ? 0 : -90))
.animation(.easeInOut(duration: 0.25), value: isExpanded)
}
}

// Expandable content
if isExpanded {
Divider()
.background(Color.white.opacity(0.1))
.padding(.top, 12)

content()
.padding(.top, 12)
.transition(.opacity)
}
}
.padding(16)
.background(Color.white.opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
Loading
Loading