mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
no message
This commit is contained in:
parent
516db55483
commit
ea35454a30
@ -4803,7 +4803,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Wallet.Send.Title" = "Send Grams";
|
||||
"Wallet.Send.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" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers.";
|
||||
"Wallet.Completed.ViewWallet" = "View My Wallet";
|
||||
"Wallet.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
@ -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"
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
|
||||
if [ -z "BUILD_NUMBER" ]; then
|
||||
echo "BUILD_NUMBER is not set"
|
||||
exit 1
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -1,221 +0,0 @@
|
||||
//
|
||||
// ASEditableTextNode.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASEditableTextNodeDelegate;
|
||||
@class ASTextKitComponents;
|
||||
|
||||
@interface ASEditableTextNodeTargetForAction: NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) id _Nullable target;
|
||||
|
||||
- (instancetype)initWithTarget:(id _Nullable)target;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
@abstract Implements a node that supports text editing.
|
||||
@discussion Does not support layer backing.
|
||||
*/
|
||||
@interface ASEditableTextNode : ASDisplayNode <UITextInputTraits>
|
||||
|
||||
/**
|
||||
* @abstract Initializes an editable text node using default TextKit components.
|
||||
*
|
||||
* @return An initialized ASEditableTextNode.
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* @abstract Initializes an editable text node using the provided TextKit components.
|
||||
*
|
||||
* @param textKitComponents The TextKit stack used to render text.
|
||||
* @param placeholderTextKitComponents The TextKit stack used to render placeholder text.
|
||||
*
|
||||
* @return An initialized ASEditableTextNode.
|
||||
*/
|
||||
- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents
|
||||
placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents;
|
||||
|
||||
//! @abstract The text node's delegate, which must conform to the <ASEditableTextNodeDelegate> protocol.
|
||||
@property (nullable, weak) id <ASEditableTextNodeDelegate> delegate;
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
/**
|
||||
@abstract Enable scrolling on the textView
|
||||
@default true
|
||||
*/
|
||||
@property (nonatomic) BOOL scrollEnabled;
|
||||
|
||||
/**
|
||||
@abstract Access to underlying UITextView for more configuration options.
|
||||
@warning This property should only be used on the main thread and should not be accessed before the editable text node's view is created.
|
||||
*/
|
||||
@property (nonatomic, readonly) UITextView *textView;
|
||||
|
||||
//! @abstract The attributes to apply to new text being entered by the user.
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *typingAttributes;
|
||||
|
||||
//! @abstract The range of text currently selected. If length is zero, the range is the cursor location.
|
||||
@property NSRange selectedRange;
|
||||
|
||||
#pragma mark - Placeholder
|
||||
/**
|
||||
@abstract Indicates if the receiver is displaying the placeholder text.
|
||||
@discussion To update the placeholder, see the <attributedPlaceholderText> property.
|
||||
@result YES if the placeholder is currently displayed; NO otherwise.
|
||||
*/
|
||||
- (BOOL)isDisplayingPlaceholder AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract The styled placeholder text displayed by the text node while no text is entered
|
||||
@discussion The placeholder is displayed when the user has not entered any text and the keyboard is not visible.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedPlaceholderText;
|
||||
|
||||
#pragma mark - Modifying User Text
|
||||
/**
|
||||
@abstract The styled text displayed by the receiver.
|
||||
@discussion When the placeholder is displayed (as indicated by -isDisplayingPlaceholder), this value is nil. Otherwise, this value is the attributed text the user has entered. This value can be modified regardless of whether the receiver is the first responder (and thus, editing) or not. Changing this value from nil to non-nil will result in the placeholder being hidden, and the new value being displayed.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
||||
|
||||
#pragma mark - Managing The Keyboard
|
||||
//! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder.
|
||||
@property (nonatomic, readonly) UITextInputMode *textInputMode;
|
||||
|
||||
/**
|
||||
@abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero.
|
||||
*/
|
||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
||||
|
||||
/**
|
||||
@abstract The maximum number of lines to display. Additional lines will require scrolling.
|
||||
@default 0 (No limit)
|
||||
*/
|
||||
@property (nonatomic) NSUInteger maximumLinesToDisplay;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user.
|
||||
@result YES if the receiver's text view is the first-responder; NO otherwise.
|
||||
*/
|
||||
- (BOOL)isFirstResponder AS_WARN_UNUSED_RESULT;
|
||||
|
||||
//! @abstract Makes the receiver's text view the first responder.
|
||||
- (BOOL)becomeFirstResponder;
|
||||
|
||||
//! @abstract Resigns the receiver's text view from first-responder status, if it has it.
|
||||
- (BOOL)resignFirstResponder;
|
||||
|
||||
#pragma mark - Geometry
|
||||
/**
|
||||
@abstract Returns the frame of the given range of characters.
|
||||
@param textRange A range of characters.
|
||||
@discussion This method raises an exception if `textRange` is not a valid range of characters within the receiver's attributed text.
|
||||
@result A CGRect that is the bounding box of the glyphs covered by the given range of characters, in the coordinate system of the receiver.
|
||||
*/
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract <UITextInputTraits> properties.
|
||||
*/
|
||||
@property (nonatomic) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences
|
||||
@property (nonatomic) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault
|
||||
@property (nonatomic) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault;
|
||||
@property (nonatomic) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault
|
||||
@property (nonatomic) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault
|
||||
@property (nonatomic) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
|
||||
@property (nonatomic) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
|
||||
@property (nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO
|
||||
|
||||
- (void)dropAutocorrection;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@interface ASEditableTextNode (Unavailable)
|
||||
|
||||
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
/**
|
||||
* The methods declared by the ASEditableTextNodeDelegate protocol allow the adopting delegate to
|
||||
* respond to notifications such as began and finished editing, selection changed and text updated;
|
||||
* and manage whether a specified text should be replaced.
|
||||
*/
|
||||
@protocol ASEditableTextNodeDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/**
|
||||
@abstract Asks the delegate if editing should begin for the text node.
|
||||
@param editableTextNode An editable text node.
|
||||
@discussion YES if editing should begin; NO if editing should not begin -- the default returns YES.
|
||||
*/
|
||||
- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
/**
|
||||
@abstract Indicates to the delegate that the text node began editing.
|
||||
@param editableTextNode An editable text node.
|
||||
@discussion The invocation of this method coincides with the keyboard animating to become visible.
|
||||
*/
|
||||
- (void)editableTextNodeDidBeginEditing:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
/**
|
||||
@abstract Asks the delegate whether the specified text should be replaced in the editable text node.
|
||||
@param editableTextNode An editable text node.
|
||||
@param range The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
|
||||
@param text The text to insert.
|
||||
@discussion YES if the old text should be replaced by the new text; NO if the replacement operation should be aborted.
|
||||
@result The text node calls this method whenever the user types a new character or deletes an existing character. Implementation of this method is optional -- the default implementation returns YES.
|
||||
*/
|
||||
- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
|
||||
|
||||
/**
|
||||
@abstract Indicates to the delegate that the text node's selection has changed.
|
||||
@param editableTextNode An editable text node.
|
||||
@param fromSelectedRange The previously selected range.
|
||||
@param toSelectedRange The current selected range. Equivalent to the <selectedRange> property.
|
||||
@param dueToEditing YES if the selection change was due to editing; NO otherwise.
|
||||
@discussion You can access the selection of the receiver via <selectedRange>.
|
||||
*/
|
||||
- (void)editableTextNodeDidChangeSelection:(ASEditableTextNode *)editableTextNode fromSelectedRange:(NSRange)fromSelectedRange toSelectedRange:(NSRange)toSelectedRange dueToEditing:(BOOL)dueToEditing;
|
||||
|
||||
/**
|
||||
@abstract Indicates to the delegate that the text node's text was updated.
|
||||
@param editableTextNode An editable text node.
|
||||
@discussion This method is called each time the user updated the text node's text. It is not called for programmatic changes made to the text via the <attributedText> property.
|
||||
*/
|
||||
- (void)editableTextNodeDidUpdateText:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
/**
|
||||
@abstract Indicates to the delegate that the text node has finished editing.
|
||||
@param editableTextNode An editable text node.
|
||||
@discussion The invocation of this method coincides with the keyboard animating to become hidden.
|
||||
*/
|
||||
- (void)editableTextNodeDidFinishEditing:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
<<<<<<< HEAD
|
||||
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
|
||||
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
|
||||
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
|
||||
|
||||
=======
|
||||
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
17
submodules/CloudData/BUCK
Normal file
17
submodules/CloudData/BUCK
Normal file
@ -0,0 +1,17 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "CloudData",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||
"//submodules/Postbox:Postbox#shared",
|
||||
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/CloudKit.framework",
|
||||
],
|
||||
)
|
||||
401
submodules/CloudData/CloudData.xcodeproj/project.pbxproj
Normal file
401
submodules/CloudData/CloudData.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,401 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>archiveVersion</key>
|
||||
<string>1</string>
|
||||
<key>classes</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>objectVersion</key>
|
||||
<string>46</string>
|
||||
<key>objects</key>
|
||||
<dict>
|
||||
<key>1DD70E29001F47FB00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>BUCK</string>
|
||||
<key>path</key>
|
||||
<string>BUCK</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.script.python</string>
|
||||
</dict>
|
||||
<key>1DD70E296A1426C400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>CloudData.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/CloudData.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>B401C979EAB5339800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Sources</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E296A1426C400000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C9795E043F5F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>CloudData</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E29001F47FB00000000</string>
|
||||
<string>B401C979EAB5339800000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E299A98EF7600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>CloudData-Debug.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/CloudData/CloudData-Debug.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>1DD70E293D3D816000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>CloudData-Profile.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/CloudData/CloudData-Profile.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>1DD70E29B0D42CC200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>CloudData-Release.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/CloudData/CloudData-Release.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>B401C9792F7F325000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Buck (Do Not Modify)</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E299A98EF7600000000</string>
|
||||
<string>1DD70E293D3D816000000000</string>
|
||||
<string>1DD70E29B0D42CC200000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C979B781F65D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Configurations</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>B401C9792F7F325000000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E29DB6520C800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libPostbox.dylib</string>
|
||||
<key>path</key>
|
||||
<string>libPostbox.dylib</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>1DD70E29D65BA68200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libSwiftSignalKit.dylib</string>
|
||||
<key>path</key>
|
||||
<string>libSwiftSignalKit.dylib</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>B401C97968022A5500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Frameworks</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E29DB6520C800000000</string>
|
||||
<string>1DD70E29D65BA68200000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E29FD41F1ED00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libCloudData.a</string>
|
||||
<key>path</key>
|
||||
<string>libCloudData.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>B401C979C806358400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Products</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E29FD41F1ED00000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C979EFB6AC4600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>mainGroup</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>B401C9795E043F5F00000000</string>
|
||||
<string>B401C979B781F65D00000000</string>
|
||||
<string>B401C97968022A5500000000</string>
|
||||
<string>B401C979C806358400000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>E7A30F046A1426C400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E296A1426C400000000</string>
|
||||
</dict>
|
||||
<key>1870857F0000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXSourcesBuildPhase</string>
|
||||
<key>files</key>
|
||||
<array>
|
||||
<string>E7A30F046A1426C400000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>E7A30F04DB6520C800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29DB6520C800000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04D65BA68200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29D65BA68200000000</string>
|
||||
</dict>
|
||||
<key>FAF5FAC90000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXCopyFilesBuildPhase</string>
|
||||
<key>files</key>
|
||||
<array>
|
||||
<string>E7A30F04DB6520C800000000</string>
|
||||
<string>E7A30F04D65BA68200000000</string>
|
||||
</array>
|
||||
<key>name</key>
|
||||
<string>Fake Swift Dependencies (Copy Files Phase)</string>
|
||||
<key>runOnlyForDeploymentPostprocessing</key>
|
||||
<integer>1</integer>
|
||||
<key>dstSubfolderSpec</key>
|
||||
<integer>16</integer>
|
||||
<key>dstPath</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<key>4952437303EDA63300000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Debug</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E299A98EF7600000000</string>
|
||||
</dict>
|
||||
<key>4952437350C7218900000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Profile</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E293D3D816000000000</string>
|
||||
</dict>
|
||||
<key>49524373A439BFE700000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E29B0D42CC200000000</string>
|
||||
</dict>
|
||||
<key>218C37090000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCConfigurationList</string>
|
||||
<key>buildConfigurations</key>
|
||||
<array>
|
||||
<string>4952437303EDA63300000000</string>
|
||||
<string>4952437350C7218900000000</string>
|
||||
<string>49524373A439BFE700000000</string>
|
||||
</array>
|
||||
<key>defaultConfigurationIsVisible</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>E66DC04E5E043F5F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXNativeTarget</string>
|
||||
<key>name</key>
|
||||
<string>CloudData</string>
|
||||
<key>productName</key>
|
||||
<string>CloudData</string>
|
||||
<key>productReference</key>
|
||||
<string>1DD70E29FD41F1ED00000000</string>
|
||||
<key>productType</key>
|
||||
<string>com.apple.product-type.library.static</string>
|
||||
<key>dependencies</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>buildPhases</key>
|
||||
<array>
|
||||
<string>1870857F0000000000000000</string>
|
||||
<string>FAF5FAC90000000000000000</string>
|
||||
</array>
|
||||
<key>buildConfigurationList</key>
|
||||
<string>218C37090000000000000000</string>
|
||||
</dict>
|
||||
<key>4952437303EDA63300000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Debug</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>4952437350C7218900000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Profile</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>49524373A439BFE700000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>218C37090000000000000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCConfigurationList</string>
|
||||
<key>buildConfigurations</key>
|
||||
<array>
|
||||
<string>4952437303EDA63300000001</string>
|
||||
<string>4952437350C7218900000001</string>
|
||||
<string>49524373A439BFE700000001</string>
|
||||
</array>
|
||||
<key>defaultConfigurationIsVisible</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>96C847935E043F5F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXProject</string>
|
||||
<key>mainGroup</key>
|
||||
<string>B401C979EFB6AC4600000000</string>
|
||||
<key>targets</key>
|
||||
<array>
|
||||
<string>E66DC04E5E043F5F00000000</string>
|
||||
</array>
|
||||
<key>buildConfigurationList</key>
|
||||
<string>218C37090000000000000001</string>
|
||||
<key>compatibilityVersion</key>
|
||||
<string>Xcode 3.2</string>
|
||||
<key>attributes</key>
|
||||
<dict>
|
||||
<key>LastUpgradeCheck</key>
|
||||
<string>9999</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>rootObject</key>
|
||||
<string>96C847935E043F5F00000000</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Scheme LastUpgradeVersion="9999" version="1.7"><BuildAction buildImplicitDependencies="YES" parallelizeBuildables="YES"><BuildActionEntries><BuildActionEntry buildForAnalyzing="YES" buildForArchiving="YES" buildForProfiling="YES" buildForRunning="YES" buildForTesting="YES"><BuildableReference BlueprintIdentifier="E66DC04E5E043F5F00000000" BlueprintName="CloudData" BuildableIdentifier="primary" BuildableName="libCloudData.a" ReferencedContainer="container:CloudData.xcodeproj"/></BuildActionEntry></BuildActionEntries></BuildAction><TestAction buildConfiguration="Debug" shouldUseLaunchSchemeArgsEnv="YES"><Testables/></TestAction><LaunchAction buildConfiguration="Debug"/><ProfileAction buildConfiguration="Release"/><AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/></Scheme>
|
||||
123
submodules/CloudData/Sources/CloudData.swift
Normal file
123
submodules/CloudData/Sources/CloudData.swift
Normal file
@ -0,0 +1,123 @@
|
||||
import Foundation
|
||||
import CloudKit
|
||||
import MtProtoKit
|
||||
import SwiftSignalKit
|
||||
|
||||
private enum FetchError {
|
||||
case generic
|
||||
case networkUnavailable
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
private func fetchRawData(prefix: String) -> Signal<Data, FetchError> {
|
||||
return Signal { subscriber in
|
||||
let container = CKContainer.default()
|
||||
let publicDatabase = container.database(with: .public)
|
||||
let recordId = CKRecord.ID(recordName: "emergency-datacenter-\(prefix)")
|
||||
publicDatabase.fetch(withRecordID: recordId, completionHandler: { record, error in
|
||||
if let error = error {
|
||||
print("publicDatabase.fetch error: \(error)")
|
||||
if let error = error as? NSError, error.domain == CKError.errorDomain, error.code == 1 {
|
||||
subscriber.putError(.networkUnavailable)
|
||||
} else {
|
||||
subscriber.putError(.generic)
|
||||
}
|
||||
} else if let record = record {
|
||||
guard let dataString = record.object(forKey: "data") as? String else {
|
||||
subscriber.putError(.generic)
|
||||
return
|
||||
}
|
||||
guard let data = Data(base64Encoded: dataString, options: [.ignoreUnknownCharacters]) else {
|
||||
subscriber.putError(.generic)
|
||||
return
|
||||
}
|
||||
var resultData = data
|
||||
resultData.count = 256
|
||||
subscriber.putNext(resultData)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.generic)
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
public func cloudDataAdditionalAddressSource(phoneNumber: Signal<String?, NoError>) -> Signal<MTBackupDatacenterData, NoError> {
|
||||
return phoneNumber
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<MTBackupDatacenterData, NoError> in
|
||||
let phoneNumber: String? = "7950"
|
||||
var prefix = ""
|
||||
if let phoneNumber = phoneNumber, phoneNumber.count >= 1 {
|
||||
prefix = String(phoneNumber[phoneNumber.startIndex ..< phoneNumber.index(after: phoneNumber.startIndex)])
|
||||
}
|
||||
return fetchRawData(prefix: prefix)
|
||||
|> map { data -> MTBackupDatacenterData? in
|
||||
if let datacenterData = MTIPDataDecode(data, phoneNumber ?? "") {
|
||||
return datacenterData
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<MTBackupDatacenterData?, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> mapToSignal { data -> Signal<MTBackupDatacenterData, NoError> in
|
||||
if let data = data {
|
||||
return .single(data)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
private final class CloudDataContextObject {
|
||||
private let queue: Queue
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
|
||||
let container = CKContainer.default()
|
||||
let publicDatabase = container.database(with: .public)
|
||||
|
||||
/*let changesOperation = CKFetchDatabaseChangesOperation(previousServerChangeToken: nil)
|
||||
changesOperation.fetchAllChanges = true
|
||||
changesOperation.recordZoneWithIDChangedBlock = { _ in
|
||||
print("recordZoneWithIDChangedBlock")
|
||||
}
|
||||
changesOperation.recordZoneWithIDWasDeletedBlock = { _ in
|
||||
|
||||
}
|
||||
changesOperation.changeTokenUpdatedBlock = { _ in
|
||||
print("changeTokenUpdatedBlock")
|
||||
}
|
||||
changesOperation.fetchDatabaseChangesCompletionBlock = { serverChangeToken, isMoreComing, error in
|
||||
print("done")
|
||||
}
|
||||
publicDatabase.add(changesOperation)*/
|
||||
}
|
||||
}
|
||||
|
||||
public protocol CloudDataContext {
|
||||
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
public final class CloudDataContextImpl: CloudDataContext {
|
||||
private let queue = Queue()
|
||||
private let impl: QueueLocalObject<CloudDataContextObject>
|
||||
|
||||
public init() {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return CloudDataContextObject(queue: queue)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -558,7 +558,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
})
|
||||
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
init(target: Any?, action: Selector?, canBegin: @escaping () -> Bool) {
|
||||
self.canBegin = canBegin
|
||||
|
||||
override init(target: Any?, action: Selector?) {
|
||||
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!
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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,11 +496,22 @@ 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 {
|
||||
duration = 0.5
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -21,7 +21,7 @@ final class InstantVideoControllerRecordingStatus {
|
||||
}
|
||||
}
|
||||
|
||||
final class InstantVideoController: LegacyController {
|
||||
final class InstantVideoController: LegacyController, StandalonePresentableController {
|
||||
private var captureController: TGVideoMessageCaptureController?
|
||||
|
||||
var onDismiss: (() -> Void)?
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -17,6 +17,7 @@ import UrlHandling
|
||||
import WalletUI
|
||||
import 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))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user