Add pending transactions

This commit is contained in:
Peter 2019-10-04 17:16:45 +04:00
parent 5cd85de807
commit 77cd22ea96
18 changed files with 3717 additions and 3367 deletions

View File

@ -2,17 +2,7 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SavedMessagesIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SavedMessagesIcon@3x.png",
"scale" : "3x"
"filename" : "ic_savedmessages.pdf"
}
],
"info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

View File

@ -4788,6 +4788,7 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.Info.Updating" = "updating";
"Wallet.Info.TransactionStorageFee" = "%@ storage fee";
"Wallet.Info.TransactionOtherFee" = "%@ transaction fee";
"Wallet.Info.TransactionPendingHeader" = "Pending";
"Wallet.Qr.ScanCode" = "Scan QR Code";
"Wallet.Qr.Title" = "QR Code";
"Wallet.Receive.Title" = "Receive Grams";
@ -4864,6 +4865,8 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard.";
"Wallet.TransactionInfo.SendGrams" = "Send Grams";
"Wallet.TransactionInfo.CommentHeader" = "COMMENT";
"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE";
"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE";
"Wallet.WordCheck.Title" = "Test Time!";
"Wallet.WordCheck.Text" = "Lets check that you wrote them down correctly. Please enter words\n**%1$@**, **%2$@** and **%3$@** below:";
"Wallet.WordCheck.Continue" = "Continue";

View File

