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.AddressHeader" = "RECIPIENT 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.AmountText" = "Grams to send";
"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.Settings.Title" = "Wallet Settings";
"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.ImportExisting" = "Import existing wallet";
"Wallet.Intro.CreateErrorTitle" = "An Error Occurred";
"Wallet.Intro.CreateErrorText" = "Sorry. Please try again.";
"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.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.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.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.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.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.EnterWords" = "Enter 24 words";
"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.Text" = "**%@ Grams** have been sent.";
"Wallet.Sent.ViewWallet" = "View My Wallet";
"Wallet.SecureStorageNotAvailable.Title" = "Setup Passcode";
"Wallet.SecureStorageNotAvailable.Text" = "Please set up Passcode on your device to enable secure payments with your Gram wallet.";
"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode";
"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.BiometryTouchId" = "Touch 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.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode 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. 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.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";
@ -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.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.CanNotRemember" = "I don't have those";
"Wallet.WordImport.CanNotRemember" = "I don't have them";
"Wallet.WordImport.IncorrectTitle" = "Incorrect words";
"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again.";
"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.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";

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">
</BuildableReference>
</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
buildForTesting = "YES"
buildForRunning = "YES"

View File

@ -1,7 +1,5 @@
#!/bin/sh
set -x
if [ -z "BUILD_NUMBER" ]; then
echo "BUILD_NUMBER is not set"
exit 1

View File

