no message

This commit is contained in:
Peter 2019-10-01 23:06:52 +04:00
parent 516db55483
commit ea35454a30
61 changed files with 3882 additions and 2891 deletions

View File

@ -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" = "Youre all set. Now you have a wallet that only you control - directly, without middlemen or bankers."; "Wallet.Completed.Text" = "Youre 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

View File

@ -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"

View File

@ -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

View File

@ -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)
} }

View File

@ -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
View 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",
],
)

View 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>

View File

@ -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>

View 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)
})
}
}

View File

@ -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

View File

@ -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:

View File

@ -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!

View File

@ -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 {

View File

@ -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()
} }
} }

View File

@ -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
}
} }

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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
} }
} }

View File

@ -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()

View File

@ -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)
}
}
} }
} }

View File

@ -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()

View File

@ -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;

View File

@ -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];
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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];
} }

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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)
}
}) })
} }

View File

@ -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)
} }
} }

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}) })
} }

View File

@ -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")
} }

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)?

View File

@ -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
} }
} }

View File

@ -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))
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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)
} }

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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
} }
}) })
} }

View File

@ -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()
} }
} }