Skip to content

Commit

Permalink
- Update used_addresses screen.
Browse files Browse the repository at this point in the history
- Add change addresses history.
- Update BitcoinKit. Use sendInfo instead of fee method
  • Loading branch information
ant013 committed Dec 29, 2023
1 parent 66ba13b commit 5cee6db
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12420,7 +12420,7 @@
repositoryURL = "https://github.com/horizontalsystems/BitcoinCore.Swift";
requirement = {
kind = exactVersion;
version = 2.3.1;
version = 2.4.0;
};
};
6B55461F2A6E73190054B524 /* XCRemoteSwiftPackageReference "UIExtensions.Swift" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,15 @@ extension BitcoinBaseAdapter {
try validate(address: address, pluginData: [:])
}

func fee(amount: Decimal, feeRate: Int, address: String?, pluginData: [UInt8: IBitcoinPluginData] = [:]) -> Decimal {
do {
let amount = convertToSatoshi(value: amount)
let fee = try abstractKit.fee(for: amount, toAddress: address, feeRate: feeRate, pluginData: pluginData)
return Decimal(fee) / coinRate
} catch {
return 0
}
func sendInfo(amount: Decimal, feeRate: Int, address: String?, pluginData: [UInt8: IBitcoinPluginData] = [:]) throws -> SendInfo {
let amount = convertToSatoshi(value: amount)
let info = try abstractKit.sendInfo(for: amount, toAddress: address, feeRate: feeRate, pluginData: pluginData)
return SendInfo(
unspentOutputs: info.unspentOutputs,
fee: Decimal(info.fee) / coinRate,
changeValue: info.changeValue.map { Decimal($0) / coinRate },
changeAddress: info.changeAddress?.stringValue
)
}

func sendSingle(amount: Decimal, address: String, feeRate: Int, pluginData: [UInt8: IBitcoinPluginData] = [:], sortMode: TransactionDataSortMode, logger: Logger) -> Single<Void> {
Expand Down Expand Up @@ -387,11 +388,11 @@ extension BitcoinBaseAdapter: IDepositAdapter {
DepositAddress(abstractKit.receiveAddress())
}

var usedAddresses: [UsedAddress] {
abstractKit.usedAddresses.map {
func usedAddresses(change: Bool) -> [UsedAddress] {
abstractKit.usedAddresses(change: change).map {
let url = explorerUrl(address: $0.address).flatMap { URL(string: $0) }
return UsedAddress(index: $0.index, address: $0.address, explorerUrl: url)
}
}.sorted { $0.index < $1.index }
}
}

Expand All @@ -415,3 +416,9 @@ public struct UsedAddress: Hashable {
}
}

struct SendInfo {
public let unspentOutputs: [UnspentOutput]
public let fee: Decimal
public let changeValue: Decimal?
public let changeAddress: String?
}
7 changes: 4 additions & 3 deletions UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Alamofire
import BigInt
import BitcoinCore
import Combine
import EvmKit
import GRDB
Expand Down Expand Up @@ -34,9 +35,9 @@ protocol IBalanceAdapter: IBaseAdapter {

protocol IDepositAdapter: IBaseAdapter {
var receiveAddress: DepositAddress { get }
var usedAddresses: [UsedAddress] { get }
var receiveAddressStatus: DataStatus<DepositAddress> { get }
var receiveAddressPublisher: AnyPublisher<DataStatus<DepositAddress>, Never> { get }
func usedAddresses(change: Bool) -> [UsedAddress]
}

extension IDepositAdapter {
Expand All @@ -48,7 +49,7 @@ extension IDepositAdapter {
Just(receiveAddressStatus).eraseToAnyPublisher()
}

var usedAddresses: [UsedAddress] { [] }
func usedAddresses(change: Bool) -> [UsedAddress] { [] }
}

protocol ITransactionsAdapter {
Expand All @@ -70,7 +71,7 @@ protocol ISendBitcoinAdapter {
func maximumSendAmount(pluginData: [UInt8: IBitcoinPluginData]) -> Decimal?
func minimumSendAmount(address: String?) -> Decimal
func validate(address: String, pluginData: [UInt8: IBitcoinPluginData]) throws
func fee(amount: Decimal, feeRate: Int, address: String?, pluginData: [UInt8: IBitcoinPluginData]) -> Decimal
func sendInfo(amount: Decimal, feeRate: Int, address: String?, pluginData: [UInt8: IBitcoinPluginData]) throws -> SendInfo
func sendSingle(amount: Decimal, address: String, feeRate: Int, pluginData: [UInt8: IBitcoinPluginData], sortMode: TransactionDataSortMode, logger: HsToolKit.Logger) -> Single<Void>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ class SendBitcoinAdapterService {

private func update(feeRate: Int, amount: Decimal, address: String?, pluginData: [UInt8: IBitcoinPluginData], updatedFrom: UpdatedField) {
queue.async { [weak self] in
if let fee = self?.adapter.fee(amount: amount, feeRate: feeRate, address: address, pluginData: pluginData) {
self?.feeState = .completed(fee)
if let sendInfo = try? self?.adapter.sendInfo(amount: amount, feeRate: feeRate, address: address, pluginData: pluginData) {
self?.feeState = .completed(sendInfo.fee)
}
if updatedFrom != .amount,
let availableBalance = self?.adapter.availableBalance(feeRate: feeRate, address: address, pluginData: pluginData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,27 @@ extension ReceiveAddressModule {
let style: HighlightedDescriptionBaseView.Style
}

enum AddressType: Int, Comparable {
case external
case change

var title: String {
switch self {
case .external: return "receive_used_addresses.external".localized
case .change: return "receive_used_addresses.change".localized
}
}

static func <(lhs: AddressType, rhs: AddressType) -> Bool { lhs.rawValue < rhs.rawValue }
}

struct ViewItem {
let copyValue: String
let highlightedDescription: HighlightedDescription
let qrItem: QrItem
let amount: String?
let active: Bool
let memo: String?
let usedAddresses: [UsedAddress]?
let usedAddresses: [AddressType: [UsedAddress]]?
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,22 @@ class ReceiveAddressService {
let isMainNet = adapter.isMainNet
adapter.receiveAddressPublisher
.sink { [weak self, weak adapter] status in
self?.updateStatus(status: status, usedAddresses: adapter?.usedAddresses, isMainNet: isMainNet)
var usedAddresses = [ReceiveAddressModule.AddressType: [UsedAddress]]()
usedAddresses[.external] = adapter?.usedAddresses(change: false) ?? []
usedAddresses[.change] = adapter?.usedAddresses(change: true) ?? []

self?.updateStatus(status: status, usedAddresses: usedAddresses, isMainNet: isMainNet)
}
.store(in: &cancellables)

updateStatus(status: adapter.receiveAddressStatus, usedAddresses: adapter.usedAddresses, isMainNet: isMainNet)
var usedAddresses = [ReceiveAddressModule.AddressType: [UsedAddress]]()
usedAddresses[.external] = adapter.usedAddresses(change: false)
usedAddresses[.change] = adapter.usedAddresses(change: true)

updateStatus(status: adapter.receiveAddressStatus, usedAddresses: usedAddresses, isMainNet: isMainNet)
}

private func updateStatus(status: DataStatus<DepositAddress>, usedAddresses: [UsedAddress]?, isMainNet: Bool) {
private func updateStatus(status: DataStatus<DepositAddress>, usedAddresses: [ReceiveAddressModule.AddressType: [UsedAddress]]?, isMainNet: Bool) {
state = status.map { address in
Item(
address: address,
Expand Down Expand Up @@ -97,7 +105,7 @@ extension ReceiveAddressService: IReceiveAddressService {
extension ReceiveAddressService {
struct Item {
let address: DepositAddress
let usedAddresses: [UsedAddress]?
let usedAddresses: [ReceiveAddressModule.AddressType: [UsedAddress]]?
let token: Token
let isMainNet: Bool
let watchAccount: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct ReceiveAddressView<Service: IReceiveAddressService, Factory: IReceiveAddr
view(memo: memo)
}

if let usedAddresses = viewItem.usedAddresses {
if let usedAddresses = viewItem.usedAddresses, !usedAddresses.isEmpty {
NavigationRow(destination: {
UsedAddressesView(
coinName: viewModel.coinName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ class ReceiveAddressViewItemFactory: IReceiveAddressViewItemFactory {
active = false
}

let notEmpty = item.usedAddresses?.contains { _, value in !value.isEmpty } ?? false
return .init(
copyValue: uri,
highlightedDescription: description,
qrItem: qrItem,
amount: amountString,
active: active,
memo: nil,
usedAddresses: item.usedAddresses.flatMap { $0.isEmpty ? nil : $0 }
usedAddresses: notEmpty ? item.usedAddresses : nil
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import SwiftUI

struct UsedAddressesView: View {
let coinName: String
let usedAddresses: [UsedAddress]
var onDismiss: (() -> ())?
let usedAddresses: [ReceiveAddressModule.AddressType: [UsedAddress]]
var onDismiss: (() -> Void)?

@State private var currentTabIndex: Int = ReceiveAddressModule.AddressType.external.rawValue

@Environment(\.presentationMode) private var presentationMode
@State private var linkUrl: URL?
Expand All @@ -15,21 +17,31 @@ struct UsedAddressesView: View {
.textSubhead2()
.padding(EdgeInsets(top: 0, leading: .margin16, bottom: 0, trailing: .margin16))

TabHeaderView(
tabs: usedAddresses.map { key, _ in key }.sorted().map { $0.title },
currentTabIndex: $currentTabIndex
)

ListSection {
ForEach(usedAddresses, id: \.self) { address in
ListRow {
HStack(spacing: .margin16) {
Text("\(address.index + 1)").textSubhead2()
Text(address.address).textSubhead2(color: .themeLeah)
Button(action: { linkUrl = address.explorerUrl }) {
Image("globe_20").renderingMode(.template)
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))
if let key = ReceiveAddressModule.AddressType(rawValue: currentTabIndex), let addresses = usedAddresses[key] {
ForEach(addresses, id: \.self) { address in
ListRow {
HStack(spacing: .margin16) {
Text("\(address.index + 1)")
.textSubhead2()
.frame(width: width(index: addresses.last?.index ?? 0 + 1), alignment: .leading)
Text(address.address).textSubhead2(color: .themeLeah)
Spacer()
Button(action: { linkUrl = address.explorerUrl }) {
Image("globe_20").renderingMode(.template)
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))

Button(action: { CopyHelper.copyAndNotify(value: address.address) }) {
Image("copy_20").renderingMode(.template)
Button(action: { CopyHelper.copyAndNotify(value: address.address) }) {
Image("copy_20").renderingMode(.template)
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))
}
}
}
Expand All @@ -56,4 +68,10 @@ struct UsedAddressesView: View {
}
.accentColor(.themeJacob)
}

private func width(index: Int) -> CGFloat {
let count = index.description.count
let maxValue = String(repeating: "9", count: count)
return maxValue.size(containerWidth: .greatestFiniteMagnitude, font: .subhead2).width
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@
"deposit.used_addresses.title" = "Used Addresses";
"deposit.used_addresses.description" = "%@ addresses change for privacy and security. Below a list of used addresses in this wallet.";

"receive_used_addresses.external" = "Receive Addresses";
"receive_used_addresses.change" = "Change Addresses";

"deposit.zcash.restore.description" = "Have you previously owned any ZEC coins?";
"deposit.zcash.restore.already_own" = "Yes, I already own";
"deposit.zcash.restore.dont_have" = "No, I don’t have";
Expand Down

0 comments on commit 5cee6db

Please sign in to comment.