@ -453,6 +453,7 @@ public protocol SharedAccountContext: class {
private final class TonInstanceData {
var config: String?
var blockchainName: String?
var instance: TonInstance?
}
@ -470,13 +471,13 @@ public final class StoredTonContext {
self.keychain = keychain
}
public func context(config: String) -> TonContext {
public func context(config: String, blockchainName: String) -> TonContext {
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)
} else {
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
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 {
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)
@ -855,7 +855,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
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)
@ -1015,7 +1015,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
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)
if let _ = self.reactionContextNode {
contentTopInset += 34.0

View File

@ -20,8 +20,19 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
public static var allCases: [DeviceMetrics] {
return [.iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax,
.iPad, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen]
return [
.iPhone4,
.iPhone5,
.iPhone6,
.iPhone6Plus,
.iPhoneX,
.iPhoneXSMax,
.iPad,
.iPadPro10Inch,
.iPadPro11Inch,
.iPadPro,
.iPadPro3rdGen
]
}
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 {
switch self {
case .iPhoneX, .iPhoneXSMax:

View File

@ -31,8 +31,11 @@ private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
var validatedGesture = false
var firstLocation: CGPoint = CGPoint()
private let canBegin: () -> Bool
override init(target: Any?, action: Selector?) {
init(target: Any?, action: Selector?, canBegin: @escaping () -> Bool) {
self.canBegin = canBegin
super.init(target: target, action: action)
self.maximumNumberOfTouches = 1
@ -45,6 +48,11 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if !self.canBegin() {
self.state = .failed
return
}
super.touchesBegan(touches, with: event)
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 {
return true
} 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.updateScroller(transition: .immediate)
@ -749,7 +749,65 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
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 {
return (0.0, 0.0)
}
@ -851,7 +909,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
offset = (effectiveInsets.top - overscroll) - topItemEdge
}
}
} else {
} else if !self.isTracking {
let areaHeight = min(completeHeight, visibleAreaHeight)
if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll {
if snapTopItem && topItemEdge < effectiveInsets.top {
@ -859,7 +917,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} else {
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
}
}
@ -931,7 +989,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
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 {
offset = .none
}
@ -2345,7 +2407,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
//print("replay after \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))")
}
if let scrollToItem = scrollToItem {
if let scrollToItem = scrollToItem, !self.areAllItemsOnScreen() {
self.stopScrolling()
for itemNode in self.itemNodes {
@ -2442,6 +2504,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.visibleSize = updateSizeAndInsets.size
var offsetFix: CGFloat
var insetDeltaOffsetFix: CGFloat = 0.0
if self.isTracking || isExperimentalSnapToScrollToItem {
offsetFix = 0.0
} 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)
}
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) {
offsetFix += snappedTopInset
@ -2532,7 +2595,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} else {
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()
}
}
@ -2586,7 +2649,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.scroller.contentOffset = self.lastContentOffset
self.ignoreScrollingEvents = wasIgnoringScrollingEvents
} 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 {
for itemNode in self.itemNodes {
@ -3509,7 +3572,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
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()
}
}

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 statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)?
@ -99,7 +102,12 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
override func 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.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
@ -121,6 +129,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
@ -161,16 +172,6 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
if let top = strongSelf.state.top {
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)
@ -187,12 +188,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
//(self.view as! NavigationControllerView).inTransition = true
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 {
return
}
//(self.view as! NavigationControllerView).inTransition = false
let topController = top.value
let bottomController = transition.previous.value
@ -201,25 +200,12 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
strongSelf.state.transition = nil
strongSelf.controllerRemoved(top.value)
//topController.viewDidDisappear(true)
//bottomController.viewDidAppear(true)
})
} 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
guard let strongSelf = self, let top = strongSelf.state.top, let transition = strongSelf.state.transition else {
return
}
//(self.view as! NavigationControllerView).inTransition = false
strongSelf.state.transition = nil
top.value.viewDidAppear(true)
@ -287,7 +273,6 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
if pending.isReady {
self.state.pending = nil
let previous = self.state.top
//previous?.value.view.endEditing(true)
self.state.top = pending.value
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout.withUpdatedInputHeight(nil), transition: pending.transition)
statusBarTransition = pending.transition
@ -315,17 +300,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
self.statusBarStyle = updatedStatusBarStyle
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) {
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 {
//assertionFailure()
currentTransition.coordinator.performCompletion(completion: {
})
}
fromValue.value.viewWillDisappear(true)
@ -379,11 +360,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size)
strongSelf.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
toValue.value.viewDidAppear(true)
//strongSelf.keyboardManager?.surfaces = toValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
}
})
} else {
//self.keyboardManager?.surfaces = toValue?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
if let fromValue = fromValue {
fromValue.value.viewWillDisappear(false)
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) {
self.keyboardViewManager?.update(leftEdge: leftEdge, transition: transition)
self.currentKeyboardLeftEdge = leftEdge
self.keyboardViewManager?.update(leftEdge: self.additionalKeyboardLeftEdgeOffset + leftEdge, transition: transition)
}
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)
}
}
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
}
private var inCallStatusBar: StatusBar?
private var rootContainer: RootContainer?
private var rootModalFrame: NavigationModalFrame?
private var modalContainers: [NavigationModalContainer] = []
@ -187,25 +188,31 @@ open class NavigationController: UINavigationController, ContainableController,
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
if let controller = self.viewControllers.last {
if let controller = controller as? ViewController {
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 rootContainer = self.rootContainer {
switch rootContainer {
case let .flat(container):
supportedOrientations = supportedOrientations.intersection(container.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
case .split:
break
}
}
for modalContainer in self.modalContainers {
supportedOrientations = supportedOrientations.intersection(modalContainer.container.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
}
return supportedOrientations
}
public func updateTheme(_ theme: NavigationControllerTheme) {
let statusBarStyleUpdated = self.theme.statusBar != theme.statusBar
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 statusBarStyleUpdated {
self.validStatusBarStyle = self.theme.statusBar
@ -216,7 +223,7 @@ open class NavigationController: UINavigationController, ContainableController,
case .white:
normalStatusBarStyle = .lightContent
}
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
//self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
}
self.controllerView.backgroundColor = theme.emptyAreaColor
}
@ -232,10 +239,13 @@ open class NavigationController: UINavigationController, ContainableController,
}
self.validLayout = layout
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) {
let navigationLayout = makeNavigationLayout(layout: layout, controllers: self._viewControllers)
let navigationLayout = makeNavigationLayout(mode: self.mode, layout: layout, controllers: self._viewControllers)
var transition = transition
var statusBarStyle: StatusBarStyle = .Ignore
@ -330,6 +340,8 @@ open class NavigationController: UINavigationController, ContainableController,
if modalContainer.supernode == nil && modalContainer.isReady {
if let previousModalContainer = previousModalContainer {
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
} else if let inCallStatusBar = self.inCallStatusBar {
self.displayNode.insertSubnode(modalContainer, belowSubnode: inCallStatusBar)
} else {
self.displayNode.addSubnode(modalContainer)
}
@ -341,12 +353,12 @@ open class NavigationController: UINavigationController, ContainableController,
if previousModalContainer == nil {
topModalDismissProgress = modalContainer.dismissProgress
if case .compact = layout.metrics.widthClass {
modalContainer.container.keyboardViewManager = self.keyboardViewManager
modalContainer.keyboardViewManager = self.keyboardViewManager
} else {
modalContainer.container.keyboardViewManager = nil
modalContainer.keyboardViewManager = nil
}
} else {
modalContainer.container.keyboardViewManager = nil
modalContainer.keyboardViewManager = nil
}
previousModalContainer = modalContainer
}
@ -601,11 +613,9 @@ open class NavigationController: UINavigationController, ContainableController,
}
self.navigationBar.removeFromSuperview()
/*let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(panRecognizer)*/
/*let inCallStatusBar = StatusBar()
self.displayNode.addSubnode(inCallStatusBar)
self.inCallStatusBar = inCallStatusBar*/
}
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) {
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) {

View File

@ -17,7 +17,7 @@ struct NavigationLayout {
var modal: [ModalContainerLayout]
}
func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewController]) -> NavigationLayout {
func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewLayout, controllers: [ViewController]) -> NavigationLayout {
var rootControllers: [ViewController] = []
var modalStack: [ModalContainerLayout] = []
for controller in controllers {
@ -52,25 +52,30 @@ func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewControl
}
}
let rootLayout: RootNavigationLayout
switch layout.metrics.widthClass {
case .compact:
switch mode {
case .single:
rootLayout = .flat(rootControllers)
case .regular:
let masterControllers = rootControllers.filter {
if case .master = $0.navigationPresentation {
return true
} else {
return false
case .automaticMasterDetail:
switch layout.metrics.widthClass {
case .compact:
rootLayout = .flat(rootControllers)
case .regular:
let masterControllers = rootControllers.filter {
if case .master = $0.navigationPresentation {
return true
} else {
return false
}
}
}
let detailControllers = rootControllers.filter {
if case .master = $0.navigationPresentation {
return false
} else {
return true
let detailControllers = rootControllers.filter {
if case .master = $0.navigationPresentation {
return false
} else {
return true
}
}
rootLayout = .split(masterControllers, detailControllers)
}
rootLayout = .split(masterControllers, detailControllers)
}
return NavigationLayout(root: rootLayout, modal: modalStack)
}

View File

@ -3,13 +3,15 @@ import UIKit
import AsyncDisplayKit
import SwiftSignalKit
final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
private var theme: NavigationControllerTheme
private let dim: ASDisplayNode
private let scrollNode: ASScrollNode
let container: NavigationContainer
private var panRecognizer: InteractiveTransitionGestureRecognizer?
private(set) var isReady: Bool = false
private(set) var dismissProgress: CGFloat = 0.0
var isReadyUpdated: (() -> Void)?
@ -18,6 +20,18 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
private var ignoreScrolling = 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) {
self.theme = theme
@ -53,8 +67,6 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveKeyboardGestureRecognizer = true
self.scrollNode.view.alwaysBounceVertical = false
self.scrollNode.view.alwaysBounceHorizontal = false
self.scrollNode.view.bounces = false
@ -65,6 +77,124 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
}
self.scrollNode.view.delaysContentTouches = false
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) {
@ -74,8 +204,14 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
progress = max(0.0, min(1.0, progress))
self.dismissProgress = progress
self.dim.alpha = 1.0 - progress
self.updateDismissProgress?(progress, .immediate)
self.applyDismissProgress(transition: .immediate, completion: {})
}
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?
@ -116,8 +252,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
})
self.ignoreScrolling = false
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>) {
@ -137,9 +273,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
return
}
self.validLayout = layout
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
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)
if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging {
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
switch layout.metrics.widthClass {
case .compact:
self.panRecognizer?.isEnabled = true
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
self.container.clipsToBounds = true
self.container.cornerRadius = 10.0
@ -174,6 +314,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
let scaledTopInset: CGFloat = topInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
case .regular:
self.panRecognizer?.isEnabled = false
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
self.container.clipsToBounds = true
self.container.cornerRadius = 10.0
@ -233,7 +374,11 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if !self.container.bounds.contains(self.view.convert(point, to: self.container.view)) {
return self.dim.view
}
var currentParent: UIView? = result
var enableScrolling = true
while true {
if currentParent == nil {
break
@ -242,16 +387,31 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
if scrollView === self.scrollNode.view {
break
}
if scrollView.isDecelerating && scrollView.contentOffset.y < scrollView.contentInset.top {
return self.scrollNode.view
if scrollView.disablesInteractiveModalDismiss {
enableScrolling = false
break
} else {
if scrollView.isDecelerating && scrollView.contentOffset.y < scrollView.contentInset.top {
return self.scrollNode.view
}
}
} 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
}
}
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
}
}

