Skip to content

Commit

Permalink
UI: Add Faucet view
Browse files Browse the repository at this point in the history
UI: Add Faucet view
UI: Add Transaction Details view (copied from BDKSwiftExampleWallet)
Task: Bump minimum iOS and Xcode versions to latest
  • Loading branch information
geigerzaehler242 authored and thunderbiscuit committed Apr 12, 2024
1 parent 766996c commit 190834e
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 48 deletions.
4 changes: 4 additions & 0 deletions iosApp/iosApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct ContentView: View {

// MARK: PROPERTIES

@EnvironmentObject var viewModel: WalletViewModel
@State var selectedTab: Int = 0

enum Tab: Int {
Expand Down Expand Up @@ -53,6 +54,9 @@ struct ContentView: View {
// WelcomeView()
// })
}
.onAppear{
viewModel.load()
}
}
}

Expand Down
272 changes: 225 additions & 47 deletions iosApp/iosApp/WalletView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct WalletView: View {

NavigationStack(path: $navigationPath) {

VStack(spacing: 40) {
VStack(spacing: 20) {

RoundedRectangle(cornerRadius: 20)
.frame(height: 200)
Expand All @@ -33,21 +33,20 @@ struct WalletView: View {
Spacer()
HStack {
Text("Bitcoin Testnet")
//VStack {
Picker("Display in BTC or sats?", selection: $satsBTC) {
ForEach(amountDisplayOptions, id: \.self) {
Text($0)
}

Picker("Display in BTC or sats?", selection: $satsBTC) {
ForEach(amountDisplayOptions, id: \.self) {
Text($0)
}
.pickerStyle(.segmented)
.onChange(of: satsBTC) {
if satsBTC == "BTC" {
viewModel.toggleBTCDisplay(displayOption: "BTC")
} else {
viewModel.toggleBTCDisplay(displayOption: "sats")
}
}
.pickerStyle(.segmented)
.onChange(of: satsBTC) {
if satsBTC == "BTC" {
viewModel.toggleBTCDisplay(displayOption: "BTC")
} else {
viewModel.toggleBTCDisplay(displayOption: "sats")
}
//}
}
}

Spacer()
Expand Down Expand Up @@ -136,18 +135,17 @@ struct WalletView: View {

HStack() {
Text("Transactions")
.font(.headline)
.font(.title)
Spacer()
}

List {
if viewModel.transactions.isEmpty {

if viewModel.transactions.isEmpty {
Text("No Transactions")
.font(.caption)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
} else {
FaucetView()

} else {
List {

ForEach(
viewModel.transactions.sorted(
by: {
Expand All @@ -156,39 +154,91 @@ struct WalletView: View {
}
),
id: \.txid
) { transaction in

// NavigationLink(
// destination: TransactionDetailsView(
// transaction: transaction,
// amount:
// transaction.sent > 0
// ? transaction.sent - transaction.received
// : transaction.received - transaction.sent
// )
// ) {
//
WalletTransactionsListItemView(transaction: transaction)
// }
}
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
}
)
{ transaction in

NavigationLink(
destination: TransactionDetailsView(
transaction: transaction,
amount:
transaction.sent > transaction.received
? transaction.sent - transaction.received
: transaction.received - transaction.sent
)
)
{

WalletTransactionsListItemView(transaction: transaction)
// .refreshable {
// viewModel.sync()
// //viewModel.getBalance()
// //viewModel.getTransactions()
// //await viewModel.getPrices()
// }
}
}
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

}.listStyle(.plain)

}
.listStyle(.plain)
}//viewModel.transactions.isEmpty

//Spacer(minLength: 0)
}
.padding(30)

}//VStack
.padding(30)

} //navigation stack
.onAppear{
viewModel.load()
}
} //body
}


struct FaucetView: View {

@EnvironmentObject var viewModel: WalletViewModel
@State private var faucetFailed = false

var body: some View {

VStack {
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.stroke(Color.black , lineWidth: 1)
.frame(width: 350, height: 150, alignment: Alignment.top )
.layoutPriority(1) // default is 0, now higher priority than Text()
Text("Hey! It looks like your transaction list is empty. Take a look around, and come back to get some coins so you can start playing with the wallet!").padding(20)
}

if viewModel.syncState == .synced { //only show button once synced!
Button(action: {
//TODO add nnetwork call to get testnet coins!!
faucetFailed = true
}, label: {
Text("Get coins \(Image(systemName: "bitcoinsign")) \(Image(systemName: "arrow.down.left"))")
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.orange)
.cornerRadius(20)
})
.padding(20)
.alert("Faucet Not Implemented Yet!",
isPresented: $faucetFailed) {
Button("Ok", role: .destructive) {
// TODO
}
} message: {
Text("Try to recover a wallet instead")
}
}

}
}
}

struct WalletTransactionsListItemView: View {
let transaction: TransactionDetails
let isRedacted: Bool
Expand All @@ -214,7 +264,7 @@ struct WalletTransactionsListItemView: View {
} else {
Image(
systemName:
transaction.sent > 0
transaction.sent > transaction.received
? "arrow.up.circle.fill" : "arrow.down.circle.fill"
)
.font(.largeTitle)
Expand Down Expand Up @@ -262,6 +312,134 @@ struct WalletTransactionsListItemView: View {
}//body
}

struct TransactionDetailsView: View {
// @ObservedObject var viewModel: TransactionDetailsViewModel
@EnvironmentObject var viewModel: WalletViewModel

let transaction: TransactionDetails
let amount: UInt64
@State private var isCopied = false
@State private var showCheckmark = false

var body: some View {

VStack {

VStack(spacing: 8) {
Image(systemName: "bitcoinsign.circle.fill")
.resizable()
.foregroundColor(.orange)
.fontWeight(.bold)
.frame(width: 100, height: 100, alignment: .center)
HStack(spacing: 3) {
Text(
transaction.sent > transaction.received ? "Send" : "Receive"
)
if transaction.confirmationTime == nil {
Text("Unconfirmed")
} else {
Text("Confirmed")
}
}
.fontWeight(.semibold)
if let height = transaction.confirmationTime?.height {
Text("Block \(height.delimiter)")
.foregroundColor(.secondary)
}
}
.font(.caption)

Spacer()

VStack(spacing: 8) {
HStack {
Text(amount.delimiter)
Text("sats")
}
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.largeTitle)
.foregroundColor(.primary)
.fontWeight(.bold)
.fontDesign(.rounded)
VStack(spacing: 4) {
if transaction.confirmationTime == nil {
Text("Unconfirmed")
} else {
VStack {
if let timestamp = transaction.confirmationTime?.timestamp {
Text(
timestamp.toDate().formatted(
date: .abbreviated,
time: Date.FormatStyle.TimeStyle.shortened
)
)
}
}
}
if let fee = transaction.fee {
Text("\(fee) sats fee")
}
}
.foregroundColor(.secondary)
.font(.callout)
}

Spacer()

HStack {
// if viewModel.network != Network.regtest.description {
// Button {
// if let esploraURL = viewModel.esploraURL {
// let urlString = "\(esploraURL)/tx/\(transaction.txid)"
// .replacingOccurrences(of: "/api", with: "")
// if let url = URL(string: urlString) {
// UIApplication.shared.open(url)
// }
// }
// } label: {
// Image(systemName: "safari")
// .fontWeight(.semibold)
// .foregroundColor(.bitcoinOrange)
// }
// Spacer()
// }
Text(transaction.txid)
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button {
UIPasteboard.general.string = transaction.txid
isCopied = true
showCheckmark = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isCopied = false
showCheckmark = false
}
} label: {
HStack {
withAnimation {
Image(systemName: showCheckmark ? "checkmark" : "doc.on.doc")
}
}
.fontWeight(.semibold)
.foregroundColor(.orange)
}
}
.fontDesign(.monospaced)
.font(.caption)
.padding()
.onAppear {
// viewModel.getNetwork()
// viewModel.getEsploraUrl()
}

}
.padding()

}
}

