mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Wallet improvements
This commit is contained in:
parent
4c26ee0654
commit
0f8c455216
@ -1053,7 +1053,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
if self.stackFromBottom {
|
||||
topOffset = 0.0
|
||||
} else {
|
||||
topOffset = self.visibleSize.height
|
||||
topOffset = 0.0
|
||||
}
|
||||
} else {
|
||||
if self.stackFromBottom {
|
||||
|
@ -85,6 +85,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
var statusBarStyle: StatusBarStyle = .Ignore
|
||||
var statusBarStyleUpdated: (() -> Void)?
|
||||
|
||||
init(controllerRemoved: @escaping (ViewController) -> Void) {
|
||||
self.controllerRemoved = controllerRemoved
|
||||
|
||||
@ -286,6 +289,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
|
||||
if let top = self.state.top {
|
||||
self.applyLayout(layout: layout, to: top, transition: transition)
|
||||
self.statusBarStyle = top.value.statusBar.statusBarStyle
|
||||
} else {
|
||||
self.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
if self.state.transition == nil {
|
||||
|
@ -238,6 +238,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
let navigationLayout = makeNavigationLayout(layout: layout, controllers: self._viewControllers)
|
||||
|
||||
var transition = transition
|
||||
var statusBarStyle: StatusBarStyle = .Ignore
|
||||
var animateStatusBarStyleTransition = transition.isAnimated
|
||||
|
||||
var modalContainers: [NavigationModalContainer] = []
|
||||
for i in 0 ..< navigationLayout.modal.count {
|
||||
@ -408,6 +410,15 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
if let rootContainer = self.rootContainer {
|
||||
switch rootContainer {
|
||||
case let .flat(container):
|
||||
statusBarStyle = container.statusBarStyle
|
||||
case .split:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
if visibleModalCount != 0 {
|
||||
@ -451,16 +462,14 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: effectiveRootModalDismissProgress, additionalProgress: additionalModalFrameProgress, completion: {})
|
||||
}
|
||||
if effectiveRootModalDismissProgress < 0.5 {
|
||||
self.statusBarHost?.setStatusBarStyle(.lightContent, animated: transition.isAnimated || forceStatusBarAnimation)
|
||||
} else {
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
switch self.theme.statusBar {
|
||||
case .black:
|
||||
normalStatusBarStyle = .default
|
||||
case .white:
|
||||
normalStatusBarStyle = .lightContent
|
||||
statusBarStyle = .White
|
||||
if forceStatusBarAnimation {
|
||||
animateStatusBarStyleTransition = true
|
||||
}
|
||||
} else {
|
||||
if forceStatusBarAnimation {
|
||||
animateStatusBarStyleTransition = true
|
||||
}
|
||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: transition.isAnimated || forceStatusBarAnimation)
|
||||
}
|
||||
if let rootContainer = self.rootContainer {
|
||||
var rootContainerNode: ASDisplayNode
|
||||
@ -494,14 +503,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: 1.0, additionalProgress: 0.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame?.removeFromSupernode()
|
||||
})
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
switch self.theme.statusBar {
|
||||
case .black:
|
||||
normalStatusBarStyle = .default
|
||||
case .white:
|
||||
normalStatusBarStyle = .lightContent
|
||||
}
|
||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: transition.isAnimated)
|
||||
}
|
||||
if let rootContainer = self.rootContainer {
|
||||
var rootContainerNode: ASDisplayNode
|
||||
@ -520,14 +521,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
rootModalFrame.updateDismissal(transition: .immediate, progress: 1.0, additionalProgress: 0.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame?.removeFromSupernode()
|
||||
})
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
switch self.theme.statusBar {
|
||||
case .black:
|
||||
normalStatusBarStyle = .default
|
||||
case .white:
|
||||
normalStatusBarStyle = .lightContent
|
||||
}
|
||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
|
||||
}
|
||||
if let rootContainer = self.rootContainer {
|
||||
var rootContainerNode: ASDisplayNode
|
||||
@ -541,16 +534,26 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
if self.validStatusBarStyle != self.theme.statusBar {
|
||||
self.validStatusBarStyle = self.theme.statusBar
|
||||
let resolvedStatusBarStyle: NavigationStatusBarStyle
|
||||
switch statusBarStyle {
|
||||
case .Ignore, .Hide:
|
||||
resolvedStatusBarStyle = self.theme.statusBar
|
||||
case .Black:
|
||||
resolvedStatusBarStyle = .black
|
||||
case .White:
|
||||
resolvedStatusBarStyle = .white
|
||||
}
|
||||
|
||||
if self.validStatusBarStyle != resolvedStatusBarStyle {
|
||||
self.validStatusBarStyle = resolvedStatusBarStyle
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
switch self.theme.statusBar {
|
||||
switch resolvedStatusBarStyle {
|
||||
case .black:
|
||||
normalStatusBarStyle = .default
|
||||
case .white:
|
||||
normalStatusBarStyle = .lightContent
|
||||
}
|
||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
|
||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: animateStatusBarStyleTransition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,44 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
@interface TONTransactionId : NSObject
|
||||
|
||||
@property (nonatomic, readonly) int64_t lt;
|
||||
@property (nonatomic, strong, readonly) NSData * _Nonnull transactionHash;
|
||||
|
||||
- (instancetype)initWithLt:(int64_t)lt transactionHash:(NSData * _Nonnull)transactionHash;
|
||||
|
||||
@end
|
||||
|
||||
@interface TONAccountState : NSObject
|
||||
|
||||
@property (nonatomic, readonly) int64_t balance;
|
||||
@property (nonatomic, readonly) int32_t seqno;
|
||||
@property (nonatomic, strong, readonly) TONTransactionId * _Nullable lastTransactionId;
|
||||
|
||||
- (instancetype)initWithBalance:(int64_t)balance seqno:(int32_t)seqno;
|
||||
- (instancetype)initWithBalance:(int64_t)balance seqno:(int32_t)seqno lastTransactionId:(TONTransactionId * _Nullable)lastTransactionId;
|
||||
|
||||
@end
|
||||
|
||||
@interface TONTransactionMessage : NSObject
|
||||
|
||||
@property (nonatomic, readonly) int64_t value;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nonnull source;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nonnull destination;
|
||||
|
||||
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination;
|
||||
|
||||
@end
|
||||
|
||||
@interface TONTransaction : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData * _Nonnull data;
|
||||
@property (nonatomic, strong, readonly) TONTransactionId * _Nonnull previousTransactionId;
|
||||
@property (nonatomic, readonly) int64_t fee;
|
||||
@property (nonatomic, strong, readonly) TONTransactionMessage * _Nullable inMessage;
|
||||
@property (nonatomic, strong, readonly) NSArray<TONTransactionMessage *> * _Nonnull outMessages;
|
||||
|
||||
- (instancetype)initWithData:(NSData * _Nonnull)data previousTransactionId:(TONTransactionId * _Nonnull)previousTransactionId inMessage:(TONTransactionMessage * _Nullable)inMessage outMessages:(NSArray<TONTransactionMessage *> * _Nonnull)outMessages;
|
||||
|
||||
@end
|
||||
|
||||
@ -35,6 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (MTSignal *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword;
|
||||
- (MTSignal *)getTestWalletAccountAddressWithPublicKey:(NSString *)publicKey;
|
||||
- (MTSignal *)getTestGiverAccountState;
|
||||
- (MTSignal *)getTestGiverAddress;
|
||||
- (MTSignal *)testGiverSendGramsWithAccountState:(TONAccountState *)accountState accountAddress:(NSString *)accountAddress amount:(int64_t)amount;
|
||||
- (MTSignal *)getAccountStateWithAddress:(NSString *)accountAddress;
|
||||
- (MTSignal *)sendGramsFromKey:(TONKey *)key localPassword:(NSData *)localPassword fromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount;
|
||||
@ -42,6 +75,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (MTSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray<NSString *> *)wordList;
|
||||
- (MTSignal *)deleteKeyWithPublicKey:(NSString *)publicKey;
|
||||
- (MTSignal *)makeWalletInitialized:(TONKey *)key localPassword:(NSData *)localPassword;
|
||||
- (MTSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -21,6 +21,34 @@ static std::string makeString(NSData * _Nonnull data) {
|
||||
}
|
||||
}
|
||||
|
||||
static NSData * _Nonnull makeData(std::string &string) {
|
||||
if (string.size() == 0) {
|
||||
return [NSData data];
|
||||
} else {
|
||||
return [[NSData alloc] initWithBytes:string.data() length:string.size()];
|
||||
}
|
||||
}
|
||||
|
||||
static NSString * _Nullable readString(std::string &string) {
|
||||
if (string.size() == 0) {
|
||||
return @"";
|
||||
} else {
|
||||
return [[NSString alloc] initWithBytes:string.data() length:string.size() encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
|
||||
static TONTransactionMessage * _Nullable parseTransactionMessage(tonlib_api::object_ptr<tonlib_api::raw_message> &message) {
|
||||
if (message == nullptr) {
|
||||
return nil;
|
||||
}
|
||||
NSString *source = readString(message->source_);
|
||||
NSString *destination = readString(message->source_);
|
||||
if (source == nil || destination == nil) {
|
||||
return nil;
|
||||
}
|
||||
return [[TONTransactionMessage alloc] initWithValue:message->value_ source:source destination:destination];
|
||||
}
|
||||
|
||||
@implementation TONKey
|
||||
|
||||
- (instancetype)initWithPublicKey:(NSString *)publicKey secret:(NSData *)secret {
|
||||
@ -36,11 +64,54 @@ static std::string makeString(NSData * _Nonnull data) {
|
||||
|
||||
@implementation TONAccountState
|
||||
|
||||
- (instancetype)initWithBalance:(int64_t)balance seqno:(int32_t)seqno {
|
||||
- (instancetype)initWithBalance:(int64_t)balance seqno:(int32_t)seqno lastTransactionId:(TONTransactionId * _Nullable)lastTransactionId {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_balance = balance;
|
||||
_seqno = seqno;
|
||||
_lastTransactionId = lastTransactionId;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TONTransactionId
|
||||
|
||||
- (instancetype)initWithLt:(int64_t)lt transactionHash:(NSData *)transactionHash {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_lt = lt;
|
||||
_transactionHash = transactionHash;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TONTransactionMessage
|
||||
|
||||
- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_value = value;
|
||||
_source = source;
|
||||
_destination = destination;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TONTransaction
|
||||
|
||||
- (instancetype)initWithData:(NSData * _Nonnull)data previousTransactionId:(TONTransactionId * _Nonnull)previousTransactionId inMessage:(TONTransactionMessage * _Nullable)inMessage outMessages:(NSArray<TONTransactionMessage *> * _Nonnull)outMessages {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = data;
|
||||
_previousTransactionId = previousTransactionId;
|
||||
_inMessage = inMessage;
|
||||
_outMessages = outMessages;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -272,7 +343,8 @@ typedef enum {
|
||||
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
|
||||
} else if (object->get_id() == tonlib_api::testGiver_accountState::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::testGiver_accountState>(object);
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->balance_ seqno:result->seqno_]];
|
||||
TONTransactionId *lastTransactionId = [[TONTransactionId alloc] initWithLt:result->last_transaction_id_->lt_ transactionHash:makeData(result->last_transaction_id_->hash_)];
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->balance_ seqno:result->seqno_ lastTransactionId:lastTransactionId]];
|
||||
[subscriber putCompletion];
|
||||
} else {
|
||||
assert(false);
|
||||
@ -287,6 +359,32 @@ typedef enum {
|
||||
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
|
||||
}
|
||||
|
||||
- (MTSignal *)getTestGiverAddress {
|
||||
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
|
||||
uint64_t requestId = _nextRequestId;
|
||||
_nextRequestId += 1;
|
||||
|
||||
_requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr<tonlib_api::Object> &object) {
|
||||
if (object->get_id() == tonlib_api::error::ID) {
|
||||
auto error = tonlib_api::move_object_as<tonlib_api::error>(object);
|
||||
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
|
||||
} else if (object->get_id() == tonlib_api::accountAddress::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::accountAddress>(object);
|
||||
[subscriber putNext:[[NSString alloc] initWithUTF8String:result->account_address_.c_str()]];
|
||||
[subscriber putCompletion];
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}];
|
||||
|
||||
auto query = make_object<tonlib_api::testGiver_getAccountAddress>();
|
||||
_client->send({ requestId, std::move(query) });
|
||||
|
||||
return [[MTBlockDisposable alloc] initWithBlock:^{
|
||||
}];
|
||||
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
|
||||
}
|
||||
|
||||
- (MTSignal *)testGiverSendGramsWithAccountState:(TONAccountState *)accountState accountAddress:(NSString *)accountAddress amount:(int64_t)amount {
|
||||
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
|
||||
uint64_t requestId = _nextRequestId;
|
||||
@ -320,11 +418,17 @@ typedef enum {
|
||||
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
|
||||
} else if (object->get_id() == tonlib_api::generic_accountStateUninited::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::generic_accountStateUninited>(object);
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->account_state_->balance_ seqno:-1]];
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->account_state_->balance_ seqno:-1 lastTransactionId:nil]];
|
||||
[subscriber putCompletion];
|
||||
} else if (object->get_id() == tonlib_api::generic_accountStateTestWallet::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::generic_accountStateTestWallet>(object);
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->account_state_->balance_ seqno:result->account_state_->seqno_]];
|
||||
TONTransactionId *lastTransactionId = [[TONTransactionId alloc] initWithLt:result->account_state_->last_transaction_id_->lt_ transactionHash:makeData(result->account_state_->last_transaction_id_->hash_)];
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->account_state_->balance_ seqno:result->account_state_->seqno_ lastTransactionId:lastTransactionId]];
|
||||
[subscriber putCompletion];
|
||||
} else if (object->get_id() == tonlib_api::generic_accountStateTestGiver::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::generic_accountStateTestGiver>(object);
|
||||
TONTransactionId *lastTransactionId = [[TONTransactionId alloc] initWithLt:result->account_state_->last_transaction_id_->lt_ transactionHash:makeData(result->account_state_->last_transaction_id_->hash_)];
|
||||
[subscriber putNext:[[TONAccountState alloc] initWithBalance:result->account_state_->balance_ seqno:result->account_state_->seqno_ lastTransactionId:lastTransactionId]];
|
||||
[subscriber putCompletion];
|
||||
} else {
|
||||
assert(false);
|
||||
@ -533,4 +637,57 @@ typedef enum {
|
||||
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
|
||||
}
|
||||
|
||||
- (MTSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash {
|
||||
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
|
||||
NSData *addressData = [address dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (addressData == nil) {
|
||||
[subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in getTransactionListWithAddress"]];
|
||||
return [[MTBlockDisposable alloc] initWithBlock:^{}];
|
||||
}
|
||||
|
||||
uint64_t requestId = _nextRequestId;
|
||||
_nextRequestId += 1;
|
||||
|
||||
_requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr<tonlib_api::Object> &object) {
|
||||
if (object->get_id() == tonlib_api::error::ID) {
|
||||
auto error = tonlib_api::move_object_as<tonlib_api::error>(object);
|
||||
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
|
||||
} else if (object->get_id() == tonlib_api::raw_transactions::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::raw_transactions>(object);
|
||||
NSMutableArray<TONTransaction *> *transactions = [[NSMutableArray alloc] init];
|
||||
for (auto &it : result->transactions_) {
|
||||
TONTransactionId *previousTransactionId = [[TONTransactionId alloc] initWithLt:it->previous_transaction_id_->lt_ transactionHash:makeData(it->previous_transaction_id_->hash_)];
|
||||
TONTransactionMessage *inMessage = parseTransactionMessage(it->in_msg_);
|
||||
NSMutableArray<TONTransactionMessage *> * outMessages = [[NSMutableArray alloc] init];
|
||||
for (auto &messageIt : it->out_msgs_) {
|
||||
TONTransactionMessage *outMessage = parseTransactionMessage(messageIt);
|
||||
if (outMessage != nil) {
|
||||
[outMessages addObject:outMessage];
|
||||
}
|
||||
}
|
||||
[transactions addObject:[[TONTransaction alloc] initWithData:makeData(it->data_) previousTransactionId:previousTransactionId inMessage:inMessage outMessages:outMessages]];
|
||||
}
|
||||
[subscriber putNext:transactions];
|
||||
[subscriber putCompletion];
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}];
|
||||
|
||||
auto query = make_object<tonlib_api::raw_getTransactions>(
|
||||
make_object<tonlib_api::accountAddress>(
|
||||
makeString(addressData)
|
||||
),
|
||||
make_object<tonlib_api::internal_transactionId>(
|
||||
lt,
|
||||
makeString(hash)
|
||||
)
|
||||
);
|
||||
_client->send({ requestId, std::move(query) });
|
||||
|
||||
return [[MTBlockDisposable alloc] initWithBlock:^{
|
||||
}];
|
||||
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
Binary file not shown.
@ -15,6 +15,7 @@ import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import WalletUI
|
||||
|
||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
@ -66,7 +67,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case resetDatabase(PresentationTheme)
|
||||
case resetHoles(PresentationTheme)
|
||||
case resetBiometricsData(PresentationTheme)
|
||||
case deleteWallets(PresentationTheme)
|
||||
case openDebugWallet(PresentationTheme)
|
||||
case getGrams(PresentationTheme)
|
||||
case optimizeDatabase(PresentationTheme)
|
||||
case photoPreview(PresentationTheme, Bool)
|
||||
case knockoutWallpaper(PresentationTheme, Bool)
|
||||
@ -83,7 +85,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .resetBiometricsData, .deleteWallets, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .gradientBubbles:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .resetBiometricsData, .openDebugWallet, .getGrams, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .gradientBubbles:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .versionInfo:
|
||||
return DebugControllerSection.info.rawValue
|
||||
@ -128,18 +130,20 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 16
|
||||
case .resetBiometricsData:
|
||||
return 17
|
||||
case .deleteWallets:
|
||||
case .openDebugWallet:
|
||||
return 18
|
||||
case .optimizeDatabase:
|
||||
case .getGrams:
|
||||
return 19
|
||||
case .photoPreview:
|
||||
case .optimizeDatabase:
|
||||
return 20
|
||||
case .knockoutWallpaper:
|
||||
case .photoPreview:
|
||||
return 21
|
||||
case .gradientBubbles:
|
||||
case .knockoutWallpaper:
|
||||
return 22
|
||||
case .versionInfo:
|
||||
case .gradientBubbles:
|
||||
return 23
|
||||
case .versionInfo:
|
||||
return 24
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,12 +471,44 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil)
|
||||
}).start()
|
||||
})
|
||||
case let .deleteWallets(theme):
|
||||
case let .openDebugWallet(theme):
|
||||
return ItemListActionItem(theme: theme, title: "Delete Wallets", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
let _ = debugDeleteWallets(postbox: context.account.postbox).start()
|
||||
let _ = (availableWallets(postbox: context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { wallets in
|
||||
if let tonContext = context.tonContext {
|
||||
if !wallets.wallets.isEmpty {
|
||||
let _ = (testGiverWalletAddress(tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { address in
|
||||
arguments.pushController(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: wallets.wallets[0], address: address))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
case let .getGrams(theme):
|
||||
return ItemListActionItem(theme: theme, title: "Update Wallet", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
let _ = (availableWallets(postbox: context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { wallets in
|
||||
if let tonContext = context.tonContext {
|
||||
if !wallets.wallets.isEmpty {
|
||||
let _ = (walletAddress(publicKey: wallets.wallets[0].publicKey, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { address in
|
||||
let _ = (getGramsFromTestGiver(address: address, amount: 1500000000, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success)
|
||||
arguments.presentController(controller, nil)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
case let .optimizeDatabase(theme):
|
||||
return ItemListActionItem(theme: theme, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
@ -556,7 +592,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
entries.append(.resetData(presentationData.theme))
|
||||
entries.append(.resetDatabase(presentationData.theme))
|
||||
entries.append(.resetHoles(presentationData.theme))
|
||||
entries.append(.deleteWallets(presentationData.theme))
|
||||
entries.append(.openDebugWallet(presentationData.theme))
|
||||
entries.append(.getGrams(presentationData.theme))
|
||||
entries.append(.optimizeDatabase(presentationData.theme))
|
||||
entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
|
||||
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
|
||||
|
@ -6,13 +6,30 @@ import TelegramPresentationData
|
||||
|
||||
private let textFont: UIFont = Font.regular(16.0)
|
||||
|
||||
public final class SolidRoundedButtonTheme {
|
||||
public let backgroundColor: UIColor
|
||||
public let foregroundColor: UIColor
|
||||
|
||||
public init(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
}
|
||||
}
|
||||
|
||||
public extension SolidRoundedButtonTheme {
|
||||
convenience init(theme: PresentationTheme) {
|
||||
self.init(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
private var theme: SolidRoundedButtonTheme
|
||||
|
||||
private let buttonBackgroundNode: ASImageNode
|
||||
private let buttonGlossNode: SolidRoundedButtonGlossNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let labelNode: ImmediateTextNode
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
private let buttonHeight: CGFloat
|
||||
private let buttonCornerRadius: CGFloat
|
||||
@ -28,7 +45,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public init(title: String? = nil, theme: PresentationTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||
self.theme = theme
|
||||
self.buttonHeight = height
|
||||
self.buttonCornerRadius = cornerRadius
|
||||
@ -38,15 +55,20 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
self.buttonBackgroundNode.isLayerBacked = true
|
||||
self.buttonBackgroundNode.displayWithoutProcessing = true
|
||||
self.buttonBackgroundNode.displaysAsynchronously = false
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.list.itemCheckColors.fillColor)
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.backgroundColor)
|
||||
|
||||
self.buttonGlossNode = SolidRoundedButtonGlossNode(color: theme.list.itemCheckColors.foregroundColor, cornerRadius: cornerRadius)
|
||||
self.buttonGlossNode = SolidRoundedButtonGlossNode(color: theme.foregroundColor, cornerRadius: cornerRadius)
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.labelNode = ImmediateTextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = icon
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.buttonBackgroundNode)
|
||||
@ -55,6 +77,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
@ -70,15 +93,15 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTheme(_ theme: PresentationTheme) {
|
||||
public func updateTheme(_ theme: SolidRoundedButtonTheme) {
|
||||
guard theme !== self.theme else {
|
||||
return
|
||||
}
|
||||
self.theme = theme
|
||||
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: self.buttonCornerRadius, color: theme.list.itemCheckColors.fillColor)
|
||||
self.buttonGlossNode.color = theme.list.itemCheckColors.foregroundColor
|
||||
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: theme.list.itemCheckColors.foregroundColor)
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: self.buttonCornerRadius, color: theme.backgroundColor)
|
||||
self.buttonGlossNode.color = theme.foregroundColor
|
||||
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.semibold(17.0), textColor: theme.foregroundColor)
|
||||
}
|
||||
|
||||
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
@ -91,11 +114,25 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
|
||||
if self.title != self.labelNode.attributedText?.string {
|
||||
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.semibold(17.0), textColor: self.theme.foregroundColor)
|
||||
}
|
||||
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize()
|
||||
let labelSize = self.labelNode.updateLayout(buttonSize)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - labelSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - labelSize.height) / 2.0)), size: labelSize)
|
||||
|
||||
let iconSpacing: CGFloat = 8.0
|
||||
|
||||
var contentWidth: CGFloat = labelSize.width
|
||||
if !iconSize.width.isZero {
|
||||
contentWidth += iconSize.width + iconSpacing
|
||||
}
|
||||
var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0)
|
||||
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
if !iconSize.width.isZero {
|
||||
nextContentOrigin += iconSize.width + iconSpacing
|
||||
}
|
||||
|
||||
let labelFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + floor((buttonFrame.height - labelSize.height) / 2.0)), size: labelSize)
|
||||
transition.updateFrame(node: self.labelNode, frame: labelFrame)
|
||||
|
||||
return buttonSize.height
|
||||
|
@ -16,7 +16,7 @@ public enum StickerPackPreviewControllerMode {
|
||||
case settings
|
||||
}
|
||||
|
||||
public final class StickerPackPreviewController: ViewController {
|
||||
public final class StickerPackPreviewController: ViewController, StandalonePresentableController {
|
||||
private var controllerNode: StickerPackPreviewControllerNode {
|
||||
return self.displayNode as! StickerPackPreviewControllerNode
|
||||
}
|
||||
|
@ -158,6 +158,31 @@ public final class TonInstance {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func testGiverWalletAddress() -> Signal<String, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
impl.withInstance { ton in
|
||||
let cancel = ton.getTestGiverAddress().start(next: { address in
|
||||
guard let address = address as? String else {
|
||||
return
|
||||
}
|
||||
subscriber.putNext(address)
|
||||
subscriber.putCompletion()
|
||||
}, error: { _ in
|
||||
}, completed: {
|
||||
})
|
||||
disposable.set(ActionDisposable {
|
||||
cancel?.dispose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func walletBalance(publicKey: WalletPublicKey) -> Signal<WalletBalance, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
@ -193,6 +218,92 @@ public final class TonInstance {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func walletLastTransactionId(address: String) -> Signal<WalletTransactionId?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
impl.withInstance { ton in
|
||||
let cancel = ton.getAccountState(withAddress: address).start(next: { state in
|
||||
guard let state = state as? TONAccountState else {
|
||||
subscriber.putNext(nil)
|
||||
return
|
||||
}
|
||||
subscriber.putNext(state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:)))
|
||||
}, error: { _ in
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
disposable.set(ActionDisposable {
|
||||
cancel?.dispose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getWalletTransactions(address: String, previousId: WalletTransactionId) -> Signal<[WalletTransaction], GetWalletTransactionsError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
impl.withInstance { ton in
|
||||
let cancel = ton.getTransactionList(withAddress: address, lt: previousId.lt, hash: previousId.transactionHash).start(next: { transactions in
|
||||
guard let transactions = transactions as? [TONTransaction] else {
|
||||
subscriber.putError(.generic)
|
||||
return
|
||||
}
|
||||
subscriber.putNext(transactions.map(WalletTransaction.init(tonTransaction:)))
|
||||
}, error: { _ in
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
disposable.set(ActionDisposable {
|
||||
cancel?.dispose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getGramsFromTestGiver(address: String, amount: Int64) -> Signal<Void, GetGramsFromTestGiverError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
impl.withInstance { ton in
|
||||
let cancel = ton.getTestGiverAccountState().start(next: { state in
|
||||
guard let state = state as? TONAccountState else {
|
||||
subscriber.putError(.generic)
|
||||
return
|
||||
}
|
||||
let cancel = ton.testGiverSendGrams(with: state, accountAddress: address, amount: amount).start(next: { _ in
|
||||
}, error: { _ in
|
||||
subscriber.putError(.generic)
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
disposable.set(ActionDisposable {
|
||||
cancel?.dispose()
|
||||
})
|
||||
}, error: { _ in
|
||||
subscriber.putError(.generic)
|
||||
}, completed: {
|
||||
})
|
||||
disposable.set(ActionDisposable {
|
||||
cancel?.dispose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func walletRestoreWords(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<[String], WalletRestoreWordsError> {
|
||||
return keychain.decrypt(walletInfo.encryptedSecret.rawValue)
|
||||
|> introduceError(WalletRestoreWordsError.self)
|
||||
@ -385,19 +496,145 @@ public func walletAddress(publicKey: WalletPublicKey, tonInstance: TonInstance)
|
||||
return tonInstance.walletAddress(publicKey: publicKey)
|
||||
}
|
||||
|
||||
public func testGiverWalletAddress(tonInstance: TonInstance) -> Signal<String, NoError> {
|
||||
return tonInstance.testGiverWalletAddress()
|
||||
}
|
||||
|
||||
public func currentWalletBalance(publicKey: WalletPublicKey, tonInstance: TonInstance) -> Signal<WalletBalance, NoError> {
|
||||
return tonInstance.walletBalance(publicKey: publicKey)
|
||||
}
|
||||
|
||||
public enum GetGramsFromTestGiverError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func getGramsFromTestGiver(address: String, amount: Int64, tonInstance: TonInstance) -> Signal<Void, GetGramsFromTestGiverError> {
|
||||
return tonInstance.getGramsFromTestGiver(address: address, amount: amount)
|
||||
}
|
||||
|
||||
public struct WalletTransactionId: Hashable {
|
||||
public var lt: Int64
|
||||
public var transactionHash: Data
|
||||
}
|
||||
|
||||
private extension WalletTransactionId {
|
||||
init(tonTransactionId: TONTransactionId) {
|
||||
self.lt = tonTransactionId.lt
|
||||
self.transactionHash = tonTransactionId.transactionHash
|
||||
}
|
||||
}
|
||||
|
||||
public final class WalletTransactionMessage: Equatable {
|
||||
public let value: Int64
|
||||
public let source: String
|
||||
public let destination: String
|
||||
|
||||
init(value: Int64, source: String, destination: String) {
|
||||
self.value = value
|
||||
self.source = source
|
||||
self.destination = destination
|
||||
}
|
||||
|
||||
public static func ==(lhs: WalletTransactionMessage, rhs: WalletTransactionMessage) -> Bool {
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.source != rhs.source {
|
||||
return false
|
||||
}
|
||||
if lhs.destination != rhs.destination {
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private extension WalletTransactionMessage {
|
||||
convenience init(tonTransactionMessage: TONTransactionMessage) {
|
||||
self.init(value: tonTransactionMessage.value, source: tonTransactionMessage.source, destination: tonTransactionMessage.destination)
|
||||
}
|
||||
}
|
||||
|
||||
public final class WalletTransaction: Equatable {
|
||||
public let data: Data
|
||||
public let previousTransactionId: WalletTransactionId
|
||||
public let fee: Int64
|
||||
public let inMessage: WalletTransactionMessage?
|
||||
public let outMessages: [WalletTransactionMessage]
|
||||
|
||||
public var transferredValue: Int64 {
|
||||
var value: Int64 = 0
|
||||
if let inMessage = self.inMessage {
|
||||
value += inMessage.value
|
||||
}
|
||||
for message in self.outMessages {
|
||||
value -= message.value
|
||||
}
|
||||
value -= self.fee
|
||||
return value
|
||||
}
|
||||
|
||||
init(data: Data, previousTransactionId: WalletTransactionId, fee: Int64, inMessage: WalletTransactionMessage?, outMessages: [WalletTransactionMessage]) {
|
||||
self.data = data
|
||||
self.previousTransactionId = previousTransactionId
|
||||
self.fee = fee
|
||||
self.inMessage = inMessage
|
||||
self.outMessages = outMessages
|
||||
}
|
||||
|
||||
public static func ==(lhs: WalletTransaction, rhs: WalletTransaction) -> Bool {
|
||||
if lhs.data != rhs.data {
|
||||
return false
|
||||
}
|
||||
if lhs.previousTransactionId != rhs.previousTransactionId {
|
||||
return false
|
||||
}
|
||||
if lhs.fee != rhs.fee {
|
||||
return false
|
||||
}
|
||||
if lhs.inMessage != rhs.inMessage {
|
||||
return false
|
||||
}
|
||||
if lhs.outMessages != rhs.outMessages {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private extension WalletTransaction {
|
||||
convenience init(tonTransaction: TONTransaction) {
|
||||
self.init(data: tonTransaction.data, previousTransactionId: WalletTransactionId(tonTransactionId: tonTransaction.previousTransactionId), fee: tonTransaction.fee, inMessage: tonTransaction.inMessage.flatMap(WalletTransactionMessage.init(tonTransactionMessage:)), outMessages: tonTransaction.outMessages.map(WalletTransactionMessage.init(tonTransactionMessage:)))
|
||||
}
|
||||
}
|
||||
|
||||
public enum GetWalletTransactionsError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func getWalletTransactions(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> {
|
||||
let previousIdValue: Signal<WalletTransactionId?, GetWalletTransactionsError>
|
||||
if let previousId = previousId {
|
||||
previousIdValue = .single(previousId)
|
||||
} else {
|
||||
previousIdValue = tonInstance.walletLastTransactionId(address: address)
|
||||
|> introduceError(GetWalletTransactionsError.self)
|
||||
}
|
||||
return previousIdValue
|
||||
|> mapToSignal { previousId in
|
||||
if let previousId = previousId {
|
||||
return tonInstance.getWalletTransactions(address: address, previousId: previousId)
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GetServerWalletSaltError {
|
||||
case generic
|
||||
}
|
||||
|
||||
private func getServerWalletSalt(network: Network) -> Signal<Data, GetServerWalletSaltError> {
|
||||
#if DEBUG
|
||||
return .single(Data())
|
||||
#endif
|
||||
|
||||
return network.request(Api.functions.wallet.getKeySecretSalt(revoke: .boolFalse))
|
||||
|> mapError { _ -> GetServerWalletSaltError in
|
||||
return .generic
|
||||
|
@ -83,7 +83,7 @@ public final class PermissionContentNode: ASDisplayNode {
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0, gloss: true)
|
||||
self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 9.0, gloss: true)
|
||||
|
||||
self.footerNode = ImmediateTextNode()
|
||||
self.footerNode.textAlignment = .center
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Wallet/BalanceGem.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/BalanceGem.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "gem.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/BalanceGem.imageset/gem.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/BalanceGem.imageset/gem.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Wallet/DuckIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/DuckIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "duck.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/DuckIcon.imageset/duck.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/DuckIcon.imageset/duck.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/NavigationSettingsIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "NavigationSettingsIcon.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Group2.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Group2.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/ReceiveButtonIcon.imageset/Group2.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Group.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Group.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/SendButtonIcon.imageset/Group.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Wallet/TransactionGem.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/TransactionGem.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "SmallGem.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/TransactionGem.imageset/SmallGem.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/TransactionGem.imageset/SmallGem.pdf
vendored
Normal file
Binary file not shown.
@ -72,7 +72,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
self.cancelButton = HighlightableButtonNode()
|
||||
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
self.doneButton = SolidRoundedButtonNode(theme: self.presentationData.theme, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
|
||||
self.dateFormatter = DateFormatter()
|
||||
self.dateFormatter.timeStyle = .none
|
||||
@ -152,7 +152,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
}
|
||||
|
||||
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
self.doneButton.updateTheme(self.presentationData.theme)
|
||||
self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme))
|
||||
}
|
||||
|
||||
private func updateMinimumDate(currentTime: Int32? = nil) {
|
||||
|
@ -1,13 +1,11 @@
|
||||
{
|
||||
"@type": "config.global",
|
||||
"liteservers": [
|
||||
{
|
||||
"@type": "liteserver.config.global",
|
||||
"ip": -1304504083,
|
||||
"ip": 1137658550,
|
||||
"port": 4924,
|
||||
"id": {
|
||||
"@type": "pub.ed25519",
|
||||
"key": "keoMMlpiIJPaY4SeXQlie8cOL6XNo81zJqrqbyVrPAQ="
|
||||
"key": "peJTw/arlRfssgTuf9BMypJzqOi7SXEqSPSWiEw2U1M="
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -17,8 +15,8 @@
|
||||
"workchain": -1,
|
||||
"shard": -9223372036854775808,
|
||||
"seqno": 0,
|
||||
"root_hash": "XV54jYRsGWJ301YvzibvM4ygmDrxxn4LwcyMa/0NOQ0=",
|
||||
"file_hash": "9nhKiv4g5fmliP9HayeebodXxjLw+J5sYDWblE75p9U="
|
||||
"root_hash": "VCSXxDHhTALFxReyTZRd8E4Ya3ySOmpOWAS4rBX9XBY=",
|
||||
"file_hash": "eh9yveSz1qMdJ7mOsO+I+H77jkLr9NpAuEkoJuseXBo="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ public func updateInfoController(context: AccountContext, appUpdateInfo: AppUpda
|
||||
}
|
||||
|
||||
let controller = ItemListController(sharedContext: context.sharedContext, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
linkActionImpl = { [weak controller, weak context] action, itemLink in
|
||||
if let strongController = controller, let context = context {
|
||||
context.sharedContext.handleTextLinkAction(context: context, peerId: nil, navigateDisposable: navigateDisposable, controller: strongController, action: action, itemLink: itemLink)
|
||||
|
@ -18,6 +18,8 @@ static_library(
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/ItemListUI:ItemListUI",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
91
submodules/WalletUI/Sources/WalletInfoEmptyNode.swift
Normal file
91
submodules/WalletUI/Sources/WalletInfoEmptyNode.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import AnimationUI
|
||||
|
||||
final class WalletInfoEmptyNode: ASDisplayNode {
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let addressNode: ImmediateTextNode
|
||||
|
||||
init(presentationData: PresentationData, address: String) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
|
||||
let title = "Wallet Created"
|
||||
let text = "Your wallet address"
|
||||
self.iconNode.image = UIImage(bundleImageName: "Wallet/DuckIcon")
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
self.titleNode.textAlignment = .center
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.lineSpacing = 0.1
|
||||
self.textNode.textAlignment = .center
|
||||
|
||||
self.addressNode = ImmediateTextNode()
|
||||
self.addressNode.displaysAsynchronously = false
|
||||
self.addressNode.attributedText = NSAttributedString(string: address, font: Font.monospace(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.addressNode.maximumNumberOfLines = 0
|
||||
self.addressNode.lineSpacing = 0.1
|
||||
self.addressNode.textAlignment = .center
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.addressNode)
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let sideInset: CGFloat = 32.0
|
||||
let buttonSideInset: CGFloat = 48.0
|
||||
let iconSpacing: CGFloat = 5.0
|
||||
let titleSpacing: CGFloat = 19.0
|
||||
let termsSpacing: CGFloat = 11.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
|
||||
let iconSize: CGSize
|
||||
var iconOffset = CGPoint()
|
||||
iconSize = self.iconNode.image?.size ?? CGSize(width: 140.0, height: 140.0)
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
let addressSize = self.addressNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
let contentVerticalOrigin: CGFloat = 0.0
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
|
||||
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
|
||||
self.animationNode.updateLayout(size: iconFrame.size)
|
||||
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
let addressFrame = CGRect(origin: CGPoint(x: floor((width - addressSize.width) / 2.0), y: textFrame.maxY + titleSpacing), size: addressSize)
|
||||
transition.updateFrameAdditive(node: self.addressNode, frame: addressFrame)
|
||||
|
||||
return addressFrame.maxY
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import TelegramCore
|
||||
import SolidRoundedButtonNode
|
||||
import AnimationUI
|
||||
import SwiftSignalKit
|
||||
import MergeLists
|
||||
|
||||
public final class WalletInfoScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
@ -28,15 +29,21 @@ public final class WalletInfoScreen: ViewController {
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let defaultNavigationPresentationData = NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultNavigationPresentationData.theme.buttonColor, disabledButtonColor: defaultNavigationPresentationData.theme.disabledButtonColor, primaryTextColor: defaultNavigationPresentationData.theme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultNavigationPresentationData.theme.badgeBackgroundColor, badgeStrokeColor: defaultNavigationPresentationData.theme.badgeStrokeColor, badgeTextColor: defaultNavigationPresentationData.theme.badgeTextColor)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultNavigationPresentationData.theme.badgeBackgroundColor, badgeStrokeColor: defaultNavigationPresentationData.theme.badgeStrokeColor, badgeTextColor: defaultNavigationPresentationData.theme.badgeTextColor)
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
self.navigationPresentation = .modalInLargeLayout
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Wallet/NavigationSettingsIcon"), color: .white), style: .plain, target: self, action: #selector(self.settingsPressed))
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
(self?.displayNode as? WalletInfoScreenNode)?.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
@ -47,22 +54,20 @@ public final class WalletInfoScreen: ViewController {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
@objc private func settingsPressed() {
|
||||
self.push(walletSettingsController(context: self.context, tonContext: self.tonContext, walletInfo: self.walletInfo))
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = WalletInfoScreenNode(account: self.context.account, tonContext: self.tonContext, presentationData: self.presentationData, walletInfo: self.walletInfo, address: self.address, exportAction: { [weak self] in
|
||||
self.displayNode = WalletInfoScreenNode(account: self.context.account, tonContext: self.tonContext, presentationData: self.presentationData, walletInfo: self.walletInfo, address: self.address, sendAction: {
|
||||
|
||||
}, receiveAction: {
|
||||
|
||||
}, openTransaction: { [weak self] transaction in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (walletRestoreWords(network: strongSelf.context.account.network, walletInfo: strongSelf.walletInfo, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain)
|
||||
|> deliverOnMainQueue).start(next: { wordList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.push(WalletWordDisplayScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, wordList: wordList))
|
||||
}, error: { _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
})
|
||||
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, walletTransaction: transaction))
|
||||
})
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
@ -75,102 +80,516 @@ public final class WalletInfoScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
private var presentationData: PresentationData
|
||||
private let walletInfo: WalletInfo
|
||||
private let exportAction: () -> Void
|
||||
private final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
private let balanceTextNode: ImmediateTextNode
|
||||
private let balanceIconNode: ASImageNode
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
var inProgress: Bool = false {
|
||||
var balance: String = " " {
|
||||
didSet {
|
||||
self.buttonNode.isUserInteractionEnabled = !self.inProgress
|
||||
self.buttonNode.alpha = self.inProgress ? 0.6 : 1.0
|
||||
self.balanceTextNode.attributedText = NSAttributedString(string: self.balance, font: Font.bold(39.0), textColor: .white)
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.balanceTextNode = ImmediateTextNode()
|
||||
self.balanceTextNode.displaysAsynchronously = false
|
||||
self.balanceTextNode.attributedText = NSAttributedString(string: " ", font: Font.bold(39.0), textColor: .white)
|
||||
self.balanceTextNode.layer.minificationFilter = .linear
|
||||
|
||||
self.balanceIconNode = ASImageNode()
|
||||
self.balanceIconNode.displaysAsynchronously = false
|
||||
self.balanceIconNode.displayWithoutProcessing = true
|
||||
self.balanceIconNode.image = UIImage(bundleImageName: "Wallet/BalanceGem")?.precomposed()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.balanceTextNode)
|
||||
self.addSubnode(self.balanceIconNode)
|
||||
}
|
||||
|
||||
func update(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let balanceIconSpacing: CGFloat = 8.0
|
||||
|
||||
let balanceTextSize = self.balanceTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
|
||||
let balanceIconSize = self.balanceIconNode.image?.size ?? CGSize(width: 38.0, height: 34.0)
|
||||
|
||||
let balanceOrigin = CGPoint(x: floor((width - balanceTextSize.width - balanceIconSpacing - balanceIconSize.width / 2.0) / 2.0), y: 0.0)
|
||||
|
||||
let balanceTextFrame = CGRect(origin: balanceOrigin, size: balanceTextSize)
|
||||
let balanceIconFrame = CGRect(origin: CGPoint(x: balanceTextFrame.maxX + balanceIconSpacing, y: balanceTextFrame.minY + floor((balanceTextFrame.height - balanceIconSize.height) / 2.0)), size: balanceIconSize)
|
||||
transition.updateFrameAdditive(node: self.balanceTextNode, frame: balanceTextFrame)
|
||||
transition.updateFrameAdditive(node: self.balanceIconNode, frame: balanceIconFrame)
|
||||
|
||||
return balanceTextSize.height
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
var balance: Int64?
|
||||
|
||||
let balanceNode: WalletInfoBalanceNode
|
||||
private let balanceSubtitleNode: ImmediateTextNode
|
||||
private let receiveButtonNode: SolidRoundedButtonNode
|
||||
private let sendButtonNode: SolidRoundedButtonNode
|
||||
private let headerBackgroundNode: ASImageNode
|
||||
|
||||
init(theme: PresentationTheme, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
||||
self.balanceNode = WalletInfoBalanceNode(theme: theme)
|
||||
|
||||
self.balanceSubtitleNode = ImmediateTextNode()
|
||||
self.balanceSubtitleNode.displaysAsynchronously = false
|
||||
self.balanceSubtitleNode.attributedText = NSAttributedString(string: "your balance", font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
|
||||
|
||||
self.headerBackgroundNode = ASImageNode()
|
||||
self.headerBackgroundNode.displaysAsynchronously = false
|
||||
self.headerBackgroundNode.displayWithoutProcessing = true
|
||||
self.headerBackgroundNode.image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: size))
|
||||
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 1)
|
||||
|
||||
self.receiveButtonNode = SolidRoundedButtonNode(title: "Receive", icon: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.sendButtonNode = SolidRoundedButtonNode(title: "Send", icon: UIImage(bundleImageName: "Wallet/SendButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.headerBackgroundNode)
|
||||
self.addSubnode(self.receiveButtonNode)
|
||||
self.addSubnode(self.sendButtonNode)
|
||||
self.addSubnode(self.balanceNode)
|
||||
self.addSubnode(self.balanceSubtitleNode)
|
||||
|
||||
self.receiveButtonNode.pressed = {
|
||||
receiveAction()
|
||||
}
|
||||
self.sendButtonNode.pressed = {
|
||||
sendAction()
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let buttonSideInset: CGFloat = 48.0
|
||||
let titleSpacing: CGFloat = 10.0
|
||||
let termsSpacing: CGFloat = 10.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
let balanceSubtitleSpacing: CGFloat = 0.0
|
||||
|
||||
let minOffset = navigationHeight
|
||||
let maxOffset = size.height
|
||||
|
||||
let minHeaderOffset = minOffset
|
||||
let maxHeaderOffset = (minOffset + maxOffset) / 2.0
|
||||
|
||||
let effectiveOffset = max(offset, navigationHeight)
|
||||
|
||||
let minButtonsOffset = maxOffset - buttonHeight - sideInset
|
||||
let maxButtonsOffset = maxOffset
|
||||
let buttonTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minButtonsOffset) / (maxButtonsOffset - minButtonsOffset)))
|
||||
let buttonAlpha = buttonTransition * 1.0
|
||||
|
||||
let balanceSubtitleSize = self.balanceSubtitleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: 200.0))
|
||||
|
||||
let balanceHeight = self.balanceNode.update(width: size.width, transition: transition)
|
||||
let balanceSize = CGSize(width: size.width, height: balanceHeight)
|
||||
|
||||
let minHeaderScale: CGFloat = 0.435
|
||||
|
||||
let minHeaderHeight: CGFloat = balanceSize.height + balanceSubtitleSize.height + balanceSubtitleSpacing
|
||||
|
||||
let minHeaderY = navigationHeight - 44.0 + floor((44.0 - minHeaderHeight) / 2.0)
|
||||
let maxHeaderY = floor((size.height - balanceSize.height) / 2.0)
|
||||
let headerScaleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minHeaderOffset) / (maxHeaderOffset - minHeaderOffset)))
|
||||
let headerPositionTransition: CGFloat = max(0.0, (effectiveOffset - minHeaderOffset) / (maxOffset - minHeaderOffset))
|
||||
let headerY = headerPositionTransition * maxHeaderY + (1.0 - headerPositionTransition) * minHeaderY
|
||||
let headerScale = headerScaleTransition * 1.0 + (1.0 - headerScaleTransition) * minHeaderScale
|
||||
|
||||
let balanceFrame = CGRect(origin: CGPoint(x: 0.0, y: headerY), size: balanceSize)
|
||||
transition.updateFrame(node: self.balanceNode, frame: balanceFrame)
|
||||
transition.updateSublayerTransformScale(node: self.balanceNode, scale: headerScale)
|
||||
|
||||
transition.updateFrameAdditive(node: self.balanceSubtitleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - balanceSubtitleSize.width) / 2.0), y: balanceFrame.midY + (balanceFrame.height / 2.0 * headerScale) + balanceSubtitleSpacing), size: balanceSubtitleSize))
|
||||
|
||||
let headerHeight: CGFloat = 1000.0
|
||||
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveOffset + 10.0 - headerHeight), size: CGSize(width: size.width, height: headerHeight)))
|
||||
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: effectiveOffset - sideInset - buttonHeight), size: CGSize(width: floor((size.width - sideInset * 3.0) / 2.0), height: buttonHeight))
|
||||
let sendButtonFrame = CGRect(origin: CGPoint(x: leftButtonFrame.maxX + sideInset, y: leftButtonFrame.minY), size: CGSize(width: size.width - leftButtonFrame.maxX - sideInset * 2.0, height: buttonHeight))
|
||||
let fullButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: effectiveOffset - sideInset - buttonHeight), size: CGSize(width: size.width - sideInset * 2.0, height: buttonHeight))
|
||||
|
||||
var receiveButtonFrame: CGRect
|
||||
if let balance = self.balance, balance > 0 {
|
||||
receiveButtonFrame = leftButtonFrame
|
||||
self.receiveButtonNode.isHidden = false
|
||||
self.sendButtonNode.isHidden = false
|
||||
} else {
|
||||
receiveButtonFrame = fullButtonFrame
|
||||
if self.balance == nil {
|
||||
self.receiveButtonNode.isHidden = true
|
||||
self.sendButtonNode.isHidden = true
|
||||
} else {
|
||||
self.receiveButtonNode.isHidden = false
|
||||
self.sendButtonNode.isHidden = true
|
||||
}
|
||||
}
|
||||
if self.balance == nil {
|
||||
self.balanceNode.isHidden = true
|
||||
self.balanceSubtitleNode.isHidden = true
|
||||
} else {
|
||||
self.balanceNode.isHidden = false
|
||||
self.balanceSubtitleNode.isHidden = false
|
||||
}
|
||||
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
|
||||
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
|
||||
self.receiveButtonNode.updateLayout(width: receiveButtonFrame.width, transition: transition)
|
||||
transition.updateFrame(node: self.sendButtonNode, frame: sendButtonFrame)
|
||||
transition.updateAlpha(node: self.sendButtonNode, alpha: buttonAlpha)
|
||||
self.sendButtonNode.updateLayout(width: sendButtonFrame.width, transition: transition)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.sendButtonNode.hitTest(self.view.convert(point, to: self.sendButtonNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
if let result = self.receiveButtonNode.hitTest(self.view.convert(point, to: self.receiveButtonNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.sendButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.receiveButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.balanceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.balanceSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
private struct WalletInfoListTransaction {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
private struct WalletInfoListEntry: Equatable, Comparable, Identifiable {
|
||||
let index: Int
|
||||
let item: WalletTransaction
|
||||
|
||||
var stableId: WalletTransactionId {
|
||||
return self.item.previousTransactionId
|
||||
}
|
||||
|
||||
static func <(lhs: WalletInfoListEntry, rhs: WalletInfoListEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(theme: PresentationTheme, action: @escaping (WalletTransaction) -> Void) -> ListViewItem {
|
||||
let item = self.item
|
||||
return WalletInfoTransactionItem(theme: theme, walletTransaction: self.item, action: {
|
||||
action(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void) -> WalletInfoListTransaction {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: presentationData.theme, action: action), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: presentationData.theme, action: action), directionHint: nil) }
|
||||
|
||||
return WalletInfoListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
|
||||
private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
private let account: Account
|
||||
private let tonContext: TonContext
|
||||
private var presentationData: PresentationData
|
||||
private let walletInfo: WalletInfo
|
||||
private let address: String
|
||||
private let openTransaction: (WalletTransaction) -> Void
|
||||
|
||||
private let headerNode: WalletInfoHeaderNode
|
||||
private let listNode: ListView
|
||||
private let emptyNode: WalletInfoEmptyNode
|
||||
|
||||
private var enqueuedTransactions: [WalletInfoListTransaction] = []
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let balanceDisposable = MetaDisposable()
|
||||
private let transactionListDisposable = MetaDisposable()
|
||||
|
||||
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, exportAction: @escaping () -> Void) {
|
||||
private var listOffset: CGFloat?
|
||||
private var loadingMoreTransactions: Bool = false
|
||||
private var canLoadMoreTransactions: Bool = true
|
||||
|
||||
private var currentEntries: [WalletInfoListEntry]?
|
||||
|
||||
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void) {
|
||||
self.account = account
|
||||
self.tonContext = tonContext
|
||||
self.presentationData = presentationData
|
||||
self.walletInfo = walletInfo
|
||||
self.exportAction = exportAction
|
||||
self.address = address
|
||||
self.openTransaction = openTransaction
|
||||
|
||||
let addressString = Data(base64Encoded: walletInfo.publicKey.rawValue).flatMap({ String(data: $0, encoding: .utf8) }) ?? ""
|
||||
self.headerNode = WalletInfoHeaderNode(theme: presentationData.theme, sendAction: sendAction, receiveAction: receiveAction)
|
||||
|
||||
let title: String = address
|
||||
let text: String = "..."
|
||||
let buttonText: String = "Export"
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
self.titleNode.textAlignment = .center
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.textAlignment = .center
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: self.presentationData.theme, height: 50.0, cornerRadius: 10.0, gloss: true)
|
||||
self.emptyNode = WalletInfoEmptyNode(presentationData: self.presentationData, address: self.address)
|
||||
self.emptyNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.pressed = {
|
||||
exportAction()
|
||||
}
|
||||
self.backgroundColor = .white
|
||||
|
||||
self.balanceDisposable.set((currentWalletBalance(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: "\(value)", font: Font.regular(16.0), textColor: strongSelf.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let firstTime = strongSelf.headerNode.balance == nil
|
||||
strongSelf.headerNode.balanceNode.balance = formatBalanceText(max(0, value.rawValue))
|
||||
strongSelf.headerNode.balance = max(0, value.rawValue)
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
if firstTime {
|
||||
strongSelf.headerNode.animateIn()
|
||||
}
|
||||
}))
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
self.addSubnode(self.emptyNode)
|
||||
self.addSubnode(self.headerNode)
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
|
||||
guard let strongSelf = self, let (layout, navigationHeight) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.listOffset = offset
|
||||
|
||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: offset, transition: listTransition)
|
||||
}
|
||||
|
||||
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard case let .known(value) = offset, value < 100.0 else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.loadingMoreTransactions && strongSelf.canLoadMoreTransactions {
|
||||
strongSelf.loadMoreTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.didEndScrolling = { [weak self] in
|
||||
guard let strongSelf = self, let (_, navigationHeight) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
switch strongSelf.listNode.visibleContentOffset() {
|
||||
case let .known(offset):
|
||||
if offset < strongSelf.listNode.insets.top {
|
||||
/*if offset > strongSelf.listNode.insets.top / 2.0 {
|
||||
strongSelf.scrollToHideHeader()
|
||||
} else {
|
||||
strongSelf.scrollToTop()
|
||||
}*/
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.refreshTransactions()
|
||||
}
|
||||
|
||||
func scrollToHideHeader() {
|
||||
guard let (_, navigationHeight) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(navigationHeight), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstLayout = self.validLayout == nil
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
let headerHeight: CGFloat = navigationHeight + 260.0
|
||||
let topInset: CGFloat = headerHeight
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||
transition.updateFrame(node: self.headerNode, frame: headerFrame)
|
||||
self.headerNode.update(size: headerFrame.size, navigationHeight: navigationHeight, offset: self.listOffset ?? 0.0, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var duration: Double = 0.0
|
||||
var curve: UInt = 0
|
||||
switch transition {
|
||||
case .immediate:
|
||||
break
|
||||
case let .animated(animationDuration, animationCurve):
|
||||
duration = animationDuration
|
||||
switch animationCurve {
|
||||
case .easeInOut, .custom:
|
||||
break
|
||||
case .spring:
|
||||
curve = 7
|
||||
}
|
||||
}
|
||||
|
||||
let listViewCurve: ListViewAnimationCurve
|
||||
if curve == 7 {
|
||||
listViewCurve = .Spring(duration: duration)
|
||||
} else {
|
||||
listViewCurve = .Default(duration: duration)
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: topInset + 3.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
let emptyNodeHeight = self.emptyNode.updateLayout(width: layout.size.width, transition: transition)
|
||||
let maxEmptyNodeHeight: CGFloat = max(100.0, layout.size.height - headerHeight)
|
||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: headerHeight + floor((maxEmptyNodeHeight - emptyNodeHeight) / 2.0)), size: CGSize(width: layout.size.width, height: emptyNodeHeight))
|
||||
transition.updateFrame(node: self.emptyNode, frame: emptyNodeFrame)
|
||||
|
||||
if isFirstLayout {
|
||||
while !self.enqueuedTransactions.isEmpty {
|
||||
self.dequeueTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshTransactions() {
|
||||
self.transactionListDisposable.set(nil)
|
||||
self.loadingMoreTransactions = true
|
||||
|
||||
self.transactionListDisposable.set((getWalletTransactions(address: self.address, previousId: nil, tonInstance: self.tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transactions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.transactionsLoaded(isReload: true, transactions: transactions)
|
||||
}, error: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
private func loadMoreTransactions() {
|
||||
if self.loadingMoreTransactions {
|
||||
return
|
||||
}
|
||||
self.loadingMoreTransactions = true
|
||||
self.transactionListDisposable.set((getWalletTransactions(address: self.address, previousId: self.currentEntries?.last?.item.previousTransactionId, tonInstance: self.tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transactions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.transactionsLoaded(isReload: false, transactions: transactions)
|
||||
}, error: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func transactionsLoaded(isReload: Bool, transactions: [WalletTransaction]) {
|
||||
self.loadingMoreTransactions = false
|
||||
self.canLoadMoreTransactions = transactions.count > 2
|
||||
|
||||
let sideInset: CGFloat = 32.0
|
||||
let buttonSideInset: CGFloat = 48.0
|
||||
let titleSpacing: CGFloat = 10.0
|
||||
let termsSpacing: CGFloat = 10.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
let isFirst = self.currentEntries == nil
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
var updatedEntries: [WalletInfoListEntry] = []
|
||||
if isReload {
|
||||
for transaction in transactions {
|
||||
updatedEntries.append(WalletInfoListEntry(index: updatedEntries.count, item: transaction))
|
||||
}
|
||||
} else {
|
||||
updatedEntries = self.currentEntries ?? []
|
||||
var existingIds = Set(updatedEntries.map { $0.item.previousTransactionId })
|
||||
for transaction in transactions {
|
||||
if !existingIds.contains(transaction.previousTransactionId) {
|
||||
existingIds.insert(transaction.previousTransactionId)
|
||||
updatedEntries.append(WalletInfoListEntry(index: updatedEntries.count, item: transaction))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let contentHeight = titleSize.height + titleSpacing + textSize.height
|
||||
let contentVerticalOrigin = floor((layout.size.height - contentHeight) / 2.0)
|
||||
let transaction = preparedTransition(from: self.currentEntries ?? [], to: updatedEntries, presentationData: self.presentationData, action: { [weak self] transaction in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openTransaction(transaction)
|
||||
})
|
||||
self.currentEntries = updatedEntries
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: contentVerticalOrigin), size: titleSize)
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
self.dequeueTransaction()
|
||||
|
||||
let minimalBottomInset: CGFloat = 60.0
|
||||
let bottomInset = layout.intrinsicInsets.bottom + minimalBottomInset
|
||||
if updatedEntries.isEmpty {
|
||||
self.emptyNode.isHidden = false
|
||||
} else {
|
||||
self.emptyNode.isHidden = true
|
||||
}
|
||||
|
||||
let buttonWidth = layout.size.width - buttonSideInset * 2.0
|
||||
if isFirst {
|
||||
if !updatedEntries.isEmpty {
|
||||
self.emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
} else {
|
||||
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransaction() {
|
||||
guard let layout = self.validLayout, let transaction = self.enqueuedTransactions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
|
||||
self.enqueuedTransactions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
|
||||
self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func formatBalanceText(_ value: Int64) -> String {
|
||||
var balanceText = "\(value)"
|
||||
while balanceText.count < 10 {
|
||||
balanceText.insert("0", at: balanceText.startIndex)
|
||||
}
|
||||
balanceText.insert(".", at: balanceText.index(balanceText.endIndex, offsetBy: -9))
|
||||
while true {
|
||||
if balanceText.hasSuffix("0") {
|
||||
if balanceText.hasSuffix(".0") {
|
||||
break
|
||||
} else {
|
||||
balanceText.removeLast()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return balanceText
|
||||
}
|
||||
|
306
submodules/WalletUI/Sources/WalletInfoTransactionItem.swift
Normal file
306
submodules/WalletUI/Sources/WalletInfoTransactionItem.swift
Normal file
@ -0,0 +1,306 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import TelegramCore
|
||||
|
||||
private let transactionIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
|
||||
|
||||
class WalletInfoTransactionItem: ListViewItem {
|
||||
let theme: PresentationTheme
|
||||
let walletTransaction: WalletTransaction
|
||||
let action: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, walletTransaction: WalletTransaction, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.walletTransaction = walletTransaction
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = WalletInfoTransactionItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, previousItem != nil, nextItem != nil)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? WalletInfoTransactionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, previousItem != nil, nextItem != nil)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectable: Bool = true
|
||||
|
||||
func selected(listView: ListView){
|
||||
listView.clearHighlightAnimated(true)
|
||||
self.action()
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.medium(17.0)
|
||||
private let textFont = Font.monospace(15.0)
|
||||
private let dateFont = Font.regular(14.0)
|
||||
private let directionFont = Font.regular(15.0)
|
||||
|
||||
class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let directionNode: TextNode
|
||||
private let iconNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
private let dateNode: TextNode
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: WalletInfoTransactionItem?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.directionNode = TextNode()
|
||||
self.directionNode.isUserInteractionEnabled = false
|
||||
self.directionNode.contentMode = .left
|
||||
self.directionNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.contentMode = .left
|
||||
self.textNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.dateNode = TextNode()
|
||||
self.dateNode.isUserInteractionEnabled = false
|
||||
self.dateNode.contentMode = .left
|
||||
self.dateNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.directionNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.dateNode)
|
||||
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletInfoTransactionItem, _ params: ListViewItemLayoutParams, _ hasPrevious: Bool, _ hasNext: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeDateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, hasPrevious, hasNext in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
let iconImage: UIImage? = transactionIcon
|
||||
let iconSize = iconImage?.size ?? CGSize(width: 10.0, height: 10.0)
|
||||
|
||||
let leftInset = 16.0 + params.leftInset
|
||||
|
||||
let title: String
|
||||
let directionText: String
|
||||
let titleColor: UIColor
|
||||
let transferredValue = item.walletTransaction.transferredValue
|
||||
var text: String = ""
|
||||
if transferredValue <= 0 {
|
||||
title = "\(formatBalanceText(transferredValue))"
|
||||
titleColor = item.theme.list.itemPrimaryTextColor
|
||||
if item.walletTransaction.outMessages.isEmpty {
|
||||
directionText = ""
|
||||
text = "Empty Transaction"
|
||||
} else {
|
||||
directionText = "to"
|
||||
for message in item.walletTransaction.outMessages {
|
||||
if !text.isEmpty {
|
||||
text.append("\n")
|
||||
}
|
||||
text.append(message.destination)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
title = "+\(formatBalanceText(transferredValue))"
|
||||
titleColor = item.theme.chatList.secretTitleColor
|
||||
directionText = "from"
|
||||
if let inMessage = item.walletTransaction.inMessage {
|
||||
text = inMessage.source
|
||||
} else {
|
||||
text = "<unknown>"
|
||||
}
|
||||
}
|
||||
let dateText = " "
|
||||
|
||||
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 (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset - 20.0 - dateLayout.size.width - directionLayout.size.width - iconSize.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemSeparatorColor: UIColor
|
||||
|
||||
itemBackgroundColor = item.theme.list.plainBackgroundColor
|
||||
itemSeparatorColor = item.theme.list.itemPlainSeparatorColor
|
||||
|
||||
let topInset: CGFloat = 11.0
|
||||
let bottomInset: CGFloat = 11.0
|
||||
let titleSpacing: CGFloat = 2.0
|
||||
|
||||
contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height + titleSpacing + textLayout.size.height + bottomInset)
|
||||
insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||
//strongSelf.activateArea.accessibilityLabel = item.title
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.iconNode.image = iconImage
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = textApply()
|
||||
let _ = dateApply()
|
||||
let _ = directionApply()
|
||||
|
||||
if strongSelf.backgroundNode.supernode != nil {
|
||||
strongSelf.backgroundNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode != nil {
|
||||
strongSelf.topStripeNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||
}
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) / 2.0) - 1.0), size: iconSize)
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
|
||||
let directionFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + 3.0, y: titleFrame.maxY - directionLayout.size.height - 1.0), size: directionLayout.size)
|
||||
strongSelf.directionNode.frame = directionFrame
|
||||
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size)
|
||||
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - dateLayout.size.width, y: topInset), size: dateLayout.size)
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel * 2.0))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||
|
||||
if highlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
if self.highlightedBackgroundNode.supernode == nil {
|
||||
var anchorNode: ASDisplayNode?
|
||||
if self.bottomStripeNode.supernode != nil {
|
||||
anchorNode = self.bottomStripeNode
|
||||
} else if self.topStripeNode.supernode != nil {
|
||||
anchorNode = self.topStripeNode
|
||||
} else if self.backgroundNode.supernode != nil {
|
||||
anchorNode = self.backgroundNode
|
||||
}
|
||||
if let anchorNode = anchorNode {
|
||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||
} else {
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
if animated {
|
||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
163
submodules/WalletUI/Sources/WalletSettingsScreen.swift
Normal file
163
submodules/WalletUI/Sources/WalletSettingsScreen.swift
Normal file
@ -0,0 +1,163 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AppBundle
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SolidRoundedButtonNode
|
||||
import AnimationUI
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import ItemListUI
|
||||
|
||||
private final class WalletSettingsControllerArguments {
|
||||
let exportWallet: () -> Void
|
||||
let deleteWallet: () -> Void
|
||||
|
||||
init(exportWallet: @escaping () -> Void, deleteWallet: @escaping () -> Void) {
|
||||
self.exportWallet = exportWallet
|
||||
self.deleteWallet = deleteWallet
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletSettingsSection: Int32 {
|
||||
case exportWallet
|
||||
case deleteWallet
|
||||
}
|
||||
|
||||
private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||
case exportWallet(PresentationTheme, String)
|
||||
case deleteWallet(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .exportWallet:
|
||||
return WalletSettingsSection.exportWallet.rawValue
|
||||
case .deleteWallet:
|
||||
return WalletSettingsSection.deleteWallet.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .exportWallet:
|
||||
return 0
|
||||
case .deleteWallet:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WalletSettingsEntry, rhs: WalletSettingsEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: WalletSettingsControllerArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .exportWallet(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.exportWallet()
|
||||
})
|
||||
case let .deleteWallet(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.deleteWallet()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WalletSettingsControllerState: Equatable {
|
||||
}
|
||||
|
||||
private func walletSettingsControllerEntries(presentationData: PresentationData, state: WalletSettingsControllerState) -> [WalletSettingsEntry] {
|
||||
var entries: [WalletSettingsEntry] = []
|
||||
|
||||
entries.append(.exportWallet(presentationData.theme, "Export Wallet"))
|
||||
entries.append(.deleteWallet(presentationData.theme, "Delete Wallet"))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func walletSettingsController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo) -> ViewController {
|
||||
let statePromise = ValuePromise(WalletSettingsControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: WalletSettingsControllerState())
|
||||
let updateState: ((WalletSettingsControllerState) -> WalletSettingsControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
var replaceAllWalletControllersImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = WalletSettingsControllerArguments(exportWallet: {
|
||||
let _ = (walletRestoreWords(network: context.account.network, walletInfo: walletInfo, tonInstance: tonContext.instance, keychain: tonContext.keychain)
|
||||
|> deliverOnMainQueue).start(next: { wordList in
|
||||
pushControllerImpl?(WalletWordDisplayScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, wordList: wordList))
|
||||
})
|
||||
}, deleteWallet: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: "Delete Wallet", color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
let _ = (debugDeleteWallets(postbox: context.account.postbox)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
if let tonContext = context.tonContext {
|
||||
replaceAllWalletControllersImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .intro))
|
||||
}
|
||||
})
|
||||
})
|
||||
]), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
presentControllerImpl?(actionSheet, nil)
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletSettingsEntry>, WalletSettingsEntry.ItemGenerationArguments)) in
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Wallet Settings"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletSettingsControllerEntries(presentationData: presentationData, state: state), style: .blocks, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.enableInteractiveDismiss = true
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
replaceAllWalletControllersImpl = { [weak controller] c in
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { listController in
|
||||
if listController === controller {
|
||||
return false
|
||||
}
|
||||
if listController is WalletInfoScreen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
controllers.append(c)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
@ -247,7 +247,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
|
||||
|
||||
self.secondaryActionButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: self.presentationData.theme, height: 50.0, cornerRadius: 10.0, gloss: true)
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: true)
|
||||
|
||||
super.init()
|
||||
|
||||
|
310
submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift
Normal file
310
submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift
Normal file
@ -0,0 +1,310 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AppBundle
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SolidRoundedButtonNode
|
||||
import AnimationUI
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import ItemListUI
|
||||
|
||||
private final class WalletTransactionInfoControllerArguments {
|
||||
let copyWalletAddress: () -> Void
|
||||
let sendGrams: () -> Void
|
||||
|
||||
init(copyWalletAddress: @escaping () -> Void, sendGrams: @escaping () -> Void) {
|
||||
self.copyWalletAddress = copyWalletAddress
|
||||
self.sendGrams = sendGrams
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoSection: Int32 {
|
||||
case amount
|
||||
case info
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
||||
case amount(PresentationTheme, WalletTransaction)
|
||||
case infoHeader(PresentationTheme, String)
|
||||
case infoAddress(PresentationTheme, String)
|
||||
case infoCopyAddress(PresentationTheme, String)
|
||||
case infoSendGrams(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .amount:
|
||||
return WalletTransactionInfoSection.amount.rawValue
|
||||
case .infoHeader, .infoAddress, .infoCopyAddress, .infoSendGrams:
|
||||
return WalletTransactionInfoSection.info.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .amount:
|
||||
return 0
|
||||
case .infoHeader:
|
||||
return 1
|
||||
case .infoAddress:
|
||||
return 2
|
||||
case .infoCopyAddress:
|
||||
return 3
|
||||
case .infoSendGrams:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WalletTransactionInfoEntry, rhs: WalletTransactionInfoEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: WalletTransactionInfoControllerArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .amount(theme, walletTransaction):
|
||||
return WalletTransactionHeaderItem(theme: theme, walletTransaction: walletTransaction, sectionId: self.section)
|
||||
case let .infoHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .infoAddress(theme, text):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks)
|
||||
case let .infoCopyAddress(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.copyWalletAddress()
|
||||
})
|
||||
case let .infoSendGrams(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.sendGrams()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WalletTransactionInfoControllerState: Equatable {
|
||||
}
|
||||
|
||||
private func extractAddress(_ walletTransaction: WalletTransaction) -> String {
|
||||
let transferredValue = walletTransaction.transferredValue
|
||||
var text = ""
|
||||
if transferredValue <= 0 {
|
||||
if walletTransaction.outMessages.isEmpty {
|
||||
text = "No Address"
|
||||
} else {
|
||||
for message in walletTransaction.outMessages {
|
||||
if !text.isEmpty {
|
||||
text.append("\n\n")
|
||||
}
|
||||
text.append(message.destination)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let inMessage = walletTransaction.inMessage {
|
||||
text = inMessage.source
|
||||
} else {
|
||||
text = "<unknown>"
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletTransaction, state: WalletTransactionInfoControllerState) -> [WalletTransactionInfoEntry] {
|
||||
var entries: [WalletTransactionInfoEntry] = []
|
||||
|
||||
entries.append(.amount(presentationData.theme, walletTransaction))
|
||||
|
||||
let transferredValue = walletTransaction.transferredValue
|
||||
let text = extractAddress(walletTransaction)
|
||||
|
||||
if transferredValue <= 0 {
|
||||
entries.append(.infoHeader(presentationData.theme, "RECIPIENT"))
|
||||
} else {
|
||||
entries.append(.infoHeader(presentationData.theme, "SENDER"))
|
||||
}
|
||||
entries.append(.infoAddress(presentationData.theme, text))
|
||||
entries.append(.infoCopyAddress(presentationData.theme, "Copy Address"))
|
||||
entries.append(.infoSendGrams(presentationData.theme, "Send Grams"))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func walletTransactionInfoController(context: AccountContext, walletTransaction: WalletTransaction) -> ViewController {
|
||||
let statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: WalletTransactionInfoControllerState())
|
||||
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
|
||||
let arguments = WalletTransactionInfoControllerArguments(copyWalletAddress: {
|
||||
let address = extractAddress(walletTransaction)
|
||||
UIPasteboard.general.string = address
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success), nil)
|
||||
}, sendGrams: {
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletTransactionInfoEntry>, WalletTransactionInfoEntry.ItemGenerationArguments)) in
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Transaction"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletTransactionInfoControllerEntries(presentationData: presentationData, walletTransaction: walletTransaction, state: state), style: .blocks, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.enableInteractiveDismiss = true
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
class WalletTransactionHeaderItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let walletTransaction: WalletTransaction
|
||||
let sectionId: ItemListSectionId
|
||||
let isAlwaysPlain: Bool = true
|
||||
|
||||
init(theme: PresentationTheme, walletTransaction: WalletTransaction, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.walletTransaction = walletTransaction
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = WalletTransactionHeaderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
guard let nodeValue = node() as? WalletTransactionHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(14.0)
|
||||
private let titleBoldFont = Font.semibold(14.0)
|
||||
|
||||
private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let iconNode: ASImageNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: WalletTransactionHeaderItem?
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.image = UIImage(bundleImageName: "Wallet/BalanceGem")?.precomposed()
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
self.activateArea.accessibilityTraits = .staticText
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletTransactionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
let verticalInset: CGFloat = 24.0
|
||||
|
||||
let title: String
|
||||
let titleColor: UIColor
|
||||
let transferredValue = item.walletTransaction.transferredValue
|
||||
if transferredValue <= 0 {
|
||||
title = "\(formatBalanceText(transferredValue))"
|
||||
titleColor = item.theme.list.itemPrimaryTextColor
|
||||
} else {
|
||||
title = "+\(formatBalanceText(transferredValue))"
|
||||
titleColor = item.theme.chatList.secretTitleColor
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.semibold(39.0), textColor: titleColor), 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 contentSize: CGSize
|
||||
|
||||
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||
//strongSelf.activateArea.accessibilityLabel = attributedText.string
|
||||
|
||||
let _ = titleApply()
|
||||
|
||||
let iconSpacing: CGFloat = 8.0
|
||||
let contentWidth = titleLayout.size.width + iconSpacing + iconSize.width / 2.0
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - contentWidth) / 2.0), y: verticalInset), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + iconSpacing, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
@ -2244,6 +2244,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? {
|
||||
private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let next: (WordCheckInputNode) -> Void
|
||||
private let focused: (WordCheckInputNode) -> Void
|
||||
private let pasteWords: ([String]) -> Void
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
private let labelNode: ImmediateTextNode
|
||||
@ -2251,12 +2252,18 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let clearButtonNode: HighlightableButtonNode
|
||||
|
||||
var text: String {
|
||||
return self.inputNode.textField.text ?? ""
|
||||
get {
|
||||
return self.inputNode.textField.text ?? ""
|
||||
} set(value) {
|
||||
self.inputNode.textField.text = value
|
||||
self.textFieldChanged(self.inputNode.textField)
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, index: Int, possibleWordList: [String], next: @escaping (WordCheckInputNode) -> Void, isLast: Bool, focused: @escaping (WordCheckInputNode) -> Void) {
|
||||
init(theme: PresentationTheme, index: Int, possibleWordList: [String], next: @escaping (WordCheckInputNode) -> Void, isLast: Bool, focused: @escaping (WordCheckInputNode) -> Void, pasteWords: @escaping ([String]) -> Void) {
|
||||
self.next = next
|
||||
self.focused = focused
|
||||
self.pasteWords = pasteWords
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
@ -2330,6 +2337,15 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
let wordList = string.split(separator: " ")
|
||||
if wordList.count == 24 {
|
||||
self.pasteWords(wordList.map(String.init))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@objc private func textFieldChanged(_ textField: UITextField) {
|
||||
let text = self.text
|
||||
if textField.isFirstResponder {
|
||||
@ -2617,18 +2633,21 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
||||
|
||||
var nextWord: ((WordCheckInputNode) -> Void)?
|
||||
var focused: ((WordCheckInputNode) -> Void)?
|
||||
var pasteWords: (([String]) -> Void)?
|
||||
|
||||
for i in 0 ..< wordIndices.count {
|
||||
inputNodes.append(WordCheckInputNode(theme: presentationData.theme, index: wordIndices[i], possibleWordList: possibleWordList, next: { node in
|
||||
nextWord?(node)
|
||||
}, isLast: i == wordIndices.count - 1, focused: { node in
|
||||
focused?(node)
|
||||
}, pasteWords: { wordList in
|
||||
pasteWords?(wordList)
|
||||
}))
|
||||
}
|
||||
|
||||
self.inputNodes = inputNodes
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: self.presentationData.theme, height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -2688,6 +2707,16 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
||||
}
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
|
||||
}
|
||||
pasteWords = { [weak self] wordList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.inputNodes.count == wordList.count {
|
||||
for i in 0 ..< strongSelf.inputNodes.count {
|
||||
strongSelf.inputNodes[i].text = wordList[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func secondaryActionPressed() {
|
||||
|
@ -88,6 +88,7 @@ public final class WalletWordDisplayScreen: ViewController {
|
||||
|
||||
private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private var presentationData: PresentationData
|
||||
private let wordList: [String]
|
||||
private let action: () -> Void
|
||||
|
||||
private let navigationBackgroundNode: ASDisplayNode
|
||||
@ -104,6 +105,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
|
||||
|
||||
init(presentationData: PresentationData, wordList: [String], action: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.wordList = wordList
|
||||
self.action = action
|
||||
|
||||
self.navigationBackgroundNode = ASDisplayNode()
|
||||
@ -169,7 +171,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
|
||||
|
||||
self.wordNodes = wordNodes
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: self.presentationData.theme, height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -207,6 +209,17 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
|
||||
#if DEBUG
|
||||
self.textNode.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.textLongPressGesture(_:))))
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc func textLongPressGesture(_ recognizer: UILongPressGestureRecognizer) {
|
||||
if case .began = recognizer.state {
|
||||
UIPasteboard.general.string = self.wordList.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
@ -91,6 +91,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29BF0846EE00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libActivityIndicator.a</string>
|
||||
<key>path</key>
|
||||
<string>libActivityIndicator.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29F523B9DE00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -117,6 +130,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E2997B4D6D800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libCheckNode.a</string>
|
||||
<key>path</key>
|
||||
<string>libCheckNode.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E295915423000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -156,6 +182,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E295A26607D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libItemListUI.a</string>
|
||||
<key>path</key>
|
||||
<string>libItemListUI.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29CE34063500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -182,6 +221,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29C37F741500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libMergeLists.a</string>
|
||||
<key>path</key>
|
||||
<string>libMergeLists.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29BBAF750C00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -221,6 +273,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>1DD70E29CBE117ED00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libProgressNavigationButtonNode.a</string>
|
||||
<key>path</key>
|
||||
<string>libProgressNavigationButtonNode.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E293E4DE92B00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -234,6 +299,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E2957B5522500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libSegmentedControlNode.a</string>
|
||||
<key>path</key>
|
||||
<string>libSegmentedControlNode.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29524F478E00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -273,6 +351,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>1DD70E2925BBFEEE00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libSwitchNode.a</string>
|
||||
<key>path</key>
|
||||
<string>libSwitchNode.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29F33FDAC300000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -414,20 +505,27 @@
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E299D2580DA00000000</string>
|
||||
<string>1DD70E29BF0846EE00000000</string>
|
||||
<string>1DD70E29F523B9DE00000000</string>
|
||||
<string>1DD70E292420028600000000</string>
|
||||
<string>1DD70E2997B4D6D800000000</string>
|
||||
<string>1DD70E295915423000000000</string>
|
||||
<string>1DD70E29FF334B1F00000000</string>
|
||||
<string>1DD70E29D6F14E1000000000</string>
|
||||
<string>1DD70E295A26607D00000000</string>
|
||||
<string>1DD70E29CE34063500000000</string>
|
||||
<string>1DD70E2936DE2CF900000000</string>
|
||||
<string>1DD70E29C37F741500000000</string>
|
||||
<string>1DD70E29BBAF750C00000000</string>
|
||||
<string>1DD70E290F1A3C6400000000</string>
|
||||
<string>1DD70E29DB6520C800000000</string>
|
||||
<string>1DD70E29CBE117ED00000000</string>
|
||||
<string>1DD70E293E4DE92B00000000</string>
|
||||
<string>1DD70E2957B5522500000000</string>
|
||||
<string>1DD70E29524F478E00000000</string>
|
||||
<string>1DD70E293594DCC000000000</string>
|
||||
<string>1DD70E29D65BA68200000000</string>
|
||||
<string>1DD70E2925BBFEEE00000000</string>
|
||||
<string>1DD70E29F33FDAC300000000</string>
|
||||
<string>1DD70E29119CDA0700000000</string>
|
||||
<string>1DD70E2984A59C1D00000000</string>
|
||||
@ -479,6 +577,17 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>text.script.python</string>
|
||||
</dict>
|
||||
<key>1DD70E298B98CCED00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletInfoEmptyNode.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletInfoEmptyNode.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E29D2B1401800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -490,6 +599,28 @@
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2900657ECF00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletInfoTransactionItem.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletInfoTransactionItem.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2986544B8D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletSettingsScreen.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletSettingsScreen.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2964068E1100000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -501,6 +632,17 @@
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E290467090400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletTransactionInfoScreen.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletTransactionInfoScreen.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2936794EB600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -533,8 +675,12 @@
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E298B98CCED00000000</string>
|
||||
<string>1DD70E29D2B1401800000000</string>
|
||||
<string>1DD70E2900657ECF00000000</string>
|
||||
<string>1DD70E2986544B8D00000000</string>
|
||||
<string>1DD70E2964068E1100000000</string>
|
||||
<string>1DD70E290467090400000000</string>
|
||||
<string>1DD70E2936794EB600000000</string>
|
||||
<string>1DD70E290678D03000000000</string>
|
||||
</array>
|
||||
@ -569,6 +715,13 @@
|
||||
<string>B401C979172FA86D00000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>E7A30F048B98CCED00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E298B98CCED00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04D2B1401800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -576,6 +729,20 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29D2B1401800000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0400657ECF00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2900657ECF00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0486544B8D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2986544B8D00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0464068E1100000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -583,6 +750,13 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2964068E1100000000</string>
|
||||
</dict>
|
||||
<key>E7A30F040467090400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E290467090400000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0436794EB600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -603,8 +777,12 @@
|
||||
<string>PBXSourcesBuildPhase</string>
|
||||
<key>files</key>
|
||||
<array>
|
||||
<string>E7A30F048B98CCED00000000</string>
|
||||
<string>E7A30F04D2B1401800000000</string>
|
||||
<string>E7A30F0400657ECF00000000</string>
|
||||
<string>E7A30F0486544B8D00000000</string>
|
||||
<string>E7A30F0464068E1100000000</string>
|
||||
<string>E7A30F040467090400000000</string>
|
||||
<string>E7A30F0436794EB600000000</string>
|
||||
<string>E7A30F040678D03000000000</string>
|
||||
</array>
|
||||
@ -728,20 +906,6 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29F523B9DE00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04BBAF750C00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29BBAF750C00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04524F478E00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29524F478E00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04D6F14E1000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -770,6 +934,69 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E292420028600000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0497B4D6D800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2997B4D6D800000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04C37F741500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29C37F741500000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04BF0846EE00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29BF0846EE00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04CBE117ED00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29CBE117ED00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0457B5522500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2957B5522500000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0425BBFEEE00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E2925BBFEEE00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F045A26607D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E295A26607D00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04BBAF750C00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29BBAF750C00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04524F478E00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29524F478E00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F043E4DE92B00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -807,12 +1034,19 @@
|
||||
<string>E7A30F04BA06E3A600000000</string>
|
||||
<string>E7A30F049D2580DA00000000</string>
|
||||
<string>E7A30F04F523B9DE00000000</string>
|
||||
<string>E7A30F04BBAF750C00000000</string>
|
||||
<string>E7A30F04524F478E00000000</string>
|
||||
<string>E7A30F04D6F14E1000000000</string>
|
||||
<string>E7A30F04CD296A8300000000</string>
|
||||
<string>E7A30F043594DCC000000000</string>
|
||||
<string>E7A30F042420028600000000</string>
|
||||
<string>E7A30F0497B4D6D800000000</string>
|
||||
<string>E7A30F04C37F741500000000</string>
|
||||
<string>E7A30F04BF0846EE00000000</string>
|
||||
<string>E7A30F04CBE117ED00000000</string>
|
||||
<string>E7A30F0457B5522500000000</string>
|
||||
<string>E7A30F0425BBFEEE00000000</string>
|
||||
<string>E7A30F045A26607D00000000</string>
|
||||
<string>E7A30F04BBAF750C00000000</string>
|
||||
<string>E7A30F04524F478E00000000</string>
|
||||
<string>E7A30F043E4DE92B00000000</string>
|
||||
<string>E7A30F04AE67341000000000</string>
|
||||
</array>
|
||||
|
@ -313,6 +313,7 @@ function(target_link_libraries_system target)
|
||||
endforeach(lib)
|
||||
endfunction(target_link_libraries_system)
|
||||
|
||||
set(TDUTILS_MIME_TYPE OFF CACHE BOOL "Generate mime type conversion")
|
||||
add_subdirectory(tdutils)
|
||||
add_subdirectory(memprof)
|
||||
add_subdirectory(tdactor)
|
||||
@ -336,7 +337,10 @@ add_subdirectory(tonlib)
|
||||
|
||||
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tdmime_auto tlb_generate_block)
|
||||
if (TDUTILS_MIME_TYPE)
|
||||
set(TDMIME_AUTO tdmime_auto)
|
||||
endif()
|
||||
add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tlb_generate_block ${TDMIME_AUTO})
|
||||
endif()
|
||||
|
||||
#TESTS
|
||||
|
@ -22,14 +22,14 @@ Example:
|
||||
|
||||
The "test giver" (a special smart contract residing in the masterchain of the Test Network that gives up to 20 test Grams to anybody who asks) has the address
|
||||
|
||||
-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||||
-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
|
||||
|
||||
in the "raw" form (notice that uppercase Latin letters 'A'..'F' may be used instead of 'a'..'f')
|
||||
|
||||
and
|
||||
|
||||
Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb (base64) or
|
||||
Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb (base64url)
|
||||
kf/8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny (base64)
|
||||
kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny (base64url)
|
||||
|
||||
in the "user-friendly" form (to be displayed by user-friendly clients). Notice that both forms (base64 and base64url) are valid and must be accepted.
|
||||
|
||||
@ -50,27 +50,27 @@ Inspecting the state of smart contracts with the aid of the TON Lite Client is e
|
||||
|
||||
> last
|
||||
...
|
||||
> getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||||
> getaccount -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
|
||||
or
|
||||
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||||
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
|
||||
|
||||
You will see something like this:
|
||||
|
||||
------------------------------------
|
||||
got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,1645):4D5E1B928490BE34A4F03C9BC996661B8AD4E988F1460DB69BE3634B1E843DAF:9D85BB116D1DA4BFD999E9092BBD43B3A9F96EC9C9ED7AFDC81B27B08D3861A0 and (-1,8000000000000000,1645):4D5E1B928490BE34A4F03C9BC996661B8AD4E988F1460DB69BE3634B1E843DAF:9D85BB116D1DA4BFD999E9092BBD43B3A9F96EC9C9ED7AFDC81B27B08D3861A0
|
||||
got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2075):BFE876CE2085274FEDAF1BD80F3ACE50F42B5A027DF230AD66DCED1F09FB39A7:522C027A721FABCB32574E3A809ABFBEE6A71DE929C1FA2B1CD0DDECF3056505
|
||||
account state is (account
|
||||
addr:(addr_std
|
||||
anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
|
||||
anycast:nothing workchain_id:-1 address:xFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260)
|
||||
storage_stat:(storage_info
|
||||
used:(storage_used
|
||||
cells:(var_uint len:1 value:3)
|
||||
bits:(var_uint len:2 value:539)
|
||||
public_cells:(var_uint len:0 value:0)) last_paid:0
|
||||
bits:(var_uint len:2 value:707)
|
||||
public_cells:(var_uint len:0 value:0)) last_paid:1568899526
|
||||
due_payment:nothing)
|
||||
storage:(account_storage last_trans_lt:0
|
||||
storage:(account_storage last_trans_lt:2310000003
|
||||
balance:(currencies
|
||||
grams:(nanograms
|
||||
amount:(var_uint len:7 value:1000000000000000))
|
||||
amount:(var_uint len:6 value:9998859500889))
|
||||
other:(extra_currencies
|
||||
dict:hme_empty))
|
||||
state:(account_active
|
||||
@ -80,7 +80,7 @@ account state is (account
|
||||
code:(just
|
||||
value:(raw@^Cell
|
||||
x{}
|
||||
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||||
x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260D31F01ED44D0D31FD166BAF2A1F8000120D74A8E11D307D459821804A817C80073FB0201FB00DED1A4C8CB1FC9ED54}
|
||||
))
|
||||
data:(just
|
||||
value:(raw@^Cell
|
||||
@ -88,9 +88,10 @@ account state is (account
|
||||
x{00009A15}
|
||||
))
|
||||
library:hme_empty))))
|
||||
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C0000000000000000000000001C0E35FA931A000134_}
|
||||
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||||
x{00000000}
|
||||
x{CFFFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB2923226020680B0C2EC1C0E300000000226BF360D8246029DFF56534_}
|
||||
x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260D31F01ED44D0D31FD166BAF2A1F8000120D74A8E11D307D459821804A817C80073FB0201FB00DED1A4C8CB1FC9ED54}
|
||||
x{00000003}
|
||||
last transaction lt = 2310000001 hash = 73F89C6F8910F598AD84504A777E5945C798AC8C847FF861C090109665EAC6BA
|
||||
------------------------------------
|
||||
|
||||
The first information line "got account state ... for ..." shows the account address and the masterchain block identifier with respect to which the account state has been dumped. Notice that even if the account state changes in a subsequent block, the `getaccount xxx` command will return the same result until the reference block is updated to a newer value by a `last` command. In this way one can study the state of all accounts and obtain consistent results.
|
||||
@ -99,7 +100,7 @@ The "account state is (account ... " line begins the pretty-printed deserialized
|
||||
|
||||
Finally, the last several lines beginning with x{CFF538... (the "raw dump") contain the same information displayed as a tree of cells. In this case, we have one root cell containing the data bits CFF...134_ (the underscore means that the last binary one and all subsequent binary zeroes are to be removed, so hexadecimal "4_" corresponds to binary "0"), and two cells that are its children (displayed with one-space indentation).
|
||||
|
||||
We can see that x{FF0020DDA4F260...} is the code of this smart contract. If we consult the Appendix A of the TON Virtual Machine documentation, we can even disassemble this code: FF00 is SETCP 0, 20 is DUP, DD is IFNOTRET, A4 is INC, F260 is THROWIF 32, and so on. (Incidentally, you can find the source code of this smartcontract in the source file crypto/block/mc0.fif .)
|
||||
We can see that x{FF0020DD20...} is the code of this smart contract. If we consult the Appendix A of the TON Virtual Machine documentation, we can even disassemble this code: FF00 is SETCP 0, 20 is DUP, DD is IFNOTRET, 20 is DUP, and so on. (Incidentally, you can find the source code of this smartcontract in the source file crypto/block/new-testgiver.fif .)
|
||||
|
||||
We can also see that x{00009A15} (the actual value you see may be different) is the persistent data of this smart contract. It is actually an unsigned 32-bit integer, used by the smart contract as the counter of operations performed so far. Notice that this value is big-endian (i.e., 3 is encoded as x{00000003}, not as x{03000000}), as are all integers inside the TON Blockchain. In this case the counter is equal to 0x9A15 = 39445.
|
||||
|
||||
@ -246,12 +247,26 @@ In the Test Network, you have another option: you can ask the "test giver" to gi
|
||||
5. Using the test giver smart contract
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You need to know the address of the test giver smart contract. We'll assume that it is -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d, or, equivalently, Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb, as indicated in one of the previous examples. You inspect the state of this smart contract in the Lite Client by typing
|
||||
You need to know the address of the test giver smart contract. We'll assume that it is -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260, or, equivalently, kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny, as indicated in one of the previous examples. You inspect the state of this smart contract in the Lite Client by typing
|
||||
|
||||
> last
|
||||
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||||
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
|
||||
|
||||
as explained above in Section 2. The only number you need from the output is the 32-bit sequence number stored in the smart contract data (it is zero in the example above, but generally it will be non-zero).
|
||||
as explained above in Section 2. The only number you need from the output is the 32-bit sequence number stored in the smart contract data (it is 0x9A15 in the example above, but generally it will be different). A simpler way of obtaining the current value of this sequence number is by typing
|
||||
|
||||
> last
|
||||
> runmethod kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny seqno
|
||||
|
||||
producing the correct value 39445 = 0x9A15:
|
||||
|
||||
--------------------------------------------
|
||||
got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2240):18E6DA7707191E76C71EABBC5277650666B7E2CFA2AEF2CE607EAFE8657A3820:4EFA2540C5D1E4A1BA2B529EE0B65415DF46BFFBD27A8EB74C4C0E17770D03B1
|
||||
creating VM
|
||||
starting VM to run method `seqno` (85143) of smart contract -1:FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260
|
||||
...
|
||||
arguments: [ 85143 ]
|
||||
result: [ 39445 ]
|
||||
--------------------------------------------
|
||||
|
||||
Next, you create an external message to the test giver asking it to send another message to your (uninitialized) smart contract carrying a specified amount of test Grams. There is a special Fift script for generating this external message located at crypto/smartcont/testgiver.fif:
|
||||
|
||||
@ -267,7 +282,7 @@ Next, you create an external message to the test giver asking it to send another
|
||||
$# 3 - -2 and ' usage if
|
||||
|
||||
// "testgiver.addr" load-address
|
||||
Masterchain 0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||||
Masterchain 0xfcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
|
||||
2constant giver_addr
|
||||
."Test giver address = " giver_addr 2dup .addr cr 6 .Addr cr
|
||||
|
||||
@ -314,8 +329,8 @@ This Fift code creates an internal message from the test giver smart contract to
|
||||
The external message is serialized and saved into the file `wallet-query.boc`. Some output is generated in the process:
|
||||
|
||||
---------------------------------------------
|
||||
Test giver address = -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||||
kf-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODZKR
|
||||
Test giver address = -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
|
||||
kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
|
||||
Requesting GR$6.666 to account 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb = 0:2ee9b4fd4f077c9b223280c35763df9edab0b41ac20d36f4009677df95c3afe2 seqno=0x9a15 bounce=0
|
||||
enveloping message: x{00009A1501}
|
||||
x{42001774DA7EA783BE4D91194061ABB1EFCF6D585A0D61069B7A004B3BEFCAE1D7F1280C6A98B4000000000000000000000000000047494654}
|
||||
@ -340,19 +355,19 @@ We will see some output:
|
||||
which means that the external message has been delivered to the collator pool. Afterwards one of the collators might choose to include this external message in a block, creating a transaction for the test giver smart contract to process this external message. We can check whether the state of the test giver has changed:
|
||||
|
||||
> last
|
||||
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||||
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
|
||||
|
||||
(If you forget to type `last`, you are likely to see the unchanged state of the test giver smart contract.) The resulting output would be:
|
||||
|
||||
---------------------------------------------
|
||||
got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,10441):2DCFB7F734913261B85B8866DFF7CBEF07205EAA0769F4EE15E242AB520A2CC5:4F96F417BCD74D5DAE0CC3CCC285E513B85652002D9AD8CC884781D8465E3591 and (-1,8000000000000000,10441):2DCFB7F734913261B85B8866DFF7CBEF07205EAA0769F4EE15E242AB520A2CC5:4F96F417BCD74D5DAE0CC3CCC285E513B85652002D9AD8CC884781D8465E3591
|
||||
got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2240):18E6DA7707191E76C71EABBC5277650666B7E2CFA2AEF2CE607EAFE8657A3820:4EFA2540C5D1E4A1BA2B529EE0B65415DF46BFFBD27A8EB74C4C0E17770D03B1
|
||||
account state is (account
|
||||
addr:(addr_std
|
||||
anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
|
||||
anycast:nothing workchain_id:-1 address:xFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260)
|
||||
storage_stat:(storage_info
|
||||
used:(storage_used
|
||||
cells:(var_uint len:1 value:3)
|
||||
bits:(var_uint len:2 value:539)
|
||||
bits:(var_uint len:2 value:707)
|
||||
public_cells:(var_uint len:0 value:0)) last_paid:0
|
||||
due_payment:nothing)
|
||||
storage:(account_storage last_trans_lt:10697000003
|
||||
|
@ -326,10 +326,10 @@ if (WINGETOPT_FOUND)
|
||||
target_link_libraries_system(create-state wingetopt)
|
||||
endif()
|
||||
|
||||
add_executable(test-block block/test-block.cpp)
|
||||
target_include_directories(test-block PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
add_executable(dump-block block/dump-block.cpp)
|
||||
target_include_directories(dump-block PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
|
||||
target_link_libraries(test-block PUBLIC ton_crypto fift-lib ton_block)
|
||||
target_link_libraries(dump-block PUBLIC ton_crypto fift-lib ton_block)
|
||||
if (WINGETOPT_FOUND)
|
||||
target_link_libraries_system(test-block wingetopt)
|
||||
target_link_libraries_system(dump-block wingetopt)
|
||||
endif()
|
||||
|
@ -191,16 +191,21 @@ void test2(vm::CellSlice& cs) {
|
||||
}
|
||||
|
||||
void usage() {
|
||||
std::cout << "usage: test-block [<boc-file>]\n\tor test-block -h\n";
|
||||
std::cout << "usage: test-block [-S][<boc-file>]\n\tor test-block -h\n\tDumps specified blockchain block or state "
|
||||
"from <boc-file>, or runs some tests\n\t-S\tDump a blockchain state\n";
|
||||
std::exit(2);
|
||||
}
|
||||
|
||||
int main(int argc, char* const argv[]) {
|
||||
int i;
|
||||
int new_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
bool dump_state = false;
|
||||
auto zerostate = std::make_unique<block::ZerostateInfo>();
|
||||
while ((i = getopt(argc, argv, "hv:")) != -1) {
|
||||
while ((i = getopt(argc, argv, "Shv:")) != -1) {
|
||||
switch (i) {
|
||||
case 'S':
|
||||
dump_state = true;
|
||||
break;
|
||||
case 'v':
|
||||
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
|
||||
break;
|
||||
@ -225,13 +230,12 @@ int main(int argc, char* const argv[]) {
|
||||
vm::CellSlice cs{vm::NoVm(), boc};
|
||||
cs.print_rec(std::cout);
|
||||
std::cout << std::endl;
|
||||
block::gen::t_Block.print_ref(std::cout, boc);
|
||||
auto& type = dump_state ? (const tlb::TLB&)block::gen::t_ShardStateUnsplit : block::gen::t_Block;
|
||||
std::string type_name = dump_state ? "ShardState" : "Block";
|
||||
type.print_ref(std::cout, boc);
|
||||
std::cout << std::endl;
|
||||
if (!block::gen::t_Block.validate_ref(boc)) {
|
||||
std::cout << "(invalid Block)" << std::endl;
|
||||
} else {
|
||||
std::cout << "(valid Block)" << std::endl;
|
||||
}
|
||||
bool ok = type.validate_ref(boc);
|
||||
std::cout << "(" << (ok ? "" : "in") << "valid " << type_name << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
@ -9,6 +9,7 @@
|
||||
// { bl word (forget) } : forget
|
||||
{ bl word 1 ' (forget) } :: [forget]
|
||||
{ char " word 1 ' type } ::_ ."
|
||||
{ char } word x>B 1 'nop } ::_ B{
|
||||
{ swap ({) over 2+ -roll swap (compile) (}) } : does
|
||||
{ 1 'nop does create } : constant
|
||||
{ 2 'nop does create } : 2constant
|
||||
|
@ -1580,39 +1580,6 @@ void interpret_pfx_dict_get(vm::Stack& stack) {
|
||||
}
|
||||
}
|
||||
|
||||
void interpret_bytes_hex_literal(IntCtx& ctx) {
|
||||
auto s = ctx.scan_word_to('}');
|
||||
std::string t;
|
||||
t.reserve(s.size() >> 1);
|
||||
int v = 1;
|
||||
for (char c : s) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
continue;
|
||||
}
|
||||
v <<= 4;
|
||||
if (c >= '0' && c <= '9') {
|
||||
v += c - '0';
|
||||
} else {
|
||||
c |= 0x20;
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
v += c - ('a' - 10);
|
||||
} else {
|
||||
v = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (v & 0x100) {
|
||||
t.push_back((char)v);
|
||||
v = 1;
|
||||
}
|
||||
}
|
||||
if (v != 1) {
|
||||
throw IntError{"Invalid bytes hexstring constant"};
|
||||
}
|
||||
ctx.stack.push_bytes(std::move(t));
|
||||
push_argcount(ctx.stack, 1);
|
||||
}
|
||||
|
||||
void interpret_bitstring_hex_literal(IntCtx& ctx) {
|
||||
auto s = ctx.scan_word_to('}');
|
||||
unsigned char buff[128];
|
||||
@ -2601,7 +2568,6 @@ void init_words_common(Dictionary& d) {
|
||||
d.def_ctx_word("dictmerge ", interpret_dict_merge);
|
||||
d.def_ctx_word("dictdiff ", interpret_dict_diff);
|
||||
// slice/bitstring constants
|
||||
d.def_active_word("B{", interpret_bytes_hex_literal);
|
||||
d.def_active_word("x{", interpret_bitstring_hex_literal);
|
||||
d.def_active_word("b{", interpret_bitstring_binary_literal);
|
||||
// boxes/holes/variables
|
||||
|
@ -209,7 +209,7 @@ Bignum& Bignum::import_lsb(const unsigned char* buffer, std::size_t size) {
|
||||
size--;
|
||||
}
|
||||
if (!size) {
|
||||
bn_assert(BN_set_word(val, 0));
|
||||
bn_assert(BN_zero(val));
|
||||
return *this;
|
||||
}
|
||||
unsigned char tmp_buff[1024];
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include "td/utils/bits.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
namespace arith {
|
||||
struct dec_string {
|
||||
@ -36,6 +37,12 @@ struct hex_string {
|
||||
explicit hex_string(const std::string& s) : str(s) {
|
||||
}
|
||||
};
|
||||
|
||||
struct bin_string {
|
||||
std::string str;
|
||||
explicit bin_string(const std::string& s) : str(s) {
|
||||
}
|
||||
};
|
||||
} // namespace arith
|
||||
|
||||
namespace arith {
|
||||
@ -70,6 +77,10 @@ class Bignum {
|
||||
~Bignum() {
|
||||
BN_free(val);
|
||||
}
|
||||
Bignum(const bin_string& bs) {
|
||||
val = BN_new();
|
||||
set_dec_str(bs.str);
|
||||
}
|
||||
Bignum(const dec_string& ds) {
|
||||
val = BN_new();
|
||||
set_dec_str(ds.str);
|
||||
@ -150,6 +161,11 @@ class Bignum {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bignum& set_raw_bytes(std::string s) {
|
||||
CHECK(BN_bin2bn(reinterpret_cast<const td::uint8*>(s.c_str()), td::narrow_cast<td::uint32>(s.size()), val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bignum& set_hex_str(std::string s) {
|
||||
bn_assert(BN_hex2bn(&val, s.c_str()));
|
||||
return *this;
|
||||
|
@ -66,9 +66,10 @@ Masterchain over
|
||||
|
||||
// SmartContract #2 (Simple money giver for test network)
|
||||
<{ SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
32 LDU SWAP // cs cnt
|
||||
c4 PUSHCTR CTOS 32 LDU ENDS // cs cnt cnt'
|
||||
TUCK EQUAL 33 THROWIFNOT // ( seqno mismatch? )
|
||||
|
50
submodules/ton/tonlib-src/crypto/smartcont/new-testgiver.fif
Normal file
50
submodules/ton/tonlib-src/crypto/smartcont/new-testgiver.fif
Normal file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env fift -s
|
||||
"TonUtil.fif" include
|
||||
"Asm.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <workchain-id> [<filename-base>]" cr
|
||||
."Creates a new money giver in specified workchain, with address saved to <filename-base>.addr" cr
|
||||
."('new-testgiver.addr' by default)" cr 1 halt
|
||||
} : usage
|
||||
$# 1- -2 and ' usage if
|
||||
|
||||
$1 parse-workchain-id =: wc // set workchain id from command line argument
|
||||
def? $2 { @' $2 } { "new-testgiver" } cond constant file-base
|
||||
|
||||
."Creating new money giver in workchain " wc . cr
|
||||
0 constant init_seqno
|
||||
|
||||
// Create new simple money giver
|
||||
<{ SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
32 LDU SWAP // cs cnt
|
||||
c4 PUSHCTR CTOS 32 LDU ENDS // cs cnt cnt'
|
||||
TUCK EQUAL 33 THROWIFNOT // ( seqno mismatch? )
|
||||
ACCEPT // cs cnt'
|
||||
SWAP DUP SREFS // cnt' cs msg?
|
||||
IF:<{
|
||||
8 LDU LDREF -ROT // cnt'' cs mode msg
|
||||
GR$20 INT 3 INT RAWRESERVE // reserve all but 20 Grams from the balance
|
||||
SWAP SENDRAWMSG
|
||||
}>
|
||||
ENDS INC NEWC 32 STU ENDC c4 POPCTR // store cnt''
|
||||
}>c
|
||||
// code
|
||||
<b init_seqno 32 u, b> // data
|
||||
null // no libraries
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b> // create StateInit
|
||||
dup ."StateInit: " <s csr. cr
|
||||
dup hash wc swap 2dup 2constant wallet_addr
|
||||
."new money giver address = " 2dup .addr cr
|
||||
2dup file-base +".addr" save-address-verbose
|
||||
."Non-bounceable address (for init): " 2dup 7 .Addr cr
|
||||
."Bounceable address (for later access): " 6 .Addr cr
|
||||
// ???
|
||||
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, init_seqno 32 u, b>
|
||||
dup ."External message for initialization is " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
file-base +"-query.boc" tuck B>file
|
||||
."(Saved money giver creating query to file " type .")" cr
|
@ -9,7 +9,7 @@
|
||||
$# 3 - -2 and ' usage if
|
||||
|
||||
// "testgiver.addr" load-address
|
||||
Masterchain 0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||||
Masterchain 0xfcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
|
||||
2constant giver_addr
|
||||
."Test giver address = " giver_addr 2dup .addr cr 6 .Addr cr
|
||||
|
||||
|
@ -572,7 +572,7 @@ TEST(bits256_scan, main) {
|
||||
}
|
||||
|
||||
bool check_exp(std::ostream& stream, const td::NegExpBinTable& tab, double x) {
|
||||
long long xx = lround(x * (1LL << 52));
|
||||
long long xx = llround(x * (1LL << 52));
|
||||
td::BigInt256 yy;
|
||||
if (!tab.nexpf(yy, -xx, 52)) {
|
||||
stream << "cannot compute exp(" << x << ") = exp(" << xx << " * 2^(-52))" << std::endl;
|
||||
|
@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
|
||||
option(TDUTILS_MIME_TYPE "Generate mime types conversion (gperf is required)" ON)
|
||||
|
||||
if (WIN32)
|
||||
if (WINGETOPT_FOUND)
|
||||
set(TD_HAVE_GETOPT 1)
|
||||
@ -96,7 +98,6 @@ set(TDUTILS_SOURCE
|
||||
td/utils/JsonBuilder.cpp
|
||||
td/utils/logging.cpp
|
||||
td/utils/misc.cpp
|
||||
td/utils/MimeType.cpp
|
||||
td/utils/MpmcQueue.cpp
|
||||
td/utils/OptionsParser.cpp
|
||||
td/utils/Random.cpp
|
||||
@ -202,7 +203,6 @@ set(TDUTILS_SOURCE
|
||||
td/utils/List.h
|
||||
td/utils/logging.h
|
||||
td/utils/MemoryLog.h
|
||||
td/utils/MimeType.h
|
||||
td/utils/misc.h
|
||||
td/utils/MovableValue.h
|
||||
td/utils/MpmcQueue.h
|
||||
@ -251,6 +251,14 @@ set(TDUTILS_SOURCE
|
||||
td/utils/VectorQueue.h
|
||||
)
|
||||
|
||||
if (TDUTILS_MIME_TYPE)
|
||||
set(TDUTILS_SOURCE
|
||||
${TDUTILS_SOURCE}
|
||||
td/utils/MimeType.cpp
|
||||
td/utils/MimeType.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(TDUTILS_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/buffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/ConcurrentHashMap.cpp
|
||||
@ -284,7 +292,7 @@ if (WIN32)
|
||||
# target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY})
|
||||
target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Normaliz)
|
||||
endif()
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
if (NOT CMAKE_CROSSCOMPILING AND TDUTILS_MIME_TYPE)
|
||||
add_dependencies(tdutils tdmime_auto)
|
||||
endif()
|
||||
|
||||
|
@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
# Generates files for MIME type <-> extension conversions
|
||||
# DEPENDS ON: gperf grep bash/powershell
|
||||
|
||||
if (NOT TDUTILS_MIME_TYPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY auto)
|
||||
|
||||
set(TDMIME_SOURCE
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -168,8 +168,9 @@ class TlParser {
|
||||
data += sizeof(int32);
|
||||
} else {
|
||||
check_len(sizeof(int32));
|
||||
result_len = data[1] + (data[2] << 8) + (data[3] << 16) + (data[4] << 24) + (static_cast<uint64>(data[5]) << 32) +
|
||||
(static_cast<uint64>(data[6]) << 40) + (static_cast<uint64>(data[7]) << 48);
|
||||
result_len = narrow_cast<size_t>(data[1] + (data[2] << 8) + (data[3] << 16) + (data[4] << 24) +
|
||||
(static_cast<uint64>(data[5]) << 32) + (static_cast<uint64>(data[6]) << 40) +
|
||||
(static_cast<uint64>(data[7]) << 48));
|
||||
if (result_len > std::numeric_limits<size_t>::max() - 3) {
|
||||
set_error("Too big string found");
|
||||
return T();
|
||||
|
@ -634,6 +634,8 @@ object_ptr<Function> Function::fetch(td::TlParser &p) {
|
||||
return tonNode_downloadBlockProofLink::fetch(p);
|
||||
case tonNode_downloadPersistentState::ID:
|
||||
return tonNode_downloadPersistentState::fetch(p);
|
||||
case tonNode_downloadPersistentStateSlice::ID:
|
||||
return tonNode_downloadPersistentStateSlice::fetch(p);
|
||||
case tonNode_downloadZeroState::ID:
|
||||
return tonNode_downloadZeroState::fetch(p);
|
||||
case tonNode_getNextBlockDescription::ID:
|
||||
@ -14965,6 +14967,70 @@ tonNode_downloadPersistentState::ReturnType tonNode_downloadPersistentState::fet
|
||||
#undef FAIL
|
||||
}
|
||||
|
||||
tonNode_downloadPersistentStateSlice::tonNode_downloadPersistentStateSlice()
|
||||
: block_()
|
||||
, masterchain_block_()
|
||||
, offset_()
|
||||
, max_size_()
|
||||
{}
|
||||
|
||||
tonNode_downloadPersistentStateSlice::tonNode_downloadPersistentStateSlice(object_ptr<tonNode_blockIdExt> &&block_, object_ptr<tonNode_blockIdExt> &&masterchain_block_, std::int64_t offset_, std::int64_t max_size_)
|
||||
: block_(std::move(block_))
|
||||
, masterchain_block_(std::move(masterchain_block_))
|
||||
, offset_(offset_)
|
||||
, max_size_(max_size_)
|
||||
{}
|
||||
|
||||
const std::int32_t tonNode_downloadPersistentStateSlice::ID;
|
||||
|
||||
object_ptr<tonNode_downloadPersistentStateSlice> tonNode_downloadPersistentStateSlice::fetch(td::TlParser &p) {
|
||||
return make_object<tonNode_downloadPersistentStateSlice>(p);
|
||||
}
|
||||
|
||||
tonNode_downloadPersistentStateSlice::tonNode_downloadPersistentStateSlice(td::TlParser &p)
|
||||
#define FAIL(error) p.set_error(error)
|
||||
: block_(TlFetchObject<tonNode_blockIdExt>::parse(p))
|
||||
, masterchain_block_(TlFetchObject<tonNode_blockIdExt>::parse(p))
|
||||
, offset_(TlFetchLong::parse(p))
|
||||
, max_size_(TlFetchLong::parse(p))
|
||||
#undef FAIL
|
||||
{}
|
||||
|
||||
void tonNode_downloadPersistentStateSlice::store(td::TlStorerCalcLength &s) const {
|
||||
(void)sizeof(s);
|
||||
s.store_binary(-169220381);
|
||||
TlStoreObject::store(block_, s);
|
||||
TlStoreObject::store(masterchain_block_, s);
|
||||
TlStoreBinary::store(offset_, s);
|
||||
TlStoreBinary::store(max_size_, s);
|
||||
}
|
||||
|
||||
void tonNode_downloadPersistentStateSlice::store(td::TlStorerUnsafe &s) const {
|
||||
(void)sizeof(s);
|
||||
s.store_binary(-169220381);
|
||||
TlStoreObject::store(block_, s);
|
||||
TlStoreObject::store(masterchain_block_, s);
|
||||
TlStoreBinary::store(offset_, s);
|
||||
TlStoreBinary::store(max_size_, s);
|
||||
}
|
||||
|
||||
void tonNode_downloadPersistentStateSlice::store(td::TlStorerToString &s, const char *field_name) const {
|
||||
if (!LOG_IS_STRIPPED(ERROR)) {
|
||||
s.store_class_begin(field_name, "tonNode_downloadPersistentStateSlice");
|
||||
if (block_ == nullptr) { s.store_field("block", "null"); } else { block_->store(s, "block"); }
|
||||
if (masterchain_block_ == nullptr) { s.store_field("masterchain_block", "null"); } else { masterchain_block_->store(s, "masterchain_block"); }
|
||||
s.store_field("offset", offset_);
|
||||
s.store_field("max_size", max_size_);
|
||||
s.store_class_end();
|
||||
}
|
||||
}
|
||||
|
||||
tonNode_downloadPersistentStateSlice::ReturnType tonNode_downloadPersistentStateSlice::fetch_result(td::TlParser &p) {
|
||||
#define FAIL(error) p.set_error(error); return ReturnType()
|
||||
return TlFetchBoxed<TlFetchObject<tonNode_data>, 1443505284>::parse(p);
|
||||
#undef FAIL
|
||||
}
|
||||
|
||||
tonNode_downloadZeroState::tonNode_downloadZeroState()
|
||||
: block_()
|
||||
{}
|
||||
|
@ -8229,6 +8229,37 @@ class tonNode_downloadPersistentState final : public Function {
|
||||
static ReturnType fetch_result(td::TlParser &p);
|
||||
};
|
||||
|
||||
class tonNode_downloadPersistentStateSlice final : public Function {
|
||||
public:
|
||||
object_ptr<tonNode_blockIdExt> block_;
|
||||
object_ptr<tonNode_blockIdExt> masterchain_block_;
|
||||
std::int64_t offset_;
|
||||
std::int64_t max_size_;
|
||||
|
||||
tonNode_downloadPersistentStateSlice();
|
||||
|
||||
tonNode_downloadPersistentStateSlice(object_ptr<tonNode_blockIdExt> &&block_, object_ptr<tonNode_blockIdExt> &&masterchain_block_, std::int64_t offset_, std::int64_t max_size_);
|
||||
|
||||
static const std::int32_t ID = -169220381;
|
||||
std::int32_t get_id() const final {
|
||||
return ID;
|
||||
}
|
||||
|
||||
using ReturnType = object_ptr<tonNode_data>;
|
||||
|
||||
static object_ptr<tonNode_downloadPersistentStateSlice> fetch(td::TlParser &p);
|
||||
|
||||
explicit tonNode_downloadPersistentStateSlice(td::TlParser &p);
|
||||
|
||||
void store(td::TlStorerCalcLength &s) const final;
|
||||
|
||||
void store(td::TlStorerUnsafe &s) const final;
|
||||
|
||||
void store(td::TlStorerToString &s, const char *field_name) const final;
|
||||
|
||||
static ReturnType fetch_result(td::TlParser &p);
|
||||
};
|
||||
|
||||
class tonNode_downloadZeroState final : public Function {
|
||||
public:
|
||||
object_ptr<tonNode_blockIdExt> block_;
|
||||
|
@ -928,6 +928,9 @@ bool downcast_call(Function &obj, const T &func) {
|
||||
case tonNode_downloadPersistentState::ID:
|
||||
func(static_cast<tonNode_downloadPersistentState &>(obj));
|
||||
return true;
|
||||
case tonNode_downloadPersistentStateSlice::ID:
|
||||
func(static_cast<tonNode_downloadPersistentStateSlice &>(obj));
|
||||
return true;
|
||||
case tonNode_downloadZeroState::ID:
|
||||
func(static_cast<tonNode_downloadZeroState &>(obj));
|
||||
return true;
|
||||
|
@ -784,6 +784,7 @@ Result<int32> tl_constructor_from_string(ton_api::Function *object, const std::s
|
||||
{"tonNode.downloadBlockProof", 1272334218},
|
||||
{"tonNode.downloadBlockProofLink", 632488134},
|
||||
{"tonNode.downloadPersistentState", 2140791736},
|
||||
{"tonNode.downloadPersistentStateSlice", -169220381},
|
||||
{"tonNode.downloadZeroState", -1379131814},
|
||||
{"tonNode.getNextBlockDescription", 341160179},
|
||||
{"tonNode.getNextKeyBlockIds", -219689029},
|
||||
@ -5396,6 +5397,33 @@ Status from_json(ton_api::tonNode_downloadPersistentState &to, JsonObject &from)
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
Status from_json(ton_api::tonNode_downloadPersistentStateSlice &to, JsonObject &from) {
|
||||
{
|
||||
TRY_RESULT(value, get_json_object_field(from, "block", JsonValue::Type::Null, true));
|
||||
if (value.type() != JsonValue::Type::Null) {
|
||||
TRY_STATUS(from_json(to.block_, value));
|
||||
}
|
||||
}
|
||||
{
|
||||
TRY_RESULT(value, get_json_object_field(from, "masterchain_block", JsonValue::Type::Null, true));
|
||||
if (value.type() != JsonValue::Type::Null) {
|
||||
TRY_STATUS(from_json(to.masterchain_block_, value));
|
||||
}
|
||||
}
|
||||
{
|
||||
TRY_RESULT(value, get_json_object_field(from, "offset", JsonValue::Type::Null, true));
|
||||
if (value.type() != JsonValue::Type::Null) {
|
||||
TRY_STATUS(from_json(to.offset_, value));
|
||||
}
|
||||
}
|
||||
{
|
||||
TRY_RESULT(value, get_json_object_field(from, "max_size", JsonValue::Type::Null, true));
|
||||
if (value.type() != JsonValue::Type::Null) {
|
||||
TRY_STATUS(from_json(to.max_size_, value));
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
Status from_json(ton_api::tonNode_downloadZeroState &to, JsonObject &from) {
|
||||
{
|
||||
TRY_RESULT(value, get_json_object_field(from, "block", JsonValue::Type::Null, true));
|
||||
@ -7651,6 +7679,18 @@ void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadPersistentState
|
||||
jo << ctie("masterchain_block", ToJson(object.masterchain_block_));
|
||||
}
|
||||
}
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadPersistentStateSlice &object) {
|
||||
auto jo = jv.enter_object();
|
||||
jo << ctie("@type", "tonNode.downloadPersistentStateSlice");
|
||||
if (object.block_) {
|
||||
jo << ctie("block", ToJson(object.block_));
|
||||
}
|
||||
if (object.masterchain_block_) {
|
||||
jo << ctie("masterchain_block", ToJson(object.masterchain_block_));
|
||||
}
|
||||
jo << ctie("offset", ToJson(JsonInt64{object.offset_}));
|
||||
jo << ctie("max_size", ToJson(JsonInt64{object.max_size_}));
|
||||
}
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadZeroState &object) {
|
||||
auto jo = jv.enter_object();
|
||||
jo << ctie("@type", "tonNode.downloadZeroState");
|
||||
|
@ -349,6 +349,7 @@ Status from_json(ton_api::tonNode_downloadBlock &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_downloadBlockProof &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_downloadBlockProofLink &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_downloadPersistentState &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_downloadPersistentStateSlice &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_downloadZeroState &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_getNextBlockDescription &to, JsonObject &from);
|
||||
Status from_json(ton_api::tonNode_getNextKeyBlockIds &to, JsonObject &from);
|
||||
@ -694,6 +695,7 @@ void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadBlock &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadBlockProof &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadBlockProofLink &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadPersistentState &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadPersistentStateSlice &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_downloadZeroState &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_getNextBlockDescription &object);
|
||||
void to_json(JsonValueScope &jv, const ton_api::tonNode_getNextKeyBlockIds &object);
|
||||
|
@ -362,6 +362,7 @@ tonNode.prepareZeroState block:tonNode.blockIdExt = tonNode.PreparedState;
|
||||
tonNode.getNextKeyBlockIds block:tonNode.blockIdExt max_size:int = tonNode.KeyBlocks;
|
||||
tonNode.downloadBlock block:tonNode.blockIdExt = tonNode.Data;
|
||||
tonNode.downloadPersistentState block:tonNode.blockIdExt masterchain_block:tonNode.blockIdExt = tonNode.Data;
|
||||
tonNode.downloadPersistentStateSlice block:tonNode.blockIdExt masterchain_block:tonNode.blockIdExt offset:long max_size:long = tonNode.Data;
|
||||
tonNode.downloadZeroState block:tonNode.blockIdExt = tonNode.Data;
|
||||
tonNode.downloadBlockProof block:tonNode.blockIdExt = tonNode.Data;
|
||||
tonNode.downloadBlockProofLink block:tonNode.blockIdExt = tonNode.Data;
|
||||
|
Binary file not shown.
@ -366,6 +366,16 @@ struct Ed25519_PublicKey {
|
||||
bool operator==(const Ed25519_PublicKey& other) const {
|
||||
return _pubkey == other._pubkey;
|
||||
}
|
||||
bool clear() {
|
||||
_pubkey.set_zero();
|
||||
return true;
|
||||
}
|
||||
bool is_zero() const {
|
||||
return _pubkey.is_zero();
|
||||
}
|
||||
bool non_zero() const {
|
||||
return !is_zero();
|
||||
}
|
||||
};
|
||||
|
||||
// represents (the contents of) a block
|
||||
|
Binary file not shown.
@ -119,7 +119,10 @@ install(TARGETS tonlibjson TonlibJson EXPORT Tonlib
|
||||
)
|
||||
|
||||
if (NOT TON_USE_ABSEIL)
|
||||
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block
|
||||
if (WIN32)
|
||||
set(WINGETOPT_TARGET wingetopt)
|
||||
endif()
|
||||
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block ${WINGETOPT_TARGET}
|
||||
tdutils tl_tonlib_api tonlib lite-client-common Tonlib EXPORT Tonlib
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
|
@ -66,8 +66,7 @@ class ExtClient {
|
||||
auto f = r_error.move_as_ok();
|
||||
return td::Status::Error(f->code_, f->message_);
|
||||
}
|
||||
auto res = ton::fetch_result<QueryT>(std::move(data));
|
||||
return std::move(res);
|
||||
return ton::fetch_result<QueryT>(std::move(data));
|
||||
}());
|
||||
});
|
||||
}
|
||||
|
@ -23,15 +23,13 @@
|
||||
|
||||
namespace tonlib {
|
||||
const block::StdAddress& TestGiver::address() {
|
||||
//static block::StdAddress res =
|
||||
//block::StdAddress::parse("-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d").move_as_ok();
|
||||
static block::StdAddress res =
|
||||
block::StdAddress::parse("kf9cW6egIIUMvWKfGOWfopbU4zLTUzrsNAwm87SUXnE5UcEG").move_as_ok();
|
||||
block::StdAddress::parse("kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
|
||||
vm::CellHash TestGiver::get_init_code_hash() {
|
||||
return vm::CellHash::from_slice(td::base64_decode("s7RouN9wfJ4Avx8h0uw6X3ZEJfN3MYOUmzrC8JXfMAw=").move_as_ok());
|
||||
return vm::CellHash::from_slice(td::base64_decode("wDkZp0yR4xo+9+BnuAPfGVjBzK6FPzqdv2DwRq3z3KE=").move_as_ok());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms,
|
||||
|
@ -48,7 +48,7 @@ struct RawDecryptedKey {
|
||||
|
||||
struct EncryptedKey;
|
||||
struct DecryptedKey {
|
||||
DecryptedKey() = default;
|
||||
DecryptedKey() = delete;
|
||||
explicit DecryptedKey(const Mnemonic &mnemonic);
|
||||
DecryptedKey(std::vector<td::SecureString> mnemonic_words, td::Ed25519::PrivateKey key);
|
||||
DecryptedKey(RawDecryptedKey key);
|
||||
|
Loading…
x
Reference in New Issue
Block a user