View File

@ -3,7 +3,14 @@ import UIKit
import AsyncDisplayKit
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
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
@ -11,7 +18,18 @@ private func generateCornerImage(radius: CGFloat, mirror: Bool) -> UIImage? {
context.setFillColor(UIColor.clear.cgColor)
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()
})
}
@ -20,8 +38,11 @@ final class NavigationModalFrame: ASDisplayNode {
private let topShade: ASDisplayNode
private let leftShade: ASDisplayNode
private let rightShade: ASDisplayNode
private let bottomShade: ASDisplayNode
private let topLeftCorner: ASImageNode
private let topRightCorner: ASImageNode
private let bottomLeftCorner: ASImageNode
private let bottomRightCorner: ASImageNode
private var currentMaxCornerRadius: CGFloat?
@ -36,6 +57,8 @@ final class NavigationModalFrame: ASDisplayNode {
self.leftShade.backgroundColor = .black
self.rightShade = ASDisplayNode()
self.rightShade.backgroundColor = .black
self.bottomShade = ASDisplayNode()
self.bottomShade.backgroundColor = .black
self.topLeftCorner = ASImageNode()
self.topLeftCorner.displaysAsynchronously = false
@ -43,14 +66,23 @@ final class NavigationModalFrame: ASDisplayNode {
self.topRightCorner = ASImageNode()
self.topRightCorner.displaysAsynchronously = false
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()
self.addSubnode(self.topShade)
self.addSubnode(self.leftShade)
self.addSubnode(self.rightShade)
self.addSubnode(self.bottomShade)
self.addSubnode(self.topLeftCorner)
self.addSubnode(self.topRightCorner)
self.addSubnode(self.bottomLeftCorner)
self.addSubnode(self.bottomRightCorner)
}
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -78,6 +110,9 @@ final class NavigationModalFrame: ASDisplayNode {
}
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 initialCornerRadius: CGFloat
if !layout.safeInsets.top.isZero {
@ -86,22 +121,29 @@ final class NavigationModalFrame: ASDisplayNode {
initialCornerRadius = 0.0
}
if self.currentMaxCornerRadius != cornerRadius {
self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: false)
self.topRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: true)
self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .topLeft)
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 cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
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.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 bottomShadeOffset: CGFloat = progress * bottomInset
let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset
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.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.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), completion: { _ in
completion()

View File

@ -10,6 +10,9 @@ final class NavigationSplitContainer: ASDisplayNode {
private let detailContainer: NavigationContainer
private let separator: ASDisplayNode
private var masterControllers: [ViewController] = []
private var detailControllers: [ViewController] = []
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
self.theme = theme
@ -29,6 +32,10 @@ final class NavigationSplitContainer: ASDisplayNode {
self.addSubnode(self.separator)
}
func updateTheme(theme: NavigationControllerTheme) {
self.separator.backgroundColor = theme.navigationBar.separatorColor
}
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 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.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 () -> ()) {
self.animatingCompletion = true
let distance = (1.0 - self.progress) * self.container.bounds.size.width
self.currentCompletion = completion
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.shadowNode.removeFromSupernode()

View File

@ -22,6 +22,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
@property (nonatomic) bool disablesInteractiveKeyboardGestureRecognizer;
@property (nonatomic) bool disablesInteractiveModalDismiss;
@property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)();
@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling;

View File

@ -42,6 +42,7 @@ static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppe
static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey;
static const void *interactiveTransitionGestureRecognizerTestKey = &interactiveTransitionGestureRecognizerTestKey;
static const void *UIViewControllerHintWillBePresentedInPreviewingContextKey = &UIViewControllerHintWillBePresentedInPreviewingContextKey;
static const void *disablesInteractiveModalDismissKey = &disablesInteractiveModalDismissKey;
static bool notyfyingShiftState = false;
@ -250,6 +251,14 @@ static bool notyfyingShiftState = false;
[self setAssociatedObject:[disablesInteractiveTransitionGestureRecognizerNow copy] forKey:disablesInteractiveTransitionGestureRecognizerNowKey];
}
- (bool)disablesInteractiveModalDismiss {
return [self associatedObjectForKey:disablesInteractiveModalDismissKey];
}
- (void)setDisablesInteractiveModalDismiss:(bool)disablesInteractiveModalDismiss {
[self setAssociatedObject:@(disablesInteractiveModalDismiss) forKey:disablesInteractiveModalDismissKey];
}
- (BOOL (^)(CGPoint))interactiveTransitionGestureRecognizerTest {
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 {
keyboardHeight = max(0.0, keyboardHeight - 24.0)
}
print("keyboardHeight: \(keyboardHeight)")
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
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
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
var inPopover = false
@ -488,10 +496,21 @@ public class Window1 {
screenHeight = UIScreen.main.bounds.width
}
var keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if inPopover {
keyboardHeight = max(0.0, keyboardHeight - 48.0)
var keyboardHeight: CGFloat
if keyboardFrame.isEmpty || keyboardFrame.maxY < screenHeight {
keyboardHeight = 0.0
} else {
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if inPopover {
if strongSelf.windowLayout.onScreenNavigationHeight != nil {
keyboardHeight = max(0.0, keyboardHeight - 24.0)
} else {
keyboardHeight = max(0.0, keyboardHeight - 48.0)
}
}
}
print("keyboardHeight: \(keyboardHeight)")
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
@ -780,7 +799,7 @@ public class Window1 {
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
self.tracingStatusBarsInvalidated = false
if self.statusBarHidden {
/*if self.statusBarHidden {
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
} else {
var statusBarSurfaces: [StatusBarSurface] = []
@ -813,7 +832,7 @@ public class Window1 {
}
}
}
//keyboardManager.surfaces = keyboardSurfaces
//keyboardManager.surfaces = keyboardSurfaces*/
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
let orientationToLock: UIInterfaceOrientationMask
@ -947,12 +966,7 @@ public class Window1 {
let boundsSize = updatingLayout.layout.size
let isLandscape = boundsSize.width > boundsSize.height
var statusBarHeight: CGFloat?
if let statusBarHost = self.statusBarHost {
statusBarHeight = statusBarHost.statusBarFrame.size.height
} else {
statusBarHeight = self.deviceMetrics.statusBarHeight
}
var statusBarHeight: CGFloat? = self.deviceMetrics.statusBarHeight(for: boundsSize)
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)

View File

@ -5,13 +5,20 @@ import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
public enum ItemListMultilineInputItemTextLimitMode {
case characters
case bytes
}
public struct ItemListMultilineInputItemTextLimit {
public let value: Int
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.display = display
self.mode = mode
}
}
@ -186,7 +193,13 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
var rightInset: CGFloat = params.rightInset
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 remainingCount = maxLength.value - textLength
if displayTextLimit {

View File

@ -211,6 +211,9 @@ static NSData *base64_decode(NSString *str) {
NSMutableArray *signals = [[NSMutableArray alloc] init];
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
if (additionalSource != nil) {
/*#if DEBUG
[signals removeAllObjects];
#endif*/
[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 strings: PresentationStrings
private let type: OverlayStatusControllerType

View File

@ -641,17 +641,21 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
}
}
if intro {
var dismissInto: (() -> Void)?
let controller = PrivacyIntroController(context: context, mode: .twoStepVerification, arguments: PrivacyIntroControllerPresentationArguments(fadeIn: false, animateIn: true), proceedAction: {
pushControllerImpl?(twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: intro, data: data.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) })), openSetupPasswordImmediately: true), false)
dismissInto?()
var nextImpl: (() -> Void)?
let introController = PrivacyIntroController(context: context, mode: .twoStepVerification, proceedAction: {
nextImpl?()
})
dismissInto = { [weak controller] in
controller?.dismiss()
nextImpl = { [weak introController] in
guard let introController = introController, let navigationController = introController.navigationController as? NavigationController else {
return
}
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)
}
presentControllerImpl?(controller)
pushControllerImpl?(introController, true)
} 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: {
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext), true)

View File

@ -24,6 +24,7 @@ framework(
"//submodules/MtProtoKit:MtProtoKit#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/CloudData:CloudData",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -18,16 +18,20 @@ private func removeMessageMedia(message: Message, mediaBox: MediaBox) {
}
}
public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId]) {
for id in ids {
if id.peerId.namespace == Namespaces.Peer.SecretChat {
if let message = transaction.getMessage(id) {
removeMessageMedia(message: message, mediaBox: mediaBox)
public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true) {
if deleteMedia {
for id in ids {
if id.peerId.namespace == Namespaces.Peer.SecretChat {
if let message = transaction.getMessage(id) {
removeMessageMedia(message: message, mediaBox: mediaBox)
}
}
}
}
transaction.deleteMessages(ids, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
if deleteMedia {
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) })
}
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
import MtProtoKitDynamic
#endif
import CloudData
#endif
public enum ConnectionStatus: Equatable {
@ -480,10 +481,26 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
}
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
//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
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,47 +292,41 @@ 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> {
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)
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.sendGrams(from: key, localPassword: serverSalt, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId).start(next: { result in
guard let result = result as? TONSendGramsResult else {
subscriber.putError(.generic)
return
}
subscriber.putCompletion()
}, error: { error in
if let error = error as? TONError {
if error.text == "Failed to parse account address" {
subscriber.putError(.invalidAddress)
} else if error.text.hasPrefix("DANGEROUS_TRANSACTION") {
subscriber.putError(.destinationIsNotInitialized)
} else {
subscriber.putError(.generic)
}
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> {
let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.sendGrams(from: key, localPassword: serverSalt, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId).start(next: { result in
guard let result = result as? TONSendGramsResult else {
subscriber.putError(.generic)
return
}
subscriber.putCompletion()
}, error: { error in
if let error = error as? TONError {
if error.text == "Failed to parse account address" {
subscriber.putError(.invalidAddress)
} else if error.text.hasPrefix("DANGEROUS_TRANSACTION") {
subscriber.putError(.destinationIsNotInitialized)
} else {
subscriber.putError(.generic)
}
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
} else {
subscriber.putError(.generic)
}
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
return disposable
}
return disposable
}
}
@ -440,10 +434,12 @@ public struct CombinedWalletState: Codable, Equatable {
public struct WalletStateRecord: PostboxCoding, Equatable {
public let info: WalletInfo
public var exportCompleted: Bool
public var state: CombinedWalletState?
public init(info: WalletInfo, state: CombinedWalletState?) {
public init(info: WalletInfo, exportCompleted: Bool, state: CombinedWalletState?) {
self.info = info
self.exportCompleted = exportCompleted
self.state = state
}
@ -451,6 +447,7 @@ public struct WalletStateRecord: PostboxCoding, Equatable {
self.info = decoder.decodeDataForKey("info").flatMap { data in
return try? JSONDecoder().decode(WalletInfo.self, from: 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
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) {
encoder.encodeData(data, forKey: "info")
}
encoder.encodeInt32(self.exportCompleted ? 1 : 0, forKey: "exportCompleted")
if let state = self.state, let data = try? JSONEncoder().encode(state) {
encoder.encodeData(data, forKey: "state")
} else {
@ -521,7 +519,7 @@ public func createWallet(postbox: Postbox, network: Network, tonInstance: TonIns
return postbox.transaction { transaction -> (WalletInfo, [String]) in
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
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 (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 {
case generic
}
@ -556,7 +569,7 @@ public func importWallet(postbox: Postbox, network: Network, tonInstance: TonIns
return postbox.transaction { transaction -> WalletInfo in
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
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 walletInfo
@ -643,57 +656,85 @@ public enum CombinedWalletStateResult {
case updated(CombinedWalletState)
}
public func getCombinedWalletState(postbox: Postbox, walletInfo: WalletInfo, tonInstance: TonInstance) -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> {
return postbox.transaction { transaction -> CombinedWalletState? in
let walletCollection = (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: [])
for item in walletCollection.wallets {
if item.info.publicKey == walletInfo.publicKey {
return item.state
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
let walletCollection = (transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection) ?? WalletCollection(wallets: [])
for item in walletCollection.wallets {
if item.info.publicKey == walletInfo.publicKey {
return item.state
}
}
return nil
}
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { cachedState -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
return .single(.cached(cachedState))
|> then(
tonInstance.walletAddress(publicKey: walletInfo.publicKey)
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { address -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
return getWalletState(address: address, tonInstance: tonInstance)
|> mapError { _ -> GetCombinedWalletStateError in
return .generic
}
|> mapToSignal { walletState, syncUtime -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError>
if walletState.lastTransactionId == cachedState?.walletState.lastTransactionId {
topTransactions = .single(cachedState?.topTransactions ?? [])
} else {
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 postbox.transaction { transaction -> CombinedWalletStateResult 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].state = combinedState
}
}
return walletCollection
})
return .updated(combinedState)
}
|> castError(GetCombinedWalletStateError.self)
}
}
}
)
}
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 nil
}
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { cachedState -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
return .single(.cached(cachedState))
|> then(
tonInstance.walletAddress(publicKey: walletInfo.publicKey)
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { address -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
return getWalletState(address: address, tonInstance: tonInstance)
|> mapError { _ -> GetCombinedWalletStateError in
return .generic
}
|> mapToSignal { walletState, syncUtime -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError>
if walletState.lastTransactionId == cachedState?.walletState.lastTransactionId {
topTransactions = .single(cachedState?.topTransactions ?? [])
} else {
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 postbox.transaction { transaction -> CombinedWalletStateResult 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].state = combinedState
}
}
return walletCollection
})
return .updated(combinedState)
}
|> castError(GetCombinedWalletStateError.self)
}
}
}
)
return .single(.cached(nil))
|> then(updated)
}
}
@ -704,17 +745,11 @@ public enum SendGramsFromWalletError {
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> {
return getServerWalletSalt(network: network)
|> mapError { _ -> SendGramsFromWalletError in
return .generic
}
|> mapToSignal { serverSalt in
return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance)
|> castError(SendGramsFromWalletError.self)
|> 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)
}
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 walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance)
|> castError(SendGramsFromWalletError.self)
|> mapToSignal { fromAddress in
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
}
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))
|> mapError { _ -> GetServerWalletSaltError in
return .generic

View File

@ -565,7 +565,7 @@ public struct PresentationResourcesChat {
public static func chatHistoryNavigationButtonBadgeImage(_ theme: PresentationTheme) -> UIImage? {
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 var cloudDataContext: Any?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
precondition(!testIsLaunched)
testIsLaunched = true
@ -240,7 +242,6 @@ final class SharedApplicationContext {
let launchStartTime = CFAbsoluteTimeGetCurrent()
let statusBarHost = ApplicationStatusBarHost()
let (window, hostView) = nativeWindowHostView()
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
@ -248,6 +249,8 @@ final class SharedApplicationContext {
self.window = window
self.nativeWindow = window
self.cloudDataContext = makeCloudDataContext()
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
@ -368,7 +371,7 @@ final class SharedApplicationContext {
|> map { token in
let data = buildConfig.bundleData(withAppToken: token)
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
Logger.shared.log("data", "\(jsonString)")
//Logger.shared.log("data", "\(jsonString)")
} else {
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.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 {
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
@ -5831,7 +5831,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
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 data.duration < 0.5 {
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)
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 messageActionSheetBottomDimNode: ASDisplayNode
@ -1077,8 +1077,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
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 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 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)))
if let inputContextPanelNode = self.inputContextPanelNode {
let panelFrame = inputContextPanelNode.placement == .overTextInput ? inputContextPanelsOverMainPanelFrame : inputContextPanelsFrame

View File

@ -381,6 +381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
self.tapRecognizer = recognizer
self.view.addGestureRecognizer(recognizer)
self.view.isExclusiveTouch = true
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
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?
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
if isProcessingContentOffsetChanged {
return
}
isProcessingContentOffsetChanged = true
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateListVisibleContentOffset(offset)
isProcessingContentOffsetChanged = false
}
}

View File

@ -17,6 +17,7 @@ import UrlHandling
import WalletUI
import LegacyMediaPickerUI
import LocalMediaResources
import OverlayStatusController
private enum CallStatusText: Equatable {
case none
@ -1031,10 +1032,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
)
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey, preferences in
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
}
let tonContext = storedContext.context(config: config)
let tonContext = storedContext.context(config: config, blockchainName: blockchainName)
if wallets.wallets.isEmpty {
if let _ = currentPublicKey {
@ -1044,15 +1046,20 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
} else {
let walletInfo = wallets.wallets[0].info
let exportCompleted = wallets.wallets[0].exportCompleted
if let currentPublicKey = currentPublicKey {
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|> deliverOnMainQueue).start(next: { address in
switch walletContext {
case .generic:
present(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address))
case let .send(address, amount, comment):
present(walletSendScreen(context: context, tonContext: tonContext, randomId: arc4random64(), walletInfo: walletInfo, address: address, amount: amount, comment: comment))
case .generic:
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):
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 {
static var defaultValue: WalletConfiguration {
return WalletConfiguration(config: nil)
return WalletConfiguration(config: nil, blockchainName: nil)
}
public let config: String?
public let blockchainName: String?
fileprivate init(config: String?) {
fileprivate init(config: String?, blockchainName: String?) {
self.config = config
self.blockchainName = blockchainName
}
public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration {
if let data = appConfiguration.data, let config = data["wallet_config"] as? String {
return WalletConfiguration(config: config)
if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String {
return WalletConfiguration(config: config, blockchainName: blockchainName)
} else {
return .defaultValue
}

View File

@ -71,10 +71,9 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
self.animationNode = AnimatedStickerNode()
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.titleNode = TextNode()
self.titleNode.displaysAsynchronously = false

View File

@ -16,8 +16,9 @@ import AnimationUI
public final class WalletInfoScreen: ViewController {
private let context: AccountContext
private let tonContext: TonContext
private let walletInfo: WalletInfo
private let walletInfo: WalletInfo?
private let address: String
private let enableDebugActions: Bool
private var presentationData: PresentationData
@ -26,11 +27,12 @@ public final class WalletInfoScreen: ViewController {
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.tonContext = tonContext
self.walletInfo = walletInfo
self.address = address
self.enableDebugActions = enableDebugActions
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -45,7 +47,9 @@ public final class WalletInfoScreen: ViewController {
self.navigationBar?.intrinsicCanTransitionInline = false
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Wallet/NavigationSettingsIcon"), color: .white), style: .plain, target: self, action: #selector(self.settingsPressed))
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.scrollToTop = { [weak self] in
(self?.displayNode as? WalletInfoScreenNode)?.scrollToTop()
@ -61,25 +65,27 @@ public final class WalletInfoScreen: ViewController {
}
@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() {
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
}
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
guard let strongSelf = self else {
guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else {
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
guard let strongSelf = self else {
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
guard let strongSelf = self else {
return
@ -154,16 +160,16 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
func update(width: CGFloat, scaleTransition: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let sideInset: CGFloat = 16.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 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 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 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)
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.updateTransformScale(node: self.balanceIntegralTextNode, scale: integralScale)
@ -194,6 +200,8 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
var isRefreshing: Bool = false
var timestamp: Int32?
private let hasActions: Bool
let balanceNode: WalletInfoBalanceNode
private let refreshNode: WalletRefreshNode
private let balanceSubtitleNode: ImmediateTextNode
@ -202,12 +210,14 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
private let headerBackgroundNode: ASDisplayNode
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.balanceSubtitleNode = ImmediateTextNode()
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.backgroundColor = .black
@ -233,8 +243,10 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.headerBackgroundNode)
self.addSubnode(self.headerCornerNode)
self.addSubnode(self.receiveButtonNode)
self.addSubnode(self.sendButtonNode)
if hasActions {
self.addSubnode(self.receiveButtonNode)
self.addSubnode(self.sendButtonNode)
}
self.addSubnode(self.balanceNode)
self.addSubnode(self.balanceSubtitleNode)
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 buttonSideInset: CGFloat = 48.0
let titleSpacing: CGFloat = 10.0
@ -255,6 +267,15 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let buttonHeight: CGFloat = 50.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 maxOffset = size.height
@ -266,7 +287,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let minButtonsOffset = maxOffset - buttonHeight - sideInset
let maxButtonsOffset = maxOffset
let buttonTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minButtonsOffset) / (maxButtonsOffset - minButtonsOffset)))
let buttonAlpha = buttonTransition * 1.0
let buttonAlpha: CGFloat = buttonTransition
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.refreshNode.isHidden = false
}
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
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)
}
private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let account: Account
private let tonContext: TonContext
private var presentationData: PresentationData
private let walletInfo: WalletInfo
private let walletInfo: WalletInfo?
private let address: String
private let openTransaction: (WalletTransaction) -> Void
@ -476,7 +499,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var updateTimestampTimer: SwiftSignalKit.Timer?
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
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.tonContext = tonContext
self.presentationData = presentationData
@ -485,12 +508,13 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.openTransaction = openTransaction
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.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
self.listNode.isHidden = true
self.listNode.view.disablesInteractiveModalDismiss = true
self.loadingIndicator = UIActivityIndicatorView(style: .whiteLarge)
@ -527,7 +551,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
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))
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))
@ -656,7 +680,14 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
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
guard let strongSelf = self else {
return
@ -694,7 +725,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
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)
@ -702,7 +733,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.headerNode.isRefreshing = false
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
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
@ -710,7 +741,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
if strongSelf.isReady && !wasReady {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate)
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false)
}
strongSelf.becameReady(animated: strongSelf.didSetContentReady)
@ -733,7 +764,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}
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
@ -742,7 +773,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.headerNode.isRefreshing = false
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
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 {

View File

@ -94,6 +94,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let titleSignNode: TextNode
private let titleNode: TextNode
private let directionNode: TextNode
private let iconNode: ASImageNode
@ -113,6 +114,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
self.bottomStripeNode = ASDisplayNode()
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.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
@ -149,6 +155,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleSignNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.iconNode)
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) {
let makeTitleSignLayout = TextNode.asyncLayout(self.titleSignNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
@ -175,10 +183,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
updatedTheme = item.theme
}
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 sign: String
let title: String
let directionText: String
let titleColor: UIColor
@ -186,7 +195,8 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
var text: String = ""
var description: String = ""
if transferredValue <= 0 {
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
sign = "-"
title = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.list.itemPrimaryTextColor
if item.walletTransaction.outMessages.isEmpty {
directionText = ""
@ -206,7 +216,8 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
}
}
} else {
title = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
sign = "+"
title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.chatList.secretTitleColor
directionText = item.strings.Wallet_Info_TransactionFrom
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))
}
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 (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
}
let _ = titleSignApply()
let _ = titleApply()
let _ = textApply()
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))
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let titleSignFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleSignLayout.size)
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
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
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()
localtime_r(&time, &timeinfo)
self.roundedTimestamp = timeinfo.tm_year * 100 + timeinfo.tm_mon
self.month = timeinfo.tm_mon
self.year = timeinfo.tm_year
self.id = Int64(self.roundedTimestamp)
if timestamp == Int32.max {
self.localTimestamp = timestamp / (granularity) * (granularity)
} else {
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
}
self.roundedTimestamp = self.localTimestamp
self.id = Int64(self.roundedTimestamp)
}
let stickDirection: ListViewItemHeaderStickDirection = .top

View File

@ -80,7 +80,7 @@ public final class WalletPasscodeScreen: ViewController {
}
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)
navigationController.setViewControllers(controllers, animated: true)
}

