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" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_savedmessages.pdf"
},
{
"idiom" : "universal",
"filename" : "SavedMessagesIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SavedMessagesIcon@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "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.Updating" = "updating";
"Wallet.Info.TransactionStorageFee" = "%@ storage fee"; "Wallet.Info.TransactionStorageFee" = "%@ storage fee";
"Wallet.Info.TransactionOtherFee" = "%@ transaction fee"; "Wallet.Info.TransactionOtherFee" = "%@ transaction fee";
"Wallet.Info.TransactionPendingHeader" = "Pending";
"Wallet.Qr.ScanCode" = "Scan QR Code"; "Wallet.Qr.ScanCode" = "Scan QR Code";
"Wallet.Qr.Title" = "QR Code"; "Wallet.Qr.Title" = "QR Code";
"Wallet.Receive.Title" = "Receive Grams"; "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.AddressCopied" = "Address copied to clipboard.";
"Wallet.TransactionInfo.SendGrams" = "Send Grams"; "Wallet.TransactionInfo.SendGrams" = "Send Grams";
"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; "Wallet.TransactionInfo.CommentHeader" = "COMMENT";
"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE";
"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE";
"Wallet.WordCheck.Title" = "Test Time!"; "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.Text" = "Lets check that you wrote them down correctly. Please enter words\n**%1$@**, **%2$@** and **%3$@** below:";
"Wallet.WordCheck.Continue" = "Continue"; "Wallet.WordCheck.Continue" = "Continue";

View File

@ -471,13 +471,13 @@ public final class StoredTonContext {
self.keychain = keychain 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 return self.currentInstance.with { data -> TonContext in
if let instance = data.instance, data.config == config, data.blockchainName == blockchainName { if let instance = data.instance, data.config == config, data.blockchainName == blockchainName {
return TonContext(instance: instance, keychain: self.keychain) return TonContext(instance: instance, keychain: self.keychain)
} else { } else {
data.config = config 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 data.instance = instance
return TonContext(instance: instance, keychain: self.keychain) 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 source;
@property (nonatomic, strong, readonly) NSString * _Nonnull destination; @property (nonatomic, strong, readonly) NSString * _Nonnull destination;
@property (nonatomic, strong, readonly) NSString * _Nonnull textMessage; @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 @end
@ -77,14 +78,15 @@ NS_ASSUME_NONNULL_BEGIN
@interface TONSendGramsResult : NSObject @interface TONSendGramsResult : NSObject
@property (nonatomic, readonly) int64_t sentUntil; @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 @end
@interface TON : NSObject @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 *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword;
- (MTSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey; - (MTSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey;

View File

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

View File

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

View File

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

View File

@ -3,20 +3,28 @@ import TelegramCore
public struct WalletConfiguration { public struct WalletConfiguration {
static var defaultValue: 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 config: String?
public let blockchainName: 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.config = config
self.blockchainName = blockchainName self.blockchainName = blockchainName
self.disableProxy = disableProxy
} }
public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration { 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 { 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 { } else {
return .defaultValue return .defaultValue
} }

View File

@ -398,21 +398,32 @@ private struct WalletInfoListTransaction {
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
} }
enum WalletInfoTransaction: Equatable {
case completed(WalletTransaction)
case pending(PendingWalletTransaction)
}
private enum WalletInfoListEntryId: Hashable { private enum WalletInfoListEntryId: Hashable {
case empty case empty
case transaction(WalletTransactionId) case transaction(WalletTransactionId)
case pendingTransaction(Data)
} }
private enum WalletInfoListEntry: Equatable, Comparable, Identifiable { private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
case empty(String) case empty(String)
case transaction(Int, WalletTransaction) case transaction(Int, WalletInfoTransaction)
var stableId: WalletInfoListEntryId { var stableId: WalletInfoListEntryId {
switch self { switch self {
case .empty: case .empty:
return .empty return .empty
case let .transaction(_, transaction): 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 { switch self {
case let .empty(address): case let .empty(address):
return WalletInfoEmptyItem(account: account, theme: theme, strings: strings, address: address, displayAddressContextMenu: { node, frame in 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -467,7 +478,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let walletInfo: WalletInfo? private let walletInfo: WalletInfo?
private let address: String private let address: String
private let openTransaction: (WalletTransaction) -> Void private let openTransaction: (WalletInfoTransaction) -> Void
private let present: (ViewController, Any?) -> Void private let present: (ViewController, Any?) -> Void
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
@ -498,7 +509,10 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var updateTimestampTimer: SwiftSignalKit.Timer? 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.account = account
self.tonContext = tonContext self.tonContext = tonContext
self.presentationData = presentationData self.presentationData = presentationData
@ -595,12 +609,55 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp) strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
}, queue: .mainQueue()) }, queue: .mainQueue())
self.updateTimestampTimer?.start() 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 { deinit {
self.stateDisposable.dispose() self.stateDisposable.dispose()
self.transactionListDisposable.dispose() self.transactionListDisposable.dispose()
self.updateTimestampTimer?.invalidate() self.updateTimestampTimer?.invalidate()
self.pollCombinedStateDisposable?.dispose()
self.watchCombinedStateDisposable?.dispose()
} }
func scrollToHideHeader() { func scrollToHideHeader() {
@ -712,49 +769,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
combinedState = state combinedState = state
} }
strongSelf.combinedState = combinedState strongSelf.updateCombinedState(combinedState: combinedState, isUpdated: isUpdated)
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))
}
}, error: { [weak self] error in }, error: { [weak self] error in
guard let strongSelf = self else { guard let strongSelf = self else {
return 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() { private func loadMoreTransactions() {
if self.loadingMoreTransactions { if self.loadingMoreTransactions {
return return
@ -807,7 +868,12 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
if let last = self.currentEntries?.last { if let last = self.currentEntries?.last {
switch last { switch last {
case let .transaction(_, transaction): case let .transaction(_, transaction):
lastTransactionId = transaction.transactionId switch transaction {
case let .completed(completed):
lastTransactionId = completed.transactionId
case .pending:
break
}
case .empty: case .empty:
break break
} }
@ -817,7 +883,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.transactionsLoaded(isReload: false, transactions: transactions) strongSelf.transactionsLoaded(isReload: false, transactions: transactions, pendingTransactions: [])
}, error: { [weak self] _ in }, error: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return 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.loadingMoreTransactions = false
self.canLoadMoreTransactions = transactions.count > 2 self.canLoadMoreTransactions = transactions.count > 2
var updatedEntries: [WalletInfoListEntry] = [] var updatedEntries: [WalletInfoListEntry] = []
if isReload { 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 { for transaction in transactions {
if !existingIds.contains(transaction.transactionId) { if !existingIds.contains(.transaction(transaction.transactionId)) {
existingIds.insert(transaction.transactionId) existingIds.insert(.transaction(transaction.transactionId))
updatedEntries.append(.transaction(updatedEntries.count, transaction)) updatedEntries.append(.transaction(updatedEntries.count, .completed(transaction)))
} }
} }
if updatedEntries.isEmpty { if updatedEntries.isEmpty {
@ -850,19 +922,19 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return true return true
} }
} }
var existingIds = Set<WalletTransactionId>() var existingIds = Set<WalletInfoListEntryId>()
for entry in updatedEntries { for entry in updatedEntries {
switch entry { switch entry {
case let .transaction(_, transaction): case let .transaction(_, transaction):
existingIds.insert(transaction.transactionId) existingIds.insert(entry.stableId)
case .empty: case .empty:
break break
} }
} }
for transaction in transactions { for transaction in transactions {
if !existingIds.contains(transaction.transactionId) { if !existingIds.contains(.transaction(transaction.transactionId)) {
existingIds.insert(transaction.transactionId) existingIds.insert(.transaction(transaction.transactionId))
updatedEntries.append(.transaction(updatedEntries.count, transaction)) updatedEntries.append(.transaction(updatedEntries.count, .completed(transaction)))
} }
} }
if updatedEntries.isEmpty { if updatedEntries.isEmpty {

View File

@ -14,18 +14,23 @@ class WalletInfoTransactionItem: ListViewItem {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let walletTransaction: WalletTransaction let walletTransaction: WalletInfoTransaction
let action: () -> Void let action: () -> Void
fileprivate let header: WalletInfoTransactionDateHeader? 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.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.walletTransaction = walletTransaction self.walletTransaction = walletTransaction
self.action = action 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 { func getDateAtBottom(top: ListViewItem?, bottom: ListViewItem?) -> Bool {
@ -101,6 +106,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
private let textNode: TextNode private let textNode: TextNode
private let descriptionNode: TextNode private let descriptionNode: TextNode
private let dateNode: TextNode private let dateNode: TextNode
private var statusNode: StatusClockNode?
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
@ -191,28 +197,50 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let title: String let title: String
let directionText: String let directionText: String
let titleColor: UIColor 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 text: String = ""
var description: String = "" var description: String = ""
if transferredValue <= 0 { if transferredValue <= 0 {
sign = "" sign = ""
title = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))" title = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.list.itemDestructiveColor titleColor = item.theme.list.itemDestructiveColor
if item.walletTransaction.outMessages.isEmpty { switch item.walletTransaction {
directionText = "" case let .completed(transaction):
text = item.strings.Wallet_Info_UnknownTransaction if transaction.outMessages.isEmpty {
} else { directionText = ""
directionText = item.strings.Wallet_Info_TransactionTo text = item.strings.Wallet_Info_UnknownTransaction
for message in item.walletTransaction.outMessages { } else {
if !text.isEmpty { directionText = item.strings.Wallet_Info_TransactionTo
text.append("\n") for message in transaction.outMessages {
} if !text.isEmpty {
text.append(formatAddress(message.destination)) text.append("\n")
}
text.append(formatAddress(message.destination))
if !description.isEmpty {
description.append("\n")
}
description.append(message.textMessage)
}
}
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 { if !description.isEmpty {
description.append("\n") description.append("\n")
} }
description.append(message.textMessage) description.append(textMessage)
} }
} }
} else { } else {
@ -220,31 +248,41 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))" title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.chatList.secretTitleColor titleColor = item.theme.chatList.secretTitleColor
directionText = item.strings.Wallet_Info_TransactionFrom directionText = item.strings.Wallet_Info_TransactionFrom
if let inMessage = item.walletTransaction.inMessage { switch item.walletTransaction {
text = formatAddress(inMessage.source) case let .completed(transaction):
description = inMessage.textMessage if let inMessage = transaction.inMessage {
} else { text = formatAddress(inMessage.source)
description = inMessage.textMessage
} else {
text = "<unknown>"
}
case .pending:
text = "<unknown>" text = "<unknown>"
} }
} }
if item.walletTransaction.storageFee != 0 { let dateText: String
let feeText = item.strings.Wallet_Info_TransactionStorageFee(formatBalanceText(-item.walletTransaction.storageFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0 switch item.walletTransaction {
if !description.isEmpty { case let .completed(transaction):
description.append("\n") 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 transaction.otherFee != 0 {
} let feeText = item.strings.Wallet_Info_TransactionOtherFee(formatBalanceText(-transaction.otherFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if item.walletTransaction.otherFee != 0 { if !description.isEmpty {
let feeText = item.strings.Wallet_Info_TransactionOtherFee(formatBalanceText(-item.walletTransaction.otherFee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0 description.append("\n")
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 (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())) 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.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)) 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 final class WalletInfoTransactionDateHeader: ListViewItemHeader {
private let timestamp: Int32 private let timestamp: Int32
private let roundedTimestamp: Int32 private let roundedTimestamp: Int32
private let month: Int32
private let year: Int32
private let localTimestamp: Int32 private let localTimestamp: Int32
let id: Int64 let id: Int64
@ -432,15 +488,8 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
self.theme = theme self.theme = theme
self.strings = strings 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 { if timestamp == Int32.max {
self.localTimestamp = timestamp / (granularity) * (granularity) self.localTimestamp = timestamp
} else { } else {
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity) self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
} }
@ -454,7 +503,7 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
let height: CGFloat = 40.0 let height: CGFloat = 40.0
func node() -> ListViewItemHeaderNode { 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 backgroundNode: ASDisplayNode
let separatorNode: 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.theme = theme
self.strings = strings self.strings = strings
@ -515,25 +564,29 @@ final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
super.init() 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 var text: String
if timeinfo.tm_year == timeinfoNow.tm_year { if roundedTimestamp == Int32.max {
if timeinfo.tm_yday == timeinfoNow.tm_yday { text = strings.Wallet_Info_TransactionPendingHeader
text = strings.Weekday_Today
} else {
text = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").0
}
} else { } 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) 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) 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) { 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 |> deliverOnMainQueue).start(error: { [weak self] error in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@ -29,6 +29,8 @@ private final class WalletTransactionInfoControllerArguments {
private enum WalletTransactionInfoSection: Int32 { private enum WalletTransactionInfoSection: Int32 {
case amount case amount
case info case info
case storageFee
case otherFee
case comment case comment
} }
@ -46,11 +48,15 @@ private enum WalletTransactionInfoEntryTag: ItemListItemTag {
} }
private enum WalletTransactionInfoEntry: ItemListNodeEntry { private enum WalletTransactionInfoEntry: ItemListNodeEntry {
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction) case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletInfoTransaction)
case infoHeader(PresentationTheme, String) case infoHeader(PresentationTheme, String)
case infoAddress(PresentationTheme, String, String?) case infoAddress(PresentationTheme, String, String?)
case infoCopyAddress(PresentationTheme, String) case infoCopyAddress(PresentationTheme, String)
case infoSendGrams(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 commentHeader(PresentationTheme, String)
case comment(PresentationTheme, String) case comment(PresentationTheme, String)
@ -60,6 +66,10 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
return WalletTransactionInfoSection.amount.rawValue return WalletTransactionInfoSection.amount.rawValue
case .infoHeader, .infoAddress, .infoCopyAddress, .infoSendGrams: case .infoHeader, .infoAddress, .infoCopyAddress, .infoSendGrams:
return WalletTransactionInfoSection.info.rawValue return WalletTransactionInfoSection.info.rawValue
case .storageFeeHeader, .storageFee:
return WalletTransactionInfoSection.storageFee.rawValue
case .otherFeeHeader, .otherFee:
return WalletTransactionInfoSection.otherFee.rawValue
case .commentHeader, .comment: case .commentHeader, .comment:
return WalletTransactionInfoSection.comment.rawValue return WalletTransactionInfoSection.comment.rawValue
} }
@ -77,10 +87,18 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
return 3 return 3
case .infoSendGrams: case .infoSendGrams:
return 4 return 4
case .commentHeader: case .storageFeeHeader:
return 5 return 5
case .comment: case .storageFee:
return 6 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: { return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.sendGrams() 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): case let .commentHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .comment(theme, text): case let .comment(theme, text):
@ -138,52 +164,68 @@ private func stringForAddress(strings: PresentationStrings, address: WalletTrans
} }
} }
private func extractAddress(_ walletTransaction: WalletTransaction) -> WalletTransactionAddress { private func extractAddress(_ walletTransaction: WalletInfoTransaction) -> WalletTransactionAddress {
let transferredValue = walletTransaction.transferredValueWithoutFees switch walletTransaction {
if transferredValue <= 0 { case let .completed(walletTransaction):
if walletTransaction.outMessages.isEmpty { let transferredValue = walletTransaction.transferredValueWithoutFees
return .none 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 { } 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 { 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 { } else {
return .unknown if let inMessage = walletTransaction.inMessage {
} text = inMessage.textMessage
}
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")
} }
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] = [] var entries: [WalletTransactionInfoEntry] = []
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction)) 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) let address = extractAddress(walletTransaction)
var singleAddress: String? var singleAddress: String?
let text = stringForAddress(strings: presentationData.strings, address: address) 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)) 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 { if !description.isEmpty {
entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CommentHeader)) entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CommentHeader))
entries.append(.comment(presentationData.theme, description)) entries.append(.comment(presentationData.theme, description))
@ -212,7 +265,7 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
return entries 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 statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: WalletTransactionInfoControllerState()) let stateValue = Atomic(value: WalletTransactionInfoControllerState())
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
@ -314,11 +367,11 @@ class WalletTransactionHeaderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let walletTransaction: WalletTransaction let walletTransaction: WalletInfoTransaction
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let isAlwaysPlain: Bool = true 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.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
@ -421,7 +474,13 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
let signString: String let signString: String
let balanceString: String let balanceString: String
let titleColor: UIColor 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 { if transferredValue <= 0 {
signString = "" signString = ""
balanceString = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))" 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 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 (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())) 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()))