mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-08 17:53:38 +00:00
no message
This commit is contained in:
parent
516db55483
commit
ea35454a30
@ -4803,7 +4803,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Wallet.Send.Title" = "Send Grams";
|
"Wallet.Send.Title" = "Send Grams";
|
||||||
"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS";
|
"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS";
|
||||||
"Wallet.Send.AddressText" = "Enter wallet address...";
|
"Wallet.Send.AddressText" = "Enter wallet address...";
|
||||||
"Wallet.Send.AddressInfo" = "Copy the 48-letter address of the recipient here or ask them to send you a ton:// link.";
|
"Wallet.Send.AddressInfo" = "Paste the 48-letter address of the recipient here or ask them to send you a ton:// link.";
|
||||||
"Wallet.Send.Balance" = "Balance: %@";
|
"Wallet.Send.Balance" = "Balance: %@";
|
||||||
"Wallet.Send.AmountText" = "Grams to send";
|
"Wallet.Send.AmountText" = "Grams to send";
|
||||||
"Wallet.Send.Confirmation" = "Confirmation";
|
"Wallet.Send.Confirmation" = "Confirmation";
|
||||||
@ -4812,23 +4812,26 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Wallet.Send.Send" = "Send";
|
"Wallet.Send.Send" = "Send";
|
||||||
"Wallet.Settings.Title" = "Wallet Settings";
|
"Wallet.Settings.Title" = "Wallet Settings";
|
||||||
"Wallet.Settings.DeleteWallet" = "Delete Wallet";
|
"Wallet.Settings.DeleteWallet" = "Delete Wallet";
|
||||||
|
"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nWallets are located in the TON Blockchain, which is not controlled by Telegram. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty.";
|
||||||
"Wallet.Intro.NotNow" = "Not Now";
|
"Wallet.Intro.NotNow" = "Not Now";
|
||||||
"Wallet.Intro.ImportExisting" = "Import existing wallet";
|
"Wallet.Intro.ImportExisting" = "Import existing wallet";
|
||||||
"Wallet.Intro.CreateErrorTitle" = "An Error Occurred";
|
"Wallet.Intro.CreateErrorTitle" = "An Error Occurred";
|
||||||
"Wallet.Intro.CreateErrorText" = "Sorry. Please try again.";
|
"Wallet.Intro.CreateErrorText" = "Sorry. Please try again.";
|
||||||
"Wallet.Intro.Title" = "Gram Wallet";
|
"Wallet.Intro.Title" = "Gram Wallet";
|
||||||
"Wallet.Intro.Text" = "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries.";
|
"Wallet.Intro.Text" = "The gram wallet allows you to make fast and secure blockchain-based payments without intermediaries.";
|
||||||
"Wallet.Intro.CreateWallet" = "Create My Wallet";
|
"Wallet.Intro.CreateWallet" = "Create My Wallet";
|
||||||
"Wallet.Intro.Terms" = "By creating the wallet you accept\n[Terms of Conditions]().";
|
"Wallet.Intro.Terms" = "By creating a wallet you accept the\n[Terms of Conditions]().";
|
||||||
"Wallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet";
|
"Wallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet";
|
||||||
"Wallet.Created.Title" = "Congratulations";
|
"Wallet.Created.Title" = "Congratulations";
|
||||||
"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode.";
|
"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode.";
|
||||||
"Wallet.Created.Proceed" = "Proceed";
|
"Wallet.Created.Proceed" = "Proceed";
|
||||||
|
"Wallet.Created.ExportErrorTitle" = "Error";
|
||||||
|
"Wallet.Created.ExportErrorText" = "Encryption error. Please make sure you have enabled a device passcode in iOS settings and try again.";
|
||||||
"Wallet.Completed.Title" = "Ready to go!";
|
"Wallet.Completed.Title" = "Ready to go!";
|
||||||
"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers.";
|
"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers.";
|
||||||
"Wallet.Completed.ViewWallet" = "View My Wallet";
|
"Wallet.Completed.ViewWallet" = "View My Wallet";
|
||||||
"Wallet.RestoreFailed.Title" = "Too Bad";
|
"Wallet.RestoreFailed.Title" = "Too Bad";
|
||||||
"Wallet.RestoreFailed.Text" = "Without the secret words, you can't'nrestore access to the wallet.";
|
"Wallet.RestoreFailed.Text" = "Without the secret words, you can't\nrestore access to your wallet.";
|
||||||
"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet";
|
"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet";
|
||||||
"Wallet.RestoreFailed.EnterWords" = "Enter 24 words";
|
"Wallet.RestoreFailed.EnterWords" = "Enter 24 words";
|
||||||
"Wallet.Sending.Title" = "Sending Grams";
|
"Wallet.Sending.Title" = "Sending Grams";
|
||||||
@ -4837,13 +4840,13 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Wallet.Sent.Title" = "Done!";
|
"Wallet.Sent.Title" = "Done!";
|
||||||
"Wallet.Sent.Text" = "**%@ Grams** have been sent.";
|
"Wallet.Sent.Text" = "**%@ Grams** have been sent.";
|
||||||
"Wallet.Sent.ViewWallet" = "View My Wallet";
|
"Wallet.Sent.ViewWallet" = "View My Wallet";
|
||||||
"Wallet.SecureStorageNotAvailable.Title" = "Setup Passcode";
|
"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode";
|
||||||
"Wallet.SecureStorageNotAvailable.Text" = "Please set up Passcode on your device to enable secure payments with your Gram wallet.";
|
"Wallet.SecureStorageNotAvailable.Text" = "Please set up a Passcode on your device to enable secure payments with your Gram wallet.";
|
||||||
"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed";
|
"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed";
|
||||||
"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID";
|
"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID";
|
||||||
"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID";
|
"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID";
|
||||||
"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off.";
|
"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off. Please enable them before proceeding.";
|
||||||
"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off.";
|
"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off. Please enable it before proceeding.";
|
||||||
"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\".";
|
"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\".";
|
||||||
"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\".";
|
"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\".";
|
||||||
"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet";
|
"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet";
|
||||||
@ -4866,7 +4869,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Wallet.WordImport.Title" = "24 Secret Words";
|
"Wallet.WordImport.Title" = "24 Secret Words";
|
||||||
"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet.";
|
"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet.";
|
||||||
"Wallet.WordImport.Continue" = "Continue";
|
"Wallet.WordImport.Continue" = "Continue";
|
||||||
"Wallet.WordImport.CanNotRemember" = "I don't have those";
|
"Wallet.WordImport.CanNotRemember" = "I don't have them";
|
||||||
"Wallet.WordImport.IncorrectTitle" = "Incorrect words";
|
"Wallet.WordImport.IncorrectTitle" = "Incorrect words";
|
||||||
"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again.";
|
"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again.";
|
||||||
"Wallet.Words.Title" = "24 Secret Words";
|
"Wallet.Words.Title" = "24 Secret Words";
|
||||||
@ -4878,5 +4881,5 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Wallet.Words.NotDoneResponse" = "Apologies Accepted";
|
"Wallet.Words.NotDoneResponse" = "Apologies Accepted";
|
||||||
|
|
||||||
"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again.";
|
"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again.";
|
||||||
"Wallet.Send.ErrorDecryptionFailed" = "Encryption error. Please check device passcode settings and try again.";
|
"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again.";
|
||||||
"Wallet.Send.SendAnyway" = "Send Anyway";
|
"Wallet.Send.SendAnyway" = "Send Anyway";
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -146,6 +146,20 @@
|
|||||||
ReferencedContainer = "container:submodules/Postbox/Postbox.xcodeproj">
|
ReferencedContainer = "container:submodules/Postbox/Postbox.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "E66DC04E5E043F5F00000000"
|
||||||
|
BuildableName = "libCloudData.a"
|
||||||
|
BlueprintName = "CloudData"
|
||||||
|
ReferencedContainer = "container:submodules/CloudData/CloudData.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
if [ -z "BUILD_NUMBER" ]; then
|
if [ -z "BUILD_NUMBER" ]; then
|
||||||
echo "BUILD_NUMBER is not set"
|
echo "BUILD_NUMBER is not set"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@ -453,6 +453,7 @@ public protocol SharedAccountContext: class {
|
|||||||
|
|
||||||
private final class TonInstanceData {
|
private final class TonInstanceData {
|
||||||
var config: String?
|
var config: String?
|
||||||
|
var blockchainName: String?
|
||||||
var instance: TonInstance?
|
var instance: TonInstance?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,13 +471,13 @@ public final class StoredTonContext {
|
|||||||
self.keychain = keychain
|
self.keychain = keychain
|
||||||
}
|
}
|
||||||
|
|
||||||
public func context(config: String) -> TonContext {
|
public func context(config: String, blockchainName: String) -> TonContext {
|
||||||
return self.currentInstance.with { data -> TonContext in
|
return self.currentInstance.with { data -> TonContext in
|
||||||
if let instance = data.instance, data.config == config {
|
if let instance = data.instance, data.config == config, data.blockchainName == blockchainName {
|
||||||
return TonContext(instance: instance, keychain: self.keychain)
|
return TonContext(instance: instance, keychain: self.keychain)
|
||||||
} else {
|
} else {
|
||||||
data.config = config
|
data.config = config
|
||||||
let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: "testnet", network: self.network)
|
let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, network: self.network)
|
||||||
data.instance = instance
|
data.instance = instance
|
||||||
return TonContext(instance: instance, keychain: self.keychain)
|
return TonContext(instance: instance, keychain: self.keychain)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,221 +0,0 @@
|
|||||||
//
|
|
||||||
// ASEditableTextNode.h
|
|
||||||
// Texture
|
|
||||||
//
|
|
||||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
|
||||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
|
||||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@protocol ASEditableTextNodeDelegate;
|
|
||||||
@class ASTextKitComponents;
|
|
||||||
|
|
||||||
@interface ASEditableTextNodeTargetForAction: NSObject
|
|
||||||
|
|
||||||
@property (nonatomic, strong, readonly) id _Nullable target;
|
|
||||||
|
|
||||||
- (instancetype)initWithTarget:(id _Nullable)target;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Implements a node that supports text editing.
|
|
||||||
@discussion Does not support layer backing.
|
|
||||||
*/
|
|
||||||
@interface ASEditableTextNode : ASDisplayNode <UITextInputTraits>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Initializes an editable text node using default TextKit components.
|
|
||||||
*
|
|
||||||
* @return An initialized ASEditableTextNode.
|
|
||||||
*/
|
|
||||||
- (instancetype)init;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Initializes an editable text node using the provided TextKit components.
|
|
||||||
*
|
|
||||||
* @param textKitComponents The TextKit stack used to render text.
|
|
||||||
* @param placeholderTextKitComponents The TextKit stack used to render placeholder text.
|
|
||||||
*
|
|
||||||
* @return An initialized ASEditableTextNode.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents
|
|
||||||
placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents;
|
|
||||||
|
|
||||||
//! @abstract The text node's delegate, which must conform to the <ASEditableTextNodeDelegate> protocol.
|
|
||||||
@property (nullable, weak) id <ASEditableTextNodeDelegate> delegate;
|
|
||||||
|
|
||||||
#pragma mark - Configuration
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Enable scrolling on the textView
|
|
||||||
@default true
|
|
||||||
*/
|
|
||||||
@property (nonatomic) BOOL scrollEnabled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Access to underlying UITextView for more configuration options.
|
|
||||||
@warning This property should only be used on the main thread and should not be accessed before the editable text node's view is created.
|
|
||||||
*/
|
|
||||||
@property (nonatomic, readonly) UITextView *textView;
|
|
||||||
|
|
||||||
//! @abstract The attributes to apply to new text being entered by the user.
|
|
||||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *typingAttributes;
|
|
||||||
|
|
||||||
//! @abstract The range of text currently selected. If length is zero, the range is the cursor location.
|
|
||||||
@property NSRange selectedRange;
|
|
||||||
|
|
||||||
#pragma mark - Placeholder
|
|
||||||
/**
|
|
||||||
@abstract Indicates if the receiver is displaying the placeholder text.
|
|
||||||
@discussion To update the placeholder, see the <attributedPlaceholderText> property.
|
|
||||||
@result YES if the placeholder is currently displayed; NO otherwise.
|
|
||||||
*/
|
|
||||||
- (BOOL)isDisplayingPlaceholder AS_WARN_UNUSED_RESULT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract The styled placeholder text displayed by the text node while no text is entered
|
|
||||||
@discussion The placeholder is displayed when the user has not entered any text and the keyboard is not visible.
|
|
||||||
*/
|
|
||||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedPlaceholderText;
|
|
||||||
|
|
||||||
#pragma mark - Modifying User Text
|
|
||||||
/**
|
|
||||||
@abstract The styled text displayed by the receiver.
|
|
||||||
@discussion When the placeholder is displayed (as indicated by -isDisplayingPlaceholder), this value is nil. Otherwise, this value is the attributed text the user has entered. This value can be modified regardless of whether the receiver is the first responder (and thus, editing) or not. Changing this value from nil to non-nil will result in the placeholder being hidden, and the new value being displayed.
|
|
||||||
*/
|
|
||||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
|
||||||
|
|
||||||
#pragma mark - Managing The Keyboard
|
|
||||||
//! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder.
|
|
||||||
@property (nonatomic, readonly) UITextInputMode *textInputMode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero.
|
|
||||||
*/
|
|
||||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract The maximum number of lines to display. Additional lines will require scrolling.
|
|
||||||
@default 0 (No limit)
|
|
||||||
*/
|
|
||||||
@property (nonatomic) NSUInteger maximumLinesToDisplay;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user.
|
|
||||||
@result YES if the receiver's text view is the first-responder; NO otherwise.
|
|
||||||
*/
|
|
||||||
- (BOOL)isFirstResponder AS_WARN_UNUSED_RESULT;
|
|
||||||
|
|
||||||
//! @abstract Makes the receiver's text view the first responder.
|
|
||||||
- (BOOL)becomeFirstResponder;
|
|
||||||
|
|
||||||
//! @abstract Resigns the receiver's text view from first-responder status, if it has it.
|
|
||||||
- (BOOL)resignFirstResponder;
|
|
||||||
|
|
||||||
#pragma mark - Geometry
|
|
||||||
/**
|
|
||||||
@abstract Returns the frame of the given range of characters.
|
|
||||||
@param textRange A range of characters.
|
|
||||||
@discussion This method raises an exception if `textRange` is not a valid range of characters within the receiver's attributed text.
|
|
||||||
@result A CGRect that is the bounding box of the glyphs covered by the given range of characters, in the coordinate system of the receiver.
|
|
||||||
*/
|
|
||||||
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract <UITextInputTraits> properties.
|
|
||||||
*/
|
|
||||||
@property (nonatomic) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences
|
|
||||||
@property (nonatomic) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault
|
|
||||||
@property (nonatomic) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault;
|
|
||||||
@property (nonatomic) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault
|
|
||||||
@property (nonatomic) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault
|
|
||||||
@property (nonatomic) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
|
|
||||||
@property (nonatomic) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
|
|
||||||
@property (nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO
|
|
||||||
|
|
||||||
- (void)dropAutocorrection;
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ASEditableTextNode (Unavailable)
|
|
||||||
|
|
||||||
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
|
||||||
|
|
||||||
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
/**
|
|
||||||
* The methods declared by the ASEditableTextNodeDelegate protocol allow the adopting delegate to
|
|
||||||
* respond to notifications such as began and finished editing, selection changed and text updated;
|
|
||||||
* and manage whether a specified text should be replaced.
|
|
||||||
*/
|
|
||||||
@protocol ASEditableTextNodeDelegate <NSObject>
|
|
||||||
|
|
||||||
@optional
|
|
||||||
/**
|
|
||||||
@abstract Asks the delegate if editing should begin for the text node.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@discussion YES if editing should begin; NO if editing should not begin -- the default returns YES.
|
|
||||||
*/
|
|
||||||
- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Indicates to the delegate that the text node began editing.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@discussion The invocation of this method coincides with the keyboard animating to become visible.
|
|
||||||
*/
|
|
||||||
- (void)editableTextNodeDidBeginEditing:(ASEditableTextNode *)editableTextNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Asks the delegate whether the specified text should be replaced in the editable text node.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@param range The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
|
|
||||||
@param text The text to insert.
|
|
||||||
@discussion YES if the old text should be replaced by the new text; NO if the replacement operation should be aborted.
|
|
||||||
@result The text node calls this method whenever the user types a new character or deletes an existing character. Implementation of this method is optional -- the default implementation returns YES.
|
|
||||||
*/
|
|
||||||
- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Indicates to the delegate that the text node's selection has changed.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@param fromSelectedRange The previously selected range.
|
|
||||||
@param toSelectedRange The current selected range. Equivalent to the <selectedRange> property.
|
|
||||||
@param dueToEditing YES if the selection change was due to editing; NO otherwise.
|
|
||||||
@discussion You can access the selection of the receiver via <selectedRange>.
|
|
||||||
*/
|
|
||||||
- (void)editableTextNodeDidChangeSelection:(ASEditableTextNode *)editableTextNode fromSelectedRange:(NSRange)fromSelectedRange toSelectedRange:(NSRange)toSelectedRange dueToEditing:(BOOL)dueToEditing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Indicates to the delegate that the text node's text was updated.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@discussion This method is called each time the user updated the text node's text. It is not called for programmatic changes made to the text via the <attributedText> property.
|
|
||||||
*/
|
|
||||||
- (void)editableTextNodeDidUpdateText:(ASEditableTextNode *)editableTextNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
@abstract Indicates to the delegate that the text node has finished editing.
|
|
||||||
@param editableTextNode An editable text node.
|
|
||||||
@discussion The invocation of this method coincides with the keyboard animating to become hidden.
|
|
||||||
*/
|
|
||||||
- (void)editableTextNodeDidFinishEditing:(ASEditableTextNode *)editableTextNode;
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
|
|
||||||
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
|
|
||||||
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
17
submodules/CloudData/BUCK
Normal file
17
submodules/CloudData/BUCK
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||||
|
|
||||||
|
static_library(
|
||||||
|
name = "CloudData",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||||
|
"//submodules/Postbox:Postbox#shared",
|
||||||
|
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||||
|
],
|
||||||
|
frameworks = [
|
||||||
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
"$SDKROOT/System/Library/Frameworks/CloudKit.framework",
|
||||||
|
],
|
||||||
|
)
|
||||||
401
submodules/CloudData/CloudData.xcodeproj/project.pbxproj
Normal file
401
submodules/CloudData/CloudData.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>archiveVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>classes</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
<key>objectVersion</key>
|
||||||
|
<string>46</string>
|
||||||
|
<key>objects</key>
|
||||||
|
<dict>
|
||||||
|
<key>1DD70E29001F47FB00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>BUCK</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>BUCK</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>SOURCE_ROOT</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>text.script.python</string>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E296A1426C400000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData.swift</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>Sources/CloudData.swift</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>SOURCE_ROOT</string>
|
||||||
|
</dict>
|
||||||
|
<key>B401C979EAB5339800000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Sources</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>1DD70E296A1426C400000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>B401C9795E043F5F00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>1DD70E29001F47FB00000000</string>
|
||||||
|
<string>B401C979EAB5339800000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E299A98EF7600000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData-Debug.xcconfig</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>../../buck-out/gen/submodules/CloudData/CloudData-Debug.xcconfig</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>SOURCE_ROOT</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>text.xcconfig</string>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E293D3D816000000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData-Profile.xcconfig</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>../../buck-out/gen/submodules/CloudData/CloudData-Profile.xcconfig</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>SOURCE_ROOT</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>text.xcconfig</string>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E29B0D42CC200000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData-Release.xcconfig</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>../../buck-out/gen/submodules/CloudData/CloudData-Release.xcconfig</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>SOURCE_ROOT</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>text.xcconfig</string>
|
||||||
|
</dict>
|
||||||
|
<key>B401C9792F7F325000000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Buck (Do Not Modify)</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>1DD70E299A98EF7600000000</string>
|
||||||
|
<string>1DD70E293D3D816000000000</string>
|
||||||
|
<string>1DD70E29B0D42CC200000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>B401C979B781F65D00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Configurations</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>B401C9792F7F325000000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E29DB6520C800000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>libPostbox.dylib</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>libPostbox.dylib</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>BUILT_PRODUCTS_DIR</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>compiled.mach-o.dylib</string>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E29D65BA68200000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>libSwiftSignalKit.dylib</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>libSwiftSignalKit.dylib</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>BUILT_PRODUCTS_DIR</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>compiled.mach-o.dylib</string>
|
||||||
|
</dict>
|
||||||
|
<key>B401C97968022A5500000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Frameworks</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>1DD70E29DB6520C800000000</string>
|
||||||
|
<string>1DD70E29D65BA68200000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>1DD70E29FD41F1ED00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXFileReference</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>libCloudData.a</string>
|
||||||
|
<key>path</key>
|
||||||
|
<string>libCloudData.a</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string>BUILT_PRODUCTS_DIR</string>
|
||||||
|
<key>explicitFileType</key>
|
||||||
|
<string>archive.ar</string>
|
||||||
|
</dict>
|
||||||
|
<key>B401C979C806358400000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Products</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>1DD70E29FD41F1ED00000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>B401C979EFB6AC4600000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXGroup</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>mainGroup</string>
|
||||||
|
<key>sourceTree</key>
|
||||||
|
<string><![CDATA[<group>]]></string>
|
||||||
|
<key>children</key>
|
||||||
|
<array>
|
||||||
|
<string>B401C9795E043F5F00000000</string>
|
||||||
|
<string>B401C979B781F65D00000000</string>
|
||||||
|
<string>B401C97968022A5500000000</string>
|
||||||
|
<string>B401C979C806358400000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>E7A30F046A1426C400000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXBuildFile</string>
|
||||||
|
<key>fileRef</key>
|
||||||
|
<string>1DD70E296A1426C400000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>1870857F0000000000000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXSourcesBuildPhase</string>
|
||||||
|
<key>files</key>
|
||||||
|
<array>
|
||||||
|
<string>E7A30F046A1426C400000000</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>E7A30F04DB6520C800000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXBuildFile</string>
|
||||||
|
<key>fileRef</key>
|
||||||
|
<string>1DD70E29DB6520C800000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>E7A30F04D65BA68200000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXBuildFile</string>
|
||||||
|
<key>fileRef</key>
|
||||||
|
<string>1DD70E29D65BA68200000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>FAF5FAC90000000000000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXCopyFilesBuildPhase</string>
|
||||||
|
<key>files</key>
|
||||||
|
<array>
|
||||||
|
<string>E7A30F04DB6520C800000000</string>
|
||||||
|
<string>E7A30F04D65BA68200000000</string>
|
||||||
|
</array>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Fake Swift Dependencies (Copy Files Phase)</string>
|
||||||
|
<key>runOnlyForDeploymentPostprocessing</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>dstSubfolderSpec</key>
|
||||||
|
<integer>16</integer>
|
||||||
|
<key>dstPath</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
<key>4952437303EDA63300000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Debug</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
<key>baseConfigurationReference</key>
|
||||||
|
<string>1DD70E299A98EF7600000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>4952437350C7218900000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Profile</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
<key>baseConfigurationReference</key>
|
||||||
|
<string>1DD70E293D3D816000000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>49524373A439BFE700000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Release</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
<key>baseConfigurationReference</key>
|
||||||
|
<string>1DD70E29B0D42CC200000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>218C37090000000000000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCConfigurationList</string>
|
||||||
|
<key>buildConfigurations</key>
|
||||||
|
<array>
|
||||||
|
<string>4952437303EDA63300000000</string>
|
||||||
|
<string>4952437350C7218900000000</string>
|
||||||
|
<string>49524373A439BFE700000000</string>
|
||||||
|
</array>
|
||||||
|
<key>defaultConfigurationIsVisible</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
<key>E66DC04E5E043F5F00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXNativeTarget</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CloudData</string>
|
||||||
|
<key>productName</key>
|
||||||
|
<string>CloudData</string>
|
||||||
|
<key>productReference</key>
|
||||||
|
<string>1DD70E29FD41F1ED00000000</string>
|
||||||
|
<key>productType</key>
|
||||||
|
<string>com.apple.product-type.library.static</string>
|
||||||
|
<key>dependencies</key>
|
||||||
|
<array>
|
||||||
|
</array>
|
||||||
|
<key>buildPhases</key>
|
||||||
|
<array>
|
||||||
|
<string>1870857F0000000000000000</string>
|
||||||
|
<string>FAF5FAC90000000000000000</string>
|
||||||
|
</array>
|
||||||
|
<key>buildConfigurationList</key>
|
||||||
|
<string>218C37090000000000000000</string>
|
||||||
|
</dict>
|
||||||
|
<key>4952437303EDA63300000001</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Debug</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>4952437350C7218900000001</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Profile</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>49524373A439BFE700000001</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCBuildConfiguration</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Release</string>
|
||||||
|
<key>buildSettings</key>
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>218C37090000000000000001</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>XCConfigurationList</string>
|
||||||
|
<key>buildConfigurations</key>
|
||||||
|
<array>
|
||||||
|
<string>4952437303EDA63300000001</string>
|
||||||
|
<string>4952437350C7218900000001</string>
|
||||||
|
<string>49524373A439BFE700000001</string>
|
||||||
|
</array>
|
||||||
|
<key>defaultConfigurationIsVisible</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
<key>96C847935E043F5F00000000</key>
|
||||||
|
<dict>
|
||||||
|
<key>isa</key>
|
||||||
|
<string>PBXProject</string>
|
||||||
|
<key>mainGroup</key>
|
||||||
|
<string>B401C979EFB6AC4600000000</string>
|
||||||
|
<key>targets</key>
|
||||||
|
<array>
|
||||||
|
<string>E66DC04E5E043F5F00000000</string>
|
||||||
|
</array>
|
||||||
|
<key>buildConfigurationList</key>
|
||||||
|
<string>218C37090000000000000001</string>
|
||||||
|
<key>compatibilityVersion</key>
|
||||||
|
<string>Xcode 3.2</string>
|
||||||
|
<key>attributes</key>
|
||||||
|
<dict>
|
||||||
|
<key>LastUpgradeCheck</key>
|
||||||
|
<string>9999</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>rootObject</key>
|
||||||
|
<string>96C847935E043F5F00000000</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Scheme LastUpgradeVersion="9999" version="1.7"><BuildAction buildImplicitDependencies="YES" parallelizeBuildables="YES"><BuildActionEntries><BuildActionEntry buildForAnalyzing="YES" buildForArchiving="YES" buildForProfiling="YES" buildForRunning="YES" buildForTesting="YES"><BuildableReference BlueprintIdentifier="E66DC04E5E043F5F00000000" BlueprintName="CloudData" BuildableIdentifier="primary" BuildableName="libCloudData.a" ReferencedContainer="container:CloudData.xcodeproj"/></BuildActionEntry></BuildActionEntries></BuildAction><TestAction buildConfiguration="Debug" shouldUseLaunchSchemeArgsEnv="YES"><Testables/></TestAction><LaunchAction buildConfiguration="Debug"/><ProfileAction buildConfiguration="Release"/><AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/></Scheme>
|
||||||
123
submodules/CloudData/Sources/CloudData.swift
Normal file
123
submodules/CloudData/Sources/CloudData.swift
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
import CloudKit
|
||||||
|
import MtProtoKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
private enum FetchError {
|
||||||
|
case generic
|
||||||
|
case networkUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 10.0, *)
|
||||||
|
private func fetchRawData(prefix: String) -> Signal<Data, FetchError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let container = CKContainer.default()
|
||||||
|
let publicDatabase = container.database(with: .public)
|
||||||
|
let recordId = CKRecord.ID(recordName: "emergency-datacenter-\(prefix)")
|
||||||
|
publicDatabase.fetch(withRecordID: recordId, completionHandler: { record, error in
|
||||||
|
if let error = error {
|
||||||
|
print("publicDatabase.fetch error: \(error)")
|
||||||
|
if let error = error as? NSError, error.domain == CKError.errorDomain, error.code == 1 {
|
||||||
|
subscriber.putError(.networkUnavailable)
|
||||||
|
} else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
}
|
||||||
|
} else if let record = record {
|
||||||
|
guard let dataString = record.object(forKey: "data") as? String else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let data = Data(base64Encoded: dataString, options: [.ignoreUnknownCharacters]) else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resultData = data
|
||||||
|
resultData.count = 256
|
||||||
|
subscriber.putNext(resultData)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
} else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 10.0, *)
|
||||||
|
public func cloudDataAdditionalAddressSource(phoneNumber: Signal<String?, NoError>) -> Signal<MTBackupDatacenterData, NoError> {
|
||||||
|
return phoneNumber
|
||||||
|
|> take(1)
|
||||||
|
|> mapToSignal { _ -> Signal<MTBackupDatacenterData, NoError> in
|
||||||
|
let phoneNumber: String? = "7950"
|
||||||
|
var prefix = ""
|
||||||
|
if let phoneNumber = phoneNumber, phoneNumber.count >= 1 {
|
||||||
|
prefix = String(phoneNumber[phoneNumber.startIndex ..< phoneNumber.index(after: phoneNumber.startIndex)])
|
||||||
|
}
|
||||||
|
return fetchRawData(prefix: prefix)
|
||||||
|
|> map { data -> MTBackupDatacenterData? in
|
||||||
|
if let datacenterData = MTIPDataDecode(data, phoneNumber ?? "") {
|
||||||
|
return datacenterData
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> `catch` { error -> Signal<MTBackupDatacenterData?, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> mapToSignal { data -> Signal<MTBackupDatacenterData, NoError> in
|
||||||
|
if let data = data {
|
||||||
|
return .single(data)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 10.0, *)
|
||||||
|
private final class CloudDataContextObject {
|
||||||
|
private let queue: Queue
|
||||||
|
|
||||||
|
init(queue: Queue) {
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
|
let container = CKContainer.default()
|
||||||
|
let publicDatabase = container.database(with: .public)
|
||||||
|
|
||||||
|
/*let changesOperation = CKFetchDatabaseChangesOperation(previousServerChangeToken: nil)
|
||||||
|
changesOperation.fetchAllChanges = true
|
||||||
|
changesOperation.recordZoneWithIDChangedBlock = { _ in
|
||||||
|
print("recordZoneWithIDChangedBlock")
|
||||||
|
}
|
||||||
|
changesOperation.recordZoneWithIDWasDeletedBlock = { _ in
|
||||||
|
|
||||||
|
}
|
||||||
|
changesOperation.changeTokenUpdatedBlock = { _ in
|
||||||
|
print("changeTokenUpdatedBlock")
|
||||||
|
}
|
||||||
|
changesOperation.fetchDatabaseChangesCompletionBlock = { serverChangeToken, isMoreComing, error in
|
||||||
|
print("done")
|
||||||
|
}
|
||||||
|
publicDatabase.add(changesOperation)*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol CloudDataContext {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 10.0, *)
|
||||||
|
public final class CloudDataContextImpl: CloudDataContext {
|
||||||
|
private let queue = Queue()
|
||||||
|
private let impl: QueueLocalObject<CloudDataContextObject>
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
let queue = self.queue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return CloudDataContextObject(queue: queue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -558,7 +558,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
})
|
})
|
||||||
|
|
||||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||||
let actionsSideInset: CGFloat = 11.0
|
let actionsSideInset: CGFloat = (validLayout?.safeInsets.left ?? 0.0) + 11.0
|
||||||
|
|
||||||
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
|
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
|
||||||
|
|
||||||
@ -855,7 +855,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if animateOutToItem, let targetNode = targetNode, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
if animateOutToItem, let targetNode = targetNode, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||||
let actionsSideInset: CGFloat = 11.0
|
let actionsSideInset: CGFloat = (validLayout?.safeInsets.left ?? 0.0) + 11.0
|
||||||
|
|
||||||
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
|
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: originalProjectedContentViewFrame.1.width, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
|
||||||
|
|
||||||
@ -1015,7 +1015,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
let actionsSideInset: CGFloat = 11.0
|
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
|
||||||
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
|
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
|
||||||
if let _ = self.reactionContextNode {
|
if let _ = self.reactionContextNode {
|
||||||
contentTopInset += 34.0
|
contentTopInset += 34.0
|
||||||
|
|||||||
@ -20,8 +20,19 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
|
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
|
||||||
|
|
||||||
public static var allCases: [DeviceMetrics] {
|
public static var allCases: [DeviceMetrics] {
|
||||||
return [.iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax,
|
return [
|
||||||
.iPad, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen]
|
.iPhone4,
|
||||||
|
.iPhone5,
|
||||||
|
.iPhone6,
|
||||||
|
.iPhone6Plus,
|
||||||
|
.iPhoneX,
|
||||||
|
.iPhoneXSMax,
|
||||||
|
.iPad,
|
||||||
|
.iPadPro10Inch,
|
||||||
|
.iPadPro11Inch,
|
||||||
|
.iPadPro,
|
||||||
|
.iPadPro3rdGen
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?) {
|
public init(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?) {
|
||||||
@ -106,6 +117,20 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func statusBarHeight(for size: CGSize) -> CGFloat? {
|
||||||
|
let value = self.statusBarHeight
|
||||||
|
switch self {
|
||||||
|
case .iPad, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
|
||||||
|
return value
|
||||||
|
default:
|
||||||
|
if size.width < size.height {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var statusBarHeight: CGFloat {
|
var statusBarHeight: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhoneX, .iPhoneXSMax:
|
case .iPhoneX, .iPhoneXSMax:
|
||||||
|
|||||||
@ -31,8 +31,11 @@ private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
|
|||||||
class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
||||||
var validatedGesture = false
|
var validatedGesture = false
|
||||||
var firstLocation: CGPoint = CGPoint()
|
var firstLocation: CGPoint = CGPoint()
|
||||||
|
private let canBegin: () -> Bool
|
||||||
|
|
||||||
|
init(target: Any?, action: Selector?, canBegin: @escaping () -> Bool) {
|
||||||
|
self.canBegin = canBegin
|
||||||
|
|
||||||
override init(target: Any?, action: Selector?) {
|
|
||||||
super.init(target: target, action: action)
|
super.init(target: target, action: action)
|
||||||
|
|
||||||
self.maximumNumberOfTouches = 1
|
self.maximumNumberOfTouches = 1
|
||||||
@ -45,6 +48,11 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
if !self.canBegin() {
|
||||||
|
self.state = .failed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
super.touchesBegan(touches, with: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
let touch = touches.first!
|
let touch = touches.first!
|
||||||
|
|||||||
@ -152,7 +152,7 @@ private func endAnimations(view: UIView) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func viewTreeContainsFirstResponder(view: UIView) -> Bool {
|
func viewTreeContainsFirstResponder(view: UIView) -> Bool {
|
||||||
if view.isFirstResponder {
|
if view.isFirstResponder {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -685,7 +685,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero {
|
if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom, insetDeltaOffsetFix: 0.0).offset.isZero {
|
||||||
self.updateVisibleContentOffset()
|
self.updateVisibleContentOffset()
|
||||||
}
|
}
|
||||||
self.updateScroller(transition: .immediate)
|
self.updateScroller(transition: .immediate)
|
||||||
@ -749,7 +749,65 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
return additionalInverseTopInset
|
return additionalInverseTopInset
|
||||||
}
|
}
|
||||||
|
|
||||||
private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil, isExperimentalSnapToScrollToItem: Bool = false) -> (snappedTopInset: CGFloat, offset: CGFloat) {
|
private func areAllItemsOnScreen() -> Bool {
|
||||||
|
if self.itemNodes.count == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var completeHeight: CGFloat = 0.0
|
||||||
|
var topItemFound = false
|
||||||
|
var bottomItemFound = false
|
||||||
|
var topItemEdge: CGFloat = 0.0
|
||||||
|
var bottomItemEdge: CGFloat = 0.0
|
||||||
|
|
||||||
|
for i in 0 ..< self.itemNodes.count {
|
||||||
|
if let index = itemNodes[i].index {
|
||||||
|
if index == 0 {
|
||||||
|
topItemFound = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveInsets = self.insets
|
||||||
|
if topItemFound && !self.stackFromBottomInsetItemFactor.isZero {
|
||||||
|
let additionalInverseTopInset = self.calculateAdditionalTopInverseInset()
|
||||||
|
effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if topItemFound {
|
||||||
|
topItemEdge = itemNodes[0].apparentFrame.origin.y
|
||||||
|
}
|
||||||
|
|
||||||
|
var bottomItemNode: ListViewItemNode?
|
||||||
|
for i in (0 ..< self.itemNodes.count).reversed() {
|
||||||
|
if let index = itemNodes[i].index {
|
||||||
|
if index == self.items.count - 1 {
|
||||||
|
bottomItemNode = itemNodes[i]
|
||||||
|
bottomItemFound = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bottomItemFound {
|
||||||
|
bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY
|
||||||
|
}
|
||||||
|
|
||||||
|
if topItemFound && bottomItemFound {
|
||||||
|
for itemNode in self.itemNodes {
|
||||||
|
completeHeight += itemNode.apparentBounds.height
|
||||||
|
}
|
||||||
|
|
||||||
|
if completeHeight <= self.visibleSize.height - self.insets.top - self.insets.bottom {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil, isExperimentalSnapToScrollToItem: Bool = false, insetDeltaOffsetFix: CGFloat) -> (snappedTopInset: CGFloat, offset: CGFloat) {
|
||||||
if self.itemNodes.count == 0 {
|
if self.itemNodes.count == 0 {
|
||||||
return (0.0, 0.0)
|
return (0.0, 0.0)
|
||||||
}
|
}
|
||||||
@ -851,7 +909,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
offset = (effectiveInsets.top - overscroll) - topItemEdge
|
offset = (effectiveInsets.top - overscroll) - topItemEdge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if !self.isTracking {
|
||||||
let areaHeight = min(completeHeight, visibleAreaHeight)
|
let areaHeight = min(completeHeight, visibleAreaHeight)
|
||||||
if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll {
|
if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll {
|
||||||
if snapTopItem && topItemEdge < effectiveInsets.top {
|
if snapTopItem && topItemEdge < effectiveInsets.top {
|
||||||
@ -859,7 +917,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
} else {
|
} else {
|
||||||
offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge
|
offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge
|
||||||
}
|
}
|
||||||
} else if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true {
|
} else if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ !self.isTracking {
|
||||||
offset = (effectiveInsets.top - overscroll) - topItemEdge
|
offset = (effectiveInsets.top - overscroll) - topItemEdge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -931,7 +989,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if topItemIndexAndMinY.0 == 0 {
|
if topItemIndexAndMinY.0 == 0 {
|
||||||
offset = .known(-(topItemIndexAndMinY.1 - self.insets.top))
|
var offsetValue: CGFloat = -(topItemIndexAndMinY.1 - self.insets.top)
|
||||||
|
if self.areAllItemsOnScreen() {
|
||||||
|
offsetValue = 0.0
|
||||||
|
}
|
||||||
|
offset = .known(offsetValue)
|
||||||
} else if topItemIndexAndMinY.0 == -1 {
|
} else if topItemIndexAndMinY.0 == -1 {
|
||||||
offset = .none
|
offset = .none
|
||||||
}
|
}
|
||||||
@ -2345,7 +2407,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
//print("replay after \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))")
|
//print("replay after \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let scrollToItem = scrollToItem {
|
if let scrollToItem = scrollToItem, !self.areAllItemsOnScreen() {
|
||||||
self.stopScrolling()
|
self.stopScrolling()
|
||||||
|
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
@ -2442,6 +2504,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.visibleSize = updateSizeAndInsets.size
|
self.visibleSize = updateSizeAndInsets.size
|
||||||
|
|
||||||
var offsetFix: CGFloat
|
var offsetFix: CGFloat
|
||||||
|
var insetDeltaOffsetFix: CGFloat = 0.0
|
||||||
if self.isTracking || isExperimentalSnapToScrollToItem {
|
if self.isTracking || isExperimentalSnapToScrollToItem {
|
||||||
offsetFix = 0.0
|
offsetFix = 0.0
|
||||||
} else if self.snapToBottomInsetUntilFirstInteraction {
|
} else if self.snapToBottomInsetUntilFirstInteraction {
|
||||||
@ -2462,7 +2525,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: offsetFix), within: self.visibleSize)
|
itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: offsetFix), within: self.visibleSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, isExperimentalSnapToScrollToItem: isExperimentalSnapToScrollToItem)
|
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, isExperimentalSnapToScrollToItem: isExperimentalSnapToScrollToItem, insetDeltaOffsetFix: insetDeltaOffsetFix)
|
||||||
|
|
||||||
if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) {
|
if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) {
|
||||||
offsetFix += snappedTopInset
|
offsetFix += snappedTopInset
|
||||||
@ -2532,7 +2595,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
} else {
|
} else {
|
||||||
self.visibleSize = updateSizeAndInsets.size
|
self.visibleSize = updateSizeAndInsets.size
|
||||||
|
|
||||||
if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom).offset.isZero {
|
if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, insetDeltaOffsetFix: 0.0).offset.isZero {
|
||||||
self.updateVisibleContentOffset()
|
self.updateVisibleContentOffset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2586,7 +2649,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.scroller.contentOffset = self.lastContentOffset
|
self.scroller.contentOffset = self.lastContentOffset
|
||||||
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
|
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
|
||||||
} else {
|
} else {
|
||||||
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem)
|
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem, insetDeltaOffsetFix: 0.0)
|
||||||
|
|
||||||
if !snappedTopInset.isZero && previousApparentFrames.isEmpty {
|
if !snappedTopInset.isZero && previousApparentFrames.isEmpty {
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
@ -3509,7 +3572,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero {
|
if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom, insetDeltaOffsetFix: 0.0).offset.isZero {
|
||||||
self.updateVisibleContentOffset()
|
self.updateVisibleContentOffset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var currentKeyboardLeftEdge: CGFloat = 0.0
|
||||||
|
private var additionalKeyboardLeftEdgeOffset: CGFloat = 0.0
|
||||||
|
|
||||||
var statusBarStyle: StatusBarStyle = .Ignore
|
var statusBarStyle: StatusBarStyle = .Ignore
|
||||||
var statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
var statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||||
|
|
||||||
@ -99,7 +102,12 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), canBegin: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strongSelf.controllers.count > 1
|
||||||
|
})
|
||||||
panRecognizer.delegate = self
|
panRecognizer.delegate = self
|
||||||
panRecognizer.delaysTouchesBegan = false
|
panRecognizer.delaysTouchesBegan = false
|
||||||
panRecognizer.cancelsTouchesInView = true
|
panRecognizer.cancelsTouchesInView = true
|
||||||
@ -121,6 +129,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
|
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -161,16 +172,6 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
if let top = strongSelf.state.top {
|
if let top = strongSelf.state.top {
|
||||||
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
|
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
|
||||||
}
|
}
|
||||||
//strongSelf.keyboardManager?.surfaces = strongSelf.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
|
|
||||||
/*for i in 0 ..< strongSelf._viewControllers.count {
|
|
||||||
if let controller = strongSelf._viewControllers[i].controller as? ViewController {
|
|
||||||
if i < strongSelf._viewControllers.count - 1 {
|
|
||||||
controller.updateNavigationCustomData((strongSelf.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0 - progress, transition: transition)
|
|
||||||
} else {
|
|
||||||
controller.updateNavigationCustomData(nil, progress: 1.0 - progress, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||||
@ -187,12 +188,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
let velocity = recognizer.velocity(in: self.view).x
|
let velocity = recognizer.velocity(in: self.view).x
|
||||||
|
|
||||||
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
|
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
|
||||||
//(self.view as! NavigationControllerView).inTransition = true
|
|
||||||
navigationTransitionCoordinator.animateCompletion(velocity, completion: { [weak self] in
|
navigationTransitionCoordinator.animateCompletion(velocity, completion: { [weak self] in
|
||||||
guard let strongSelf = self, let layout = strongSelf.state.layout, let transition = strongSelf.state.transition, let top = strongSelf.state.top else {
|
guard let strongSelf = self, let layout = strongSelf.state.layout, let transition = strongSelf.state.transition, let top = strongSelf.state.top else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//(self.view as! NavigationControllerView).inTransition = false
|
|
||||||
|
|
||||||
let topController = top.value
|
let topController = top.value
|
||||||
let bottomController = transition.previous.value
|
let bottomController = transition.previous.value
|
||||||
@ -201,25 +200,12 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
strongSelf.state.transition = nil
|
strongSelf.state.transition = nil
|
||||||
|
|
||||||
strongSelf.controllerRemoved(top.value)
|
strongSelf.controllerRemoved(top.value)
|
||||||
|
|
||||||
//topController.viewDidDisappear(true)
|
|
||||||
//bottomController.viewDidAppear(true)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
/*if self.viewControllers.count >= 2 {
|
|
||||||
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
|
|
||||||
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
|
|
||||||
|
|
||||||
topController.viewWillAppear(true)
|
|
||||||
bottomController.viewWillDisappear(true)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//(self.view as! NavigationControllerView).inTransition = true
|
|
||||||
navigationTransitionCoordinator.animateCancel({ [weak self] in
|
navigationTransitionCoordinator.animateCancel({ [weak self] in
|
||||||
guard let strongSelf = self, let top = strongSelf.state.top, let transition = strongSelf.state.transition else {
|
guard let strongSelf = self, let top = strongSelf.state.top, let transition = strongSelf.state.transition else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//(self.view as! NavigationControllerView).inTransition = false
|
|
||||||
strongSelf.state.transition = nil
|
strongSelf.state.transition = nil
|
||||||
|
|
||||||
top.value.viewDidAppear(true)
|
top.value.viewDidAppear(true)
|
||||||
@ -287,7 +273,6 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
if pending.isReady {
|
if pending.isReady {
|
||||||
self.state.pending = nil
|
self.state.pending = nil
|
||||||
let previous = self.state.top
|
let previous = self.state.top
|
||||||
//previous?.value.view.endEditing(true)
|
|
||||||
self.state.top = pending.value
|
self.state.top = pending.value
|
||||||
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout.withUpdatedInputHeight(nil), transition: pending.transition)
|
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout.withUpdatedInputHeight(nil), transition: pending.transition)
|
||||||
statusBarTransition = pending.transition
|
statusBarTransition = pending.transition
|
||||||
@ -315,17 +300,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
self.statusBarStyle = updatedStatusBarStyle
|
self.statusBarStyle = updatedStatusBarStyle
|
||||||
self.statusBarStyleUpdated?(statusBarTransition)
|
self.statusBarStyleUpdated?(statusBarTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.state.transition == nil {
|
|
||||||
//self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func topTransition(from fromValue: Child?, to toValue: Child?, transitionType: PendingChild.TransitionType, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
private func topTransition(from fromValue: Child?, to toValue: Child?, transitionType: PendingChild.TransitionType, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
if case .animated = transition, let fromValue = fromValue, let toValue = toValue {
|
if case .animated = transition, let fromValue = fromValue, let toValue = toValue {
|
||||||
//self.keyboardManager?.surfaces = fromValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
|
|
||||||
if let currentTransition = self.state.transition {
|
if let currentTransition = self.state.transition {
|
||||||
//assertionFailure()
|
currentTransition.coordinator.performCompletion(completion: {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fromValue.value.viewWillDisappear(true)
|
fromValue.value.viewWillDisappear(true)
|
||||||
@ -379,11 +360,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
strongSelf.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
|
strongSelf.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
|
||||||
toValue.value.viewDidAppear(true)
|
toValue.value.viewDidAppear(true)
|
||||||
//strongSelf.keyboardManager?.surfaces = toValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
//self.keyboardManager?.surfaces = toValue?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
|
|
||||||
if let fromValue = fromValue {
|
if let fromValue = fromValue {
|
||||||
fromValue.value.viewWillDisappear(false)
|
fromValue.value.viewWillDisappear(false)
|
||||||
fromValue.value.setIgnoreAppearanceMethodInvocations(true)
|
fromValue.value.setIgnoreAppearanceMethodInvocations(true)
|
||||||
@ -438,8 +417,14 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAdditionalKeyboardLeftEdgeOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.additionalKeyboardLeftEdgeOffset = offset
|
||||||
|
self.syncKeyboard(leftEdge: self.currentKeyboardLeftEdge, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
private func syncKeyboard(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) {
|
private func syncKeyboard(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
self.keyboardViewManager?.update(leftEdge: leftEdge, transition: transition)
|
self.currentKeyboardLeftEdge = leftEdge
|
||||||
|
self.keyboardViewManager?.update(leftEdge: self.additionalKeyboardLeftEdgeOffset + leftEdge, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pendingChildIsReady(_ child: PendingChild) {
|
private func pendingChildIsReady(_ child: PendingChild) {
|
||||||
@ -454,4 +439,42 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
self.update(layout: layout, canBeClosed: canBeClosed, controllers: self.controllers, transition: .immediate)
|
self.update(layout: layout, canBeClosed: canBeClosed, controllers: self.controllers, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if !self.bounds.contains(point) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if self.state.transition != nil {
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||||
|
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
|
||||||
|
if let controller = self.controllers.last {
|
||||||
|
if controller.lockOrientation {
|
||||||
|
if let lockedOrientation = controller.lockedOrientation {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation))
|
||||||
|
} else {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let transition = self.state.transition {
|
||||||
|
let controller = transition.previous.value
|
||||||
|
if controller.lockOrientation {
|
||||||
|
if let lockedOrientation = controller.lockedOrientation {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation))
|
||||||
|
} else {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supportedOrientations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,6 +118,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
return self.view as! NavigationControllerView
|
return self.view as! NavigationControllerView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var inCallStatusBar: StatusBar?
|
||||||
private var rootContainer: RootContainer?
|
private var rootContainer: RootContainer?
|
||||||
private var rootModalFrame: NavigationModalFrame?
|
private var rootModalFrame: NavigationModalFrame?
|
||||||
private var modalContainers: [NavigationModalContainer] = []
|
private var modalContainers: [NavigationModalContainer] = []
|
||||||
@ -187,18 +188,16 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
|
|
||||||
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||||
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
|
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
|
||||||
if let controller = self.viewControllers.last {
|
if let rootContainer = self.rootContainer {
|
||||||
if let controller = controller as? ViewController {
|
switch rootContainer {
|
||||||
if controller.lockOrientation {
|
case let .flat(container):
|
||||||
if let lockedOrientation = controller.lockedOrientation {
|
supportedOrientations = supportedOrientations.intersection(container.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
|
||||||
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation))
|
case .split:
|
||||||
} else {
|
break
|
||||||
supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for modalContainer in self.modalContainers {
|
||||||
|
supportedOrientations = supportedOrientations.intersection(modalContainer.container.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
|
||||||
}
|
}
|
||||||
return supportedOrientations
|
return supportedOrientations
|
||||||
}
|
}
|
||||||
@ -206,6 +205,14 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
public func updateTheme(_ theme: NavigationControllerTheme) {
|
public func updateTheme(_ theme: NavigationControllerTheme) {
|
||||||
let statusBarStyleUpdated = self.theme.statusBar != theme.statusBar
|
let statusBarStyleUpdated = self.theme.statusBar != theme.statusBar
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
if let rootContainer = self.rootContainer {
|
||||||
|
switch rootContainer {
|
||||||
|
case let .split(container):
|
||||||
|
container.updateTheme(theme: theme)
|
||||||
|
case .flat:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.isViewLoaded {
|
if self.isViewLoaded {
|
||||||
if statusBarStyleUpdated {
|
if statusBarStyleUpdated {
|
||||||
self.validStatusBarStyle = self.theme.statusBar
|
self.validStatusBarStyle = self.theme.statusBar
|
||||||
@ -216,7 +223,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
case .white:
|
case .white:
|
||||||
normalStatusBarStyle = .lightContent
|
normalStatusBarStyle = .lightContent
|
||||||
}
|
}
|
||||||
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
|
//self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
|
||||||
}
|
}
|
||||||
self.controllerView.backgroundColor = theme.emptyAreaColor
|
self.controllerView.backgroundColor = theme.emptyAreaColor
|
||||||
}
|
}
|
||||||
@ -232,10 +239,13 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
self.updateContainers(layout: layout, transition: transition)
|
self.updateContainers(layout: layout, transition: transition)
|
||||||
|
|
||||||
|
self.inCallStatusBar?.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(40.0, layout.safeInsets.top)))
|
||||||
|
self.inCallStatusBar?.updateState(statusBar: nil, withSafeInsets: false, inCallText: "In Call Text", animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateContainers(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
private func updateContainers(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
let navigationLayout = makeNavigationLayout(layout: layout, controllers: self._viewControllers)
|
let navigationLayout = makeNavigationLayout(mode: self.mode, layout: layout, controllers: self._viewControllers)
|
||||||
|
|
||||||
var transition = transition
|
var transition = transition
|
||||||
var statusBarStyle: StatusBarStyle = .Ignore
|
var statusBarStyle: StatusBarStyle = .Ignore
|
||||||
@ -330,6 +340,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
if modalContainer.supernode == nil && modalContainer.isReady {
|
if modalContainer.supernode == nil && modalContainer.isReady {
|
||||||
if let previousModalContainer = previousModalContainer {
|
if let previousModalContainer = previousModalContainer {
|
||||||
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
|
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
|
||||||
|
} else if let inCallStatusBar = self.inCallStatusBar {
|
||||||
|
self.displayNode.insertSubnode(modalContainer, belowSubnode: inCallStatusBar)
|
||||||
} else {
|
} else {
|
||||||
self.displayNode.addSubnode(modalContainer)
|
self.displayNode.addSubnode(modalContainer)
|
||||||
}
|
}
|
||||||
@ -341,12 +353,12 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
if previousModalContainer == nil {
|
if previousModalContainer == nil {
|
||||||
topModalDismissProgress = modalContainer.dismissProgress
|
topModalDismissProgress = modalContainer.dismissProgress
|
||||||
if case .compact = layout.metrics.widthClass {
|
if case .compact = layout.metrics.widthClass {
|
||||||
modalContainer.container.keyboardViewManager = self.keyboardViewManager
|
modalContainer.keyboardViewManager = self.keyboardViewManager
|
||||||
} else {
|
} else {
|
||||||
modalContainer.container.keyboardViewManager = nil
|
modalContainer.keyboardViewManager = nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modalContainer.container.keyboardViewManager = nil
|
modalContainer.keyboardViewManager = nil
|
||||||
}
|
}
|
||||||
previousModalContainer = modalContainer
|
previousModalContainer = modalContainer
|
||||||
}
|
}
|
||||||
@ -601,11 +613,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
self.navigationBar.removeFromSuperview()
|
self.navigationBar.removeFromSuperview()
|
||||||
|
|
||||||
/*let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
/*let inCallStatusBar = StatusBar()
|
||||||
panRecognizer.delegate = self
|
self.displayNode.addSubnode(inCallStatusBar)
|
||||||
panRecognizer.delaysTouchesBegan = false
|
self.inCallStatusBar = inCallStatusBar*/
|
||||||
panRecognizer.cancelsTouchesInView = true
|
|
||||||
self.view.addGestureRecognizer(panRecognizer)*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pushViewController(_ controller: ViewController) {
|
public func pushViewController(_ controller: ViewController) {
|
||||||
@ -742,53 +752,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
|
|
||||||
override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
|
override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
/*if let controller = viewControllerToPresent as? NavigationController {
|
|
||||||
controller.navigation_setDismiss({ [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.dismiss(animated: false, completion: nil)
|
|
||||||
}
|
|
||||||
}, rootController: self.view!.window!.rootViewController)
|
|
||||||
self._presentedViewController = controller
|
|
||||||
|
|
||||||
self.view.endEditing(true)
|
|
||||||
if let validLayout = self.validLayout {
|
|
||||||
controller.containerLayoutUpdated(validLayout, transition: .immediate)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ready: Signal<Bool, NoError> = .single(true)
|
|
||||||
|
|
||||||
if let controller = controller.topViewController as? ViewController {
|
|
||||||
ready = controller.ready.get()
|
|
||||||
|> filter { $0 }
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue
|
|
||||||
}
|
|
||||||
|
|
||||||
self.currentPresentDisposable.set(ready.start(next: { [weak self] _ in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if flag {
|
|
||||||
controller.view.frame = strongSelf.view.bounds.offsetBy(dx: 0.0, dy: strongSelf.view.bounds.height)
|
|
||||||
strongSelf.view.addSubview(controller.view)
|
|
||||||
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: {
|
|
||||||
controller.view.frame = strongSelf.view.bounds
|
|
||||||
}, completion: { _ in
|
|
||||||
if let completion = completion {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
controller.view.frame = strongSelf.view.bounds
|
|
||||||
strongSelf.view.addSubview(controller.view)
|
|
||||||
|
|
||||||
if let completion = completion {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.")
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ struct NavigationLayout {
|
|||||||
var modal: [ModalContainerLayout]
|
var modal: [ModalContainerLayout]
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewController]) -> NavigationLayout {
|
func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewLayout, controllers: [ViewController]) -> NavigationLayout {
|
||||||
var rootControllers: [ViewController] = []
|
var rootControllers: [ViewController] = []
|
||||||
var modalStack: [ModalContainerLayout] = []
|
var modalStack: [ModalContainerLayout] = []
|
||||||
for controller in controllers {
|
for controller in controllers {
|
||||||
@ -52,6 +52,10 @@ func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rootLayout: RootNavigationLayout
|
let rootLayout: RootNavigationLayout
|
||||||
|
switch mode {
|
||||||
|
case .single:
|
||||||
|
rootLayout = .flat(rootControllers)
|
||||||
|
case .automaticMasterDetail:
|
||||||
switch layout.metrics.widthClass {
|
switch layout.metrics.widthClass {
|
||||||
case .compact:
|
case .compact:
|
||||||
rootLayout = .flat(rootControllers)
|
rootLayout = .flat(rootControllers)
|
||||||
@ -72,5 +76,6 @@ func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewControl
|
|||||||
}
|
}
|
||||||
rootLayout = .split(masterControllers, detailControllers)
|
rootLayout = .split(masterControllers, detailControllers)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return NavigationLayout(root: rootLayout, modal: modalStack)
|
return NavigationLayout(root: rootLayout, modal: modalStack)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,15 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||||
private var theme: NavigationControllerTheme
|
private var theme: NavigationControllerTheme
|
||||||
|
|
||||||
private let dim: ASDisplayNode
|
private let dim: ASDisplayNode
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
let container: NavigationContainer
|
let container: NavigationContainer
|
||||||
|
|
||||||
|
private var panRecognizer: InteractiveTransitionGestureRecognizer?
|
||||||
|
|
||||||
private(set) var isReady: Bool = false
|
private(set) var isReady: Bool = false
|
||||||
private(set) var dismissProgress: CGFloat = 0.0
|
private(set) var dismissProgress: CGFloat = 0.0
|
||||||
var isReadyUpdated: (() -> Void)?
|
var isReadyUpdated: (() -> Void)?
|
||||||
@ -18,6 +20,18 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var ignoreScrolling = false
|
private var ignoreScrolling = false
|
||||||
private var isDismissed = false
|
private var isDismissed = false
|
||||||
|
private var isInteractiveDimissEnabled = true
|
||||||
|
|
||||||
|
private var validLayout: ContainerViewLayout?
|
||||||
|
private var horizontalDismissOffset: CGFloat?
|
||||||
|
|
||||||
|
var keyboardViewManager: KeyboardViewManager? {
|
||||||
|
didSet {
|
||||||
|
if self.keyboardViewManager !== oldValue {
|
||||||
|
self.container.keyboardViewManager = self.keyboardViewManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -53,8 +67,6 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
|
||||||
|
|
||||||
self.scrollNode.view.alwaysBounceVertical = false
|
self.scrollNode.view.alwaysBounceVertical = false
|
||||||
self.scrollNode.view.alwaysBounceHorizontal = false
|
self.scrollNode.view.alwaysBounceHorizontal = false
|
||||||
self.scrollNode.view.bounces = false
|
self.scrollNode.view.bounces = false
|
||||||
@ -65,6 +77,124 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
self.scrollNode.view.delaysContentTouches = false
|
self.scrollNode.view.delaysContentTouches = false
|
||||||
self.scrollNode.view.delegate = self
|
self.scrollNode.view.delegate = self
|
||||||
|
|
||||||
|
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), canBegin: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !strongSelf.isDismissed
|
||||||
|
})
|
||||||
|
self.panRecognizer = panRecognizer
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
switch layout.metrics.widthClass {
|
||||||
|
case .compact:
|
||||||
|
panRecognizer.isEnabled = true
|
||||||
|
case .regular:
|
||||||
|
panRecognizer.isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panRecognizer.delegate = self
|
||||||
|
panRecognizer.delaysTouchesBegan = false
|
||||||
|
panRecognizer.cancelsTouchesInView = true
|
||||||
|
self.view.addGestureRecognizer(panRecognizer)
|
||||||
|
|
||||||
|
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||||
|
switch recognizer.state {
|
||||||
|
case .began:
|
||||||
|
self.horizontalDismissOffset = 0.0
|
||||||
|
case .changed:
|
||||||
|
let translation = max(0.0, recognizer.translation(in: self.view).x)
|
||||||
|
let progress = translation / self.bounds.width
|
||||||
|
self.horizontalDismissOffset = translation
|
||||||
|
self.dismissProgress = progress
|
||||||
|
self.applyDismissProgress(transition: .immediate, completion: {})
|
||||||
|
self.container.updateAdditionalKeyboardLeftEdgeOffset(translation, transition: .immediate)
|
||||||
|
case .ended, .cancelled:
|
||||||
|
let translation = max(0.0, recognizer.translation(in: self.view).x)
|
||||||
|
let progress = translation / self.bounds.width
|
||||||
|
let velocity = recognizer.velocity(in: self.view).x
|
||||||
|
|
||||||
|
if velocity > 1000 || progress > 0.2 {
|
||||||
|
self.isDismissed = true
|
||||||
|
self.horizontalDismissOffset = self.bounds.width
|
||||||
|
self.dismissProgress = 1.0
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
|
||||||
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.bounds.width, y: 0.0), size: self.scrollNode.bounds.size))
|
||||||
|
self.container.updateAdditionalKeyboardLeftEdgeOffset(self.bounds.width, transition: transition)
|
||||||
|
self.applyDismissProgress(transition: transition, completion: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.keyboardViewManager?.dismissEditingWithoutAnimation(view: strongSelf.view)
|
||||||
|
strongSelf.interactivelyDismissed?()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.horizontalDismissOffset = nil
|
||||||
|
self.dismissProgress = 0.0
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut)
|
||||||
|
self.applyDismissProgress(transition: transition, completion: {})
|
||||||
|
self.container.updateAdditionalKeyboardLeftEdgeOffset(0.0, transition: transition)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
if !self.isDismissed {
|
||||||
|
self.dismisWithAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismisWithAnimation() {
|
||||||
|
let scrollView = self.scrollNode.view
|
||||||
|
let targetOffset: CGFloat
|
||||||
|
let duration = 0.3
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
let dismissProgress: CGFloat
|
||||||
|
dismissProgress = 1.0
|
||||||
|
targetOffset = 0.0
|
||||||
|
transition = .animated(duration: duration, curve: .easeInOut)
|
||||||
|
self.isDismissed = true
|
||||||
|
self.ignoreScrolling = true
|
||||||
|
let deltaY = targetOffset - scrollView.contentOffset.y
|
||||||
|
scrollView.setContentOffset(scrollView.contentOffset, animated: false)
|
||||||
|
scrollView.setContentOffset(CGPoint(x: 0.0, y: targetOffset), animated: false)
|
||||||
|
transition.animateOffsetAdditive(layer: self.scrollNode.layer, offset: -deltaY, completion: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if targetOffset == 0.0 {
|
||||||
|
strongSelf.interactivelyDismissed?()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.ignoreScrolling = false
|
||||||
|
self.dismissProgress = dismissProgress
|
||||||
|
|
||||||
|
self.applyDismissProgress(transition: transition, completion: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
@ -74,8 +204,14 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
|
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
|
||||||
progress = max(0.0, min(1.0, progress))
|
progress = max(0.0, min(1.0, progress))
|
||||||
self.dismissProgress = progress
|
self.dismissProgress = progress
|
||||||
self.dim.alpha = 1.0 - progress
|
self.applyDismissProgress(transition: .immediate, completion: {})
|
||||||
self.updateDismissProgress?(progress, .immediate)
|
}
|
||||||
|
|
||||||
|
private func applyDismissProgress(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||||
|
transition.updateAlpha(node: self.dim, alpha: 1.0 - self.dismissProgress, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
self.updateDismissProgress?(self.dismissProgress, transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var endDraggingVelocity: CGPoint?
|
private var endDraggingVelocity: CGPoint?
|
||||||
@ -116,8 +252,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
})
|
})
|
||||||
self.ignoreScrolling = false
|
self.ignoreScrolling = false
|
||||||
self.dismissProgress = dismissProgress
|
self.dismissProgress = dismissProgress
|
||||||
transition.updateAlpha(node: self.dim, alpha: 1.0 - dismissProgress)
|
|
||||||
self.updateDismissProgress?(dismissProgress, transition)
|
self.applyDismissProgress(transition: transition, completion: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
@ -137,9 +273,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validLayout = layout
|
||||||
|
|
||||||
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
self.ignoreScrolling = true
|
self.ignoreScrolling = true
|
||||||
self.scrollNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
self.scrollNode.view.isScrollEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0) && self.isInteractiveDimissEnabled
|
||||||
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.horizontalDismissOffset ?? 0.0, y: 0.0), size: layout.size))
|
||||||
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: layout.size.height * 2.0)
|
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: layout.size.height * 2.0)
|
||||||
if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging {
|
if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging {
|
||||||
let defaultBounds = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)
|
let defaultBounds = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)
|
||||||
@ -154,6 +293,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let containerScale: CGFloat
|
let containerScale: CGFloat
|
||||||
switch layout.metrics.widthClass {
|
switch layout.metrics.widthClass {
|
||||||
case .compact:
|
case .compact:
|
||||||
|
self.panRecognizer?.isEnabled = true
|
||||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||||
self.container.clipsToBounds = true
|
self.container.clipsToBounds = true
|
||||||
self.container.cornerRadius = 10.0
|
self.container.cornerRadius = 10.0
|
||||||
@ -174,6 +314,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let scaledTopInset: CGFloat = topInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
|
let scaledTopInset: CGFloat = topInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
|
||||||
containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
|
containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
|
||||||
case .regular:
|
case .regular:
|
||||||
|
self.panRecognizer?.isEnabled = false
|
||||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||||
self.container.clipsToBounds = true
|
self.container.clipsToBounds = true
|
||||||
self.container.cornerRadius = 10.0
|
self.container.cornerRadius = 10.0
|
||||||
@ -233,7 +374,11 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
guard let result = super.hitTest(point, with: event) else {
|
guard let result = super.hitTest(point, with: event) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if !self.container.bounds.contains(self.view.convert(point, to: self.container.view)) {
|
||||||
|
return self.dim.view
|
||||||
|
}
|
||||||
var currentParent: UIView? = result
|
var currentParent: UIView? = result
|
||||||
|
var enableScrolling = true
|
||||||
while true {
|
while true {
|
||||||
if currentParent == nil {
|
if currentParent == nil {
|
||||||
break
|
break
|
||||||
@ -242,16 +387,31 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if scrollView === self.scrollNode.view {
|
if scrollView === self.scrollNode.view {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if scrollView.disablesInteractiveModalDismiss {
|
||||||
|
enableScrolling = false
|
||||||
|
break
|
||||||
|
} else {
|
||||||
if scrollView.isDecelerating && scrollView.contentOffset.y < scrollView.contentInset.top {
|
if scrollView.isDecelerating && scrollView.contentOffset.y < scrollView.contentInset.top {
|
||||||
return self.scrollNode.view
|
return self.scrollNode.view
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if let listView = currentParent as? ListViewBackingView, let listNode = listView.target {
|
} else if let listView = currentParent as? ListViewBackingView, let listNode = listView.target {
|
||||||
if listNode.scroller.isDecelerating && listNode.scroller.contentOffset.y < listNode.scroller.contentInset.top {
|
if listNode.view.disablesInteractiveModalDismiss {
|
||||||
|
enableScrolling = false
|
||||||
|
break
|
||||||
|
} else if listNode.scroller.isDecelerating && listNode.scroller.contentOffset.y < listNode.scroller.contentInset.top {
|
||||||
return self.scrollNode.view
|
return self.scrollNode.view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentParent = currentParent?.superview
|
currentParent = currentParent?.superview
|
||||||
}
|
}
|
||||||
|
self.isInteractiveDimissEnabled = enableScrolling
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
if layout.inputHeight != nil && layout.inputHeight != 0.0 {
|
||||||
|
enableScrolling = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.scrollNode.view.isScrollEnabled = enableScrolling
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,14 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
private func generateCornerImage(radius: CGFloat, mirror: Bool) -> UIImage? {
|
private enum CornerType {
|
||||||
|
case topLeft
|
||||||
|
case topRight
|
||||||
|
case bottomLeft
|
||||||
|
case bottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateCornerImage(radius: CGFloat, type: CornerType) -> UIImage? {
|
||||||
return generateImage(CGSize(width: radius, height: radius), rotatedContext: { size, context in
|
return generateImage(CGSize(width: radius, height: radius), rotatedContext: { size, context in
|
||||||
context.setFillColor(UIColor.black.cgColor)
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
@ -11,7 +18,18 @@ private func generateCornerImage(radius: CGFloat, mirror: Bool) -> UIImage? {
|
|||||||
context.setFillColor(UIColor.clear.cgColor)
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: mirror ? (-radius) : 0.0, y: 0.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)), cornerRadius: radius).fill()
|
let origin: CGPoint
|
||||||
|
switch type {
|
||||||
|
case .topLeft:
|
||||||
|
origin = CGPoint()
|
||||||
|
case .topRight:
|
||||||
|
origin = CGPoint(x: -radius, y: 0.0)
|
||||||
|
case .bottomLeft:
|
||||||
|
origin = CGPoint(x: 0.0, y: -radius)
|
||||||
|
case .bottomRight:
|
||||||
|
origin = CGPoint(x: -radius, y: -radius)
|
||||||
|
}
|
||||||
|
UIBezierPath(roundedRect: CGRect(origin: origin, size: CGSize(width: radius * 2.0, height: radius * 2.0)), cornerRadius: radius).fill()
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -20,8 +38,11 @@ final class NavigationModalFrame: ASDisplayNode {
|
|||||||
private let topShade: ASDisplayNode
|
private let topShade: ASDisplayNode
|
||||||
private let leftShade: ASDisplayNode
|
private let leftShade: ASDisplayNode
|
||||||
private let rightShade: ASDisplayNode
|
private let rightShade: ASDisplayNode
|
||||||
|
private let bottomShade: ASDisplayNode
|
||||||
private let topLeftCorner: ASImageNode
|
private let topLeftCorner: ASImageNode
|
||||||
private let topRightCorner: ASImageNode
|
private let topRightCorner: ASImageNode
|
||||||
|
private let bottomLeftCorner: ASImageNode
|
||||||
|
private let bottomRightCorner: ASImageNode
|
||||||
|
|
||||||
private var currentMaxCornerRadius: CGFloat?
|
private var currentMaxCornerRadius: CGFloat?
|
||||||
|
|
||||||
@ -36,6 +57,8 @@ final class NavigationModalFrame: ASDisplayNode {
|
|||||||
self.leftShade.backgroundColor = .black
|
self.leftShade.backgroundColor = .black
|
||||||
self.rightShade = ASDisplayNode()
|
self.rightShade = ASDisplayNode()
|
||||||
self.rightShade.backgroundColor = .black
|
self.rightShade.backgroundColor = .black
|
||||||
|
self.bottomShade = ASDisplayNode()
|
||||||
|
self.bottomShade.backgroundColor = .black
|
||||||
|
|
||||||
self.topLeftCorner = ASImageNode()
|
self.topLeftCorner = ASImageNode()
|
||||||
self.topLeftCorner.displaysAsynchronously = false
|
self.topLeftCorner.displaysAsynchronously = false
|
||||||
@ -43,14 +66,23 @@ final class NavigationModalFrame: ASDisplayNode {
|
|||||||
self.topRightCorner = ASImageNode()
|
self.topRightCorner = ASImageNode()
|
||||||
self.topRightCorner.displaysAsynchronously = false
|
self.topRightCorner.displaysAsynchronously = false
|
||||||
self.topRightCorner.displayWithoutProcessing = true
|
self.topRightCorner.displayWithoutProcessing = true
|
||||||
|
self.bottomLeftCorner = ASImageNode()
|
||||||
|
self.bottomLeftCorner.displaysAsynchronously = false
|
||||||
|
self.bottomLeftCorner.displayWithoutProcessing = true
|
||||||
|
self.bottomRightCorner = ASImageNode()
|
||||||
|
self.bottomRightCorner.displaysAsynchronously = false
|
||||||
|
self.bottomRightCorner.displayWithoutProcessing = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.topShade)
|
self.addSubnode(self.topShade)
|
||||||
self.addSubnode(self.leftShade)
|
self.addSubnode(self.leftShade)
|
||||||
self.addSubnode(self.rightShade)
|
self.addSubnode(self.rightShade)
|
||||||
|
self.addSubnode(self.bottomShade)
|
||||||
self.addSubnode(self.topLeftCorner)
|
self.addSubnode(self.topLeftCorner)
|
||||||
self.addSubnode(self.topRightCorner)
|
self.addSubnode(self.topRightCorner)
|
||||||
|
self.addSubnode(self.bottomLeftCorner)
|
||||||
|
self.addSubnode(self.bottomRightCorner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
@ -78,6 +110,9 @@ final class NavigationModalFrame: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
let additionalTopInset: CGFloat = 10.0
|
let additionalTopInset: CGFloat = 10.0
|
||||||
|
|
||||||
|
let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width
|
||||||
|
let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset
|
||||||
|
|
||||||
let cornerRadius: CGFloat = 9.0
|
let cornerRadius: CGFloat = 9.0
|
||||||
let initialCornerRadius: CGFloat
|
let initialCornerRadius: CGFloat
|
||||||
if !layout.safeInsets.top.isZero {
|
if !layout.safeInsets.top.isZero {
|
||||||
@ -86,22 +121,29 @@ final class NavigationModalFrame: ASDisplayNode {
|
|||||||
initialCornerRadius = 0.0
|
initialCornerRadius = 0.0
|
||||||
}
|
}
|
||||||
if self.currentMaxCornerRadius != cornerRadius {
|
if self.currentMaxCornerRadius != cornerRadius {
|
||||||
self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: false)
|
self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .topLeft)
|
||||||
self.topRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: true)
|
self.topRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .topRight)
|
||||||
|
self.bottomLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .bottomLeft)
|
||||||
|
self.bottomRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .bottomRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius
|
let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius
|
||||||
let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||||
let cornerTopOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
let cornerTopOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
||||||
|
let cornerBottomOffset: CGFloat = progress * bottomInset
|
||||||
transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||||
transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||||
|
transition.updateFrame(node: self.bottomLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||||
|
transition.updateFrame(node: self.bottomRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||||
|
|
||||||
let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
||||||
|
let bottomShadeOffset: CGFloat = progress * bottomInset
|
||||||
let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||||
let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset
|
let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||||
let rightShadeOffset: CGFloat = layout.size.width - rightShadeWidth
|
let rightShadeOffset: CGFloat = layout.size.width - rightShadeWidth
|
||||||
|
|
||||||
transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset)))
|
transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset)))
|
||||||
|
transition.updateFrame(node: self.bottomShade, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomShadeOffset), size: CGSize(width: layout.size.width, height: bottomShadeOffset)))
|
||||||
transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height)))
|
transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height)))
|
||||||
transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), completion: { _ in
|
transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), completion: { _ in
|
||||||
completion()
|
completion()
|
||||||
|
|||||||
@ -10,6 +10,9 @@ final class NavigationSplitContainer: ASDisplayNode {
|
|||||||
private let detailContainer: NavigationContainer
|
private let detailContainer: NavigationContainer
|
||||||
private let separator: ASDisplayNode
|
private let separator: ASDisplayNode
|
||||||
|
|
||||||
|
private var masterControllers: [ViewController] = []
|
||||||
|
private var detailControllers: [ViewController] = []
|
||||||
|
|
||||||
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
@ -29,6 +32,10 @@ final class NavigationSplitContainer: ASDisplayNode {
|
|||||||
self.addSubnode(self.separator)
|
self.addSubnode(self.separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTheme(theme: NavigationControllerTheme) {
|
||||||
|
self.separator.backgroundColor = theme.navigationBar.separatorColor
|
||||||
|
}
|
||||||
|
|
||||||
func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], transition: ContainedViewLayoutTransition) {
|
func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], transition: ContainedViewLayoutTransition) {
|
||||||
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
|
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
|
||||||
let detailWidth = layout.size.width - masterWidth
|
let detailWidth = layout.size.width - masterWidth
|
||||||
@ -39,5 +46,29 @@ final class NavigationSplitContainer: ASDisplayNode {
|
|||||||
|
|
||||||
self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition)
|
self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition)
|
||||||
self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition)
|
self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition)
|
||||||
|
|
||||||
|
var controllersUpdated = false
|
||||||
|
if self.detailControllers.last !== detailControllers.last {
|
||||||
|
controllersUpdated = true
|
||||||
|
} else if self.masterControllers.count != masterControllers.count {
|
||||||
|
controllersUpdated = true
|
||||||
|
} else {
|
||||||
|
for i in 0 ..< masterControllers.count {
|
||||||
|
if masterControllers[i] !== self.masterControllers[i] {
|
||||||
|
controllersUpdated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.masterControllers = masterControllers
|
||||||
|
self.detailControllers = detailControllers
|
||||||
|
|
||||||
|
if controllersUpdated {
|
||||||
|
let data = self.detailControllers.last?.customData
|
||||||
|
for controller in self.masterControllers {
|
||||||
|
controller.updateNavigationCustomData(data, progress: 1.0, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -203,26 +203,28 @@ class NavigationTransitionCoordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func performCompletion(completion: @escaping () -> ()) {
|
||||||
|
self.updateProgress(1.0, transition: .immediate, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.dimNode.removeFromSupernode()
|
||||||
|
strongSelf.shadowNode.removeFromSupernode()
|
||||||
|
|
||||||
|
strongSelf.endNavigationBarTransition()
|
||||||
|
|
||||||
|
if let currentCompletion = strongSelf.currentCompletion {
|
||||||
|
strongSelf.currentCompletion = nil
|
||||||
|
currentCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) {
|
func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) {
|
||||||
self.animatingCompletion = true
|
self.animatingCompletion = true
|
||||||
let distance = (1.0 - self.progress) * self.container.bounds.size.width
|
let distance = (1.0 - self.progress) * self.container.bounds.size.width
|
||||||
self.currentCompletion = completion
|
self.currentCompletion = completion
|
||||||
let f = {
|
let f = {
|
||||||
/*switch self.transition {
|
|
||||||
case .Push:
|
|
||||||
if let viewSuperview = self.viewSuperview {
|
|
||||||
viewSuperview.addSubview(self.bottomView)
|
|
||||||
} else {
|
|
||||||
self.bottomView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
case .Pop:
|
|
||||||
if let viewSuperview = self.viewSuperview {
|
|
||||||
viewSuperview.addSubview(self.topView)
|
|
||||||
} else {
|
|
||||||
self.topView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
self.dimNode.removeFromSupernode()
|
self.dimNode.removeFromSupernode()
|
||||||
self.shadowNode.removeFromSupernode()
|
self.shadowNode.removeFromSupernode()
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
|
|||||||
|
|
||||||
@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
|
@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
|
||||||
@property (nonatomic) bool disablesInteractiveKeyboardGestureRecognizer;
|
@property (nonatomic) bool disablesInteractiveKeyboardGestureRecognizer;
|
||||||
|
@property (nonatomic) bool disablesInteractiveModalDismiss;
|
||||||
@property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)();
|
@property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)();
|
||||||
|
|
||||||
@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling;
|
@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling;
|
||||||
|
|||||||
@ -42,6 +42,7 @@ static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppe
|
|||||||
static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey;
|
static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey;
|
||||||
static const void *interactiveTransitionGestureRecognizerTestKey = &interactiveTransitionGestureRecognizerTestKey;
|
static const void *interactiveTransitionGestureRecognizerTestKey = &interactiveTransitionGestureRecognizerTestKey;
|
||||||
static const void *UIViewControllerHintWillBePresentedInPreviewingContextKey = &UIViewControllerHintWillBePresentedInPreviewingContextKey;
|
static const void *UIViewControllerHintWillBePresentedInPreviewingContextKey = &UIViewControllerHintWillBePresentedInPreviewingContextKey;
|
||||||
|
static const void *disablesInteractiveModalDismissKey = &disablesInteractiveModalDismissKey;
|
||||||
|
|
||||||
static bool notyfyingShiftState = false;
|
static bool notyfyingShiftState = false;
|
||||||
|
|
||||||
@ -250,6 +251,14 @@ static bool notyfyingShiftState = false;
|
|||||||
[self setAssociatedObject:[disablesInteractiveTransitionGestureRecognizerNow copy] forKey:disablesInteractiveTransitionGestureRecognizerNowKey];
|
[self setAssociatedObject:[disablesInteractiveTransitionGestureRecognizerNow copy] forKey:disablesInteractiveTransitionGestureRecognizerNowKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (bool)disablesInteractiveModalDismiss {
|
||||||
|
return [self associatedObjectForKey:disablesInteractiveModalDismissKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDisablesInteractiveModalDismiss:(bool)disablesInteractiveModalDismiss {
|
||||||
|
[self setAssociatedObject:@(disablesInteractiveModalDismiss) forKey:disablesInteractiveModalDismissKey];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL (^)(CGPoint))interactiveTransitionGestureRecognizerTest {
|
- (BOOL (^)(CGPoint))interactiveTransitionGestureRecognizerTest {
|
||||||
return [self associatedObjectForKey:interactiveTransitionGestureRecognizerTestKey];
|
return [self associatedObjectForKey:interactiveTransitionGestureRecognizerTestKey];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -454,6 +454,9 @@ public class Window1 {
|
|||||||
if strongSelf.deviceMetrics.type == .tablet, abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
|
if strongSelf.deviceMetrics.type == .tablet, abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
|
||||||
keyboardHeight = max(0.0, keyboardHeight - 24.0)
|
keyboardHeight = max(0.0, keyboardHeight - 24.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("keyboardHeight: \(keyboardHeight)")
|
||||||
|
|
||||||
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
|
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
|
||||||
if duration > Double.ulpOfOne {
|
if duration > Double.ulpOfOne {
|
||||||
duration = 0.5
|
duration = 0.5
|
||||||
@ -473,7 +476,12 @@ public class Window1 {
|
|||||||
|
|
||||||
self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
|
self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
|
var keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
|
||||||
|
if let keyboardView = strongSelf.statusBarHost?.keyboardView {
|
||||||
|
if keyboardFrame.width.isEqual(to: keyboardView.bounds.width) && keyboardFrame.height.isEqual(to: keyboardView.bounds.height) && keyboardFrame.minX.isEqual(to: keyboardView.frame.minX) {
|
||||||
|
keyboardFrame.origin.y = keyboardView.frame.minY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let screenHeight: CGFloat
|
let screenHeight: CGFloat
|
||||||
var inPopover = false
|
var inPopover = false
|
||||||
@ -488,10 +496,21 @@ public class Window1 {
|
|||||||
screenHeight = UIScreen.main.bounds.width
|
screenHeight = UIScreen.main.bounds.width
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
|
var keyboardHeight: CGFloat
|
||||||
|
if keyboardFrame.isEmpty || keyboardFrame.maxY < screenHeight {
|
||||||
|
keyboardHeight = 0.0
|
||||||
|
} else {
|
||||||
|
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
|
||||||
if inPopover {
|
if inPopover {
|
||||||
|
if strongSelf.windowLayout.onScreenNavigationHeight != nil {
|
||||||
|
keyboardHeight = max(0.0, keyboardHeight - 24.0)
|
||||||
|
} else {
|
||||||
keyboardHeight = max(0.0, keyboardHeight - 48.0)
|
keyboardHeight = max(0.0, keyboardHeight - 48.0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("keyboardHeight: \(keyboardHeight)")
|
||||||
|
|
||||||
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
|
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
|
||||||
if duration > Double.ulpOfOne {
|
if duration > Double.ulpOfOne {
|
||||||
@ -780,7 +799,7 @@ public class Window1 {
|
|||||||
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
||||||
self.tracingStatusBarsInvalidated = false
|
self.tracingStatusBarsInvalidated = false
|
||||||
|
|
||||||
if self.statusBarHidden {
|
/*if self.statusBarHidden {
|
||||||
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
|
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
|
||||||
} else {
|
} else {
|
||||||
var statusBarSurfaces: [StatusBarSurface] = []
|
var statusBarSurfaces: [StatusBarSurface] = []
|
||||||
@ -813,7 +832,7 @@ public class Window1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//keyboardManager.surfaces = keyboardSurfaces
|
//keyboardManager.surfaces = keyboardSurfaces*/
|
||||||
|
|
||||||
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||||
let orientationToLock: UIInterfaceOrientationMask
|
let orientationToLock: UIInterfaceOrientationMask
|
||||||
@ -947,12 +966,7 @@ public class Window1 {
|
|||||||
|
|
||||||
let boundsSize = updatingLayout.layout.size
|
let boundsSize = updatingLayout.layout.size
|
||||||
let isLandscape = boundsSize.width > boundsSize.height
|
let isLandscape = boundsSize.width > boundsSize.height
|
||||||
var statusBarHeight: CGFloat?
|
var statusBarHeight: CGFloat? = self.deviceMetrics.statusBarHeight(for: boundsSize)
|
||||||
if let statusBarHost = self.statusBarHost {
|
|
||||||
statusBarHeight = statusBarHost.statusBarFrame.size.height
|
|
||||||
} else {
|
|
||||||
statusBarHeight = self.deviceMetrics.statusBarHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.deviceMetrics.type == .tablet, let onScreenNavigationHeight = self.hostView.onScreenNavigationHeight, onScreenNavigationHeight != self.deviceMetrics.onScreenNavigationHeight(inLandscape: false) {
|
if self.deviceMetrics.type == .tablet, let onScreenNavigationHeight = self.hostView.onScreenNavigationHeight, onScreenNavigationHeight != self.deviceMetrics.onScreenNavigationHeight(inLandscape: false) {
|
||||||
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHeight ?? defaultStatusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight)
|
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHeight ?? defaultStatusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight)
|
||||||
|
|||||||
@ -5,13 +5,20 @@ import AsyncDisplayKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
public enum ItemListMultilineInputItemTextLimitMode {
|
||||||
|
case characters
|
||||||
|
case bytes
|
||||||
|
}
|
||||||
|
|
||||||
public struct ItemListMultilineInputItemTextLimit {
|
public struct ItemListMultilineInputItemTextLimit {
|
||||||
public let value: Int
|
public let value: Int
|
||||||
public let display: Bool
|
public let display: Bool
|
||||||
|
public let mode: ItemListMultilineInputItemTextLimitMode
|
||||||
|
|
||||||
public init(value: Int, display: Bool) {
|
public init(value: Int, display: Bool, mode: ItemListMultilineInputItemTextLimitMode = .characters) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.display = display
|
self.display = display
|
||||||
|
self.mode = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +193,13 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
|||||||
var rightInset: CGFloat = params.rightInset
|
var rightInset: CGFloat = params.rightInset
|
||||||
|
|
||||||
if let maxLength = item.maxLength, maxLength.display {
|
if let maxLength = item.maxLength, maxLength.display {
|
||||||
let textLength = item.text.count
|
let textLength: Int
|
||||||
|
switch maxLength.mode {
|
||||||
|
case .characters:
|
||||||
|
textLength = item.text.count
|
||||||
|
case .bytes:
|
||||||
|
textLength = item.text.data(using: .utf8, allowLossyConversion: true)?.count ?? 0
|
||||||
|
}
|
||||||
let displayTextLimit = textLength > maxLength.value * 70 / 100
|
let displayTextLimit = textLength > maxLength.value * 70 / 100
|
||||||
let remainingCount = maxLength.value - textLength
|
let remainingCount = maxLength.value - textLength
|
||||||
if displayTextLimit {
|
if displayTextLimit {
|
||||||
|
|||||||
@ -211,6 +211,9 @@ static NSData *base64_decode(NSString *str) {
|
|||||||
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
||||||
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
|
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
|
||||||
if (additionalSource != nil) {
|
if (additionalSource != nil) {
|
||||||
|
/*#if DEBUG
|
||||||
|
[signals removeAllObjects];
|
||||||
|
#endif*/
|
||||||
[signals addObject:additionalSource];
|
[signals addObject:additionalSource];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@ private final class OverlayStatusControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class OverlayStatusController: ViewController {
|
public final class OverlayStatusController: ViewController, StandalonePresentableController {
|
||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let type: OverlayStatusControllerType
|
private let type: OverlayStatusControllerType
|
||||||
|
|||||||
@ -641,17 +641,21 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if intro {
|
if intro {
|
||||||
var dismissInto: (() -> Void)?
|
var nextImpl: (() -> Void)?
|
||||||
let controller = PrivacyIntroController(context: context, mode: .twoStepVerification, arguments: PrivacyIntroControllerPresentationArguments(fadeIn: false, animateIn: true), proceedAction: {
|
let introController = PrivacyIntroController(context: context, mode: .twoStepVerification, proceedAction: {
|
||||||
pushControllerImpl?(twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: intro, data: data.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) })), openSetupPasswordImmediately: true), false)
|
nextImpl?()
|
||||||
dismissInto?()
|
|
||||||
})
|
})
|
||||||
dismissInto = { [weak controller] in
|
nextImpl = { [weak introController] in
|
||||||
controller?.dismiss()
|
guard let introController = introController, let navigationController = introController.navigationController as? NavigationController else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
presentControllerImpl?(controller)
|
let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: intro, data: data.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) })))
|
||||||
|
navigationController.replaceController(introController, with: controller, animated: true)
|
||||||
|
}
|
||||||
|
pushControllerImpl?(introController, true)
|
||||||
} else {
|
} else {
|
||||||
pushControllerImpl?(twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: intro, data: data.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) }))), true)
|
let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: intro, data: data.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) })))
|
||||||
|
pushControllerImpl?(controller, true)
|
||||||
}
|
}
|
||||||
}, openActiveSessions: {
|
}, openActiveSessions: {
|
||||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext), true)
|
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext), true)
|
||||||
|
|||||||
@ -24,6 +24,7 @@ framework(
|
|||||||
"//submodules/MtProtoKit:MtProtoKit#shared",
|
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||||
"//submodules/Postbox:Postbox#shared",
|
"//submodules/Postbox:Postbox#shared",
|
||||||
|
"//submodules/CloudData:CloudData",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
|||||||
@ -18,7 +18,8 @@ private func removeMessageMedia(message: Message, mediaBox: MediaBox) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId]) {
|
public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true) {
|
||||||
|
if deleteMedia {
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if id.peerId.namespace == Namespaces.Peer.SecretChat {
|
if id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
if let message = transaction.getMessage(id) {
|
if let message = transaction.getMessage(id) {
|
||||||
@ -26,8 +27,11 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
transaction.deleteMessages(ids, forEachMedia: { media in
|
transaction.deleteMessages(ids, forEachMedia: { media in
|
||||||
|
if deleteMedia {
|
||||||
processRemovedMedia(mediaBox, media)
|
processRemovedMedia(mediaBox, media)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,7 @@ public func resendMessages(account: Account, messageIds: [MessageId]) -> Signal<
|
|||||||
}
|
}
|
||||||
let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages.map { (false, $0) })
|
let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages.map { (false, $0) })
|
||||||
}
|
}
|
||||||
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: removeMessageIds)
|
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: removeMessageIds, deleteMedia: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import Foundation
|
|||||||
#else
|
#else
|
||||||
import MtProtoKitDynamic
|
import MtProtoKitDynamic
|
||||||
#endif
|
#endif
|
||||||
|
import CloudData
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public enum ConnectionStatus: Equatable {
|
public enum ConnectionStatus: Equatable {
|
||||||
@ -480,10 +481,26 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.keychain = keychain
|
context.keychain = keychain
|
||||||
context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: nil, phoneNumber: phoneNumber))
|
var wrappedAdditionalSource: MTSignal?
|
||||||
|
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
let additionalSource = cloudDataAdditionalAddressSource(phoneNumber: .single(phoneNumber ?? ""))
|
||||||
|
wrappedAdditionalSource = MTSignal(generator: { subscriber in
|
||||||
|
let disposable = additionalSource.start(next: { value in
|
||||||
|
subscriber?.putNext(value)
|
||||||
|
}, completed: {
|
||||||
|
subscriber?.putCompletion()
|
||||||
|
})
|
||||||
|
return MTBlockDisposable(block: {
|
||||||
|
disposable.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber))
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
//let _ = MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: nil, phoneNumber: phoneNumber).start(next: nil)
|
//let _ = MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber).start(next: nil)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let mtProto = MTProto(context: context, datacenterId: datacenterId, usageCalculationInfo: usageCalculationInfo(basePath: basePath, category: nil))!
|
let mtProto = MTProto(context: context, datacenterId: datacenterId, usageCalculationInfo: usageCalculationInfo(basePath: basePath, category: nil))!
|
||||||
@ -1027,3 +1044,11 @@ class Keychain: NSObject, MTKeychain {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeCloudDataContext() -> Any? {
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
return CloudDataContextImpl()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -292,12 +292,7 @@ public final class TonInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func sendGramsFromWallet(keychain: TonKeychain, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
|
fileprivate func sendGramsFromWallet(decryptedSecret: Data, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
|
||||||
return keychain.decrypt(walletInfo.encryptedSecret)
|
|
||||||
|> mapError { _ -> SendGramsFromWalletError in
|
|
||||||
return .secretDecryptionFailed
|
|
||||||
}
|
|
||||||
|> mapToSignal { decryptedSecret -> Signal<Never, SendGramsFromWalletError> in
|
|
||||||
let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
|
let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@ -334,7 +329,6 @@ public final class TonInstance {
|
|||||||
return disposable
|
return disposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func walletRestoreWords(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<[String], WalletRestoreWordsError> {
|
fileprivate func walletRestoreWords(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<[String], WalletRestoreWordsError> {
|
||||||
return keychain.decrypt(walletInfo.encryptedSecret)
|
return keychain.decrypt(walletInfo.encryptedSecret)
|
||||||
@ -440,10 +434,12 @@ public struct CombinedWalletState: Codable, Equatable {
|
|||||||
|
|
||||||
public struct WalletStateRecord: PostboxCoding, Equatable {
|
public struct WalletStateRecord: PostboxCoding, Equatable {
|
||||||
public let info: WalletInfo
|
public let info: WalletInfo
|
||||||
|
public var exportCompleted: Bool
|
||||||
public var state: CombinedWalletState?
|
public var state: CombinedWalletState?
|
||||||
|
|
||||||
public init(info: WalletInfo, state: CombinedWalletState?) {
|
public init(info: WalletInfo, exportCompleted: Bool, state: CombinedWalletState?) {
|
||||||
self.info = info
|
self.info = info
|
||||||
|
self.exportCompleted = exportCompleted
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,6 +447,7 @@ public struct WalletStateRecord: PostboxCoding, Equatable {
|
|||||||
self.info = decoder.decodeDataForKey("info").flatMap { data in
|
self.info = decoder.decodeDataForKey("info").flatMap { data in
|
||||||
return try? JSONDecoder().decode(WalletInfo.self, from: data)
|
return try? JSONDecoder().decode(WalletInfo.self, from: data)
|
||||||
} ?? WalletInfo(publicKey: WalletPublicKey(rawValue: ""), encryptedSecret: TonKeychainEncryptedData(publicKey: Data(), data: Data()))
|
} ?? WalletInfo(publicKey: WalletPublicKey(rawValue: ""), encryptedSecret: TonKeychainEncryptedData(publicKey: Data(), data: Data()))
|
||||||
|
self.exportCompleted = decoder.decodeInt32ForKey("exportCompleted", orElse: 0) != 0
|
||||||
self.state = decoder.decodeDataForKey("state").flatMap { data in
|
self.state = decoder.decodeDataForKey("state").flatMap { data in
|
||||||
return try? JSONDecoder().decode(CombinedWalletState.self, from: data)
|
return try? JSONDecoder().decode(CombinedWalletState.self, from: data)
|
||||||
}
|
}
|
||||||
@ -460,6 +457,7 @@ public struct WalletStateRecord: PostboxCoding, Equatable {
|
|||||||
if let data = try? JSONEncoder().encode(self.info) {
|
if let data = try? JSONEncoder().encode(self.info) {
|
||||||
encoder.encodeData(data, forKey: "info")
|
encoder.encodeData(data, forKey: "info")
|
||||||
}
|
}
|
||||||
|
encoder.encodeInt32(self.exportCompleted ? 1 : 0, forKey: "exportCompleted")
|
||||||
if let state = self.state, let data = try? JSONEncoder().encode(state) {
|
if let state = self.state, let data = try? JSONEncoder().encode(state) {
|
||||||
encoder.encodeData(data, forKey: "state")
|
encoder.encodeData(data, forKey: "state")
|
||||||
} else {
|
} else {
|
||||||
@ -521,7 +519,7 @@ public func createWallet(postbox: Postbox, network: Network, tonInstance: TonIns
|
|||||||
return postbox.transaction { transaction -> (WalletInfo, [String]) in
|
return postbox.transaction { transaction -> (WalletInfo, [String]) in
|
||||||
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
|
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
|
||||||
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
|
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
|
||||||
walletCollection.wallets = [WalletStateRecord(info: walletInfo, state: nil)]
|
walletCollection.wallets = [WalletStateRecord(info: walletInfo, exportCompleted: false, state: nil)]
|
||||||
return walletCollection
|
return walletCollection
|
||||||
})
|
})
|
||||||
return (walletInfo, wordList)
|
return (walletInfo, wordList)
|
||||||
@ -531,6 +529,21 @@ public func createWallet(postbox: Postbox, network: Network, tonInstance: TonIns
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func confirmWalletExported(postbox: Postbox, walletInfo: WalletInfo) -> Signal<Never, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
|
||||||
|
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
|
||||||
|
for i in 0 ..< walletCollection.wallets.count {
|
||||||
|
if walletCollection.wallets[i].info.publicKey == walletInfo.publicKey {
|
||||||
|
walletCollection.wallets[i].exportCompleted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walletCollection
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
private enum ImportWalletInternalError {
|
private enum ImportWalletInternalError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
@ -556,7 +569,7 @@ public func importWallet(postbox: Postbox, network: Network, tonInstance: TonIns
|
|||||||
return postbox.transaction { transaction -> WalletInfo in
|
return postbox.transaction { transaction -> WalletInfo in
|
||||||
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
|
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
|
||||||
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
|
var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
|
||||||
walletCollection.wallets = [WalletStateRecord(info: walletInfo, state: nil)]
|
walletCollection.wallets = [WalletStateRecord(info: walletInfo, exportCompleted: true, state: nil)]
|
||||||
return walletCollection
|
return walletCollection
|
||||||
})
|
})
|
||||||
return walletInfo
|
return walletInfo
|
||||||
@ -643,7 +656,14 @@ public enum CombinedWalletStateResult {
|
|||||||
case updated(CombinedWalletState)
|
case updated(CombinedWalletState)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getCombinedWalletState(postbox: Postbox, walletInfo: WalletInfo, tonInstance: TonInstance) -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> {
|
public enum CombinedWalletStateSubject {
|
||||||
|
case wallet(WalletInfo)
|
||||||
|
case address(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStateSubject, tonInstance: TonInstance) -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> {
|
||||||
|
switch subject {
|
||||||
|
case let .wallet(walletInfo):
|
||||||
return postbox.transaction { transaction -> CombinedWalletState? in
|
return postbox.transaction { transaction -> CombinedWalletState? in
|
||||||
let walletCollection = (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: [])
|
let walletCollection = (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: [])
|
||||||
for item in walletCollection.wallets {
|
for item in walletCollection.wallets {
|
||||||
@ -695,6 +715,27 @@ public func getCombinedWalletState(postbox: Postbox, walletInfo: WalletInfo, ton
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case let .address(address):
|
||||||
|
let updated = getWalletState(address: address, tonInstance: tonInstance)
|
||||||
|
|> mapError { _ -> GetCombinedWalletStateError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { walletState, syncUtime -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
|
||||||
|
let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError>
|
||||||
|
|
||||||
|
topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance)
|
||||||
|
|> mapError { _ -> GetCombinedWalletStateError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
return topTransactions
|
||||||
|
|> mapToSignal { topTransactions -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
|
||||||
|
let combinedState = CombinedWalletState(walletState: walletState, timestamp: syncUtime, topTransactions: topTransactions)
|
||||||
|
return .single(.updated(combinedState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .single(.cached(nil))
|
||||||
|
|> then(updated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SendGramsFromWalletError {
|
public enum SendGramsFromWalletError {
|
||||||
@ -704,17 +745,11 @@ public enum SendGramsFromWalletError {
|
|||||||
case destinationIsNotInitialized
|
case destinationIsNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, keychain: TonKeychain, walletInfo: WalletInfo, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
|
public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, serverSalt: Data, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal<Never, SendGramsFromWalletError> {
|
||||||
return getServerWalletSalt(network: network)
|
|
||||||
|> mapError { _ -> SendGramsFromWalletError in
|
|
||||||
return .generic
|
|
||||||
}
|
|
||||||
|> mapToSignal { serverSalt in
|
|
||||||
return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance)
|
return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance)
|
||||||
|> castError(SendGramsFromWalletError.self)
|
|> castError(SendGramsFromWalletError.self)
|
||||||
|> mapToSignal { fromAddress in
|
|> mapToSignal { fromAddress in
|
||||||
return tonInstance.sendGramsFromWallet(keychain: keychain, serverSalt: serverSalt, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId)
|
return tonInstance.sendGramsFromWallet(decryptedSecret: decryptedSecret, serverSalt: serverSalt, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -871,7 +906,7 @@ public enum GetServerWalletSaltError {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getServerWalletSalt(network: Network) -> Signal<Data, GetServerWalletSaltError> {
|
public func getServerWalletSalt(network: Network) -> Signal<Data, GetServerWalletSaltError> {
|
||||||
return network.request(Api.functions.wallet.getKeySecretSalt(revoke: .boolFalse))
|
return network.request(Api.functions.wallet.getKeySecretSalt(revoke: .boolFalse))
|
||||||
|> mapError { _ -> GetServerWalletSaltError in
|
|> mapError { _ -> GetServerWalletSaltError in
|
||||||
return .generic
|
return .generic
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -565,7 +565,7 @@ public struct PresentationResourcesChat {
|
|||||||
|
|
||||||
public static func chatHistoryNavigationButtonBadgeImage(_ theme: PresentationTheme) -> UIImage? {
|
public static func chatHistoryNavigationButtonBadgeImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatHistoryNavigationButtonBadgeImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatHistoryNavigationButtonBadgeImage.rawValue, { theme in
|
||||||
return generateStretchableFilledCircleImage(diameter: 18.0, color: theme.chat.historyNavigation.badgeBackgroundColor, strokeColor: theme.chat.historyNavigation.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)
|
return generateFilledCircleImage(diameter: 18.0, color: theme.chat.historyNavigation.badgeBackgroundColor, strokeColor: theme.chat.historyNavigation.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -231,6 +231,8 @@ final class SharedApplicationContext {
|
|||||||
|
|
||||||
private let deviceToken = Promise<Data?>(nil)
|
private let deviceToken = Promise<Data?>(nil)
|
||||||
|
|
||||||
|
private var cloudDataContext: Any?
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
precondition(!testIsLaunched)
|
precondition(!testIsLaunched)
|
||||||
testIsLaunched = true
|
testIsLaunched = true
|
||||||
@ -240,7 +242,6 @@ final class SharedApplicationContext {
|
|||||||
|
|
||||||
let launchStartTime = CFAbsoluteTimeGetCurrent()
|
let launchStartTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
|
|
||||||
let statusBarHost = ApplicationStatusBarHost()
|
let statusBarHost = ApplicationStatusBarHost()
|
||||||
let (window, hostView) = nativeWindowHostView()
|
let (window, hostView) = nativeWindowHostView()
|
||||||
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||||
@ -248,6 +249,8 @@ final class SharedApplicationContext {
|
|||||||
self.window = window
|
self.window = window
|
||||||
self.nativeWindow = window
|
self.nativeWindow = window
|
||||||
|
|
||||||
|
self.cloudDataContext = makeCloudDataContext()
|
||||||
|
|
||||||
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||||
@ -368,7 +371,7 @@ final class SharedApplicationContext {
|
|||||||
|> map { token in
|
|> map { token in
|
||||||
let data = buildConfig.bundleData(withAppToken: token)
|
let data = buildConfig.bundleData(withAppToken: token)
|
||||||
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
|
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
|
||||||
Logger.shared.log("data", "\(jsonString)")
|
//Logger.shared.log("data", "\(jsonString)")
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("data", "can't deserialize")
|
Logger.shared.log("data", "can't deserialize")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5777,7 +5777,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.recorderFeedback?.prepareImpact(.light)
|
self.recorderFeedback?.prepareImpact(.light)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: currentInputPanelFrame, context: self.context, peerId: peerId, slowmodeState: !self.presentationInterfaceState.isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, send: { [weak self] message in
|
self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: self.view.convert(currentInputPanelFrame, to: nil), context: self.context, peerId: peerId, slowmodeState: !self.presentationInterfaceState.isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, send: { [weak self] message in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||||
@ -5831,7 +5831,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
case .send:
|
case .send:
|
||||||
let _ = (audioRecorderValue.takenRecordedData() |> deliverOnMainQueue).start(next: { [weak self] data in
|
let _ = (audioRecorderValue.takenRecordedData()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
if let strongSelf = self, let data = data {
|
if let strongSelf = self, let data = data {
|
||||||
if data.duration < 0.5 {
|
if data.duration < 0.5 {
|
||||||
strongSelf.recorderFeedback?.error()
|
strongSelf.recorderFeedback?.error()
|
||||||
|
|||||||
@ -908,7 +908,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let menuHeight = controller.controllerNode.updateLayout(layout: layout, horizontalOrigin: globalSelfOrigin.x, transition: transition)
|
let menuHeight = controller.controllerNode.updateLayout(layout: layout, horizontalOrigin: globalSelfOrigin.x, transition: transition)
|
||||||
ensureTopInsetForOverlayHighlightedItems = menuHeight
|
ensureTopInsetForOverlayHighlightedItems = menuHeight
|
||||||
|
|
||||||
let bottomInset = containerInsets.bottom + inputPanelsHeight + UIScreenPixel
|
let bottomInset = containerInsets.bottom + inputPanelsHeight
|
||||||
let bottomFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset), size: CGSize(width: layout.size.width, height: max(0.0, bottomInset - (layout.inputHeight ?? 0.0))))
|
let bottomFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset), size: CGSize(width: layout.size.width, height: max(0.0, bottomInset - (layout.inputHeight ?? 0.0))))
|
||||||
|
|
||||||
let messageActionSheetBottomDimNode: ASDisplayNode
|
let messageActionSheetBottomDimNode: ASDisplayNode
|
||||||
@ -1077,8 +1077,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
transition.updateAlpha(node: accessoryPanelNode, alpha: 1.0)
|
transition.updateAlpha(node: accessoryPanelNode, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - inputPanelsHeight - insets.top - UIScreenPixel)))
|
let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - inputPanelsHeight - insets.top)))
|
||||||
let inputContextPanelsOverMainPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - (inputPanelSize == nil ? CGFloat(0.0) : inputPanelSize!.height) - insets.top - UIScreenPixel)))
|
let inputContextPanelsOverMainPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - (inputPanelSize == nil ? CGFloat(0.0) : inputPanelSize!.height) - insets.top)))
|
||||||
|
|
||||||
if let inputContextPanelNode = self.inputContextPanelNode {
|
if let inputContextPanelNode = self.inputContextPanelNode {
|
||||||
let panelFrame = inputContextPanelNode.placement == .overTextInput ? inputContextPanelsOverMainPanelFrame : inputContextPanelsFrame
|
let panelFrame = inputContextPanelNode.placement == .overTextInput ? inputContextPanelsOverMainPanelFrame : inputContextPanelsFrame
|
||||||
|
|||||||
@ -381,6 +381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
}
|
}
|
||||||
self.tapRecognizer = recognizer
|
self.tapRecognizer = recognizer
|
||||||
self.view.addGestureRecognizer(recognizer)
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
self.view.isExclusiveTouch = true
|
||||||
|
|
||||||
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
||||||
replyRecognizer.shouldBegin = { [weak self] in
|
replyRecognizer.shouldBegin = { [weak self] in
|
||||||
|
|||||||
@ -21,7 +21,7 @@ final class InstantVideoControllerRecordingStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class InstantVideoController: LegacyController {
|
final class InstantVideoController: LegacyController, StandalonePresentableController {
|
||||||
private var captureController: TGVideoMessageCaptureController?
|
private var captureController: TGVideoMessageCaptureController?
|
||||||
|
|
||||||
var onDismiss: (() -> Void)?
|
var onDismiss: (() -> Void)?
|
||||||
|
|||||||
@ -155,9 +155,15 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isProcessingContentOffsetChanged = false
|
||||||
self.peerSelectionNode.contentOffsetChanged = { [weak self] offset in
|
self.peerSelectionNode.contentOffsetChanged = { [weak self] offset in
|
||||||
|
if isProcessingContentOffsetChanged {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isProcessingContentOffsetChanged = true
|
||||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||||
searchContentNode.updateListVisibleContentOffset(offset)
|
searchContentNode.updateListVisibleContentOffset(offset)
|
||||||
|
isProcessingContentOffsetChanged = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -17,6 +17,7 @@ import UrlHandling
|
|||||||
import WalletUI
|
import WalletUI
|
||||||
import LegacyMediaPickerUI
|
import LegacyMediaPickerUI
|
||||||
import LocalMediaResources
|
import LocalMediaResources
|
||||||
|
import OverlayStatusController
|
||||||
|
|
||||||
private enum CallStatusText: Equatable {
|
private enum CallStatusText: Equatable {
|
||||||
case none
|
case none
|
||||||
@ -1031,10 +1032,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey, preferences in
|
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey, preferences in
|
||||||
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
||||||
guard let config = WalletConfiguration.with(appConfiguration: appConfiguration).config else {
|
let walletConfiguration = WalletConfiguration.with(appConfiguration: appConfiguration)
|
||||||
|
guard let config = walletConfiguration.config, let blockchainName = walletConfiguration.blockchainName else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tonContext = storedContext.context(config: config)
|
let tonContext = storedContext.context(config: config, blockchainName: blockchainName)
|
||||||
|
|
||||||
if wallets.wallets.isEmpty {
|
if wallets.wallets.isEmpty {
|
||||||
if let _ = currentPublicKey {
|
if let _ = currentPublicKey {
|
||||||
@ -1044,13 +1046,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let walletInfo = wallets.wallets[0].info
|
let walletInfo = wallets.wallets[0].info
|
||||||
|
let exportCompleted = wallets.wallets[0].exportCompleted
|
||||||
if let currentPublicKey = currentPublicKey {
|
if let currentPublicKey = currentPublicKey {
|
||||||
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
|
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
|
||||||
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|
||||||
|> deliverOnMainQueue).start(next: { address in
|
|> deliverOnMainQueue).start(next: { address in
|
||||||
switch walletContext {
|
switch walletContext {
|
||||||
case .generic:
|
case .generic:
|
||||||
present(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address))
|
if exportCompleted {
|
||||||
|
present(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild))
|
||||||
|
} else {
|
||||||
|
present(WalletSplashScreen(context: context, tonContext: tonContext, mode: .created(walletInfo, nil), walletCreatedPreloadState: nil))
|
||||||
|
}
|
||||||
case let .send(address, amount, comment):
|
case let .send(address, amount, comment):
|
||||||
present(walletSendScreen(context: context, tonContext: tonContext, randomId: arc4random64(), walletInfo: walletInfo, address: address, amount: amount, comment: comment))
|
present(walletSendScreen(context: context, tonContext: tonContext, randomId: arc4random64(), walletInfo: walletInfo, address: address, amount: amount, comment: comment))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,18 +3,20 @@ import TelegramCore
|
|||||||
|
|
||||||
public struct WalletConfiguration {
|
public struct WalletConfiguration {
|
||||||
static var defaultValue: WalletConfiguration {
|
static var defaultValue: WalletConfiguration {
|
||||||
return WalletConfiguration(config: nil)
|
return WalletConfiguration(config: nil, blockchainName: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public let config: String?
|
public let config: String?
|
||||||
|
public let blockchainName: String?
|
||||||
|
|
||||||
fileprivate init(config: String?) {
|
fileprivate init(config: String?, blockchainName: String?) {
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.blockchainName = blockchainName
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration {
|
public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration {
|
||||||
if let data = appConfiguration.data, let config = data["wallet_config"] as? String {
|
if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String {
|
||||||
return WalletConfiguration(config: config)
|
return WalletConfiguration(config: config, blockchainName: blockchainName)
|
||||||
} else {
|
} else {
|
||||||
return .defaultValue
|
return .defaultValue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,10 +71,9 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.animationNode = AnimatedStickerNode()
|
self.animationNode = AnimatedStickerNode()
|
||||||
if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") {
|
||||||
self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
|
self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, playbackMode: .once, mode: .direct)
|
||||||
self.animationNode.visibility = true
|
self.animationNode.visibility = true
|
||||||
}
|
}
|
||||||
self.animationNode.visibility = true
|
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|||||||
@ -16,8 +16,9 @@ import AnimationUI
|
|||||||
public final class WalletInfoScreen: ViewController {
|
public final class WalletInfoScreen: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let tonContext: TonContext
|
private let tonContext: TonContext
|
||||||
private let walletInfo: WalletInfo
|
private let walletInfo: WalletInfo?
|
||||||
private let address: String
|
private let address: String
|
||||||
|
private let enableDebugActions: Bool
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
@ -26,11 +27,12 @@ public final class WalletInfoScreen: ViewController {
|
|||||||
return self._ready
|
return self._ready
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String) {
|
public init(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo?, address: String, enableDebugActions: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.tonContext = tonContext
|
self.tonContext = tonContext
|
||||||
self.walletInfo = walletInfo
|
self.walletInfo = walletInfo
|
||||||
self.address = address
|
self.address = address
|
||||||
|
self.enableDebugActions = enableDebugActions
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
@ -45,7 +47,9 @@ public final class WalletInfoScreen: ViewController {
|
|||||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
if let _ = walletInfo {
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Wallet/NavigationSettingsIcon"), color: .white), style: .plain, target: self, action: #selector(self.settingsPressed))
|
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.scrollToTop = { [weak self] in
|
||||||
(self?.displayNode as? WalletInfoScreenNode)?.scrollToTop()
|
(self?.displayNode as? WalletInfoScreenNode)?.scrollToTop()
|
||||||
@ -61,25 +65,27 @@ public final class WalletInfoScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func settingsPressed() {
|
@objc private func settingsPressed() {
|
||||||
self.push(walletSettingsController(context: self.context, tonContext: self.tonContext, walletInfo: self.walletInfo))
|
if let walletInfo = self.walletInfo {
|
||||||
|
self.push(walletSettingsController(context: self.context, tonContext: self.tonContext, walletInfo: walletInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = WalletInfoScreenNode(account: self.context.account, tonContext: self.tonContext, presentationData: self.presentationData, walletInfo: self.walletInfo, address: self.address, sendAction: { [weak self] in
|
self.displayNode = WalletInfoScreenNode(account: self.context.account, tonContext: self.tonContext, presentationData: self.presentationData, walletInfo: self.walletInfo, address: self.address, sendAction: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.push(walletSendScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, randomId: arc4random64(), walletInfo: strongSelf.walletInfo))
|
strongSelf.push(walletSendScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, randomId: arc4random64(), walletInfo: walletInfo))
|
||||||
}, receiveAction: { [weak self] in
|
}, receiveAction: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.push(walletReceiveScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, address: strongSelf.address))
|
strongSelf.push(walletReceiveScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: strongSelf.address))
|
||||||
}, openTransaction: { [weak self] transaction in
|
}, openTransaction: { [weak self] transaction in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, walletTransaction: transaction))
|
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, walletTransaction: transaction, enableDebugActions: strongSelf.enableDebugActions))
|
||||||
}, present: { [weak self] c, a in
|
}, present: { [weak self] c, a in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -154,16 +160,16 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
|
|||||||
func update(width: CGFloat, scaleTransition: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
func update(width: CGFloat, scaleTransition: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
let balanceIconSpacing: CGFloat = scaleTransition * 0.0 + (1.0 - scaleTransition) * (-12.0)
|
let balanceIconSpacing: CGFloat = scaleTransition * 0.0 + (1.0 - scaleTransition) * (-12.0)
|
||||||
let balanceVerticalIconOffset: CGFloat = scaleTransition * (-6.0) + (1.0 - scaleTransition) * (-2.0)
|
let balanceVerticalIconOffset: CGFloat = scaleTransition * (-2.0) + (1.0 - scaleTransition) * (-2.0)
|
||||||
|
|
||||||
let balanceIntegralTextSize = self.balanceIntegralTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
|
let balanceIntegralTextSize = self.balanceIntegralTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
|
||||||
let balanceFractionalTextSize = self.balanceFractionalTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
|
let balanceFractionalTextSize = self.balanceFractionalTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
|
||||||
let balanceIconSize = CGSize(width: 60.0, height: 60.0)
|
let balanceIconSize = CGSize(width: 50.0, height: 50.0)
|
||||||
|
|
||||||
let integralScale: CGFloat = scaleTransition * 1.0 + (1.0 - scaleTransition) * 0.8
|
let integralScale: CGFloat = scaleTransition * 1.0 + (1.0 - scaleTransition) * 0.8
|
||||||
let fractionalScale: CGFloat = scaleTransition * 0.5 + (1.0 - scaleTransition) * 0.8
|
let fractionalScale: CGFloat = scaleTransition * 0.5 + (1.0 - scaleTransition) * 0.8
|
||||||
|
|
||||||
let balanceOrigin = CGPoint(x: floor((width - balanceIntegralTextSize.width * integralScale - balanceFractionalTextSize.width * fractionalScale - balanceIconSpacing - balanceIconSize.width / 2.0) / 2.0), y: 0.0)
|
let balanceOrigin = CGPoint(x: floor((width - balanceIntegralTextSize.width * integralScale - balanceFractionalTextSize.width * fractionalScale - balanceIconSpacing + balanceIconSize.width / 2.0) / 2.0), y: 0.0)
|
||||||
|
|
||||||
let balanceIntegralTextFrame = CGRect(origin: balanceOrigin, size: balanceIntegralTextSize)
|
let balanceIntegralTextFrame = CGRect(origin: balanceOrigin, size: balanceIntegralTextSize)
|
||||||
let apparentBalanceIntegralTextFrame = CGRect(origin: balanceIntegralTextFrame.origin, size: CGSize(width: balanceIntegralTextFrame.width * integralScale, height: balanceIntegralTextFrame.height * integralScale))
|
let apparentBalanceIntegralTextFrame = CGRect(origin: balanceIntegralTextFrame.origin, size: CGSize(width: balanceIntegralTextFrame.width * integralScale, height: balanceIntegralTextFrame.height * integralScale))
|
||||||
@ -174,7 +180,7 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
|
|||||||
balanceFractionalTextFrame.origin.y += balanceFractionalTextFrame.height * 0.5 * (0.8 - fractionalScale)
|
balanceFractionalTextFrame.origin.y += balanceFractionalTextFrame.height * 0.5 * (0.8 - fractionalScale)
|
||||||
|
|
||||||
let balanceIconFrame: CGRect
|
let balanceIconFrame: CGRect
|
||||||
balanceIconFrame = CGRect(origin: CGPoint(x: apparentBalanceFractionalTextFrame.maxX + balanceIconSpacing * integralScale, y: balanceIntegralTextFrame.midY - balanceIconSize.height / 2.0 + balanceVerticalIconOffset), size: balanceIconSize)
|
balanceIconFrame = CGRect(origin: CGPoint(x: apparentBalanceIntegralTextFrame.minX - balanceIconSize.width - balanceIconSpacing * integralScale, y: balanceIntegralTextFrame.midY - balanceIconSize.height / 2.0 + balanceVerticalIconOffset), size: balanceIconSize)
|
||||||
|
|
||||||
transition.updateFrameAsPositionAndBounds(node: self.balanceIntegralTextNode, frame: balanceIntegralTextFrame)
|
transition.updateFrameAsPositionAndBounds(node: self.balanceIntegralTextNode, frame: balanceIntegralTextFrame)
|
||||||
transition.updateTransformScale(node: self.balanceIntegralTextNode, scale: integralScale)
|
transition.updateTransformScale(node: self.balanceIntegralTextNode, scale: integralScale)
|
||||||
@ -194,6 +200,8 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
var isRefreshing: Bool = false
|
var isRefreshing: Bool = false
|
||||||
var timestamp: Int32?
|
var timestamp: Int32?
|
||||||
|
|
||||||
|
private let hasActions: Bool
|
||||||
|
|
||||||
let balanceNode: WalletInfoBalanceNode
|
let balanceNode: WalletInfoBalanceNode
|
||||||
private let refreshNode: WalletRefreshNode
|
private let refreshNode: WalletRefreshNode
|
||||||
private let balanceSubtitleNode: ImmediateTextNode
|
private let balanceSubtitleNode: ImmediateTextNode
|
||||||
@ -202,12 +210,14 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
private let headerBackgroundNode: ASDisplayNode
|
private let headerBackgroundNode: ASDisplayNode
|
||||||
private let headerCornerNode: ASImageNode
|
private let headerCornerNode: ASImageNode
|
||||||
|
|
||||||
init(account: Account, presentationData: PresentationData, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
init(account: Account, presentationData: PresentationData, hasActions: Bool, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
||||||
|
self.hasActions = hasActions
|
||||||
|
|
||||||
self.balanceNode = WalletInfoBalanceNode(account: account, theme: presentationData.theme, dateTimeFormat: presentationData.dateTimeFormat)
|
self.balanceNode = WalletInfoBalanceNode(account: account, theme: presentationData.theme, dateTimeFormat: presentationData.dateTimeFormat)
|
||||||
|
|
||||||
self.balanceSubtitleNode = ImmediateTextNode()
|
self.balanceSubtitleNode = ImmediateTextNode()
|
||||||
self.balanceSubtitleNode.displaysAsynchronously = false
|
self.balanceSubtitleNode.displaysAsynchronously = false
|
||||||
self.balanceSubtitleNode.attributedText = NSAttributedString(string: presentationData.strings.Wallet_Info_YourBalance, font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
|
self.balanceSubtitleNode.attributedText = NSAttributedString(string: hasActions ? presentationData.strings.Wallet_Info_YourBalance : "balance", font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
|
||||||
|
|
||||||
self.headerBackgroundNode = ASDisplayNode()
|
self.headerBackgroundNode = ASDisplayNode()
|
||||||
self.headerBackgroundNode.backgroundColor = .black
|
self.headerBackgroundNode.backgroundColor = .black
|
||||||
@ -233,8 +243,10 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.addSubnode(self.headerBackgroundNode)
|
self.addSubnode(self.headerBackgroundNode)
|
||||||
self.addSubnode(self.headerCornerNode)
|
self.addSubnode(self.headerCornerNode)
|
||||||
|
if hasActions {
|
||||||
self.addSubnode(self.receiveButtonNode)
|
self.addSubnode(self.receiveButtonNode)
|
||||||
self.addSubnode(self.sendButtonNode)
|
self.addSubnode(self.sendButtonNode)
|
||||||
|
}
|
||||||
self.addSubnode(self.balanceNode)
|
self.addSubnode(self.balanceNode)
|
||||||
self.addSubnode(self.balanceSubtitleNode)
|
self.addSubnode(self.balanceSubtitleNode)
|
||||||
self.addSubnode(self.refreshNode)
|
self.addSubnode(self.refreshNode)
|
||||||
@ -247,7 +259,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition, isScrolling: Bool) {
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
let buttonSideInset: CGFloat = 48.0
|
let buttonSideInset: CGFloat = 48.0
|
||||||
let titleSpacing: CGFloat = 10.0
|
let titleSpacing: CGFloat = 10.0
|
||||||
@ -255,6 +267,15 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
let buttonHeight: CGFloat = 50.0
|
let buttonHeight: CGFloat = 50.0
|
||||||
let balanceSubtitleSpacing: CGFloat = 0.0
|
let balanceSubtitleSpacing: CGFloat = 0.0
|
||||||
|
|
||||||
|
let alphaTransition: ContainedViewLayoutTransition
|
||||||
|
if transition.isAnimated {
|
||||||
|
alphaTransition = transition
|
||||||
|
} else if isScrolling {
|
||||||
|
alphaTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
} else {
|
||||||
|
alphaTransition = transition
|
||||||
|
}
|
||||||
|
|
||||||
let minOffset = navigationHeight
|
let minOffset = navigationHeight
|
||||||
let maxOffset = size.height
|
let maxOffset = size.height
|
||||||
|
|
||||||
@ -266,7 +287,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
let minButtonsOffset = maxOffset - buttonHeight - sideInset
|
let minButtonsOffset = maxOffset - buttonHeight - sideInset
|
||||||
let maxButtonsOffset = maxOffset
|
let maxButtonsOffset = maxOffset
|
||||||
let buttonTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minButtonsOffset) / (maxButtonsOffset - minButtonsOffset)))
|
let buttonTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minButtonsOffset) / (maxButtonsOffset - minButtonsOffset)))
|
||||||
let buttonAlpha = buttonTransition * 1.0
|
let buttonAlpha: CGFloat = buttonTransition
|
||||||
|
|
||||||
let balanceSubtitleSize = self.balanceSubtitleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: 200.0))
|
let balanceSubtitleSize = self.balanceSubtitleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: 200.0))
|
||||||
|
|
||||||
@ -341,6 +362,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
self.balanceSubtitleNode.isHidden = false
|
self.balanceSubtitleNode.isHidden = false
|
||||||
self.refreshNode.isHidden = false
|
self.refreshNode.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
|
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
|
||||||
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
|
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
|
||||||
self.receiveButtonNode.updateLayout(width: receiveButtonFrame.width, transition: transition)
|
self.receiveButtonNode.updateLayout(width: receiveButtonFrame.width, transition: transition)
|
||||||
@ -438,11 +460,12 @@ private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEn
|
|||||||
return WalletInfoListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
return WalletInfoListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let tonContext: TonContext
|
private let tonContext: TonContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let walletInfo: WalletInfo
|
private let walletInfo: WalletInfo?
|
||||||
private let address: String
|
private let address: String
|
||||||
|
|
||||||
private let openTransaction: (WalletTransaction) -> Void
|
private let openTransaction: (WalletTransaction) -> Void
|
||||||
@ -476,7 +499,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var updateTimestampTimer: SwiftSignalKit.Timer?
|
private var updateTimestampTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo?, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.tonContext = tonContext
|
self.tonContext = tonContext
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -485,12 +508,13 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
self.openTransaction = openTransaction
|
self.openTransaction = openTransaction
|
||||||
self.present = present
|
self.present = present
|
||||||
|
|
||||||
self.headerNode = WalletInfoHeaderNode(account: account, presentationData: presentationData, sendAction: sendAction, receiveAction: receiveAction)
|
self.headerNode = WalletInfoHeaderNode(account: account, presentationData: presentationData, hasActions: walletInfo != nil, sendAction: sendAction, receiveAction: receiveAction)
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
||||||
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
||||||
self.listNode.isHidden = true
|
self.listNode.isHidden = true
|
||||||
|
self.listNode.view.disablesInteractiveModalDismiss = true
|
||||||
|
|
||||||
self.loadingIndicator = UIActivityIndicatorView(style: .whiteLarge)
|
self.loadingIndicator = UIActivityIndicatorView(style: .whiteLarge)
|
||||||
|
|
||||||
@ -527,7 +551,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
strongSelf.refreshTransactions()
|
strongSelf.refreshTransactions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: offset, transition: listTransition)
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: offset, transition: listTransition, isScrolling: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +638,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: visualHeaderHeight))
|
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: visualHeaderHeight))
|
||||||
transition.updateFrame(node: self.headerNode, frame: headerFrame)
|
transition.updateFrame(node: self.headerNode, frame: headerFrame)
|
||||||
self.headerNode.update(size: headerFrame.size, navigationHeight: navigationHeight, offset: visualHeaderOffset, transition: transition)
|
self.headerNode.update(size: headerFrame.size, navigationHeight: navigationHeight, offset: visualHeaderOffset, transition: transition, isScrolling: false)
|
||||||
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: visualListOffset), size: layout.size))
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: visualListOffset), size: layout.size))
|
||||||
|
|
||||||
@ -656,7 +680,14 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
self.headerNode.isRefreshing = true
|
self.headerNode.isRefreshing = true
|
||||||
|
|
||||||
self.stateDisposable.set((getCombinedWalletState(postbox: self.account.postbox, walletInfo: self.walletInfo, tonInstance: self.tonContext.instance)
|
let subject: CombinedWalletStateSubject
|
||||||
|
if let walletInfo = self.walletInfo {
|
||||||
|
subject = .wallet(walletInfo)
|
||||||
|
} else {
|
||||||
|
subject = .address(self.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stateDisposable.set((getCombinedWalletState(postbox: self.account.postbox, subject: subject, tonInstance: self.tonContext.instance)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -694,7 +725,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
|
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
|
||||||
|
|
||||||
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate, isScrolling: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions)
|
strongSelf.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions)
|
||||||
@ -702,7 +733,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
strongSelf.headerNode.isRefreshing = false
|
strongSelf.headerNode.isRefreshing = false
|
||||||
|
|
||||||
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut), isScrolling: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let wasReady = strongSelf.isReady
|
let wasReady = strongSelf.isReady
|
||||||
@ -710,7 +741,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
if strongSelf.isReady && !wasReady {
|
if strongSelf.isReady && !wasReady {
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate)
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.becameReady(animated: strongSelf.didSetContentReady)
|
strongSelf.becameReady(animated: strongSelf.didSetContentReady)
|
||||||
@ -733,7 +764,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate, isScrolling: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.loadingMoreTransactions = false
|
strongSelf.loadingMoreTransactions = false
|
||||||
@ -742,7 +773,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
strongSelf.headerNode.isRefreshing = false
|
strongSelf.headerNode.isRefreshing = false
|
||||||
|
|
||||||
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
|
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut), isScrolling: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strongSelf.didSetContentReady {
|
if !strongSelf.didSetContentReady {
|
||||||
|
|||||||
@ -94,6 +94,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let titleSignNode: TextNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let directionNode: TextNode
|
private let directionNode: TextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
@ -113,6 +114,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
self.bottomStripeNode = ASDisplayNode()
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
self.bottomStripeNode.isLayerBacked = true
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.titleSignNode = TextNode()
|
||||||
|
self.titleSignNode.isUserInteractionEnabled = false
|
||||||
|
self.titleSignNode.contentMode = .left
|
||||||
|
self.titleSignNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.contentMode = .left
|
self.titleNode.contentMode = .left
|
||||||
@ -149,6 +155,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.titleSignNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
self.addSubnode(self.directionNode)
|
self.addSubnode(self.directionNode)
|
||||||
@ -160,6 +167,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: WalletInfoTransactionItem, _ params: ListViewItemLayoutParams, _ hasPrevious: Bool, _ hasNext: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: WalletInfoTransactionItem, _ params: ListViewItemLayoutParams, _ hasPrevious: Bool, _ hasNext: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTitleSignLayout = TextNode.asyncLayout(self.titleSignNode)
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
|
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
@ -175,10 +183,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
updatedTheme = item.theme
|
updatedTheme = item.theme
|
||||||
}
|
}
|
||||||
let iconImage: UIImage? = transactionIcon
|
let iconImage: UIImage? = transactionIcon
|
||||||
let iconSize = iconImage?.size ?? CGSize(width: 10.0, height: 10.0)
|
let iconSize = /*iconImage?.size ??*/ CGSize(width: 14.0, height: 12.0)
|
||||||
|
|
||||||
let leftInset = 16.0 + params.leftInset
|
let leftInset = 16.0 + params.leftInset
|
||||||
|
|
||||||
|
let sign: String
|
||||||
let title: String
|
let title: String
|
||||||
let directionText: String
|
let directionText: String
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
@ -186,7 +195,8 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var description: String = ""
|
var description: String = ""
|
||||||
if transferredValue <= 0 {
|
if transferredValue <= 0 {
|
||||||
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
sign = "-"
|
||||||
|
title = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||||
titleColor = item.theme.list.itemPrimaryTextColor
|
titleColor = item.theme.list.itemPrimaryTextColor
|
||||||
if item.walletTransaction.outMessages.isEmpty {
|
if item.walletTransaction.outMessages.isEmpty {
|
||||||
directionText = ""
|
directionText = ""
|
||||||
@ -206,7 +216,8 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
sign = "+"
|
||||||
|
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||||
titleColor = item.theme.chatList.secretTitleColor
|
titleColor = item.theme.chatList.secretTitleColor
|
||||||
directionText = item.strings.Wallet_Info_TransactionFrom
|
directionText = item.strings.Wallet_Info_TransactionFrom
|
||||||
if let inMessage = item.walletTransaction.inMessage {
|
if let inMessage = item.walletTransaction.inMessage {
|
||||||
@ -233,6 +244,10 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
titleString.append(NSAttributedString(string: title, font: Font.bold(17.0), textColor: titleColor))
|
titleString.append(NSAttributedString(string: title, font: Font.bold(17.0), textColor: titleColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let signString = NSAttributedString(string: sign, font: Font.bold(17.0), textColor: titleColor)
|
||||||
|
|
||||||
|
let (titleSignLayout, titleSignApply) = makeTitleSignLayout(TextNodeLayoutArguments(attributedString: signString, 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 (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, 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 (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, 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 (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()))
|
||||||
@ -282,6 +297,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
strongSelf.iconNode.image = iconImage
|
strongSelf.iconNode.image = iconImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = titleSignApply()
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
let _ = descriptionApply()
|
let _ = descriptionApply()
|
||||||
@ -299,13 +315,16 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
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)
|
let titleSignFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleSignLayout.size)
|
||||||
strongSelf.titleNode.frame = titleFrame
|
strongSelf.titleSignNode.frame = titleSignFrame
|
||||||
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) / 2.0) - 1.0), size: iconSize)
|
let iconFrame = CGRect(origin: CGPoint(x: titleSignFrame.maxX + 1.0, y: titleSignFrame.minY + floor((titleSignFrame.height - iconSize.height) / 2.0) - 1.0), size: iconSize)
|
||||||
strongSelf.iconNode.frame = iconFrame
|
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)
|
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + 1.0, y: topInset), size: titleLayout.size)
|
||||||
|
strongSelf.titleNode.frame = titleFrame
|
||||||
|
|
||||||
|
let directionFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.maxY - directionLayout.size.height - 1.0), size: directionLayout.size)
|
||||||
strongSelf.directionNode.frame = directionFrame
|
strongSelf.directionNode.frame = directionFrame
|
||||||
|
|
||||||
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size)
|
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size)
|
||||||
@ -402,17 +421,17 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
|
|||||||
var timeinfo: tm = tm()
|
var timeinfo: tm = tm()
|
||||||
localtime_r(&time, &timeinfo)
|
localtime_r(&time, &timeinfo)
|
||||||
|
|
||||||
self.roundedTimestamp = timeinfo.tm_year * 100 + timeinfo.tm_mon
|
|
||||||
self.month = timeinfo.tm_mon
|
self.month = timeinfo.tm_mon
|
||||||
self.year = timeinfo.tm_year
|
self.year = timeinfo.tm_year
|
||||||
|
|
||||||
self.id = Int64(self.roundedTimestamp)
|
|
||||||
|
|
||||||
if timestamp == Int32.max {
|
if timestamp == Int32.max {
|
||||||
self.localTimestamp = timestamp / (granularity) * (granularity)
|
self.localTimestamp = timestamp / (granularity) * (granularity)
|
||||||
} else {
|
} else {
|
||||||
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
|
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.roundedTimestamp = self.localTimestamp
|
||||||
|
self.id = Int64(self.roundedTimestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let stickDirection: ListViewItemHeaderStickDirection = .top
|
let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||||
|
|||||||
@ -80,7 +80,7 @@ public final class WalletPasscodeScreen: ViewController {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sending(walletInfo, address, amount, comment, strongSelf.randomId), walletCreatedPreloadState: nil))
|
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sending(walletInfo, address, amount, comment, strongSelf.randomId, Data()), walletCreatedPreloadState: nil))
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.endEditing(true)
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,10 @@ import TextFormat
|
|||||||
import DeviceAccess
|
import DeviceAccess
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import UrlHandling
|
import UrlHandling
|
||||||
|
import OverlayStatusController
|
||||||
|
|
||||||
private let balanceIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
|
private let balanceIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
|
||||||
|
private let textLimit: Int = 124
|
||||||
|
|
||||||
private final class WalletSendScreenArguments {
|
private final class WalletSendScreenArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -226,7 +228,7 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
|||||||
case let .commentHeader(theme, text):
|
case let .commentHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .comment(theme, placeholder, value):
|
case let .comment(theme, placeholder, value):
|
||||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 124, display: true), sectionId: self.section, style: .blocks, returnKeyType: .send, textUpdated: { text in
|
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: textLimit, display: true, mode: .bytes), sectionId: self.section, style: .blocks, returnKeyType: .send, textUpdated: { text in
|
||||||
arguments.updateText(WalletSendScreenEntryTag.comment, text)
|
arguments.updateText(WalletSendScreenEntryTag.comment, text)
|
||||||
}, updatedFocus: { focus in
|
}, updatedFocus: { focus in
|
||||||
if focus {
|
if focus {
|
||||||
@ -279,6 +281,13 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
|
|||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let serverSaltValue = Promise<Data?>()
|
||||||
|
serverSaltValue.set(getServerWalletSalt(network: context.account.network)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Data?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
})
|
||||||
|
|
||||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||||
var presentInGlobalOverlayImpl: ((ViewController, Any?) -> Void)?
|
var presentInGlobalOverlayImpl: ((ViewController, Any?) -> Void)?
|
||||||
var pushImpl: ((ViewController) -> Void)?
|
var pushImpl: ((ViewController) -> Void)?
|
||||||
@ -372,7 +381,37 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
|
|||||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Wallet_Send_ConfirmationConfirm, action: {
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Wallet_Send_ConfirmationConfirm, action: {
|
||||||
dismissAlertImpl?(false)
|
dismissAlertImpl?(false)
|
||||||
dismissInputImpl?()
|
dismissInputImpl?()
|
||||||
pushImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .sending(walletInfo, state.address, amount, state.comment, randomId), walletCreatedPreloadState: nil))
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.15, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
|
var serverSaltSignal = serverSaltValue.get()
|
||||||
|
|> take(1)
|
||||||
|
|
||||||
|
serverSaltSignal = serverSaltSignal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (serverSaltSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { serverSalt in
|
||||||
|
if let serverSalt = serverSalt {
|
||||||
|
pushImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .sending(walletInfo, state.address, amount, state.comment, randomId, serverSalt), walletCreatedPreloadState: nil))
|
||||||
|
}
|
||||||
|
})
|
||||||
})], allowInputInset: false, dismissAutomatically: false)
|
})], allowInputInset: false, dismissAutomatically: false)
|
||||||
presentInGlobalOverlayImpl?(controller, nil)
|
presentInGlobalOverlayImpl?(controller, nil)
|
||||||
|
|
||||||
@ -385,7 +424,7 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let walletState: Signal<WalletState?, NoError> = getCombinedWalletState(postbox: context.account.postbox, walletInfo: walletInfo, tonInstance: tonContext.instance)
|
let walletState: Signal<WalletState?, NoError> = getCombinedWalletState(postbox: context.account.postbox, subject: .wallet(walletInfo), tonInstance: tonContext.instance)
|
||||||
|> map { combinedState in
|
|> map { combinedState in
|
||||||
var state: WalletState?
|
var state: WalletState?
|
||||||
switch combinedState {
|
switch combinedState {
|
||||||
@ -416,7 +455,9 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
|
|||||||
let amount = amountValue(state.amount)
|
let amount = amountValue(state.amount)
|
||||||
var sendEnabled = false
|
var sendEnabled = false
|
||||||
if let balance = balance {
|
if let balance = balance {
|
||||||
sendEnabled = isValidAddress(state.address, exactLength: true) && amount > 0 && amount <= balance.balance && state.comment.count <= 124
|
let textLength: Int = state.comment.data(using: .utf8, allowLossyConversion: true)?.count ?? 0
|
||||||
|
|
||||||
|
sendEnabled = isValidAddress(state.address, exactLength: true) && amount > 0 && amount <= balance.balance && textLength <= textLimit
|
||||||
}
|
}
|
||||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Send_Send), style: .bold, enabled: sendEnabled, action: {
|
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Send_Send), style: .bold, enabled: sendEnabled, action: {
|
||||||
arguments.proceed()
|
arguments.proceed()
|
||||||
|
|||||||
@ -31,12 +31,13 @@ private enum WalletSettingsSection: Int32 {
|
|||||||
private enum WalletSettingsEntry: ItemListNodeEntry {
|
private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||||
case exportWallet(PresentationTheme, String)
|
case exportWallet(PresentationTheme, String)
|
||||||
case deleteWallet(PresentationTheme, String)
|
case deleteWallet(PresentationTheme, String)
|
||||||
|
case deleteWalletInfo(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .exportWallet:
|
case .exportWallet:
|
||||||
return WalletSettingsSection.exportWallet.rawValue
|
return WalletSettingsSection.exportWallet.rawValue
|
||||||
case .deleteWallet:
|
case .deleteWallet, .deleteWalletInfo:
|
||||||
return WalletSettingsSection.deleteWallet.rawValue
|
return WalletSettingsSection.deleteWallet.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +48,8 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
|
|||||||
return 0
|
return 0
|
||||||
case .deleteWallet:
|
case .deleteWallet:
|
||||||
return 1
|
return 1
|
||||||
|
case .deleteWalletInfo:
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +67,8 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
|
|||||||
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.deleteWallet()
|
arguments.deleteWallet()
|
||||||
})
|
})
|
||||||
|
case let .deleteWalletInfo(theme, text):
|
||||||
|
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,6 +81,8 @@ private func walletSettingsControllerEntries(presentationData: PresentationData,
|
|||||||
|
|
||||||
entries.append(.exportWallet(presentationData.theme, "Export Wallet"))
|
entries.append(.exportWallet(presentationData.theme, "Export Wallet"))
|
||||||
entries.append(.deleteWallet(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWallet))
|
entries.append(.deleteWallet(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWallet))
|
||||||
|
entries.append(.deleteWalletInfo(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWalletInfo))
|
||||||
|
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
@ -108,6 +115,7 @@ public func walletSettingsController(context: AccountContext, tonContext: TonCon
|
|||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: presentationData.strings.Wallet_Settings_DeleteWalletInfo),
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Wallet_Settings_DeleteWallet, color: .destructive, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: presentationData.strings.Wallet_Settings_DeleteWallet, color: .destructive, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||||
|
|||||||
@ -23,10 +23,10 @@ public enum WalletSecureStorageResetReason {
|
|||||||
|
|
||||||
public enum WalletSplashMode {
|
public enum WalletSplashMode {
|
||||||
case intro
|
case intro
|
||||||
case created(WalletInfo, [String])
|
case created(WalletInfo, [String]?)
|
||||||
case success(WalletInfo)
|
case success(WalletInfo)
|
||||||
case restoreFailed
|
case restoreFailed
|
||||||
case sending(WalletInfo, String, Int64, String, Int64)
|
case sending(WalletInfo, String, Int64, String, Int64, Data)
|
||||||
case sent(WalletInfo, Int64)
|
case sent(WalletInfo, Int64)
|
||||||
case secureStorageNotAvailable
|
case secureStorageNotAvailable
|
||||||
case secureStorageReset(WalletSecureStorageResetReason)
|
case secureStorageReset(WalletSecureStorageResetReason)
|
||||||
@ -36,7 +36,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let tonContext: TonContext
|
private let tonContext: TonContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let mode: WalletSplashMode
|
private var mode: WalletSplashMode
|
||||||
|
|
||||||
private let walletCreatedPreloadState: Promise<CombinedWalletStateResult>?
|
private let walletCreatedPreloadState: Promise<CombinedWalletStateResult>?
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
self.walletCreatedPreloadState = walletCreatedPreloadState
|
self.walletCreatedPreloadState = walletCreatedPreloadState
|
||||||
} else {
|
} else {
|
||||||
self.walletCreatedPreloadState = Promise()
|
self.walletCreatedPreloadState = Promise()
|
||||||
self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.account.postbox, walletInfo: walletInfo, tonInstance: tonContext.instance)
|
self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.account.postbox, subject: .wallet(walletInfo), tonInstance: tonContext.instance)
|
||||||
|> `catch` { _ -> Signal<CombinedWalletStateResult, NoError> in
|
|> `catch` { _ -> Signal<CombinedWalletStateResult, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
})
|
})
|
||||||
@ -66,7 +66,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
self.walletCreatedPreloadState = walletCreatedPreloadState
|
self.walletCreatedPreloadState = walletCreatedPreloadState
|
||||||
} else {
|
} else {
|
||||||
self.walletCreatedPreloadState = Promise()
|
self.walletCreatedPreloadState = Promise()
|
||||||
self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.account.postbox, walletInfo: walletInfo, tonInstance: tonContext.instance)
|
self.walletCreatedPreloadState?.set(getCombinedWalletState(postbox: context.account.postbox, subject: .wallet(walletInfo), tonInstance: tonContext.instance)
|
||||||
|> `catch` { _ -> Signal<CombinedWalletStateResult, NoError> in
|
|> `catch` { _ -> Signal<CombinedWalletStateResult, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
})
|
})
|
||||||
@ -86,12 +86,28 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
case .intro:
|
case .intro:
|
||||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Wallet_Intro_NotNow, style: .plain, target: self, action: #selector(self.backPressed)), animated: false)
|
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Wallet_Intro_NotNow, style: .plain, target: self, action: #selector(self.backPressed)), animated: false)
|
||||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Wallet_Intro_ImportExisting, style: .plain, target: self, action: #selector(self.importPressed)), animated: false)
|
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Wallet_Intro_ImportExisting, style: .plain, target: self, action: #selector(self.importPressed)), animated: false)
|
||||||
case let .sending(walletInfo, address, amount, textMessage, randomId):
|
case let .sending(walletInfo, address, amount, textMessage, randomId, serverSalt):
|
||||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false)
|
self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false)
|
||||||
self.sendGrams(walletInfo: walletInfo, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: false, randomId: randomId)
|
let _ = (self.tonContext.keychain.decrypt(walletInfo.encryptedSecret)
|
||||||
case .sent, .created:
|
|> deliverOnMainQueue).start(next: { [weak self] decryptedSecret in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.sendGrams(walletInfo: walletInfo, decryptedSecret: decryptedSecret, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: false, randomId: randomId, serverSalt: serverSalt)
|
||||||
|
}, error: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let text = strongSelf.presentationData.strings.Wallet_Send_ErrorDecryptionFailed
|
||||||
|
let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||||
|
self?.dismiss()
|
||||||
|
})])
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
|
strongSelf.dismiss()
|
||||||
|
})
|
||||||
|
case .sent:
|
||||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false)
|
self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false)
|
||||||
case .restoreFailed, .secureStorageNotAvailable, .secureStorageReset:
|
case .restoreFailed, .secureStorageNotAvailable, .secureStorageReset, .created:
|
||||||
break
|
break
|
||||||
case .success:
|
case .success:
|
||||||
break
|
break
|
||||||
@ -112,8 +128,8 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
self.push(WalletWordCheckScreen(context: self.context, tonContext: self.tonContext, mode: .import, walletCreatedPreloadState: nil))
|
self.push(WalletWordCheckScreen(context: self.context, tonContext: self.tonContext, mode: .import, walletCreatedPreloadState: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendGrams(walletInfo: WalletInfo, address: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, randomId: Int64) {
|
private func sendGrams(walletInfo: WalletInfo, decryptedSecret: Data, address: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, randomId: Int64, serverSalt: Data) {
|
||||||
let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, keychain: self.tonContext.keychain, walletInfo: walletInfo, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId)
|
let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, serverSalt: serverSalt, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId)
|
||||||
|> deliverOnMainQueue).start(error: { [weak self] error in
|
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -136,7 +152,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
TextAlertAction(type: .defaultAction, title: "Send Anyway", action: {
|
TextAlertAction(type: .defaultAction, title: "Send Anyway", action: {
|
||||||
self?.sendGrams(walletInfo: walletInfo, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: true, randomId: randomId)
|
self?.sendGrams(walletInfo: walletInfo, decryptedSecret: decryptedSecret, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: true, randomId: randomId, serverSalt: serverSalt)
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
strongSelf.present(controller, in: .window(.root))
|
strongSelf.present(controller, in: .window(.root))
|
||||||
@ -164,7 +180,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
var controllers: [UIViewController] = []
|
var controllers: [UIViewController] = []
|
||||||
for controller in navigationController.viewControllers {
|
for controller in navigationController.viewControllers {
|
||||||
if let controller = controller as? WalletInfoScreen {
|
if let controller = controller as? WalletInfoScreen {
|
||||||
let infoScreen = WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address)
|
let infoScreen = WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address, enableDebugActions: false)
|
||||||
infoScreen.navigationPresentation = controller.navigationPresentation
|
infoScreen.navigationPresentation = controller.navigationPresentation
|
||||||
controllers.append(infoScreen)
|
controllers.append(infoScreen)
|
||||||
} else {
|
} else {
|
||||||
@ -206,7 +222,30 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
], actionLayout: .vertical), in: .window(.root))
|
], actionLayout: .vertical), in: .window(.root))
|
||||||
})
|
})
|
||||||
case let .created(walletInfo, wordList):
|
case let .created(walletInfo, wordList):
|
||||||
|
if let wordList = wordList {
|
||||||
strongSelf.push(WalletWordDisplayScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, wordList: wordList, mode: .check, walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
|
strongSelf.push(WalletWordDisplayScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, wordList: wordList, mode: .check, walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
|
||||||
|
} else {
|
||||||
|
let controller = OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .loading(cancelled: nil))
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
|
let _ = (walletRestoreWords(network: strongSelf.context.account.network, walletInfo: walletInfo, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain)
|
||||||
|
|> deliverOnMainQueue).start(next: { wordList in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.mode = .created(walletInfo, wordList)
|
||||||
|
controller.dismiss()
|
||||||
|
strongSelf.push(WalletWordDisplayScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, wordList: wordList, mode: .check, walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
|
||||||
|
}, error: { _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss()
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.Wallet_Created_ExportErrorTitle, text: strongSelf.presentationData.strings.Wallet_Created_ExportErrorText, actions: [
|
||||||
|
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||||
|
})
|
||||||
|
], actionLayout: .vertical), in: .window(.root))
|
||||||
|
})
|
||||||
|
}
|
||||||
case let .success(walletInfo):
|
case let .success(walletInfo):
|
||||||
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: strongSelf.tonContext.instance)
|
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: strongSelf.tonContext.instance)
|
||||||
|> deliverOnMainQueue).start(next: { address in
|
|> deliverOnMainQueue).start(next: { address in
|
||||||
@ -270,7 +309,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
controllers.append(WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address))
|
controllers.append(WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address, enableDebugActions: false))
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.endEditing(true)
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
}
|
}
|
||||||
@ -302,7 +341,7 @@ public final class WalletSplashScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !controllers.contains(where: { $0 is WalletInfoScreen }) {
|
if !controllers.contains(where: { $0 is WalletInfoScreen }) {
|
||||||
let infoScreen = WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address)
|
let infoScreen = WalletInfoScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, address: address, enableDebugActions: false)
|
||||||
infoScreen.navigationPresentation = .modal
|
infoScreen.navigationPresentation = .modal
|
||||||
controllers.append(infoScreen)
|
controllers.append(infoScreen)
|
||||||
}
|
}
|
||||||
@ -466,7 +505,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
|
|||||||
buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet
|
buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet
|
||||||
termsText = NSAttributedString(string: "")
|
termsText = NSAttributedString(string: "")
|
||||||
self.iconNode.image = nil
|
self.iconNode.image = nil
|
||||||
if let path = getAppBundle().path(forResource: "celebrate", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") {
|
||||||
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
||||||
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
||||||
self.animationNode.visibility = true
|
self.animationNode.visibility = true
|
||||||
@ -478,7 +517,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
|
|||||||
buttonText = self.presentationData.strings.Wallet_RestoreFailed_CreateWallet
|
buttonText = self.presentationData.strings.Wallet_RestoreFailed_CreateWallet
|
||||||
termsText = NSAttributedString(string: "")
|
termsText = NSAttributedString(string: "")
|
||||||
self.iconNode.image = nil
|
self.iconNode.image = nil
|
||||||
if let path = getAppBundle().path(forResource: "sad", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "WalletNotAvailable", ofType: "tgs") {
|
||||||
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
||||||
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
||||||
self.animationNode.visibility = true
|
self.animationNode.visibility = true
|
||||||
@ -504,7 +543,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
|
|||||||
buttonText = self.presentationData.strings.Wallet_Sent_ViewWallet
|
buttonText = self.presentationData.strings.Wallet_Sent_ViewWallet
|
||||||
termsText = NSAttributedString(string: "")
|
termsText = NSAttributedString(string: "")
|
||||||
self.iconNode.image = nil
|
self.iconNode.image = nil
|
||||||
if let path = getAppBundle().path(forResource: "celebrate", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") {
|
||||||
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
||||||
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
||||||
self.animationNode.visibility = true
|
self.animationNode.visibility = true
|
||||||
@ -561,7 +600,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
termsText = NSAttributedString(string: "")
|
termsText = NSAttributedString(string: "")
|
||||||
self.iconNode.image = nil
|
self.iconNode.image = nil
|
||||||
if let path = getAppBundle().path(forResource: "sad", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "WalletNotAvailable", ofType: "tgs") {
|
||||||
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
self.animationNode.setup(account: account, resource: .localFile(path), width: 260, height: 260, playbackMode: .once, mode: .direct)
|
||||||
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
self.animationSize = CGSize(width: 130.0, height: 130.0)
|
||||||
self.animationNode.visibility = true
|
self.animationNode.visibility = true
|
||||||
|
|||||||
@ -33,6 +33,7 @@ private enum WalletTransactionInfoSection: Int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum WalletTransactionInfoEntryTag: ItemListItemTag {
|
private enum WalletTransactionInfoEntryTag: ItemListItemTag {
|
||||||
|
case address
|
||||||
case comment
|
case comment
|
||||||
|
|
||||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
@ -47,7 +48,7 @@ private enum WalletTransactionInfoEntryTag: ItemListItemTag {
|
|||||||
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
||||||
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction)
|
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction)
|
||||||
case infoHeader(PresentationTheme, String)
|
case infoHeader(PresentationTheme, String)
|
||||||
case infoAddress(PresentationTheme, String)
|
case infoAddress(PresentationTheme, String, String?)
|
||||||
case infoCopyAddress(PresentationTheme, String)
|
case infoCopyAddress(PresentationTheme, String)
|
||||||
case infoSendGrams(PresentationTheme, String)
|
case infoSendGrams(PresentationTheme, String)
|
||||||
case commentHeader(PresentationTheme, String)
|
case commentHeader(PresentationTheme, String)
|
||||||
@ -93,8 +94,12 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
|||||||
return WalletTransactionHeaderItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, walletTransaction: walletTransaction, sectionId: self.section)
|
return WalletTransactionHeaderItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, walletTransaction: walletTransaction, sectionId: self.section)
|
||||||
case let .infoHeader(theme, text):
|
case let .infoHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .infoAddress(theme, text):
|
case let .infoAddress(theme, text, address):
|
||||||
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks)
|
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks, longTapAction: address == nil ? nil : {
|
||||||
|
if let address = address {
|
||||||
|
arguments.displayContextMenu(WalletTransactionInfoEntryTag.address, address)
|
||||||
|
}
|
||||||
|
}, tag: WalletTransactionInfoEntryTag.address)
|
||||||
case let .infoCopyAddress(theme, text):
|
case let .infoCopyAddress(theme, text):
|
||||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.copyWalletAddress()
|
arguments.copyWalletAddress()
|
||||||
@ -173,13 +178,14 @@ private func extractDescription(_ walletTransaction: WalletTransaction) -> Strin
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletTransaction, state: WalletTransactionInfoControllerState) -> [WalletTransactionInfoEntry] {
|
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletTransaction, state: WalletTransactionInfoControllerState, walletInfo: WalletInfo?) -> [WalletTransactionInfoEntry] {
|
||||||
var entries: [WalletTransactionInfoEntry] = []
|
var entries: [WalletTransactionInfoEntry] = []
|
||||||
|
|
||||||
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction))
|
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction))
|
||||||
|
|
||||||
let transferredValue = walletTransaction.transferredValue
|
let transferredValue = walletTransaction.transferredValue
|
||||||
let address = extractAddress(walletTransaction)
|
let address = extractAddress(walletTransaction)
|
||||||
|
var singleAddress: String?
|
||||||
let text = stringForAddress(strings: presentationData.strings, address: address)
|
let text = stringForAddress(strings: presentationData.strings, address: address)
|
||||||
let description = extractDescription(walletTransaction)
|
let description = extractDescription(walletTransaction)
|
||||||
|
|
||||||
@ -188,9 +194,12 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
|
|||||||
} else {
|
} else {
|
||||||
entries.append(.infoHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SenderHeader))
|
entries.append(.infoHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SenderHeader))
|
||||||
}
|
}
|
||||||
|
var singleAddres: String?
|
||||||
entries.append(.infoAddress(presentationData.theme, text))
|
if case let .list(list) = address, list.count == 1 {
|
||||||
if case .list = address {
|
singleAddres = list.first
|
||||||
|
}
|
||||||
|
entries.append(.infoAddress(presentationData.theme, text, singleAddres))
|
||||||
|
if case .list = address, walletInfo != nil {
|
||||||
entries.append(.infoCopyAddress(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CopyAddress))
|
entries.append(.infoCopyAddress(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CopyAddress))
|
||||||
entries.append(.infoSendGrams(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SendGrams))
|
entries.append(.infoSendGrams(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SendGrams))
|
||||||
}
|
}
|
||||||
@ -203,7 +212,7 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletTransactionInfoController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, walletTransaction: WalletTransaction) -> ViewController {
|
func walletTransactionInfoController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo?, walletTransaction: WalletTransaction, enableDebugActions: Bool) -> ViewController {
|
||||||
let statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
|
let statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: WalletTransactionInfoControllerState())
|
let stateValue = Atomic(value: WalletTransactionInfoControllerState())
|
||||||
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
|
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
|
||||||
@ -223,6 +232,9 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
|||||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.Wallet_TransactionInfo_AddressCopied, false)), nil)
|
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.Wallet_TransactionInfo_AddressCopied, false)), nil)
|
||||||
}
|
}
|
||||||
}, sendGrams: {
|
}, sendGrams: {
|
||||||
|
guard let walletInfo = walletInfo else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let address = extractAddress(walletTransaction)
|
let address = extractAddress(walletTransaction)
|
||||||
if case let .list(addresses) = address, let address = addresses.first {
|
if case let .list(addresses) = address, let address = addresses.first {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
@ -235,7 +247,7 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
|||||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletTransactionInfoEntry>, WalletTransactionInfoEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletTransactionInfoEntry>, WalletTransactionInfoEntry.ItemGenerationArguments)) in
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_TransactionInfo_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_TransactionInfo_Title), 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)
|
let listState = ItemListNodeState(entries: walletTransactionInfoControllerEntries(presentationData: presentationData, walletTransaction: walletTransaction, state: state, walletInfo: walletInfo), style: .blocks, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -271,9 +283,19 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if let resultItemNode = resultItemNode {
|
if let resultItemNode = resultItemNode {
|
||||||
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: {
|
var actions: [ContextMenuAction] = []
|
||||||
|
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||||
UIPasteboard.general.string = value
|
UIPasteboard.general.string = value
|
||||||
})])
|
}))
|
||||||
|
if enableDebugActions {
|
||||||
|
if case .address = tag {
|
||||||
|
actions.append(ContextMenuAction(content: .text(title: "View Transactions", accessibilityLabel: "View Transactions"), action: {
|
||||||
|
pushImpl?(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: nil, address: value, enableDebugActions: enableDebugActions))
|
||||||
|
//dismissImpl?()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let contextMenuController = ContextMenuController(actions: actions)
|
||||||
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
||||||
if let strongController = controller, let resultItemNode = resultItemNode {
|
if let strongController = controller, let resultItemNode = resultItemNode {
|
||||||
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
||||||
@ -345,6 +367,7 @@ private let titleFont = Font.regular(14.0)
|
|||||||
private let titleBoldFont = Font.semibold(14.0)
|
private let titleBoldFont = Font.semibold(14.0)
|
||||||
|
|
||||||
private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
||||||
|
private let titleSignNode: TextNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let subtitleNode: TextNode
|
private let subtitleNode: TextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
@ -353,6 +376,11 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
private var item: WalletTransactionHeaderItem?
|
private var item: WalletTransactionHeaderItem?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.titleSignNode = TextNode()
|
||||||
|
self.titleSignNode.isUserInteractionEnabled = false
|
||||||
|
self.titleSignNode.contentMode = .left
|
||||||
|
self.titleSignNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.contentMode = .left
|
self.titleNode.contentMode = .left
|
||||||
@ -373,6 +401,7 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.titleSignNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.subtitleNode)
|
self.addSubnode(self.subtitleNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
@ -380,6 +409,7 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: WalletTransactionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: WalletTransactionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTitleSignLayout = TextNode.asyncLayout(self.titleSignNode)
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||||
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
|
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
|
||||||
@ -388,14 +418,17 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||||
let verticalInset: CGFloat = 24.0
|
let verticalInset: CGFloat = 24.0
|
||||||
|
|
||||||
|
let signString: String
|
||||||
let balanceString: String
|
let balanceString: String
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
let transferredValue = item.walletTransaction.transferredValue
|
let transferredValue = item.walletTransaction.transferredValue
|
||||||
if transferredValue <= 0 {
|
if transferredValue <= 0 {
|
||||||
balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
signString = "-"
|
||||||
|
balanceString = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||||
titleColor = item.theme.list.itemPrimaryTextColor
|
titleColor = item.theme.list.itemPrimaryTextColor
|
||||||
} else {
|
} else {
|
||||||
balanceString = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
signString = "+"
|
||||||
|
balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||||
titleColor = item.theme.chatList.secretTitleColor
|
titleColor = item.theme.chatList.secretTitleColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,9 +441,11 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
} else {
|
} else {
|
||||||
title.append(NSAttributedString(string: balanceString, font: Font.bold(48.0), textColor: titleColor))
|
title.append(NSAttributedString(string: balanceString, font: Font.bold(48.0), textColor: titleColor))
|
||||||
}
|
}
|
||||||
|
let titleSign = NSAttributedString(string: signString, font: Font.bold(48.0), textColor: titleColor)
|
||||||
|
|
||||||
let subtitle: String = stringForFullDate(timestamp: Int32(clamping: item.walletTransaction.timestamp), strings: item.strings, dateTimeFormat: item.dateTimeFormat)
|
let subtitle: String = stringForFullDate(timestamp: Int32(clamping: item.walletTransaction.timestamp), strings: item.strings, dateTimeFormat: item.dateTimeFormat)
|
||||||
|
|
||||||
|
let (titleSignLayout, titleSignApply) = makeTitleSignLayout(TextNodeLayoutArguments(attributedString: titleSign, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: item.theme.list.freeTextColor), 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 (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: item.theme.list.freeTextColor), 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()))
|
||||||
@ -431,18 +466,25 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
|||||||
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.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
|
//strongSelf.activateArea.accessibilityLabel = attributedText.string
|
||||||
|
|
||||||
|
let _ = titleSignApply()
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = subtitleApply()
|
let _ = subtitleApply()
|
||||||
|
|
||||||
let iconSpacing: CGFloat = 8.0
|
let iconSpacing: CGFloat = 4.0
|
||||||
let contentWidth = titleLayout.size.width + iconSpacing + iconSize.width / 2.0
|
let contentWidth = titleSignLayout.size.width + iconSpacing + titleLayout.size.width + iconSpacing + iconSize.width * 3.0 / 2.0
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - contentWidth) / 2.0), y: verticalInset), size: titleLayout.size)
|
let contentOrigin = floor((params.width - contentWidth) / 2.0)
|
||||||
|
let titleSignFrame = CGRect(origin: CGPoint(x: contentOrigin, y: verticalInset), size: titleSignLayout.size)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: contentOrigin + titleSignFrame.width * titleScale + iconSpacing, y: titleSignFrame.minY + floor((titleSignFrame.height - iconSize.height) / 2.0) - 2.0), size: iconSize)
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + iconSpacing, y: verticalInset), size: titleLayout.size)
|
||||||
let subtitleFrame = CGRect(origin: CGPoint(x: floor((params.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY - 5.0), size: subtitleLayout.size)
|
let subtitleFrame = CGRect(origin: CGPoint(x: floor((params.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY - 5.0), size: subtitleLayout.size)
|
||||||
|
strongSelf.titleSignNode.position = titleSignFrame.center
|
||||||
|
strongSelf.titleSignNode.bounds = CGRect(origin: CGPoint(), size: titleSignFrame.size)
|
||||||
|
strongSelf.titleSignNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
|
||||||
strongSelf.titleNode.position = titleFrame.center
|
strongSelf.titleNode.position = titleFrame.center
|
||||||
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
strongSelf.titleNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
|
strongSelf.titleNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
|
||||||
strongSelf.subtitleNode.frame = subtitleFrame
|
strongSelf.subtitleNode.frame = subtitleFrame
|
||||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: floor(titleFrame.midX + titleFrame.width / 2.0 * titleScale + iconSpacing), y: titleFrame.minY + floor((titleFrame.height - iconSize.height) / 2.0) - 2.0), size: iconSize)
|
strongSelf.iconNode.frame = iconFrame
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2142,6 +2142,7 @@ public final class WalletWordCheckScreen: ViewController {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
let _ = confirmWalletExported(postbox: strongSelf.context.account.postbox, walletInfo: walletInfo).start()
|
||||||
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
|
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.endEditing(true)
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
@ -2247,6 +2248,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||||
|
private let previous: (WordCheckInputNode) -> Void
|
||||||
private let next: (WordCheckInputNode, Bool) -> Void
|
private let next: (WordCheckInputNode, Bool) -> Void
|
||||||
private let focused: (WordCheckInputNode) -> Void
|
private let focused: (WordCheckInputNode) -> Void
|
||||||
private let pasteWords: ([String]) -> Void
|
private let pasteWords: ([String]) -> Void
|
||||||
@ -2267,7 +2269,8 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: PresentationTheme, index: Int, possibleWordList: [String], next: @escaping (WordCheckInputNode, Bool) -> Void, isLast: Bool, focused: @escaping (WordCheckInputNode) -> Void, pasteWords: @escaping ([String]) -> Void) {
|
init(theme: PresentationTheme, index: Int, possibleWordList: [String], previous: @escaping (WordCheckInputNode) -> Void, next: @escaping (WordCheckInputNode, Bool) -> Void, isLast: Bool, focused: @escaping (WordCheckInputNode) -> Void, pasteWords: @escaping ([String]) -> Void) {
|
||||||
|
self.previous = previous
|
||||||
self.next = next
|
self.next = next
|
||||||
self.focused = focused
|
self.focused = focused
|
||||||
self.pasteWords = pasteWords
|
self.pasteWords = pasteWords
|
||||||
@ -2316,6 +2319,12 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
self.addSubnode(self.inputNode)
|
self.addSubnode(self.inputNode)
|
||||||
self.addSubnode(self.clearButtonNode)
|
self.addSubnode(self.clearButtonNode)
|
||||||
|
|
||||||
|
self.inputNode.textField.didDeleteBackwardWhileEmpty = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.previous(strongSelf)
|
||||||
|
}
|
||||||
self.inputNode.textField.delegate = self
|
self.inputNode.textField.delegate = self
|
||||||
self.inputNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
self.inputNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
|
|
||||||
@ -2644,12 +2653,15 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
|||||||
|
|
||||||
var inputNodes: [WordCheckInputNode] = []
|
var inputNodes: [WordCheckInputNode] = []
|
||||||
|
|
||||||
|
var previousWord: ((WordCheckInputNode) -> Void)?
|
||||||
var nextWord: ((WordCheckInputNode, Bool) -> Void)?
|
var nextWord: ((WordCheckInputNode, Bool) -> Void)?
|
||||||
var focused: ((WordCheckInputNode) -> Void)?
|
var focused: ((WordCheckInputNode) -> Void)?
|
||||||
var pasteWords: (([String]) -> Void)?
|
var pasteWords: (([String]) -> Void)?
|
||||||
|
|
||||||
for i in 0 ..< wordIndices.count {
|
for i in 0 ..< wordIndices.count {
|
||||||
inputNodes.append(WordCheckInputNode(theme: presentationData.theme, index: wordIndices[i], possibleWordList: possibleWordList, next: { node, done in
|
inputNodes.append(WordCheckInputNode(theme: presentationData.theme, index: wordIndices[i], possibleWordList: possibleWordList, previous: { node in
|
||||||
|
previousWord?(node)
|
||||||
|
}, next: { node, done in
|
||||||
nextWord?(node, done)
|
nextWord?(node, done)
|
||||||
}, isLast: i == wordIndices.count - 1, focused: { node in
|
}, isLast: i == wordIndices.count - 1, focused: { node in
|
||||||
focused?(node)
|
focused?(node)
|
||||||
@ -2703,6 +2715,14 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
|||||||
|
|
||||||
self.secondaryActionButtonNode.addTarget(self, action: #selector(self.secondaryActionPressed), forControlEvents: .touchUpInside)
|
self.secondaryActionButtonNode.addTarget(self, action: #selector(self.secondaryActionPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
|
previousWord = { [weak self] node in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }), index != 0 {
|
||||||
|
strongSelf.inputNodes[index - 1].focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
nextWord = { [weak self] node, done in
|
nextWord = { [weak self] node, done in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -2714,7 +2734,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
|||||||
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: true)
|
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
|
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }), index != strongSelf.inputNodes.count - 1 {
|
||||||
strongSelf.inputNodes[index + 1].focus()
|
strongSelf.inputNodes[index + 1].focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user