View File

@ -14,8 +14,10 @@ import TextFormat
import DeviceAccess
import TelegramStringFormatting
import UrlHandling
import OverlayStatusController
private let balanceIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
private let textLimit: Int = 124
private final class WalletSendScreenArguments {
let context: AccountContext
@ -226,7 +228,7 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
case let .commentHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
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)
}, updatedFocus: { focus in
if focus {
@ -279,6 +281,13 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
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 presentInGlobalOverlayImpl: ((ViewController, Any?) -> 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: {
dismissAlertImpl?(false)
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)
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
var state: WalletState?
switch combinedState {
@ -416,7 +455,9 @@ public func walletSendScreen(context: AccountContext, tonContext: TonContext, ra
let amount = amountValue(state.amount)
var sendEnabled = false
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: {
arguments.proceed()

View File

@ -31,12 +31,13 @@ private enum WalletSettingsSection: Int32 {
private enum WalletSettingsEntry: ItemListNodeEntry {
case exportWallet(PresentationTheme, String)
case deleteWallet(PresentationTheme, String)
case deleteWalletInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .exportWallet:
return WalletSettingsSection.exportWallet.rawValue
case .deleteWallet:
case .deleteWallet, .deleteWalletInfo:
return WalletSettingsSection.deleteWallet.rawValue
}
}
@ -47,6 +48,8 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
return 0
case .deleteWallet:
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: {
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(.deleteWallet(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWallet))
entries.append(.deleteWalletInfo(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWalletInfo))
return entries
}
@ -108,6 +115,7 @@ public func walletSettingsController(context: AccountContext, tonContext: TonCon
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.Wallet_Settings_DeleteWalletInfo),
ActionSheetButtonItem(title: presentationData.strings.Wallet_Settings_DeleteWallet, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
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 {
case intro
case created(WalletInfo, [String])
case created(WalletInfo, [String]?)
case success(WalletInfo)
case restoreFailed
case sending(WalletInfo, String, Int64, String, Int64)
case sending(WalletInfo, String, Int64, String, Int64, Data)
case sent(WalletInfo, Int64)
case secureStorageNotAvailable
case secureStorageReset(WalletSecureStorageResetReason)
@ -36,7 +36,7 @@ public final class WalletSplashScreen: ViewController {
private let context: AccountContext
private let tonContext: TonContext
private var presentationData: PresentationData
private let mode: WalletSplashMode
private var mode: WalletSplashMode
private let walletCreatedPreloadState: Promise<CombinedWalletStateResult>?
@ -56,7 +56,7 @@ public final class WalletSplashScreen: ViewController {
self.walletCreatedPreloadState = walletCreatedPreloadState
} else {
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
return .complete()
})
@ -66,7 +66,7 @@ public final class WalletSplashScreen: ViewController {
self.walletCreatedPreloadState = walletCreatedPreloadState
} else {
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
return .complete()
})
@ -86,12 +86,28 @@ public final class WalletSplashScreen: ViewController {
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.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.sendGrams(walletInfo: walletInfo, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: false, randomId: randomId)
case .sent, .created:
let _ = (self.tonContext.keychain.decrypt(walletInfo.encryptedSecret)
|> 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)
case .restoreFailed, .secureStorageNotAvailable, .secureStorageReset:
case .restoreFailed, .secureStorageNotAvailable, .secureStorageReset, .created:
break
case .success:
break
@ -112,8 +128,8 @@ public final class WalletSplashScreen: ViewController {
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) {
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)
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, 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
guard let strongSelf = self else {
return
@ -136,7 +152,7 @@ public final class WalletSplashScreen: ViewController {
}
}),
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))
@ -164,7 +180,7 @@ public final class WalletSplashScreen: ViewController {
var controllers: [UIViewController] = []
for controller in navigationController.viewControllers {
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
controllers.append(infoScreen)
} else {
@ -206,7 +222,30 @@ public final class WalletSplashScreen: ViewController {
], actionLayout: .vertical), in: .window(.root))
})
case let .created(walletInfo, wordList):
strongSelf.push(WalletWordDisplayScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: walletInfo, wordList: wordList, mode: .check, walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
if let wordList = wordList {
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):
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: strongSelf.tonContext.instance)
|> deliverOnMainQueue).start(next: { address in
@ -270,7 +309,7 @@ public final class WalletSplashScreen: ViewController {
}
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)
navigationController.setViewControllers(controllers, animated: true)
}
@ -302,7 +341,7 @@ public final class WalletSplashScreen: ViewController {
}
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
controllers.append(infoScreen)
}
@ -466,7 +505,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet
termsText = NSAttributedString(string: "")
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.animationSize = CGSize(width: 130.0, height: 130.0)
self.animationNode.visibility = true
@ -478,7 +517,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
buttonText = self.presentationData.strings.Wallet_RestoreFailed_CreateWallet
termsText = NSAttributedString(string: "")
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.animationSize = CGSize(width: 130.0, height: 130.0)
self.animationNode.visibility = true
@ -504,7 +543,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
buttonText = self.presentationData.strings.Wallet_Sent_ViewWallet
termsText = NSAttributedString(string: "")
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.animationSize = CGSize(width: 130.0, height: 130.0)
self.animationNode.visibility = true
@ -561,7 +600,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
}
termsText = NSAttributedString(string: "")
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.animationSize = CGSize(width: 130.0, height: 130.0)
self.animationNode.visibility = true