@ -471,13 +471,13 @@ public final class StoredTonContext {
self.keychain = keychain
}
public func context(config: String, blockchainName: String) -> TonContext {
public func context(config: String, blockchainName: String, enableProxy: Bool) -> TonContext {
return self.currentInstance.with { data -> TonContext in
if let instance = data.instance, data.config == config, data.blockchainName == blockchainName {
return TonContext(instance: instance, keychain: self.keychain)
} else {
data.config = config
let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, network: self.network)
let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, network: enableProxy ? self.network : nil)
data.instance = instance
return TonContext(instance: instance, keychain: self.keychain)
}

View File

@ -46,8 +46,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, readonly) NSString * _Nonnull source;
@property (nonatomic, strong, readonly) NSString * _Nonnull destination;
@property (nonatomic, strong, readonly) NSString * _Nonnull textMessage;
@property (nonatomic, strong, readonly) NSData * _Nonnull bodyHash;
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination textMessage:(NSString * _Nonnull)textMessage;
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination textMessage:(NSString * _Nonnull)textMessage bodyHash:(NSData * _Nonnull)bodyHash;
@end
@ -77,14 +78,15 @@ NS_ASSUME_NONNULL_BEGIN
@interface TONSendGramsResult : NSObject
@property (nonatomic, readonly) int64_t sentUntil;
@property (nonatomic, strong, readonly) NSData * _Nonnull bodyHash;
- (instancetype)initWithSentUntil:(int64_t)sentUntil;
- (instancetype)initWithSentUntil:(int64_t)sentUntil bodyHash:(NSData *)bodyHash;
@end
@interface TON : NSObject
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest;
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests;
- (MTSignal *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword;
- (MTSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey;

View File

@ -50,7 +50,7 @@ static TONTransactionMessage * _Nullable parseTransactionMessage(tonlib_api::obj
if (textMessage == nil) {
textMessage = @"";
}
return [[TONTransactionMessage alloc] initWithValue:message->value_ source:source destination:destination textMessage:textMessage];
return [[TONTransactionMessage alloc] initWithValue:message->value_ source:source destination:destination textMessage:textMessage bodyHash:makeData(message->body_hash_)];
}
@implementation TONKey
@ -97,13 +97,14 @@ static TONTransactionMessage * _Nullable parseTransactionMessage(tonlib_api::obj
@implementation TONTransactionMessage
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination textMessage:(NSString * _Nonnull)textMessage {
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination textMessage:(NSString * _Nonnull)textMessage bodyHash:(NSData * _Nonnull)bodyHash {
self = [super init];
if (self != nil) {
_value = value;
_source = source;
_destination = destination;
_textMessage = textMessage;
_bodyHash = bodyHash;
}
return self;
}
@ -143,10 +144,11 @@ static TONTransactionMessage * _Nullable parseTransactionMessage(tonlib_api::obj
@implementation TONSendGramsResult
- (instancetype)initWithSentUntil:(int64_t)sentUntil {
- (instancetype)initWithSentUntil:(int64_t)sentUntil bodyHash:(NSData *)bodyHash {
self = [super init];
if (self != nil) {
_sentUntil = sentUntil;
_bodyHash = bodyHash;
}
return self;
}
@ -234,7 +236,7 @@ typedef enum {
}
}
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest {
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests {
self = [super init];
if (self != nil) {
_queue = [MTQueue mainQueue];
@ -295,7 +297,7 @@ typedef enum {
[[NSFileManager defaultManager] createDirectoryAtPath:keystoreDirectory withIntermediateDirectories:true attributes:nil error:nil];
MTPipe *initializedStatus = _initializedStatus;
[[self requestInitWithConfigString:config blockchainName:blockchainName keystoreDirectory:keystoreDirectory] startWithNext:nil error:^(id error) {
[[self requestInitWithConfigString:config blockchainName:blockchainName keystoreDirectory:keystoreDirectory enableExternalRequests:enableExternalRequests] startWithNext:nil error:^(id error) {
NSString *errorText = @"Unknown error";
if ([error isKindOfClass:[TONError class]]) {
errorText = ((TONError *)error).text;
@ -308,7 +310,7 @@ typedef enum {
return self;
}
- (MTSignal *)requestInitWithConfigString:(NSString *)configString blockchainName:(NSString *)blockchainName keystoreDirectory:(NSString *)keystoreDirectory {
- (MTSignal *)requestInitWithConfigString:(NSString *)configString blockchainName:(NSString *)blockchainName keystoreDirectory:(NSString *)keystoreDirectory enableExternalRequests:(bool)enableExternalRequests {
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
uint64_t requestId = _nextRequestId;
_nextRequestId += 1;
@ -326,7 +328,7 @@ typedef enum {
make_object<tonlib_api::config>(
configString.UTF8String,
blockchainName.UTF8String,
true,
enableExternalRequests,
false
),
make_object<tonlib_api::keyStoreTypeDirectory>(
@ -482,7 +484,7 @@ typedef enum {
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
} else if (object->get_id() == tonlib_api::sendGramsResult::ID) {
auto result = tonlib_api::move_object_as<tonlib_api::sendGramsResult>(object);
TONSendGramsResult *sendResult = [[TONSendGramsResult alloc] initWithSentUntil:result->sent_until_];
TONSendGramsResult *sendResult = [[TONSendGramsResult alloc] initWithSentUntil:result->sent_until_ bodyHash:makeData(result->body_hash_)];
[subscriber putNext:sendResult];
[subscriber putCompletion];
} else {

View File

@ -782,7 +782,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
presentControllerImpl?(usernameSetupController(context: context), nil)
pushControllerImpl?(usernameSetupController(context: context))
})
}, openProxy: {
let _ = (contextValue.get()

View File

@ -48,10 +48,10 @@ private final class TonInstanceImpl {
private let basePath: String
private let config: String
private let blockchainName: String
private let network: Network
private let network: Network?
private var instance: TON?
init(queue: Queue, basePath: String, config: String, blockchainName: String, network: Network) {
init(queue: Queue, basePath: String, config: String, blockchainName: String, network: Network?) {
self.queue = queue
self.basePath = basePath
self.config = config
@ -66,18 +66,22 @@ private final class TonInstanceImpl {
} else {
let network = self.network
instance = TON(keystoreDirectory: self.basePath + "/ton-keystore", config: self.config, blockchainName: self.blockchainName, performExternalRequest: { request in
let _ = (
network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: request.data)))
|> timeout(20.0, queue: .concurrentDefaultQueue(), alternate: .fail(MTRpcError(errorCode: 500, errorDescription: "NETWORK_ERROR")))
).start(next: { result in
switch result {
case let .liteResponse(response):
request.onResult(response.makeData(), nil)
}
}, error: { error in
request.onResult(nil, error.errorDescription)
})
})
if let network = network {
let _ = (
network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: request.data)))
|> timeout(20.0, queue: .concurrentDefaultQueue(), alternate: .fail(MTRpcError(errorCode: 500, errorDescription: "NETWORK_ERROR")))
).start(next: { result in
switch result {
case let .liteResponse(response):
request.onResult(response.makeData(), nil)
}
}, error: { error in
request.onResult(nil, error.errorDescription)
})
} else {
request.onResult(nil, "NETWORK_DISABLED")
}
}, enableExternalRequests: network != nil)
self.instance = instance
}
f(instance)
@ -88,7 +92,7 @@ public final class TonInstance {
private let queue: Queue
private let impl: QueueLocalObject<TonInstanceImpl>
public init(basePath: String, config: String, blockchainName: String, network: Network) {
public init(basePath: String, config: String, blockchainName: String, network: Network?) {
self.queue = .mainQueue()
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
@ -322,7 +326,7 @@ public final class TonInstance {
}
}
fileprivate func sendGramsFromWallet(decryptedSecret: Data, localPassword: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
fileprivate func sendGramsFromWallet(decryptedSecret: Data, localPassword: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<PendingWalletTransaction, SendGramsFromWalletError> {
let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
return Signal { subscriber in
let disposable = MetaDisposable()
@ -334,6 +338,7 @@ public final class TonInstance {
subscriber.putError(.generic)
return
}
subscriber.putNext(PendingWalletTransaction(timestamp: Int64(Date().timeIntervalSince1970), validUntilTimestamp: result.sentUntil, bodyHash: result.bodyHash, address: toAddress, value: amount, comment: textMessage))
subscriber.putCompletion()
}, error: { error in
if let error = error as? TONError {
@ -454,6 +459,7 @@ public struct CombinedWalletState: Codable, Equatable {
public var walletState: WalletState
public var timestamp: Int64
public var topTransactions: [WalletTransaction]
public var pendingTransactions: [PendingWalletTransaction]
}
public struct WalletStateRecord: PostboxCoding, Equatable {
@ -708,7 +714,29 @@ public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStat
}
return topTransactions
|> mapToSignal { topTransactions -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions)
let lastTransactionTimestamp = topTransactions.last?.timestamp
var listTransactionBodyHashes = Set<Data>()
for transaction in topTransactions {
if let message = transaction.inMessage {
listTransactionBodyHashes.insert(message.bodyHash)
}
for message in transaction.outMessages {
listTransactionBodyHashes.insert(message.bodyHash)
}
}
let pendingTransactions = (cachedState?.pendingTransactions ?? []).filter { transaction in
if transaction.validUntilTimestamp <= syncUtime {
return false
} else if let lastTransactionTimestamp = lastTransactionTimestamp, transaction.validUntilTimestamp <= lastTransactionTimestamp {
return false
} else {
if listTransactionBodyHashes.contains(transaction.bodyHash) {
return false
}
return true
}
}
let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: pendingTransactions)
return postbox.transaction { transaction -> CombinedWalletStateResult in
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
@ -741,7 +769,7 @@ public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStat
}
return topTransactions
|> mapToSignal { topTransactions -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions)
let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions, pendingTransactions: [])
return .single(.updated(combinedState))
}
}
@ -760,11 +788,31 @@ public enum SendGramsFromWalletError {
case network
}
public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, localPassword: Data, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
public func sendGramsFromWallet(postbox: Postbox, network: Network, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, localPassword: Data, toAddress: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> {
return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance)
|> castError(SendGramsFromWalletError.self)
|> mapToSignal { fromAddress in
|> mapToSignal { fromAddress -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in
return tonInstance.sendGramsFromWallet(decryptedSecret: decryptedSecret, localPassword: localPassword, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId)
|> mapToSignal { result -> Signal<[PendingWalletTransaction], SendGramsFromWalletError> in
return postbox.transaction { transaction -> [PendingWalletTransaction] in
var updatedPendingTransactions: [PendingWalletTransaction] = []
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
for i in 0 ..< walletCollection.wallets.count {
if walletCollection.wallets[i].info.publicKey == walletInfo.publicKey {
if var state = walletCollection.wallets[i].state {
state.pendingTransactions.insert(result, at: 0)
walletCollection.wallets[i].state = state
updatedPendingTransactions = state.pendingTransactions
}
}
}
return walletCollection
})
return updatedPendingTransactions
}
|> castError(SendGramsFromWalletError.self)
}
}
}
@ -785,12 +833,14 @@ public final class WalletTransactionMessage: Codable, Equatable {
public let source: String
public let destination: String
public let textMessage: String
public let bodyHash: Data
init(value: Int64, source: String, destination: String, textMessage: String) {
init(value: Int64, source: String, destination: String, textMessage: String, bodyHash: Data) {
self.value = value
self.source = source
self.destination = destination
self.textMessage = textMessage
self.bodyHash = bodyHash
}
public static func ==(lhs: WalletTransactionMessage, rhs: WalletTransactionMessage) -> Bool {
@ -806,13 +856,53 @@ public final class WalletTransactionMessage: Codable, Equatable {
if lhs.textMessage != rhs.textMessage {
return false
}
if lhs.bodyHash != rhs.bodyHash {
return false
}
return true
}
}
private extension WalletTransactionMessage {
convenience init(tonTransactionMessage: TONTransactionMessage) {
self.init(value: tonTransactionMessage.value, source: tonTransactionMessage.source, destination: tonTransactionMessage.destination, textMessage: tonTransactionMessage.textMessage)
self.init(value: tonTransactionMessage.value, source: tonTransactionMessage.source, destination: tonTransactionMessage.destination, textMessage: tonTransactionMessage.textMessage, bodyHash: tonTransactionMessage.bodyHash)
}
}
public final class PendingWalletTransaction: Codable, Equatable {
public let timestamp: Int64
public let validUntilTimestamp: Int64
public let bodyHash: Data
public let address: String
public let value: Int64
public let comment: Data
public init(timestamp: Int64, validUntilTimestamp: Int64, bodyHash: Data, address: String, value: Int64, comment: Data) {
self.timestamp = timestamp
self.validUntilTimestamp = validUntilTimestamp
self.bodyHash = bodyHash
self.address = address
self.value = value
self.comment = comment
}
public static func ==(lhs: PendingWalletTransaction, rhs: PendingWalletTransaction) -> Bool {
if lhs.timestamp != rhs.timestamp {
return false
}
if lhs.validUntilTimestamp != rhs.validUntilTimestamp {
return false
}
if lhs.bodyHash != rhs.bodyHash {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.comment != rhs.comment {
return false
}
return true
}
}

View File

@ -1040,7 +1040,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
guard let config = walletConfiguration.config, let blockchainName = walletConfiguration.blockchainName else {
return
}
let tonContext = storedContext.context(config: config, blockchainName: blockchainName)
let tonContext = storedContext.context(config: config, blockchainName: blockchainName, enableProxy: !walletConfiguration.disableProxy)
if wallets.wallets.isEmpty {
if case .send = walletContext {

View File

@ -3,20 +3,28 @@ import TelegramCore
public struct WalletConfiguration {
static var defaultValue: WalletConfiguration {
return WalletConfiguration(config: nil, blockchainName: nil)
return WalletConfiguration(config: nil, blockchainName: nil, disableProxy: false)
}
public let config: String?
public let blockchainName: String?
public let disableProxy: Bool
fileprivate init(config: String?, blockchainName: String?) {
fileprivate init(config: String?, blockchainName: String?, disableProxy: Bool) {
self.config = config
self.blockchainName = blockchainName
self.disableProxy = disableProxy
}
public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration {
if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String {
return WalletConfiguration(config: config, blockchainName: blockchainName)
var disableProxy = false
if let value = data["wallet_disable_proxy"] as? String {
disableProxy = value != "0"
} else if let value = data["wallet_disable_proxy"] as? Int {
disableProxy = value != 0
}
return WalletConfiguration(config: config, blockchainName: blockchainName, disableProxy: disableProxy)
} else {
return .defaultValue
}

View File

@ -398,21 +398,32 @@ private struct WalletInfoListTransaction {
let updates: [ListViewUpdateItem]
}
enum WalletInfoTransaction: Equatable {
case completed(WalletTransaction)
case pending(PendingWalletTransaction)
}
private enum WalletInfoListEntryId: Hashable {
case empty
case transaction(WalletTransactionId)
case pendingTransaction(Data)
}
private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
case empty(String)
case transaction(Int, WalletTransaction)
case transaction(Int, WalletInfoTransaction)
var stableId: WalletInfoListEntryId {
switch self {
case .empty:
return .empty
case let .transaction(_, transaction):
return .transaction(transaction.transactionId)
switch transaction {
case let .completed(completed):
return .transaction(completed.transactionId)
case let .pending(pending):
return .pendingTransaction(pending.bodyHash)
}
}
}
@ -435,7 +446,7 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> ListViewItem {
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletInfoTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> ListViewItem {
switch self {
case let .empty(address):
return WalletInfoEmptyItem(account: account, theme: theme, strings: strings, address: address, displayAddressContextMenu: { node, frame in
@ -449,7 +460,7 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> WalletInfoListTransaction {
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletInfoTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> WalletInfoListTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -467,7 +478,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let walletInfo: WalletInfo?
private let address: String
private let openTransaction: (WalletTransaction) -> Void
private let openTransaction: (WalletInfoTransaction) -> Void
private let present: (ViewController, Any?) -> Void
private let hapticFeedback = HapticFeedback()
@ -498,7 +509,10 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var updateTimestampTimer: SwiftSignalKit.Timer?
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo?, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
private var pollCombinedStateDisposable: Disposable?
private var watchCombinedStateDisposable: Disposable?
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo?, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletInfoTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.tonContext = tonContext
self.presentationData = presentationData
@ -595,12 +609,55 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
}, queue: .mainQueue())
self.updateTimestampTimer?.start()
let subject: CombinedWalletStateSubject
if let walletInfo = walletInfo {
subject = .wallet(walletInfo)
self.watchCombinedStateDisposable = (account.postbox.preferencesView(keys: [PreferencesKeys.walletCollection])
|> deliverOnMainQueue).start(next: { [weak self] view in
guard let strongSelf = self, let wallets = view.values[PreferencesKeys.walletCollection] as? WalletCollection else {
return
}
for wallet in wallets.wallets {
if wallet.info.publicKey == walletInfo.publicKey {
if let state = wallet.state {
if state.pendingTransactions != strongSelf.combinedState?.pendingTransactions || state.timestamp != strongSelf.combinedState?.timestamp {
if !strongSelf.reloadingState {
strongSelf.updateCombinedState(combinedState: state, isUpdated: true)
}
}
}
break
}
}
})
} else {
subject = .address(address)
}
let pollCombinedState: Signal<Never, NoError> = (
getCombinedWalletState(postbox: account.postbox, subject: subject, tonInstance: tonContext.instance)
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> then(
Signal<Never, NoError>.complete()
|> delay(10.0, queue: .mainQueue())
)
)
|> restart
self.pollCombinedStateDisposable = (pollCombinedState
|> deliverOnMainQueue).start()
}
deinit {
self.stateDisposable.dispose()
self.transactionListDisposable.dispose()
self.updateTimestampTimer?.invalidate()
self.pollCombinedStateDisposable?.dispose()
self.watchCombinedStateDisposable?.dispose()
}
func scrollToHideHeader() {
@ -712,49 +769,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
combinedState = state
}
strongSelf.combinedState = combinedState
if let combinedState = combinedState {
strongSelf.headerNode.balanceNode.balance = formatBalanceText(max(0, combinedState.walletState.balance), decimalSeparator: strongSelf.presentationData.dateTimeFormat.decimalSeparator)
strongSelf.headerNode.balance = max(0, combinedState.walletState.balance)
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
}
strongSelf.reloadingState = false
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate, isScrolling: false)
}
strongSelf.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions)
if isUpdated {
strongSelf.headerNode.isRefreshing = false
}
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut), isScrolling: false)
}
let wasReady = strongSelf.isReady
strongSelf.isReady = strongSelf.combinedState != nil
if strongSelf.isReady && !wasReady {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false)
}
strongSelf.becameReady(animated: strongSelf.didSetContentReady)
}
}
if !strongSelf.didSetContentReady {
strongSelf.didSetContentReady = true
strongSelf.contentReady.set(.single(true))
}
strongSelf.updateCombinedState(combinedState: combinedState, isUpdated: isUpdated)
}, error: { [weak self] error in
guard let strongSelf = self else {
return
@ -798,6 +813,52 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}))
}
private func updateCombinedState(combinedState: CombinedWalletState?, isUpdated: Bool) {
self.combinedState = combinedState
if let combinedState = combinedState {
self.headerNode.balanceNode.balance = formatBalanceText(max(0, combinedState.walletState.balance), decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator)
self.headerNode.balance = max(0, combinedState.walletState.balance)
if self.isReady, let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
}
self.reloadingState = false
self.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
if self.isReady, let (layout, navigationHeight) = self.validLayout {
self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: self.listOffset ?? 0.0, transition: .immediate, isScrolling: false)
}
self.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions, pendingTransactions: combinedState.pendingTransactions)
if isUpdated {
self.headerNode.isRefreshing = false
}
if self.isReady, let (layout, navigationHeight) = self.validLayout {
self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: self.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut), isScrolling: false)
}
let wasReady = self.isReady
self.isReady = self.combinedState != nil
if self.isReady && !wasReady {
if let (layout, navigationHeight) = self.validLayout {
self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false)
}
self.becameReady(animated: self.didSetContentReady)
}
}
if !self.didSetContentReady {
self.didSetContentReady = true
self.contentReady.set(.single(true))
}
}
private func loadMoreTransactions() {
if self.loadingMoreTransactions {
return
@ -807,7 +868,12 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
if let last = self.currentEntries?.last {
switch last {
case let .transaction(_, transaction):
lastTransactionId = transaction.transactionId
switch transaction {
case let .completed(completed):
lastTransactionId = completed.transactionId
case .pending:
break
}
case .empty:
break
}
@ -817,7 +883,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.transactionsLoaded(isReload: false, transactions: transactions)
strongSelf.transactionsLoaded(isReload: false, transactions: transactions, pendingTransactions: [])
}, error: { [weak self] _ in
guard let strongSelf = self else {
return
@ -825,17 +891,23 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}))
}
private func transactionsLoaded(isReload: Bool, transactions: [WalletTransaction]) {
private func transactionsLoaded(isReload: Bool, transactions: [WalletTransaction], pendingTransactions: [PendingWalletTransaction]) {
self.loadingMoreTransactions = false
self.canLoadMoreTransactions = transactions.count > 2
var updatedEntries: [WalletInfoListEntry] = []
if isReload {
var existingIds = Set<WalletTransactionId>()
var existingIds = Set<WalletInfoListEntryId>()
for transaction in pendingTransactions {
if !existingIds.contains(.pendingTransaction(transaction.bodyHash)) {
existingIds.insert(.pendingTransaction(transaction.bodyHash))
updatedEntries.append(.transaction(updatedEntries.count, .pending(transaction)))
}
}
for transaction in transactions {
if !existingIds.contains(transaction.transactionId) {
existingIds.insert(transaction.transactionId)
updatedEntries.append(.transaction(updatedEntries.count, transaction))
if !existingIds.contains(.transaction(transaction.transactionId)) {
existingIds.insert(.transaction(transaction.transactionId))
updatedEntries.append(.transaction(updatedEntries.count, .completed(transaction)))
}
}
if updatedEntries.isEmpty {
@ -850,19 +922,19 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return true
}
}
var existingIds = Set<WalletTransactionId>()
var existingIds = Set<WalletInfoListEntryId>()
for entry in updatedEntries {
switch entry {
case let .transaction(_, transaction):
existingIds.insert(transaction.transactionId)
existingIds.insert(entry.stableId)
case .empty:
break
}
}
for transaction in transactions {
if !existingIds.contains(transaction.transactionId) {
existingIds.insert(transaction.transactionId)
updatedEntries.append(.transaction(updatedEntries.count, transaction))
if !existingIds.contains(.transaction(transaction.transactionId)) {
existingIds.insert(.transaction(transaction.transactionId))
updatedEntries.append(.transaction(updatedEntries.count, .completed(transaction)))
}
}
if updatedEntries.isEmpty {

View File

@ -14,18 +14,23 @@ class WalletInfoTransactionItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let walletTransaction: WalletTransaction
let walletTransaction: WalletInfoTransaction
let action: () -> Void
fileprivate let header: WalletInfoTransactionDateHeader?
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, walletTransaction: WalletTransaction, action: @escaping () -> Void) {
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, walletTransaction: WalletInfoTransaction, action: @escaping () -> Void) {
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.walletTransaction = walletTransaction
self.action = action
self.header = WalletInfoTransactionDateHeader(timestamp: Int32(clamping: walletTransaction.timestamp), theme: theme, strings: strings)
switch walletTransaction {
case let .completed(transaction):
self.header = WalletInfoTransactionDateHeader(timestamp: Int32(clamping: transaction.timestamp), theme: theme, strings: strings)
case .pending:
self.header = WalletInfoTransactionDateHeader(timestamp: Int32.max, theme: theme, strings: strings)
}
}
func getDateAtBottom(top: ListViewItem?, bottom: ListViewItem?) -> Bool {
@ -101,6 +106,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
private let textNode: TextNode
private let descriptionNode: TextNode
private let dateNode: TextNode
private var statusNode: StatusClockNode?
private let activateArea: AccessibilityAreaNode
@ -191,28 +197,50 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let title: String
let directionText: String
let titleColor: UIColor
let transferredValue = item.walletTransaction.transferredValueWithoutFees
let transferredValue: Int64
switch item.walletTransaction {
case let .completed(transaction):
transferredValue = transaction.transferredValueWithoutFees
case let .pending(transaction):
transferredValue = -transaction.value
}
var text: String = ""
var description: String = ""
if transferredValue <= 0 {
sign = ""
title = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.list.itemDestructiveColor
if item.walletTransaction.outMessages.isEmpty {
directionText = ""
text = item.strings.Wallet_Info_UnknownTransaction
} else {
directionText = item.strings.Wallet_Info_TransactionTo
for message in item.walletTransaction.outMessages {
if !text.isEmpty {
text.append("\n")
switch item.walletTransaction {
case let .completed(transaction):
if transaction.outMessages.isEmpty {
directionText = ""
text = item.strings.Wallet_Info_UnknownTransaction
} else {
directionText = item.strings.Wallet_Info_TransactionTo
for message in transaction.outMessages {
if !text.isEmpty {
text.append("\n")
}
text.append(formatAddress(message.destination))
if !description.isEmpty {
description.append("\n")
}
description.append(message.textMessage)
}
text.append(formatAddress(message.destination))
}
case let .pending(transaction):
directionText = item.strings.Wallet_Info_TransactionTo
if !text.isEmpty {
text.append("\n")
}
text.append(formatAddress(transaction.address))
if let textMessage = String(data: transaction.comment, encoding: .utf8), !textMessage.isEmpty {
if !description.isEmpty {
description.append("\n")
}
description.append(message.textMessage)
description.append(textMessage)
}
}
} else {
@ -220,31 +248,41 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.chatList.secretTitleColor
directionText = item.strings.Wallet_Info_TransactionFrom
if let inMessage = item.walletTransaction.inMessage {
text = formatAddress(inMessage.source)
description = inMessage.textMessage
} else {
switch item.walletTransaction {
case let .completed(transaction):
if let inMessage = transaction.inMessage {
text = formatAddress(inMessage.source)
description = inMessage.textMessage
} else {
text = "<unknown>"
}
case .pending:
text = "<unknown>"
}
}
if item.walletTransaction.storageFee != 0 {
let feeText = item.strings.Wallet_Info_TransactionStorageFee(formatBalanceText(-item.walletTransaction.storageFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if !description.isEmpty {
description.append("\n")
let dateText: String
switch item.walletTransaction {
case let .completed(transaction):
if transaction.storageFee != 0 {
let feeText = item.strings.Wallet_Info_TransactionStorageFee(formatBalanceText(-transaction.storageFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if !description.isEmpty {
description.append("\n")
}
description += "\(feeText)"
}
description += "\(feeText)"
}
if item.walletTransaction.otherFee != 0 {
let feeText = item.strings.Wallet_Info_TransactionOtherFee(formatBalanceText(-item.walletTransaction.otherFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if !description.isEmpty {
description.append("\n")
if transaction.otherFee != 0 {
let feeText = item.strings.Wallet_Info_TransactionOtherFee(formatBalanceText(-transaction.otherFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if !description.isEmpty {
description.append("\n")
}
description += "\(feeText)"
}
description += "\(feeText)"
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
case let .pending(transaction):
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
}
let dateText = stringForMessageTimestamp(timestamp: Int32(clamping: item.walletTransaction.timestamp), dateTimeFormat: item.dateTimeFormat)
let (dateLayout, dateApply) = makeDateLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dateText, font: dateFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (directionLayout, directionApply) = makeDirectionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: directionText, font: directionFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -347,7 +385,27 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
strongSelf.descriptionNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textFrame.maxY + textSpacing), size: descriptionLayout.size)
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - dateLayout.size.width, y: topInset), size: dateLayout.size)
let dateFrame = CGRect(origin: CGPoint(x: params.width - leftInset - dateLayout.size.width, y: topInset), size: dateLayout.size)
strongSelf.dateNode.frame = dateFrame
switch item.walletTransaction {
case .pending:
let statusNode: StatusClockNode
if let current = strongSelf.statusNode {
statusNode = current
} else {
statusNode = StatusClockNode(theme: item.theme)
strongSelf.statusNode = statusNode
strongSelf.addSubnode(statusNode)
}
let statusSize = CGSize(width: 11.0, height: 11.0)
statusNode.frame = CGRect(origin: CGPoint(x: dateFrame.minX - statusSize.width - 4.0, y: dateFrame.minY + floor((dateFrame.height - statusSize.height) / 2.0) - UIScreenPixel), size: statusSize)
case .completed:
if let statusNode = strongSelf.statusNode {
strongSelf.statusNode = nil
statusNode.removeFromSupernode()
}
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topHighlightInset + -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel * 2.0 - topHighlightInset))
}
@ -419,8 +477,6 @@ private let granularity: Int32 = 60 * 60 * 24
private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
private let timestamp: Int32
private let roundedTimestamp: Int32
private let month: Int32
private let year: Int32
private let localTimestamp: Int32
let id: Int64
@ -432,15 +488,8 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
self.theme = theme
self.strings = strings
var time: time_t = time_t(timestamp + timezoneOffset)
var timeinfo: tm = tm()
localtime_r(&time, &timeinfo)
self.month = timeinfo.tm_mon
self.year = timeinfo.tm_year
if timestamp == Int32.max {
self.localTimestamp = timestamp / (granularity) * (granularity)
self.localTimestamp = timestamp
} else {
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
}
@ -454,7 +503,7 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
let height: CGFloat = 40.0
func node() -> ListViewItemHeaderNode {
return WalletInfoTransactionDateHeaderNode(theme: self.theme, strings: self.strings, roundedTimestamp: self.localTimestamp, month: self.month, year: self.year)
return WalletInfoTransactionDateHeaderNode(theme: self.theme, strings: self.strings, roundedTimestamp: self.localTimestamp)
}
}
@ -498,7 +547,7 @@ final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
let backgroundNode: ASDisplayNode
let separatorNode: ASDisplayNode
init(theme: PresentationTheme, strings: PresentationStrings, roundedTimestamp: Int32, month: Int32, year: Int32) {
init(theme: PresentationTheme, strings: PresentationStrings, roundedTimestamp: Int32) {
self.theme = theme
self.strings = strings
@ -515,25 +564,29 @@ final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
super.init()
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(roundedTimestamp)
var timeinfo: tm = tm()
gmtime_r(&t, &timeinfo)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
var text: String
if timeinfo.tm_year == timeinfoNow.tm_year {
if timeinfo.tm_yday == timeinfoNow.tm_yday {
text = strings.Weekday_Today
} else {
text = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").0
}
if roundedTimestamp == Int32.max {
text = strings.Wallet_Info_TransactionPendingHeader
} else {
text = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").0
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(roundedTimestamp)
var timeinfo: tm = tm()
gmtime_r(&t, &timeinfo)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
if timeinfo.tm_year == timeinfoNow.tm_year {
if timeinfo.tm_yday == timeinfoNow.tm_yday {
text = strings.Weekday_Today
} else {
text = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").0
}
} else {
text = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").0
}
}
self.addSubnode(self.backgroundNode)
@ -568,3 +621,64 @@ final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
transition.updateAlpha(node: self.separatorNode, alpha: (1.0 - factor) * 0.0 + factor * 1.0)
}
}
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
if let _ = layer.animation(forKey: "clockFrameAnimation") {
return
}
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.duration = duration
basicAnimation.fromValue = NSNumber(value: Float(0.0))
basicAnimation.toValue = NSNumber(value: Float(Double.pi * 2.0))
basicAnimation.repeatCount = Float.infinity
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
basicAnimation.beginTime = 1.0
layer.add(basicAnimation, forKey: "clockFrameAnimation")
}
private final class StatusClockNode: ASDisplayNode {
private var clockFrameNode: ASImageNode
private var clockMinNode: ASImageNode
init(theme: PresentationTheme) {
self.clockFrameNode = ASImageNode()
self.clockMinNode = ASImageNode()
super.init()
self.clockFrameNode.image = PresentationResourcesChatList.clockFrameImage(theme)
self.clockMinNode.image = PresentationResourcesChatList.clockMinImage(theme)
self.addSubnode(self.clockFrameNode)
self.addSubnode(self.clockMinNode)
}
override func didEnterHierarchy() {
super.didEnterHierarchy()
maybeAddRotationAnimation(self.clockFrameNode.layer, duration: 6.0)
maybeAddRotationAnimation(self.clockMinNode.layer, duration: 1.0)
}
override func didExitHierarchy() {
super.didExitHierarchy()
self.clockFrameNode.layer.removeAllAnimations()
self.clockMinNode.layer.removeAllAnimations()
}
override func layout() {
super.layout()
let bounds = self.bounds
if let frameImage = self.clockFrameNode.image {
self.clockFrameNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - frameImage.size.width) / 2.0), y: floorToScreenPixels((bounds.height - frameImage.size.height) / 2.0)), size: frameImage.size)
}
if let minImage = self.clockMinNode.image {
self.clockMinNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - minImage.size.width) / 2.0), y: floorToScreenPixels((bounds.height - minImage.size.height) / 2.0)), size: minImage.size)
}
}
}

View File

@ -135,7 +135,7 @@ public final class WalletSplashScreen: ViewController {
}
private func sendGrams(walletInfo: WalletInfo, decryptedSecret: Data, address: String, amount: Int64, textMessage: Data, forceIfDestinationNotInitialized: Bool, randomId: Int64, serverSalt: Data) {
let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: serverSalt, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId)
let _ = (sendGramsFromWallet(postbox: self.context.account.postbox, network: self.context.account.network, tonInstance: self.tonContext.instance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: serverSalt, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId)
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let strongSelf = self else {
return

View File

@ -29,6 +29,8 @@ private final class WalletTransactionInfoControllerArguments {
private enum WalletTransactionInfoSection: Int32 {
case amount
case info
case storageFee
case otherFee
case comment
}
@ -46,11 +48,15 @@ private enum WalletTransactionInfoEntryTag: ItemListItemTag {
}
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction)
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletInfoTransaction)
case infoHeader(PresentationTheme, String)
case infoAddress(PresentationTheme, String, String?)
case infoCopyAddress(PresentationTheme, String)
case infoSendGrams(PresentationTheme, String)
case storageFeeHeader(PresentationTheme, String)
case storageFee(PresentationTheme, String)
case otherFeeHeader(PresentationTheme, String)
case otherFee(PresentationTheme, String)
case commentHeader(PresentationTheme, String)
case comment(PresentationTheme, String)
@ -60,6 +66,10 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
return WalletTransactionInfoSection.amount.rawValue
case .infoHeader, .infoAddress, .infoCopyAddress, .infoSendGrams:
return WalletTransactionInfoSection.info.rawValue
case .storageFeeHeader, .storageFee:
return WalletTransactionInfoSection.storageFee.rawValue
case .otherFeeHeader, .otherFee:
return WalletTransactionInfoSection.otherFee.rawValue
case .commentHeader, .comment:
return WalletTransactionInfoSection.comment.rawValue
}
@ -77,10 +87,18 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
return 3
case .infoSendGrams:
return 4
case .commentHeader:
case .storageFeeHeader:
return 5
case .comment:
case .storageFee:
return 6
case .otherFeeHeader:
return 7
case .otherFee:
return 8
case .commentHeader:
return 9
case .comment:
return 10
}
}
@ -108,6 +126,14 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.sendGrams()
})
case let .storageFeeHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .storageFee(theme, text):
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil)
case let .otherFeeHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .otherFee(theme, text):
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil)
case let .commentHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .comment(theme, text):
@ -138,52 +164,68 @@ private func stringForAddress(strings: PresentationStrings, address: WalletTrans
}
}
private func extractAddress(_ walletTransaction: WalletTransaction) -> WalletTransactionAddress {
let transferredValue = walletTransaction.transferredValueWithoutFees
if transferredValue <= 0 {
if walletTransaction.outMessages.isEmpty {
return .none
private func extractAddress(_ walletTransaction: WalletInfoTransaction) -> WalletTransactionAddress {
switch walletTransaction {
case let .completed(walletTransaction):
let transferredValue = walletTransaction.transferredValueWithoutFees
if transferredValue <= 0 {
if walletTransaction.outMessages.isEmpty {
return .none
} else {
var addresses: [String] = []
for message in walletTransaction.outMessages {
addresses.append(message.destination)
}
return .list(addresses)
}
} else {
var addresses: [String] = []
if let inMessage = walletTransaction.inMessage {
return .list([inMessage.source])
} else {
return .unknown
}
}
return .none
case let .pending(pending):
return .list([pending.address])
}
}
private func extractDescription(_ walletTransaction: WalletInfoTransaction) -> String {
switch walletTransaction {
case let .completed(walletTransaction):
let transferredValue = walletTransaction.transferredValueWithoutFees
var text = ""
if transferredValue <= 0 {
for message in walletTransaction.outMessages {
addresses.append(message.destination)
if !text.isEmpty {
text.append("\n\n")
}
text.append(message.textMessage)
}
return .list(addresses)
}
} else {
if let inMessage = walletTransaction.inMessage {
return .list([inMessage.source])
} else {
return .unknown
}
}
return .none
}
private func extractDescription(_ walletTransaction: WalletTransaction) -> String {
let transferredValue = walletTransaction.transferredValueWithoutFees
var text = ""
if transferredValue <= 0 {
for message in walletTransaction.outMessages {
if !text.isEmpty {
text.append("\n\n")
if let inMessage = walletTransaction.inMessage {
text = inMessage.textMessage
}
text.append(message.textMessage)
}
} else {
if let inMessage = walletTransaction.inMessage {
text = inMessage.textMessage
}
return text
case let .pending(pending):
return String(data: pending.comment, encoding: .utf8) ?? ""
}
return text
}
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletTransaction, state: WalletTransactionInfoControllerState, walletInfo: WalletInfo?) -> [WalletTransactionInfoEntry] {
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletInfoTransaction, state: WalletTransactionInfoControllerState, walletInfo: WalletInfo?) -> [WalletTransactionInfoEntry] {
var entries: [WalletTransactionInfoEntry] = []
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction))
let transferredValue = walletTransaction.transferredValueWithoutFees
let transferredValue: Int64
switch walletTransaction {
case let .completed(transaction):
transferredValue = transaction.transferredValueWithoutFees
case let .pending(transaction):
transferredValue = transaction.value
}
let address = extractAddress(walletTransaction)
var singleAddress: String?
let text = stringForAddress(strings: presentationData.strings, address: address)
@ -204,6 +246,17 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
entries.append(.infoSendGrams(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SendGrams))
}
if case let .completed(transaction) = walletTransaction {
if transaction.storageFee != 0 {
entries.append(.storageFeeHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_StorageFeeHeader))
entries.append(.storageFee(presentationData.theme, formatBalanceText(-transaction.storageFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
}
if transaction.otherFee != 0 {
entries.append(.otherFeeHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_OtherFeeHeader))
entries.append(.otherFee(presentationData.theme, formatBalanceText(-transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
}
}
if !description.isEmpty {
entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CommentHeader))
entries.append(.comment(presentationData.theme, description))
@ -212,7 +265,7 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
return entries
}
func walletTransactionInfoController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo?, walletTransaction: WalletTransaction, enableDebugActions: Bool) -> ViewController {
func walletTransactionInfoController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo?, walletTransaction: WalletInfoTransaction, enableDebugActions: Bool) -> ViewController {
let statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: WalletTransactionInfoControllerState())
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
@ -314,11 +367,11 @@ class WalletTransactionHeaderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let walletTransaction: WalletTransaction
let walletTransaction: WalletInfoTransaction
let sectionId: ItemListSectionId
let isAlwaysPlain: Bool = true
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, walletTransaction: WalletTransaction, sectionId: ItemListSectionId) {
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, walletTransaction: WalletInfoTransaction, sectionId: ItemListSectionId) {
self.theme = theme
self.strings = strings
self.dateTimeFormat = dateTimeFormat
@ -421,7 +474,13 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
let signString: String
let balanceString: String
let titleColor: UIColor
let transferredValue = item.walletTransaction.transferredValueWithoutFees
let transferredValue: Int64
switch item.walletTransaction {
case let .completed(transaction):
transferredValue = transaction.transferredValueWithoutFees
case let .pending(transaction):
transferredValue = transaction.value
}
if transferredValue <= 0 {
signString = ""
balanceString = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
@ -443,7 +502,14 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
}
let titleSign = NSAttributedString(string: signString, font: Font.bold(48.0), textColor: titleColor)
let subtitle: String = stringForFullDate(timestamp: Int32(clamping: item.walletTransaction.timestamp), strings: item.strings, dateTimeFormat: item.dateTimeFormat)
let timestamp: Int64
switch item.walletTransaction {
case let .completed(transaction):
timestamp = transaction.timestamp
case let .pending(transaction):
timestamp = transaction.timestamp
}
let subtitle: String = stringForFullDate(timestamp: Int32(clamping: timestamp), strings: item.strings, dateTimeFormat: item.dateTimeFormat)
let (titleSignLayout, titleSignApply) = makeTitleSignLayout(TextNodeLayoutArguments(attributedString: titleSign, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))