#Preview {
WalletView(selectedTab: .constant (0))
.environmentObject(WalletViewModel())
Expand Down
35 changes: 34 additions & 1 deletion iosApp/iosApp/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,32 @@ extension TransactionDetails: Comparable {
}
}

extension UInt32 {
private static var numberFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal

return numberFormatter
}()

var delimiter: String {
return UInt32.numberFormatter.string(from: NSNumber(value: self)) ?? ""
}
}

extension UInt64 {

private static var numberFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal

return numberFormatter
}()

var delimiter: String {
return UInt64.numberFormatter.string(from: NSNumber(value: self)) ?? ""
}

func toDate() -> Date {
return Date(timeIntervalSince1970: TimeInterval(self))
}
Expand Down Expand Up @@ -123,7 +148,15 @@ class WalletViewModel: ObservableObject {
case loaded(Wallet, Blockchain)
}

enum SyncState {
enum SyncState : Equatable {

static func == (lhs: WalletViewModel.SyncState, rhs: WalletViewModel.SyncState) -> Bool {
switch (lhs, rhs) {
case (.empty, .empty),(.syncing, .syncing), (.synced, .synced), (.failed, .failed): return true
default: return false
}
}

case empty
case syncing
case synced
Expand Down

0 comments on commit 190834e

Please sign in to comment.