View File

@ -33,6 +33,7 @@ private enum WalletTransactionInfoSection: Int32 {
}
private enum WalletTransactionInfoEntryTag: ItemListItemTag {
case address
case comment
func isEqual(to other: ItemListItemTag) -> Bool {
@ -47,7 +48,7 @@ private enum WalletTransactionInfoEntryTag: ItemListItemTag {
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction)
case infoHeader(PresentationTheme, String)
case infoAddress(PresentationTheme, String)
case infoAddress(PresentationTheme, String, String?)
case infoCopyAddress(PresentationTheme, String)
case infoSendGrams(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)
case let .infoHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .infoAddress(theme, text):
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks)
case let .infoAddress(theme, text, address):
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):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.copyWalletAddress()
@ -173,13 +178,14 @@ private func extractDescription(_ walletTransaction: WalletTransaction) -> Strin
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] = []
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction))
let transferredValue = walletTransaction.transferredValue
let address = extractAddress(walletTransaction)
var singleAddress: String?
let text = stringForAddress(strings: presentationData.strings, address: address)
let description = extractDescription(walletTransaction)
@ -188,9 +194,12 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
} else {
entries.append(.infoHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SenderHeader))
}
entries.append(.infoAddress(presentationData.theme, text))
if case .list = address {
var singleAddres: String?
if case let .list(list) = address, list.count == 1 {
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(.infoSendGrams(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SendGrams))
}
@ -203,7 +212,7 @@ private func walletTransactionInfoControllerEntries(presentationData: Presentati
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 stateValue = Atomic(value: WalletTransactionInfoControllerState())
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)
}
}, sendGrams: {
guard let walletInfo = walletInfo else {
return
}
let address = extractAddress(walletTransaction)
if case let .list(addresses) = address, let address = addresses.first {
dismissImpl?()
@ -235,7 +247,7 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletTransactionInfoEntry>, WalletTransactionInfoEntry.ItemGenerationArguments)) in
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(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))
}
@ -271,9 +283,19 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
return false
})
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
})])
}))
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
if let strongController = controller, let resultItemNode = resultItemNode {
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 class WalletTransactionHeaderItemNode: ListViewItemNode {
private let titleSignNode: TextNode
private let titleNode: TextNode
private let subtitleNode: TextNode
private let iconNode: ASImageNode
@ -353,6 +376,11 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
private var item: WalletTransactionHeaderItem?
init() {
self.titleSignNode = TextNode()
self.titleSignNode.isUserInteractionEnabled = false
self.titleSignNode.contentMode = .left
self.titleSignNode.contentsScale = UIScreen.main.scale
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
@ -373,6 +401,7 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleSignNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.iconNode)
@ -380,6 +409,7 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
}
func asyncLayout() -> (_ item: WalletTransactionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleSignLayout = TextNode.asyncLayout(self.titleSignNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
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 verticalInset: CGFloat = 24.0
let signString: String
let balanceString: String
let titleColor: UIColor
let transferredValue = item.walletTransaction.transferredValue
if transferredValue <= 0 {
balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
signString = "-"
balanceString = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.list.itemPrimaryTextColor
} else {
balanceString = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
signString = "+"
balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.chatList.secretTitleColor
}
@ -408,9 +441,11 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
} else {
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 (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 (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.accessibilityLabel = attributedText.string
let _ = titleSignApply()
let _ = titleApply()
let _ = subtitleApply()
let iconSpacing: CGFloat = 8.0
let contentWidth = titleLayout.size.width + iconSpacing + iconSize.width / 2.0
let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - contentWidth) / 2.0), y: verticalInset), size: titleLayout.size)
let iconSpacing: CGFloat = 4.0
let contentWidth = titleSignLayout.size.width + iconSpacing + titleLayout.size.width + iconSpacing + iconSize.width * 3.0 / 2.0
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)
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.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
strongSelf.titleNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
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
}
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))
strongSelf.view.endEditing(true)
navigationController.setViewControllers(controllers, animated: true)
@ -2247,6 +2248,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? {
}
private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
private let previous: (WordCheckInputNode) -> Void
private let next: (WordCheckInputNode, Bool) -> Void
private let focused: (WordCheckInputNode) -> 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.focused = focused
self.pasteWords = pasteWords
@ -2316,6 +2319,12 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
self.addSubnode(self.inputNode)
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.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
@ -2644,12 +2653,15 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
var inputNodes: [WordCheckInputNode] = []
var previousWord: ((WordCheckInputNode) -> Void)?
var nextWord: ((WordCheckInputNode, Bool) -> Void)?
var focused: ((WordCheckInputNode) -> Void)?
var pasteWords: (([String]) -> Void)?
for i in 0 ..< wordIndices.count {
inputNodes.append(WordCheckInputNode(theme: presentationData.theme, index: wordIndices[i], possibleWordList: possibleWordList, next: { node, 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)
}, isLast: i == wordIndices.count - 1, focused: { node in
focused?(node)
@ -2703,6 +2715,14 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
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
guard let strongSelf = self else {
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)
}
} 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()
}
}