Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2019-10-04 04:32:56 +03:00
commit 0a717a2173
174 changed files with 6933 additions and 3737 deletions

View file

@ -168,6 +168,28 @@ package_arm64:
PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \
sh package_app.sh iphoneos-arm64 $(BUCK) $(BUCK_OPTIONS) ${BUCK_RELEASE_OPTIONS}
package_debug_arm64:
PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \
PACKAGE_CODE_SIGN_IDENTITY="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \
PACKAGE_PROVISIONING_PROFILE_APP="${DEVELOPMENT_PROVISIONING_PROFILE_APP}" \
PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \
PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE}" \
PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \
PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \
PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \
PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \
PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \
PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \
PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \
PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \
PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \
PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP}" \
PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" \
PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \
ENABLE_GET_TASK_ALLOW=1 \
CODESIGNING_PROFILES_VARIANT="development" \
sh package_app.sh iphoneos-arm64 $(BUCK) $(BUCK_OPTIONS) ${BUCK_RELEASE_OPTIONS}
package:
PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \
PACKAGE_CODE_SIGN_IDENTITY="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \
@ -192,7 +214,7 @@ app: build package
app_arm64: build_arm64 package_arm64
app_debug_arm64: build_debug_arm64 package_arm64
app_debug_arm64: build_debug_arm64 package_debug_arm64
build_buckdebug: check_env
BUCK_DEBUG_MODE=1 $(BUCK) build \

View file

@ -302,6 +302,11 @@
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIFileSharingEnabled</key>
<false/>
<key>UILaunchStoryboardName</key>
@ -357,10 +362,5 @@
</dict>
</dict>
</array>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
</dict>
</plist>

View file

@ -4780,6 +4780,7 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.Info.Send" = "Send";
"Wallet.Info.RefreshErrorTitle" = "An Error Occurred";
"Wallet.Info.RefreshErrorText" = "The wallet state can not be retrieved at this time. Please try again later.";
"Wallet.Info.RefreshErrorNetworkText" = "The wallet state can not be retrieved at this time. Please try again later.";
"Wallet.Info.UnknownTransaction" = "Empty Transaction";
"Wallet.Info.TransactionTo" = "to";
"Wallet.Info.TransactionFrom" = "from";
@ -4880,12 +4881,15 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.Words.NotDoneText" = "You didn't have enough time to write those words down.";
"Wallet.Words.NotDoneOk" = "OK, Sorry";
"Wallet.Words.NotDoneResponse" = "Apologies Accepted";
"Wallet.Send.NetworkError" = "Network Error";
"Wallet.Send.ErrorNotEnoughFunds" = "Not Enough Funds";
"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again.";
"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again.";
"Wallet.Send.UninitializedTitle" = "Warning";
"Wallet.Send.UninitializedText" = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?";
"Wallet.Send.SendAnyway" = "Send Anyway";
"Wallet.Receive.CreateInvoice" = "Create Invoice";
"Wallet.Receive.CreateInvoiceInfo" = "You can specify amount and purpose of the payment to save the sender some time.";
"Wallet.Send.SendingToYourself" = "Sending Grams from a wallet to the same wallet doesn't make sense, you will simply waste a portion of the value on blockchain fees.";
"Conversation.WalletRequiredTitle" = "Gram Wallet Required";
"Conversation.WalletRequiredText" = "This link can be used to send money on the TON Blockchain. To do this, you need to set up a Gram wallet first.";
"Conversation.WalletRequiredNotNow" = "Not Now";

View file

@ -184,6 +184,12 @@ for ITEM in $APP_ITEMS_WITH_PROVISIONING_PROFILE; do
fi
done
if [ "$ENABLE_GET_TASK_ALLOW" == "1" ]; then
KEY="com.apple.security.get-task-allow"
PLUTIL_KEY=$(echo "$KEY" | sed 's/\./\\\./g')
plutil -insert "$PLUTIL_KEY" -xml "<false/>" "$PROFILE_ENTITLEMENTS_PATH"
fi
ENTITLEMENTS_VAR=PACKAGE_ENTITLEMENTS_$ITEM
if [ ! -z "${!ENTITLEMENTS_VAR}" ]; then
if [ ! -f "${!ENTITLEMENTS_VAR}" ]; then

View file

@ -24,6 +24,6 @@
+ (void)getHardwareEncryptionAvailableWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion;
+ (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, NSData * _Nullable))completion;
+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion;
+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, bool))completion;
@end

View file

@ -694,28 +694,29 @@ API_AVAILABLE(ios(10))
});
}
+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion {
+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, bool))completion {
dispatch_async([self encryptionQueue], ^{
LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false];
if (privateKey == nil) {
completion(nil);
completion(nil, false);
return;
}
if (privateKey == nil) {
completion(nil);
completion(nil, false);
return;
}
NSData *currentPublicKey = [privateKey getPublicKey];
if (currentPublicKey == nil) {
completion(nil);
completion(nil, false);
return;
}
if (![publicKey isEqualToData:currentPublicKey]) {
completion(nil);
completion(nil, false);
return;
}
NSData *result = [privateKey decrypt:secret cancelled:nil];
completion(result);
bool cancelled = false;
NSData *result = [privateKey decrypt:secret cancelled:&cancelled];
completion(result, cancelled);
});
}

View file

@ -47,33 +47,47 @@ private func fetchRawData(prefix: String) -> Signal<Data, FetchError> {
}
@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
private final class CloudDataPrefixContext {
private let prefix: String
private let value = Promise<Data?>()
private var lastRequestTimestamp: Double?
init(prefix: String) {
self.prefix = prefix
}
private func fetch() {
let fetchSignal = (
fetchRawData(prefix: self.prefix)
|> map(Optional.init)
|> `catch` { error -> Signal<Data?, NoError> in
switch error {
case .networkUnavailable:
return .complete()
default:
return .single(nil)
}
}
|> restart
)
|> take(1)
self.value.set(fetchSignal)
}
func get() -> Signal<Data?, NoError> {
var shouldFetch = false
let timestamp = CFAbsoluteTimeGetCurrent()
if let lastRequestTimestamp = self.lastRequestTimestamp {
shouldFetch = timestamp >= lastRequestTimestamp + 1.0 * 60.0
} else {
shouldFetch = true
}
|> `catch` { error -> Signal<MTBackupDatacenterData?, NoError> in
return .complete()
}
|> mapToSignal { data -> Signal<MTBackupDatacenterData, NoError> in
if let data = data {
return .single(data)
} else {
return .complete()
}
if shouldFetch {
self.lastRequestTimestamp = timestamp
self.fetch()
}
return self.value.get()
}
}
@ -81,32 +95,25 @@ public func cloudDataAdditionalAddressSource(phoneNumber: Signal<String?, NoErro
private final class CloudDataContextObject {
private let queue: Queue
private var prefixContexts: [String: CloudDataPrefixContext] = [:]
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")
}
func get(prefix: String) -> Signal<Data?, NoError> {
let context: CloudDataPrefixContext
if let current = self.prefixContexts[prefix] {
context = current
} else {
context = CloudDataPrefixContext(prefix: prefix)
}
changesOperation.recordZoneWithIDWasDeletedBlock = { _ in
}
changesOperation.changeTokenUpdatedBlock = { _ in
print("changeTokenUpdatedBlock")
}
changesOperation.fetchDatabaseChangesCompletionBlock = { serverChangeToken, isMoreComing, error in
print("done")
}
publicDatabase.add(changesOperation)*/
return context.get()
}
}
public protocol CloudDataContext {
func get(phoneNumber: Signal<String?, NoError>) -> Signal<MTBackupDatacenterData, NoError>
}
@available(iOS 10.0, *)
@ -120,4 +127,29 @@ public final class CloudDataContextImpl: CloudDataContext {
return CloudDataContextObject(queue: queue)
})
}
public func get(phoneNumber: Signal<String?, NoError>) -> Signal<MTBackupDatacenterData, NoError> {
return phoneNumber
|> take(1)
|> mapToSignal { phoneNumber -> Signal<MTBackupDatacenterData, NoError> in
var prefix = ""
if let phoneNumber = phoneNumber, phoneNumber.count >= 1 {
prefix = String(phoneNumber[phoneNumber.startIndex ..< phoneNumber.index(after: phoneNumber.startIndex)])
}
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.get(prefix: prefix).start(next: { data in
if let data = data, let datacenterData = MTIPDataDecode(data, phoneNumber ?? "") {
subscriber.putNext(datacenterData)
subscriber.putCompletion()
} else {
subscriber.putCompletion()
}
}))
}
return disposable
}
}
}
}

View file

@ -392,7 +392,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple
controller?.present(c, in: .window(.root), with: a)
}
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
//controller?.view.endEditing(true)
controller?.dismiss()
}
ensureTextVisibleImpl = { [weak controller] in

View file

@ -1234,11 +1234,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
case let .controller(controller):
let controllerPoint = self.view.convert(point, to: controller.controller.view)
if let result = controller.controller.view.hitTest(controllerPoint, with: event) {
if result is UIScrollView {
return result
} else if result is ListViewBackingView {
return result
}
return result
}
}
}

View file

@ -57,6 +57,8 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
override public func reset() {
super.reset()
self.endPressedAppearance()
self.currentProgress = 0.0
self.delayTimer?.invalidate()
self.delayTimer = nil
@ -152,9 +154,8 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
if let touch = touches.first {
if !self.currentProgress.isZero, self.isValidated {
if #available(iOS 9.0, *) {
self.activationProgress?(0.0, .ended(self.currentProgress))
}
self.currentProgress = 0.0
self.activationProgress?(0.0, .ended(self.currentProgress))
}
self.externalEnded?((self.view, touch.location(in: self.view)))
@ -170,9 +171,8 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
super.touchesCancelled(touches, with: event)
if let touch = touches.first, !self.currentProgress.isZero, self.isValidated {
if #available(iOS 9.0, *) {
self.activationProgress?(0.0, .ended(self.currentProgress))
}
self.currentProgress = 0.0
self.activationProgress?(0.0, .ended(self.currentProgress))
}
self.delayTimer?.invalidate()
@ -183,6 +183,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
public func cancel() {
if !self.currentProgress.isZero, self.isValidated {
self.currentProgress = 0.0
self.activationProgress?(0.0, .ended(self.currentProgress))
self.delayTimer?.invalidate()

View file

@ -83,7 +83,7 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode:
self.addSubnode(self.searchBar)
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
//self?.searchBar.deactivate(clear: false)
self?.cancel()
}
@ -159,6 +159,8 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
self.navigationPresentation = .modal
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in
@ -191,7 +193,6 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.controllerNode.animateIn()
self.navigationContentNode?.activate()
}

View file

@ -111,7 +111,6 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
}
self.sections = sections
var sectionTitles = sections.map { $0.0 }
sectionTitles.insert(UITableView.indexSearch, at: 0)
self.sectionTitles = sectionTitles
super.init()

View file

@ -25,6 +25,7 @@ open class ActionSheetController: ViewController, PresentableController, Standal
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true
}

View file

@ -38,6 +38,8 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder,
self.hasHapticFeedback = hasHapticFeedback
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {

View file

@ -95,10 +95,8 @@ final class GlobalOverlayPresentationContext {
}
return keyboardWindow
} else {
if underStatusBar, let view = self.parentView {
if let view = self.parentView {
return view
} else {
return statusBarHost.statusBarWindow
}
}
}

View file

@ -3313,6 +3313,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
indicatorFrame.origin.y = self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorBottomInset) - indicatorFrame.height
}
if indicatorFrame.origin.y.isNaN {
indicatorFrame.origin.y = indicatorTopInset
}
if indicatorHeight >= visibleHeightWithoutIndicatorInsets {
verticalScrollIndicator.isHidden = true
verticalScrollIndicator.frame = indicatorFrame

View file

@ -76,13 +76,22 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
private(set) var controllers: [ViewController] = []
private var state: State = State(layout: nil, canBeClosed: nil, top: nil, transition: nil, pending: nil)
private var ignoreInputHeight: Bool = false
private(set) var isReady: Bool = false
var isReadyUpdated: (() -> Void)?
var controllerRemoved: (ViewController) -> Void
var keyboardViewManager: KeyboardViewManager? {
didSet {
if self.keyboardViewManager !== oldValue {
}
}
var canHaveKeyboardFocus: Bool = false {
didSet {
if self.canHaveKeyboardFocus != oldValue {
if !self.canHaveKeyboardFocus {
self.view.endEditing(true)
self.performUpdate(transition: .animated(duration: 0.5, curve: .spring))
}
}
}
}
@ -147,6 +156,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
guard self.state.transition == nil else {
return
}
let beginGesture = self.controllers.count > 1
if beginGesture {
@ -163,7 +173,11 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
topController.viewWillDisappear(true)
let topNode = topController.displayNode
bottomController.containerLayoutUpdated(layout, transition: .immediate)
var bottomControllerLayout = layout
if bottomController.view.disableAutomaticKeyboardHandling.isEmpty {
bottomControllerLayout = bottomControllerLayout.withUpdatedInputHeight(nil)
}
bottomController.containerLayoutUpdated(bottomControllerLayout, transition: .immediate)
bottomController.viewWillAppear(true)
let bottomNode = bottomController.displayNode
@ -195,11 +209,16 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
let topController = top.value
let bottomController = transition.previous.value
if viewTreeContainsFirstResponder(view: top.value.view) {
strongSelf.ignoreInputHeight = true
}
strongSelf.keyboardViewManager?.dismissEditingWithoutAnimation(view: topController.view)
strongSelf.state.transition = nil
strongSelf.controllerRemoved(top.value)
strongSelf.ignoreInputHeight = false
})
} else {
navigationTransitionCoordinator.animateCancel({ [weak self] in
@ -259,7 +278,11 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
} else {
transitionType = .pop
}
self.state.pending = PendingChild(value: self.makeChild(layout: layout.withUpdatedInputHeight(nil), value: last), transitionType: transitionType, transition: transition, update: { [weak self] pendingChild in
var updatedLayout = layout
if last.view.disableAutomaticKeyboardHandling.isEmpty {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
self.state.pending = PendingChild(value: self.makeChild(layout: updatedLayout, value: last), transitionType: transitionType, transition: transition, update: { [weak self] pendingChild in
self?.pendingChildIsReady(pendingChild)
})
}
@ -274,7 +297,11 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
self.state.pending = nil
let previous = self.state.top
self.state.top = pending.value
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout.withUpdatedInputHeight(nil), transition: pending.transition)
var updatedLayout = layout
if pending.value.value.view.disableAutomaticKeyboardHandling.isEmpty {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: updatedLayout, transition: pending.transition)
statusBarTransition = pending.transition
if !self.isReady {
self.isReady = true
@ -291,7 +318,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
var updatedStatusBarStyle = self.statusBarStyle
if let top = self.state.top {
self.applyLayout(layout: layout, to: top, isMaster: true, transition: transition)
var updatedLayout = layout
if let topTransition = self.state.transition, top.value.view.disableAutomaticKeyboardHandling.isEmpty {
if !viewTreeContainsFirstResponder(view: top.value.view) {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
}
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
} else {
updatedStatusBarStyle = .Ignore
@ -349,6 +382,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
guard let strongSelf = self, let topTransition = topTransition, strongSelf.state.transition === topTransition else {
return
}
if viewTreeContainsFirstResponder(view: topTransition.previous.value.view) {
strongSelf.ignoreInputHeight = true
}
strongSelf.keyboardViewManager?.dismissEditingWithoutAnimation(view: topTransition.previous.value.view)
strongSelf.state.transition = nil
@ -361,6 +398,8 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
strongSelf.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
toValue.value.viewDidAppear(true)
}
strongSelf.ignoreInputHeight = false
})
} else {
if let fromValue = fromValue {
@ -383,12 +422,19 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
private func makeChild(layout: ContainerViewLayout, value: ViewController) -> Child {
value.containerLayoutUpdated(layout, transition: .immediate)
return Child(value: value, layout: layout)
var updatedLayout = layout
if value.view.disableAutomaticKeyboardHandling.isEmpty {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
value.containerLayoutUpdated(updatedLayout, transition: .immediate)
return Child(value: value, layout: updatedLayout)
}
private func applyLayout(layout: ContainerViewLayout, to child: Child, isMaster: Bool, transition: ContainedViewLayoutTransition) {
var childFrame = CGRect(origin: CGPoint(), size: layout.size)
var updatedLayout = layout
var shouldSyncKeyboard = false
if let transition = self.state.transition {
childFrame.origin.x = child.value.displayNode.frame.origin.x
@ -400,10 +446,21 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
case .push:
break
}
if updatedLayout.inputHeight != nil {
if !self.canHaveKeyboardFocus && child.value.view.disableAutomaticKeyboardHandling.isEmpty {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
}
} else {
if isMaster {
shouldSyncKeyboard = true
}
if updatedLayout.inputHeight != nil && child.value.view.disableAutomaticKeyboardHandling.isEmpty {
if !self.canHaveKeyboardFocus || self.ignoreInputHeight {
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
}
}
}
if child.value.displayNode.frame != childFrame {
transition.updateFrame(node: child.value.displayNode, frame: childFrame)
@ -411,9 +468,9 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
if shouldSyncKeyboard && isMaster {
self.syncKeyboard(leftEdge: childFrame.minX, transition: transition)
}
if child.layout != layout {
child.layout = layout
child.value.containerLayoutUpdated(layout, transition: transition)
if child.layout != updatedLayout {
child.layout = updatedLayout
child.value.containerLayoutUpdated(updatedLayout, transition: transition)
}
}
@ -430,13 +487,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
private func pendingChildIsReady(_ child: PendingChild) {
if let pending = self.state.pending, pending === child {
pending.isReady = true
self.performUpdate()
self.performUpdate(transition: .immediate)
}
}
private func performUpdate() {
private func performUpdate(transition: ContainedViewLayoutTransition) {
if let layout = self.state.layout, let canBeClosed = self.state.canBeClosed {
self.update(layout: layout, canBeClosed: canBeClosed, controllers: self.controllers, transition: .immediate)
self.update(layout: layout, canBeClosed: canBeClosed, controllers: self.controllers, transition: transition)
}
}

View file

@ -118,12 +118,19 @@ open class NavigationController: UINavigationController, ContainableController,
return self.view as! NavigationControllerView
}
var inCallNavigate: (() -> Void)?
private var inCallStatusBar: StatusBar?
private var globalScrollToTopNode: ScrollToTopNode?
private var rootContainer: RootContainer?
private var rootModalFrame: NavigationModalFrame?
private var modalContainers: [NavigationModalContainer] = []
private var overlayContainers: [NavigationOverlayContainer] = []
private var validLayout: ContainerViewLayout?
private var validStatusBarStyle: NavigationStatusBarStyle?
private var validStatusBarHidden: Bool = false
private var ignoreInputHeight: Bool = false
private var currentStatusBarExternalHidden: Bool = false
private var scheduledLayoutTransitionRequestId: Int = 0
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
@ -148,10 +155,20 @@ open class NavigationController: UINavigationController, ContainableController,
private var _displayNode: ASDisplayNode?
public var displayNode: ASDisplayNode {
if let value = self._displayNode {
return value
}
if !self.isViewLoaded {
self.loadView()
}
return self._displayNode!
}
var statusBarHost: StatusBarHost?
var statusBarHost: StatusBarHost? {
didSet {
}
}
var keyboardViewManager: KeyboardViewManager?
public func updateMasterDetailsBlackout(_ blackout: MasterDetailLayoutBlackout?, transition: ContainedViewLayoutTransition) {
@ -199,6 +216,9 @@ open class NavigationController: UINavigationController, ContainableController,
for modalContainer in self.modalContainers {
supportedOrientations = supportedOrientations.intersection(modalContainer.container.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
}
for overlayContrainer in self.overlayContainers {
supportedOrientations = supportedOrientations.intersection(overlayContrainer.controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
}
return supportedOrientations
}
@ -214,18 +234,10 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
if self.isViewLoaded {
if statusBarStyleUpdated {
self.validStatusBarStyle = self.theme.statusBar
let normalStatusBarStyle: UIStatusBarStyle
switch self.theme.statusBar {
case .black:
normalStatusBarStyle = .default
case .white:
normalStatusBarStyle = .lightContent
}
//self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false)
}
self.controllerView.backgroundColor = theme.emptyAreaColor
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate)
}
}
}
@ -239,16 +251,41 @@ 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) {
private weak var currentTopVisibleOverlayContainerStatusBar: NavigationOverlayContainer? = nil
private func updateContainers(layout rawLayout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var layout = rawLayout
if self.ignoreInputHeight {
if layout.inputHeight == nil {
self.ignoreInputHeight = false
} else {
layout = layout.withUpdatedInputHeight(nil)
}
}
if let globalScrollToTopNode = self.globalScrollToTopNode {
globalScrollToTopNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: layout.size.width, height: 1.0))
}
if let inCallStatusBar = self.inCallStatusBar {
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(40.0, layout.safeInsets.top)))
if inCallStatusBar.frame.isEmpty {
inCallStatusBar.frame = inCallStatusBarFrame
} else {
transition.updateFrame(node: inCallStatusBar, frame: inCallStatusBarFrame)
}
layout.statusBarHeight = inCallStatusBarFrame.height
self.inCallStatusBar?.frame = inCallStatusBarFrame
}
let navigationLayout = makeNavigationLayout(mode: self.mode, layout: layout, controllers: self._viewControllers)
var transition = transition
var statusBarStyle: StatusBarStyle = .Ignore
var statusBarHidden = false
var animateStatusBarStyleTransition = transition.isAnimated
var modalContainers: [NavigationModalContainer] = []
@ -289,14 +326,16 @@ open class NavigationController: UINavigationController, ContainableController,
strongSelf.updateContainers(layout: layout, transition: transition)
}
}
modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] in
modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] hadInputFocus in
guard let strongSelf = self, let modalContainer = modalContainer else {
return
}
let controllers = strongSelf._viewControllers.filter { controller in
return !modalContainer.container.controllers.contains(where: { $0 === controller })
}
strongSelf.ignoreInputHeight = hadInputFocus
strongSelf.setViewControllers(controllers, animated: false)
strongSelf.ignoreInputHeight = false
}
}
modalContainers.append(modalContainer)
@ -304,13 +343,68 @@ open class NavigationController: UINavigationController, ContainableController,
for container in self.modalContainers {
if !modalContainers.contains(where: { $0 === container }) {
transition = container.dismiss(transition: transition, completion: { [weak container] in
if viewTreeContainsFirstResponder(view: container.view) {
self.ignoreInputHeight = true
container.view.endEditing(true)
}
transition = container.dismiss(transition: transition, completion: { [weak self, weak container] in
container?.removeFromSupernode()
})
}
}
self.modalContainers = modalContainers
var previousOverlayContainer: NavigationOverlayContainer?
var topVisibleOverlayContainerWithStatusBar: NavigationOverlayContainer?
for i in (0 ..< self.overlayContainers.count).reversed() {
let overlayContainer = self.overlayContainers[i]
let containerTransition: ContainedViewLayoutTransition
if overlayContainer.supernode == nil {
containerTransition = .immediate
} else {
containerTransition = transition
}
containerTransition.updateFrame(node: overlayContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
overlayContainer.update(layout: layout, transition: containerTransition)
if overlayContainer.supernode == nil && overlayContainer.isReady {
if let previousOverlayContainer = previousOverlayContainer {
self.displayNode.insertSubnode(overlayContainer, belowSubnode: previousOverlayContainer)
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalScrollToTopNode)
} else {
self.displayNode.addSubnode(overlayContainer)
}
overlayContainer.transitionIn()
}
if overlayContainer.supernode != nil {
previousOverlayContainer = overlayContainer
let controllerStatusBarStyle = overlayContainer.controller.statusBar.statusBarStyle
switch controllerStatusBarStyle {
case .Black, .White, .Hide:
if topVisibleOverlayContainerWithStatusBar == nil {
topVisibleOverlayContainerWithStatusBar = overlayContainer
}
if case .Hide = controllerStatusBarStyle {
statusBarHidden = true
} else {
statusBarHidden = overlayContainer.controller.statusBar.alpha.isZero
}
case .Ignore:
break
}
}
}
if self.currentTopVisibleOverlayContainerStatusBar !== topVisibleOverlayContainerWithStatusBar {
animateStatusBarStyleTransition = true
self.currentTopVisibleOverlayContainerStatusBar = topVisibleOverlayContainerWithStatusBar
}
var previousModalContainer: NavigationModalContainer?
var visibleModalCount = 0
var topModalDismissProgress: CGFloat = 0.0
@ -342,6 +436,10 @@ open class NavigationController: UINavigationController, ContainableController,
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
} else if let inCallStatusBar = self.inCallStatusBar {
self.displayNode.insertSubnode(modalContainer, belowSubnode: inCallStatusBar)
} else if let previousOverlayContainer = previousOverlayContainer {
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousOverlayContainer)
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
self.displayNode.insertSubnode(modalContainer, belowSubnode: globalScrollToTopNode)
} else {
self.displayNode.addSubnode(modalContainer)
}
@ -354,11 +452,14 @@ open class NavigationController: UINavigationController, ContainableController,
topModalDismissProgress = modalContainer.dismissProgress
if case .compact = layout.metrics.widthClass {
modalContainer.keyboardViewManager = self.keyboardViewManager
modalContainer.canHaveKeyboardFocus = true
} else {
modalContainer.keyboardViewManager = nil
modalContainer.canHaveKeyboardFocus = true
}
} else {
modalContainer.keyboardViewManager = nil
modalContainer.canHaveKeyboardFocus = false
}
previousModalContainer = modalContainer
}
@ -371,8 +472,10 @@ open class NavigationController: UINavigationController, ContainableController,
case let .flat(flatContainer):
if previousModalContainer == nil {
flatContainer.keyboardViewManager = self.keyboardViewManager
flatContainer.canHaveKeyboardFocus = true
} else {
flatContainer.keyboardViewManager = nil
flatContainer.canHaveKeyboardFocus = false
}
transition.updateFrame(node: flatContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: transition)
@ -386,6 +489,13 @@ open class NavigationController: UINavigationController, ContainableController,
}
strongSelf.updateContainers(layout: layout, transition: transition)
}
if previousModalContainer == nil {
flatContainer.keyboardViewManager = self.keyboardViewManager
flatContainer.canHaveKeyboardFocus = true
} else {
flatContainer.keyboardViewManager = nil
flatContainer.canHaveKeyboardFocus = false
}
self.displayNode.insertSubnode(flatContainer, at: 0)
self.rootContainer = .flat(flatContainer)
flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size)
@ -402,6 +512,13 @@ open class NavigationController: UINavigationController, ContainableController,
}
strongSelf.updateContainers(layout: layout, transition: transition)
}
if previousModalContainer == nil {
flatContainer.keyboardViewManager = self.keyboardViewManager
flatContainer.canHaveKeyboardFocus = true
} else {
flatContainer.keyboardViewManager = nil
flatContainer.canHaveKeyboardFocus = false
}
self.displayNode.insertSubnode(flatContainer, at: 0)
self.rootContainer = .flat(flatContainer)
flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size)
@ -413,23 +530,42 @@ open class NavigationController: UINavigationController, ContainableController,
case let .flat(flatContainer):
let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
self?.controllerRemoved(controller)
}, scrollToTop: { [weak self] subject in
self?.scrollToTop(subject)
})
self.displayNode.insertSubnode(splitContainer, at: 0)
self.rootContainer = .split(splitContainer)
if previousModalContainer == nil {
splitContainer.canHaveKeyboardFocus = true
} else {
splitContainer.canHaveKeyboardFocus = false
}
splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size)
splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate)
flatContainer.statusBarStyleUpdated = nil
flatContainer.removeFromSupernode()
case let .split(splitContainer):
if previousModalContainer == nil {
splitContainer.canHaveKeyboardFocus = true
} else {
splitContainer.canHaveKeyboardFocus = false
}
transition.updateFrame(node: splitContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: transition)
}
} else {
let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
self?.controllerRemoved(controller)
}, scrollToTop: { [weak self] subject in
self?.scrollToTop(subject)
})
self.displayNode.insertSubnode(splitContainer, at: 0)
self.rootContainer = .split(splitContainer)
if previousModalContainer == nil {
splitContainer.canHaveKeyboardFocus = true
} else {
splitContainer.canHaveKeyboardFocus = false
}
splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size)
splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate)
}
@ -439,8 +575,9 @@ open class NavigationController: UINavigationController, ContainableController,
switch rootContainer {
case let .flat(container):
statusBarStyle = container.statusBarStyle
self.globalScrollToTopNode?.isHidden = false
case .split:
break
self.globalScrollToTopNode?.isHidden = true
}
}
@ -559,6 +696,18 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
if self.inCallStatusBar != nil {
statusBarStyle = .White
}
if let topVisibleOverlayContainerWithStatusBar = topVisibleOverlayContainerWithStatusBar {
statusBarStyle = topVisibleOverlayContainerWithStatusBar.controller.statusBar.statusBarStyle
}
if self.currentStatusBarExternalHidden {
statusBarHidden = true
}
let resolvedStatusBarStyle: NavigationStatusBarStyle
switch statusBarStyle {
case .Ignore, .Hide:
@ -580,6 +729,11 @@ open class NavigationController: UINavigationController, ContainableController,
}
self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: animateStatusBarStyleTransition)
}
if self.validStatusBarHidden != statusBarHidden {
self.validStatusBarHidden = statusBarHidden
self.statusBarHost?.setStatusBarHidden(statusBarHidden, animated: animateStatusBarStyleTransition)
}
}
private func controllerRemoved(_ controller: ViewController) {
@ -589,6 +743,28 @@ open class NavigationController: UINavigationController, ContainableController,
public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
}
private func scrollToTop(_ subject: NavigationSplitContainerScrollToTop) {
if let _ = self.inCallStatusBar {
self.inCallNavigate?()
} else if let rootContainer = self.rootContainer {
if let modalContainer = self.modalContainers.last {
modalContainer.container.controllers.last?.scrollToTop?()
} else {
switch rootContainer {
case let .flat(container):
container.controllers.last?.scrollToTop?()
case let .split(container):
switch subject {
case .master:
container.masterControllers.last?.scrollToTop?()
case .detail:
container.detailControllers.last?.scrollToTop?()
}
}
}
}
}
public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
/*for record in self._viewControllers {
if let controller = record.controller as? ContainableController {
@ -613,9 +789,11 @@ open class NavigationController: UINavigationController, ContainableController,
}
self.navigationBar.removeFromSuperview()
/*let inCallStatusBar = StatusBar()
self.displayNode.addSubnode(inCallStatusBar)
self.inCallStatusBar = inCallStatusBar*/
let globalScrollToTopNode = ScrollToTopNode(action: { [weak self] in
self?.scrollToTop(.master)
})
self.displayNode.addSubnode(globalScrollToTopNode)
self.globalScrollToTopNode = globalScrollToTopNode
}
public func pushViewController(_ controller: ViewController) {
@ -663,7 +841,11 @@ open class NavigationController: UINavigationController, ContainableController,
public func filterController(_ controller: ViewController, animated: Bool) {
let controllers = self.viewControllers.filter({ $0 !== controller })
if controllers.count != self.viewControllers.count {
if controller.isViewLoaded && viewTreeContainsFirstResponder(view: controller.view) {
self.ignoreInputHeight = true
}
self.setViewControllers(controllers, animated: animated)
self.ignoreInputHeight = false
}
}
@ -750,6 +932,53 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
public func presentOverlay(controller: ViewController, inGlobal: Bool = false) {
let container = NavigationOverlayContainer(controller: controller, controllerRemoved: { [weak self] controller in
guard let strongSelf = self else {
return
}
for i in 0 ..< strongSelf.overlayContainers.count {
let overlayContainer = strongSelf.overlayContainers[i]
if overlayContainer.controller === controller {
overlayContainer.removeFromSupernode()
strongSelf.overlayContainers.remove(at: i)
break
}
}
if let layout = strongSelf.validLayout {
strongSelf.updateContainers(layout: layout, transition: .immediate)
}
}, statusBarUpdated: { [weak self] transition in
guard let strongSelf = self else {
return
}
if let layout = strongSelf.validLayout {
strongSelf.updateContainers(layout: layout, transition: transition)
}
})
self.overlayContainers.append(container)
container.isReadyUpdated = { [weak self, weak container] in
guard let strongSelf = self, let container = container else {
return
}
if let layout = strongSelf.validLayout {
strongSelf.updateContainers(layout: layout, transition: .immediate)
}
}
if let layout = self.validLayout {
self.updateContainers(layout: layout, transition: .immediate)
}
}
func updateExternalStatusBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) {
if self.currentStatusBarExternalHidden != value {
self.currentStatusBarExternalHidden = value
if let layout = self.validLayout {
self.updateContainers(layout: layout, transition: transition)
}
}
}
override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
preconditionFailure()
}
@ -816,4 +1045,49 @@ open class NavigationController: UINavigationController, ContainableController,
self.containerLayoutUpdated(validLayout, transition: transition)
}
}
public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) {
if let forceInCallStatusBarText = forceInCallStatusBarText {
let inCallStatusBar: StatusBar
if let current = self.inCallStatusBar {
inCallStatusBar = current
} else {
inCallStatusBar = StatusBar()
inCallStatusBar.inCallNavigate = { [weak self] in
self?.scrollToTop(.master)
}
inCallStatusBar.alpha = 0.0
self.inCallStatusBar = inCallStatusBar
var bottomOverlayContainer: NavigationOverlayContainer?
for overlayContainer in self.overlayContainers {
if overlayContainer.supernode != nil {
bottomOverlayContainer = overlayContainer
break
}
}
if let bottomOverlayContainer = bottomOverlayContainer {
self.displayNode.insertSubnode(inCallStatusBar, belowSubnode: bottomOverlayContainer)
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
self.displayNode.insertSubnode(inCallStatusBar, belowSubnode: globalScrollToTopNode)
} else {
self.displayNode.addSubnode(inCallStatusBar)
}
transition.updateAlpha(node: inCallStatusBar, alpha: 1.0)
}
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: transition)
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallText: forceInCallStatusBarText, animated: false)
}
} else if let inCallStatusBar = self.inCallStatusBar {
self.inCallStatusBar = nil
transition.updateAlpha(node: inCallStatusBar, alpha: 0.0, completion: { [weak inCallStatusBar] _ in
inCallStatusBar?.removeFromSupernode()
})
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: transition)
}
}
}
}

View file

@ -16,8 +16,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
private(set) var dismissProgress: CGFloat = 0.0
var isReadyUpdated: (() -> Void)?
var updateDismissProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
var interactivelyDismissed: (() -> Void)?
var interactivelyDismissed: ((Bool) -> Void)?
private var isUpdatingState = false
private var ignoreScrolling = false
private var isDismissed = false
private var isInteractiveDimissEnabled = true
@ -33,6 +34,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
}
}
var canHaveKeyboardFocus: Bool = false {
didSet {
self.container.canHaveKeyboardFocus = self.canHaveKeyboardFocus
}
}
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
self.theme = theme
@ -57,7 +64,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.isReadyUpdated?()
if !strongSelf.isUpdatingState {
strongSelf.isReadyUpdated?()
}
}
}
@ -146,8 +155,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
guard let strongSelf = self else {
return
}
let hadInputFocus = viewTreeContainsFirstResponder(view: strongSelf.view)
strongSelf.keyboardViewManager?.dismissEditingWithoutAnimation(view: strongSelf.view)
strongSelf.interactivelyDismissed?()
strongSelf.interactivelyDismissed?(hadInputFocus)
})
} else {
self.horizontalDismissOffset = nil
@ -188,13 +198,15 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
return
}
if targetOffset == 0.0 {
strongSelf.interactivelyDismissed?()
strongSelf.interactivelyDismissed?(false)
}
})
self.ignoreScrolling = false
self.dismissProgress = dismissProgress
self.applyDismissProgress(transition: transition, completion: {})
self.view.endEditing(true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
@ -247,7 +259,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
return
}
if targetOffset == 0.0 {
strongSelf.interactivelyDismissed?()
strongSelf.interactivelyDismissed?(false)
}
})
self.ignoreScrolling = false
@ -273,6 +285,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
return
}
self.isUpdatingState = true
self.validLayout = layout
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
@ -338,6 +352,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
transition.updateFrameAsPositionAndBounds(node: self.container, frame: containerFrame.offsetBy(dx: 0.0, dy: layout.size.height))
transition.updateTransformScale(node: self.container, scale: containerScale)
self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition)
self.isUpdatingState = false
}
func animateIn(transition: ContainedViewLayoutTransition) {

View file

@ -0,0 +1,86 @@
import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
final class NavigationOverlayContainer: ASDisplayNode {
let controller: ViewController
private(set) var isReady: Bool = false
var isReadyUpdated: (() -> Void)?
private var isReadyDisposable: Disposable?
private var validLayout: ContainerViewLayout?
private var isUpdatingState: Bool = false
var keyboardViewManager: KeyboardViewManager? {
didSet {
if self.keyboardViewManager !== oldValue {
}
}
}
init(controller: ViewController, controllerRemoved: @escaping (ViewController) -> Void, statusBarUpdated: @escaping (ContainedViewLayoutTransition) -> Void) {
self.controller = controller
super.init()
self.controller.navigation_setDismiss({ [weak self] in
guard let strongSelf = self else {
return
}
controllerRemoved(strongSelf.controller)
}, rootController: nil)
self.controller.statusBar.alphaUpdated = { transition in
statusBarUpdated(transition)
}
self.isReadyDisposable = (self.controller.ready.get()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
if !strongSelf.isReady {
strongSelf.isReady = true
if !strongSelf.isUpdatingState {
strongSelf.isReadyUpdated?()
}
}
})
}
deinit {
self.isReadyDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
}
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.isUpdatingState = true
let updateLayout = self.validLayout != layout
self.validLayout = layout
if updateLayout {
transition.updateFrame(node: self.controller.displayNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.controller.containerLayoutUpdated(layout, transition: transition)
}
self.isUpdatingState = false
}
func transitionIn() {
self.controller.viewWillAppear(false)
self.controller.setIgnoreAppearanceMethodInvocations(true)
self.addSubnode(self.controller.displayNode)
self.controller.setIgnoreAppearanceMethodInvocations(false)
self.controller.viewDidAppear(false)
}
}

View file

@ -3,19 +3,42 @@ import UIKit
import AsyncDisplayKit
import SwiftSignalKit
enum NavigationSplitContainerScrollToTop {
case master
case detail
}
final class NavigationSplitContainer: ASDisplayNode {
private var theme: NavigationControllerTheme
private let masterScrollToTopView: ScrollToTopView
private let detailScrollToTopView: ScrollToTopView
private let masterContainer: NavigationContainer
private let detailContainer: NavigationContainer
private let separator: ASDisplayNode
private var masterControllers: [ViewController] = []
private var detailControllers: [ViewController] = []
private(set) var masterControllers: [ViewController] = []
private(set) var detailControllers: [ViewController] = []
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
var canHaveKeyboardFocus: Bool = false {
didSet {
self.masterContainer.canHaveKeyboardFocus = self.canHaveKeyboardFocus
self.detailContainer.canHaveKeyboardFocus = self.canHaveKeyboardFocus
}
}
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void, scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) {
self.theme = theme
self.masterScrollToTopView = ScrollToTopView(frame: CGRect())
self.masterScrollToTopView.action = {
scrollToTop(.master)
}
self.detailScrollToTopView = ScrollToTopView(frame: CGRect())
self.detailScrollToTopView.action = {
scrollToTop(.detail)
}
self.masterContainer = NavigationContainer(controllerRemoved: controllerRemoved)
self.masterContainer.clipsToBounds = true
@ -30,6 +53,8 @@ final class NavigationSplitContainer: ASDisplayNode {
self.addSubnode(self.masterContainer)
self.addSubnode(self.detailContainer)
self.addSubnode(self.separator)
self.view.addSubview(self.masterScrollToTopView)
self.view.addSubview(self.detailScrollToTopView)
}
func updateTheme(theme: NavigationControllerTheme) {
@ -40,6 +65,9 @@ final class NavigationSplitContainer: ASDisplayNode {
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
let detailWidth = layout.size.width - masterWidth
self.masterScrollToTopView.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: masterWidth, height: 1.0))
self.detailScrollToTopView.frame = CGRect(origin: CGPoint(x: masterWidth, y: -1.0), size: CGSize(width: detailWidth, height: 1.0))
transition.updateFrame(node: self.masterContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: layout.size.height)))
transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)))
transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)))

View file

@ -35,3 +35,15 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate {
return false
}
}
class ScrollToTopNode: ASDisplayNode {
init(action: @escaping () -> Void) {
super.init()
self.setViewBlock({
let view = ScrollToTopView(frame: CGRect())
view.action = action
return view
})
}
}

View file

@ -68,6 +68,7 @@ public final class StatusBar: ASDisplayNode {
didSet {
if self.statusBarStyle != oldValue {
self.layer.invalidateUpTheTree()
self.alphaUpdated?(.immediate)
}
}
}
@ -94,6 +95,13 @@ public final class StatusBar: ASDisplayNode {
}
}
var alphaUpdated: ((ContainedViewLayoutTransition) -> Void)?
public func updateAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
self.alpha = alpha
self.alphaUpdated?(transition)
}
public override init() {
self.inCallLabel = StatusBarLabelNode()
self.inCallLabel.isUserInteractionEnabled = false

View file

@ -12,5 +12,8 @@ public protocol StatusBarHost {
var handleVolumeControl: Signal<Bool, NoError> { get }
var isApplicationInForeground: Bool { get }
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
func setStatusBarHidden(_ value: Bool, animated: Bool)
}

View file

@ -127,6 +127,15 @@ open class TabBarController: ViewController {
self.theme = theme
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.scrollToTop = { [weak self] in
guard let strongSelf = self else {
return
}
if let controller = strongSelf.currentController {
controller.scrollToTop?()
}
}
}
required public init(coder aDecoder: NSCoder) {

View file

@ -246,7 +246,7 @@ public enum ViewControllerNavigationPresentation {
}
private func updateScrollToTopView() {
if self.scrollToTop != nil {
/*if self.scrollToTop != nil {
if let displayNode = self._displayNode , self.scrollToTopView == nil {
let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0))
scrollToTopView.action = { [weak self] in
@ -257,7 +257,7 @@ public enum ViewControllerNavigationPresentation {
self.scrollToTopView = scrollToTopView
self.view.addSubview(scrollToTopView)
}
} else if let scrollToTopView = self.scrollToTopView {
} else*/ if let scrollToTopView = self.scrollToTopView {
scrollToTopView.removeFromSuperview()
self.scrollToTopView = nil
}

View file

@ -280,7 +280,7 @@ public class Window1 {
private var deviceMetrics: DeviceMetrics
private let statusBarHost: StatusBarHost?
private let statusBarManager: StatusBarManager?
//private let statusBarManager: StatusBarManager?
private let keyboardManager: KeyboardManager?
private let keyboardViewManager: KeyboardViewManager?
private var statusBarChangeObserver: AnyObject?
@ -312,7 +312,7 @@ public class Window1 {
public private(set) var forceInCallStatusBarText: String? = nil
public var inCallNavigate: (() -> Void)? {
didSet {
self.statusBarManager?.inCallNavigate = self.inCallNavigate
//self.statusBarManager?.inCallNavigate = self.inCallNavigate
}
}
@ -323,21 +323,17 @@ public class Window1 {
private var keyboardTypeChangeTimer: SwiftSignalKit.Timer?
private let volumeControlStatusBar: VolumeControlStatusBar
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
//private let volumeControlStatusBar: VolumeControlStatusBar
//private let volumeControlStatusBarNode: VolumeControlStatusBarNode
private var isInteractionBlocked = false
/*private var accessibilityElements: [Any]? {
return self.viewController?.view.accessibilityElements
}*/
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
self.hostView = hostView
self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false))
self.volumeControlStatusBarNode = VolumeControlStatusBarNode()
self.volumeControlStatusBarNode.isHidden = true
//self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false))
//self.volumeControlStatusBarNode = VolumeControlStatusBarNode()
//self.volumeControlStatusBarNode.isHidden = true
let boundsSize = self.hostView.eventView.bounds.size
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHost?.statusBarFrame.height ?? defaultStatusBarHeight, onScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
@ -346,12 +342,12 @@ public class Window1 {
let statusBarHeight: CGFloat
if let statusBarHost = statusBarHost {
statusBarHeight = statusBarHost.statusBarFrame.size.height
self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode)
//self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode)
self.keyboardManager = KeyboardManager(host: statusBarHost)
self.keyboardViewManager = KeyboardViewManager(host: statusBarHost)
} else {
statusBarHeight = self.deviceMetrics.statusBarHeight
self.statusBarManager = nil
//self.statusBarManager = nil
self.keyboardManager = nil
self.keyboardViewManager = nil
}
@ -394,7 +390,7 @@ public class Window1 {
}
self.hostView.layoutSubviews = { [weak self] in
self?.layoutSubviews()
self?.layoutSubviews(force: false)
}
self.hostView.updateToInterfaceOrientation = { [weak self] orientation in
@ -433,7 +429,7 @@ public class Window1 {
}*/
self.presentationContext.view = self.hostView.containerView
self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view
//self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
@ -455,7 +451,7 @@ public class Window1 {
keyboardHeight = max(0.0, keyboardHeight - 24.0)
}
print("keyboardHeight: \(keyboardHeight)")
//print("rotation keyboardHeight: \(keyboardHeight)")
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
@ -483,12 +479,33 @@ public class Window1 {
}
}
var popoverDelta: CGFloat = 0.0
let screenHeight: CGFloat
var inPopover = false
if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) {
var portraitScreenSize = UIScreen.main.bounds.size
if portraitScreenSize.width > portraitScreenSize.height {
portraitScreenSize = CGSize(width: portraitScreenSize.height, height: portraitScreenSize.width)
}
var portraitLayoutSize = strongSelf.windowLayout.size
if portraitLayoutSize.width > portraitLayoutSize.height {
portraitLayoutSize = CGSize(width: portraitLayoutSize.height, height: portraitLayoutSize.width)
}
if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 41.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 41.0 {
popoverDelta = 48.0
inPopover = true
screenHeight = strongSelf.windowLayout.size.height
} else {
screenHeight = UIScreen.main.bounds.height
}
} else if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 39.0 {
screenHeight = UIScreen.main.bounds.height
inPopover = true
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 39.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 39.0 {
popoverDelta = 40.0
inPopover = true
}
} else {
screenHeight = strongSelf.windowLayout.size.height
}
@ -503,14 +520,16 @@ public class Window1 {
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if inPopover {
if strongSelf.windowLayout.onScreenNavigationHeight != nil {
keyboardHeight = max(0.0, keyboardHeight - 24.0)
if !keyboardHeight.isZero {
keyboardHeight = max(0.0, keyboardHeight + popoverDelta / 2.0)
}
} else {
keyboardHeight = max(0.0, keyboardHeight - 48.0)
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
}
}
}
print("keyboardHeight: \(keyboardHeight)")
print("keyboardHeight: \(keyboardHeight) (raw: \(keyboardFrame))")
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
@ -586,8 +605,8 @@ public class Window1 {
self.windowPanRecognizer = recognizer
self.hostView.containerView.addGestureRecognizer(recognizer)
self.hostView.containerView.addSubview(self.volumeControlStatusBar)
self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view)
//self.hostView.containerView.addSubview(self.volumeControlStatusBar)
//self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view)
}
public required init(coder aDecoder: NSCoder) {
@ -613,7 +632,7 @@ public class Window1 {
}
public func setupVolumeControlStatusBarGraphics(_ graphics: (UIImage, UIImage, UIImage)) {
self.volumeControlStatusBarNode.graphics = graphics
//self.volumeControlStatusBarNode.graphics = graphics
}
public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) {
@ -701,6 +720,9 @@ public class Window1 {
transition = .immediate
}
self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), safeInsets: self.deviceMetrics.safeInsets(inLandscape: value.width > value.height), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) }
if let statusBarHost = self.statusBarHost, !statusBarHost.isApplicationInForeground {
self.layoutSubviews(force: true)
}
}
private var _rootController: ContainableController?
@ -718,6 +740,9 @@ public class Window1 {
if let rootController = rootController as? NavigationController {
rootController.statusBarHost = self.statusBarHost
rootController.keyboardViewManager = self.keyboardViewManager
rootController.inCallNavigate = { [weak self] in
self?.inCallNavigate?()
}
}
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
rootController.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
@ -738,6 +763,9 @@ public class Window1 {
}
set(value) {
for controller in self._topLevelOverlayControllers {
if let controller = controller as? ViewController {
controller.statusBar.alphaUpdated = nil
}
controller.view.removeFromSuperview()
}
self._topLevelOverlayControllers = value
@ -750,7 +778,25 @@ public class Window1 {
if let coveringView = self.coveringView {
self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView)
} else {
self.hostView.containerView.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view)
self.hostView.containerView.addSubview(controller.view)
}
if let controller = controller as? ViewController {
controller.statusBar.alphaUpdated = { [weak self] transition in
guard let strongSelf = self, let navigationController = strongSelf._rootController as? NavigationController else {
return
}
var isStatusBarHidden: Bool = false
for controller in strongSelf._topLevelOverlayControllers {
if let controller = controller as? ViewController {
if case .Hide = controller.statusBar.statusBarStyle {
isStatusBarHidden = true
}
}
}
navigationController.updateExternalStatusBarHidden(isStatusBarHidden, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
@ -771,7 +817,7 @@ public class Window1 {
coveringView.layer.removeAnimation(forKey: "opacity")
coveringView.layer.allowsGroupOpacity = false
coveringView.alpha = 1.0
self.hostView.containerView.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view)
self.hostView.containerView.addSubview(coveringView)
if !self.windowLayout.size.width.isZero {
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
coveringView.updateLayout(self.windowLayout.size)
@ -781,7 +827,7 @@ public class Window1 {
}
}
private func layoutSubviews() {
private func layoutSubviews(force: Bool) {
var hasPreview = false
var updatedHasPreview = false
for subview in self.hostView.eventView.subviews {
@ -796,7 +842,7 @@ public class Window1 {
updatedHasPreview = true
}
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
if self.tracingStatusBarsInvalidated || updatedHasPreview, let keyboardManager = keyboardManager {
self.tracingStatusBarsInvalidated = false
/*if self.statusBarHidden {
@ -902,7 +948,9 @@ public class Window1 {
}
}
if !UIWindow.isDeviceRotating() {
if force {
self.commitUpdatingLayout()
} else if !UIWindow.isDeviceRotating() {
if !self.hostView.isUpdatingOrientationLayout {
self.commitUpdatingLayout()
} else {
@ -967,6 +1015,9 @@ public class Window1 {
let boundsSize = updatingLayout.layout.size
let isLandscape = boundsSize.width > boundsSize.height
var statusBarHeight: CGFloat? = self.deviceMetrics.statusBarHeight(for: boundsSize)
if let statusBarHeightValue = statusBarHeight, let statusBarHost = self.statusBarHost {
statusBarHeight = max(statusBarHeightValue, statusBarHost.statusBarFrame.size.height)
}
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)
@ -1023,8 +1074,8 @@ public class Window1 {
})
}
self.volumeControlStatusBarNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
self.volumeControlStatusBarNode.updateLayout(layout: childLayout, transition: updatingLayout.transition)
//self.volumeControlStatusBarNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
//self.volumeControlStatusBarNode.updateLayout(layout: childLayout, transition: updatingLayout.transition)
if let coveringView = self.coveringView {
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
@ -1035,7 +1086,11 @@ public class Window1 {
}
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
if level.rawValue <= 3, let navigationController = self._rootController as? NavigationController, let controller = controller as? ViewController {
navigationController.presentOverlay(controller: controller, inGlobal: false)
} else {
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
}
public func presentInGlobalOverlay(_ controller: ContainableController) {

View file

@ -275,14 +275,14 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
UIView.animate(withDuration: 0.3, animations: {
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
self.navigationBar?.alpha = alpha
self.statusBar?.alpha = alpha
self.statusBar?.updateAlpha(alpha, transition: .animated(duration: 0.3, curve: .easeInOut))
self.footerNode.alpha = alpha
self.updateThumbnailContainerNodeAlpha(.immediate)
})
} else {
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
self.navigationBar?.alpha = alpha
self.statusBar?.alpha = alpha
self.statusBar?.updateAlpha(alpha, transition: .immediate)
self.footerNode.alpha = alpha
self.updateThumbnailContainerNodeAlpha(.immediate)
}
@ -374,7 +374,11 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
self.updateThumbnailContainerNodeAlpha(.immediate)
if !self.areControlsHidden {
self.statusBar?.alpha = transition
if transition < 0.5 {
self.statusBar?.statusBarStyle = .Ignore
} else {
self.statusBar?.statusBarStyle = .White
}
self.navigationBar?.alpha = transition
self.footerNode.alpha = transition

View file

@ -52,8 +52,6 @@ final class InstantPageReferenceController: ViewController {
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -31,6 +31,8 @@ public final class JoinLinkPreviewController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
@ -76,8 +78,6 @@ public final class JoinLinkPreviewController: ViewController {
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -30,6 +30,8 @@ public final class LanguageLinkPreviewController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
@ -85,8 +87,6 @@ public final class LanguageLinkPreviewController: ViewController {
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -739,7 +739,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[UIView animateWithDuration:0.3f animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}];
}
@ -1816,7 +1816,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[UIView animateWithDuration:0.3f animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}];
[self setInterfaceHidden:true animated:true];
@ -1892,7 +1892,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
{
_finishedWithResult = true;
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
self.view.hidden = true;

View file

@ -93,13 +93,15 @@
- (BOOL)prefersStatusBarHidden
{
if (!TGIsPad() && iosMajorVersion() >= 11 && UIInterfaceOrientationIsLandscape([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]))
return true;
/*if (!TGIsPad() && iosMajorVersion() >= 11 && UIInterfaceOrientationIsLandscape([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]))
return true;
if (self.childViewControllers.count > 0)
return [self.childViewControllers.lastObject prefersStatusBarHidden];
return [super prefersStatusBarHidden];
return [super prefersStatusBarHidden];*/
}
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
@ -262,7 +264,7 @@
{
[UIView animateWithDuration:0.2 animations:^
{
[strongSelf->_context setApplicationStatusBarAlpha:1.0f];
//[strongSelf->_context setApplicationStatusBarAlpha:1.0f];
}];
}
@ -533,7 +535,7 @@
{
[UIView animateWithDuration:0.2 animations:^
{
[strongSelf->_context setApplicationStatusBarAlpha:1.0f];
//[strongSelf->_context setApplicationStatusBarAlpha:1.0f];
}];
}
@ -1155,7 +1157,7 @@ static CGFloat transformRotation(CGAffineTransform transform)
{
[UIView animateWithDuration:0.2 animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}];
}
}
@ -1228,7 +1230,7 @@ static CGFloat transformRotation(CGAffineTransform transform)
if (self.adjustsStatusBarVisibility)
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}
}

View file

@ -475,12 +475,12 @@
{
[UIView animateWithDuration:0.3 animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}];
}
else
{
[_context setApplicationStatusBarAlpha:1.0f];
//[_context setApplicationStatusBarAlpha:1.0f];
}
}

View file

@ -247,7 +247,7 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
public func setApplicationStatusBarAlpha(_ alpha: CGFloat) {
if let controller = self.controller {
controller.statusBar.alpha = alpha
controller.statusBar.updateAlpha(alpha, transition: .immediate)
self.updateDeferScreenEdgeGestures()
}
}

View file

@ -21,6 +21,8 @@ public extension Peer {
}
} else if let lastName = user.lastName {
return lastName
} else if let phone = user.phone {
return "+\(phone)"
} else {
return strings.User_DeletedAccount
}

View file

@ -71,19 +71,23 @@ static NSData *base64_decode(NSString *str) {
+ (MTSignal *)fetchBackupIpsResolveGoogle:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext addressOverride:(NSString *)addressOverride {
NSArray *hosts = @[
@"google.com",
@"www.google.com",
@"google.ru"
@[@"dns.google.com", @""],
@[@"www.google.com", @"dns.google.com"],
];
NSDictionary *headers = @{@"Host": @"dns.google.com"};
NSMutableArray *signals = [[NSMutableArray alloc] init];
for (NSString *host in hosts) {
for (NSArray *hostAndHostname in hosts) {
NSString *host = hostAndHostname[0];
NSString *hostName = hostAndHostname[1];
NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];
if ([hostName length] != 0) {
headers[@"Host"] = hostName;
}
NSString *apvHost = @"apv3.stel.com";
if (addressOverride != nil) {
apvHost = addressOverride;
}
MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/resolve?name=%@&type=16", host, isTesting ? @"tapv3.stel.com" : apvHost]] headers:headers] mapToSignal:^MTSignal *(NSData *data) {
MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/resolve?name=%@&type=16&random_padding=%@", host, isTesting ? @"tapv3.stel.com" : apvHost, makeRandomPadding()]] headers:headers] mapToSignal:^MTSignal *(NSData *data) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([dict respondsToSelector:@selector(objectForKey:)]) {
NSArray *answer = dict[@"Answer"];
@ -132,6 +136,90 @@ static NSData *base64_decode(NSString *str) {
return [[MTSignal mergeSignals:signals] take:1];
}
static NSString *makeRandomPadding() {
char validCharacters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int maxIndex = sizeof(validCharacters) - 1;
int minPadding = 13;
int maxPadding = 128;
int padding = minPadding + arc4random_uniform(maxPadding - minPadding);
NSMutableData *result = [[NSMutableData alloc] initWithLength:padding];
for (NSUInteger i = 0; i < result.length; i++) {
int index = arc4random_uniform(maxIndex);
assert(index >= 0 && index < maxIndex);
((uint8_t *)(result.mutableBytes))[i] = validCharacters[index];
}
NSString *string = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
return string;
}
+ (MTSignal *)fetchBackupIpsResolveCloudflare:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext addressOverride:(NSString *)addressOverride {
NSArray *hosts = @[
@[@"mozilla.cloudflare-dns.com", @""],
];
NSMutableArray *signals = [[NSMutableArray alloc] init];
for (NSArray *hostAndHostname in hosts) {
NSString *host = hostAndHostname[0];
NSString *hostName = hostAndHostname[1];
NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];
headers[@"accept"] = @"application/dns-json";
if ([hostName length] != 0) {
headers[@"Host"] = hostName;
}
NSString *apvHost = @"apv3.stel.com";
if (addressOverride != nil) {
apvHost = addressOverride;
}
MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/dns-query?name=%@&type=16&random_padding=%@", host, isTesting ? @"tapv3.stel.com" : apvHost, makeRandomPadding()]] headers:headers] mapToSignal:^MTSignal *(NSData *data) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([dict respondsToSelector:@selector(objectForKey:)]) {
NSArray *answer = dict[@"Answer"];
NSMutableArray *strings = [[NSMutableArray alloc] init];
if ([answer respondsToSelector:@selector(objectAtIndex:)]) {
for (NSDictionary *value in answer) {
if ([value respondsToSelector:@selector(objectForKey:)]) {
NSString *part = value[@"data"];
if ([part respondsToSelector:@selector(characterAtIndex:)]) {
[strings addObject:part];
}
}
}
[strings sortUsingComparator:^NSComparisonResult(NSString *lhs, NSString *rhs) {
if (lhs.length > rhs.length) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
NSString *finalString = @"";
for (NSString *string in strings) {
finalString = [finalString stringByAppendingString:[string stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]];
}
NSData *result = base64_decode(finalString);
NSMutableData *finalData = [[NSMutableData alloc] initWithData:result];
[finalData setLength:256];
MTBackupDatacenterData *datacenterData = MTIPDataDecode(finalData, phoneNumber);
if (datacenterData != nil && [self checkIpData:datacenterData timestamp:(int32_t)[currentContext globalTime] source:@"resolveCloudflare"]) {
return [MTSignal single:datacenterData];
}
}
}
return [MTSignal complete];
}] catch:^MTSignal *(__unused id error) {
return [MTSignal complete];
}];
if (signals.count != 0) {
signal = [signal delay:signals.count onQueue:[[MTQueue alloc] init]];
}
[signals addObject:signal];
}
return [[MTSignal mergeSignals:signals] take:1];
}
+ (MTSignal *)fetchConfigFromAddress:(MTBackupDatacenterAddress *)address currentContext:(MTContext *)currentContext {
MTApiEnvironment *apiEnvironment = [currentContext.apiEnvironment copy];
@ -210,11 +298,18 @@ static NSData *base64_decode(NSString *str) {
+ (MTSignal * _Nonnull)fetchBackupIps:(bool)isTestingEnvironment currentContext:(MTContext * _Nonnull)currentContext additionalSource:(MTSignal * _Nullable)additionalSource phoneNumber:(NSString * _Nullable)phoneNumber {
NSMutableArray *signals = [[NSMutableArray alloc] init];
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
[signals addObject:[self fetchBackupIpsResolveCloudflare:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
if (additionalSource != nil) {
/*#if DEBUG
[signals removeAllObjects];
#endif*/
[signals addObject:additionalSource];
[signals addObject:[additionalSource mapToSignal:^MTSignal *(MTBackupDatacenterData *datacenterData) {
if (![datacenterData isKindOfClass:[MTBackupDatacenterData class]]) {
return [MTSignal complete];
}
if (datacenterData != nil && [self checkIpData:datacenterData timestamp:(int32_t)[currentContext globalTime] source:@"resolveExternal"]) {
return [MTSignal single:datacenterData];
} else {
return [MTSignal complete];
}
}]];
}
return [[[MTSignal mergeSignals:signals] take:1] mapToSignal:^MTSignal *(MTBackupDatacenterData *data) {

View file

@ -964,6 +964,9 @@ static NSData *decrypt_TL_data(unsigned char buffer[256]) {
@end
MTBackupDatacenterData *MTIPDataDecode(NSData *data, NSString *phoneNumber) {
if (data.length < 256) {
return nil;
}
unsigned char buffer[256];
memcpy(buffer, data.bytes, 256);
NSData *result = decrypt_TL_data(buffer);

View file

@ -36,7 +36,7 @@
[subscriber putCompletion];
} failure:^(__unused NSOperation *operation, __unused NSError *error)
{
[subscriber putError:nil];
[subscriber putError:error];
}];
[operation start];

View file

@ -92,6 +92,7 @@ NS_ASSUME_NONNULL_BEGIN
- (MTSignal *)exportKey:(TONKey *)key localPassword:(NSData *)localPassword;
- (MTSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray<NSString *> *)wordList;
- (MTSignal *)deleteKey:(TONKey *)key;
- (MTSignal *)deleteAllKeys;
- (MTSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash;
@end

View file

@ -325,10 +325,12 @@ typedef enum {
make_object<tonlib_api::config>(
configString.UTF8String,
blockchainName.UTF8String,
false,
true,
false
),
keystoreDirectory.UTF8String
make_object<tonlib_api::keyStoreTypeDirectory>(
keystoreDirectory.UTF8String
)
));
_client->send({ requestId, std::move(query) });
@ -636,6 +638,28 @@ typedef enum {
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
- (MTSignal *)deleteAllKeys {
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
uint64_t requestId = _nextRequestId;
_nextRequestId += 1;
_requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr<tonlib_api::Object> &object) {
if (object->get_id() == tonlib_api::error::ID) {
auto error = tonlib_api::move_object_as<tonlib_api::error>(object);
[subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
} else {
[subscriber putCompletion];
}
}];
auto query = make_object<tonlib_api::deleteAllKeys>();
_client->send({ requestId, std::move(query) });
return [[MTBlockDisposable alloc] initWithBlock:^{
}];
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
- (MTSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash {
return [[[[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
NSData *addressData = [address dataUsingEncoding:NSUTF8StringEncoding];

View file

@ -135,6 +135,55 @@ public func retry<T, E>(_ delayIncrement: Double, maxDelay: Double, onQueue queu
}
}
public func retry<T, E>(retryOnError: @escaping (E) -> Bool, delayIncrement: Double, maxDelay: Double, maxRetries: Int, onQueue queue: Queue) -> (_ signal: Signal<T, E>) -> Signal<T, E> {
return { signal in
return Signal { subscriber in
let shouldRetry = Atomic(value: true)
let currentDelay = Atomic<(Double, Int)>(value: (0.0, 0))
let currentDisposable = MetaDisposable()
let start = recursiveFunction { recurse in
let currentShouldRetry = shouldRetry.with { value in
return value
}
if currentShouldRetry {
let disposable = signal.start(next: { next in
subscriber.putNext(next)
}, error: { error in
if !retryOnError(error) {
subscriber.putError(error)
} else {
let (delay, count) = currentDelay.modify { value, count in
return (min(maxDelay, value + delayIncrement), count + 1)
}
if count >= maxRetries {
subscriber.putError(error)
} else {
let time: DispatchTime = DispatchTime.now() + Double(delay)
queue.queue.asyncAfter(deadline: time, execute: {
recurse()
})
}
}
}, completed: {
let _ = shouldRetry.swap(false)
subscriber.putCompletion()
})
currentDisposable.set(disposable)
}
}
start()
return ActionDisposable {
currentDisposable.dispose()
let _ = shouldRetry.swap(false)
}
}
}
}
public func restartIfError<T, E>(_ signal: Signal<T, E>) -> Signal<T, NoError> {
return Signal<T, NoError> { subscriber in
let shouldRetry = Atomic(value: true)

View file

@ -447,7 +447,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
(controller?.navigationController as? NavigationController)?.pushViewController(value)
}
presentControllerImpl = { [weak controller] value, arguments in
controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
controller?.present(value, in: .window(.root), with: arguments)
}
dismissImpl = { [weak controller] in
let _ = (controller?.navigationController as? NavigationController)?.popViewController(animated: true)

View file

@ -1102,9 +1102,13 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|> mapToSignal { context in
return context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> Bool in
let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
let configuration = WalletConfiguration.with(appConfiguration: appConfiguration)
return configuration.config != nil
if #available(iOSApplicationExtension 10.3, iOS 10.3, *) {
let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
let configuration = WalletConfiguration.with(appConfiguration: appConfiguration)
return configuration.config != nil
} else {
return false
}
}
}
@ -1375,7 +1379,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true, animationOptions: [.removeOnMasterDetails])
}
presentControllerImpl = { [weak controller] value, arguments in
controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet), blockInteraction: true)
controller?.present(value, in: .window(.root), with: arguments, blockInteraction: true)
}
presentInGlobalOverlayImpl = { [weak controller] value, arguments in
controller?.presentInGlobalOverlay(value, with: arguments)

View file

@ -43,7 +43,7 @@ public final class TabBarAccountSwitchController: ViewController {
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Hide
self.statusBar.statusBarStyle = .Ignore
self.statusBar.ignoreInCall = true
self.lockOrientation = true

View file

@ -54,7 +54,7 @@ public extension TermsOfServiceControllerTheme {
}
}
public class TermsOfServiceController: ViewController {
public class TermsOfServiceController: ViewController, StandalonePresentableController {
private var controllerNode: TermsOfServiceControllerNode {
return self.displayNode as! TermsOfServiceControllerNode
}

View file

@ -271,6 +271,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
override func didLoad() {
super.didLoad()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.delegate = self

View file

@ -228,6 +228,11 @@ public final class ShareController: ViewController {
return self.displayNode as! ShareControllerNode
}
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private var animatedIn = false
private let sharedContext: SharedAccountContext
@ -267,6 +272,8 @@ public final class ShareController: ViewController {
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
switch subject {
case let .url(text):
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.ShareMenu_CopyShareLink, action: { [weak self] in
@ -598,7 +605,7 @@ public final class ShareController: ViewController {
return .preparing
case let .done(items):
if let strongSelf = self, !items.isEmpty {
strongSelf.ready.set(.single(true))
strongSelf._ready.set(.single(true))
var activityItems: [Any] = []
for item in items {
switch item {
@ -663,13 +670,11 @@ public final class ShareController: ViewController {
strongSelf.controllerNode.updatePeers(account: strongSelf.currentAccount, switchableAccounts: strongSelf.switchableAccounts, peers: next.0, accountPeer: next.1, defaultAction: strongSelf.defaultAction)
}
}))
self.ready.set(self.controllerNode.ready.get())
self._ready.set(self.controllerNode.ready.get())
}
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -100,13 +100,7 @@ public final class ActiveSessionsContext {
return .complete()
}
var mergedSessions = strongSelf._state.sessions
for i in (0 ..< mergedSessions.count).reversed() {
if mergedSessions[i].hash != 0 {
mergedSessions.remove(at: i)
break
}
}
let mergedSessions = strongSelf._state.sessions.filter({ $0.hash == 0 })
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions)
return .complete()

View file

@ -417,6 +417,8 @@ public struct NetworkInitializationArguments {
}
}
private let cloudDataContext = makeCloudDataContext()
func initializedNetwork(arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal<Network, NoError> {
return Signal { subscriber in
let queue = Queue()
@ -484,17 +486,18 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
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()
if let cloudDataContext = cloudDataContext {
wrappedAdditionalSource = MTSignal(generator: { subscriber in
let disposable = cloudDataContext.get(phoneNumber: .single(phoneNumber)).start(next: { value in
subscriber?.putNext(value)
}, completed: {
subscriber?.putCompletion()
})
return MTBlockDisposable(block: {
disposable.dispose()
})
})
return MTBlockDisposable(block: {
disposable.dispose()
})
})
}
}
context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber))
@ -1010,7 +1013,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
}
public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {
return signal |> retry(0.2, maxDelay: 5.0, onQueue: Queue.concurrentDefaultQueue())
return signal
|> retry(0.2, maxDelay: 5.0, onQueue: Queue.concurrentDefaultQueue())
}
class Keychain: NSObject, MTKeychain {
@ -1045,7 +1049,7 @@ class Keychain: NSObject, MTKeychain {
}
}
public func makeCloudDataContext() -> Any? {
func makeCloudDataContext() -> CloudDataContext? {
if #available(iOS 10.0, *) {
return CloudDataContextImpl()
} else {

View file

@ -129,7 +129,7 @@ public extension Peer {
var isDeleted: Bool {
switch self {
case let user as TelegramUser:
return user.firstName == nil && user.lastName == nil
return user.firstName == nil && user.lastName == nil && user.phone == nil
default:
return false
}

View file

@ -28,6 +28,7 @@ public enum TonKeychainEncryptDataError {
public enum TonKeychainDecryptDataError {
case generic
case publicKeyMismatch
case cancelled
}
public struct TonKeychain {
@ -65,7 +66,10 @@ private final class TonInstanceImpl {
} else {
let network = self.network
instance = TON(keystoreDirectory: self.basePath + "/ton-keystore", config: self.config, blockchainName: self.blockchainName, performExternalRequest: { request in
let _ = (network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: request.data)))).start(next: { result in
let _ = (
network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: request.data)))
|> timeout(10.0, queue: .concurrentDefaultQueue(), alternate: .fail(MTRpcError(errorCode: 500, errorDescription: "NETWORK_ERROR")))
).start(next: { result in
switch result {
case let .liteResponse(response):
request.onResult(response.makeData(), nil)
@ -92,13 +96,13 @@ public final class TonInstance {
})
}
fileprivate func exportKey(key: TONKey, serverSalt: Data) -> Signal<[String], NoError> {
fileprivate func exportKey(key: TONKey, localPassword: Data) -> Signal<[String], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.export(key, localPassword: serverSalt).start(next: { wordList in
let cancel = ton.export(key, localPassword: localPassword).start(next: { wordList in
guard let wordList = wordList as? [String] else {
assertionFailure()
return
@ -116,18 +120,18 @@ public final class TonInstance {
}
}
fileprivate func createWallet(keychain: TonKeychain, serverSalt: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> {
fileprivate func createWallet(keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.createKey(withLocalPassword: serverSalt, mnemonicPassword: Data()).start(next: { key in
let cancel = ton.createKey(withLocalPassword: localPassword, mnemonicPassword: Data()).start(next: { key in
guard let key = key as? TONKey else {
assertionFailure()
return
}
let cancel = keychain.encrypt(key.secret).start(next: { encryptedSecretData in
let _ = self.exportKey(key: key, serverSalt: serverSalt).start(next: { wordList in
let _ = self.exportKey(key: key, localPassword: localPassword).start(next: { wordList in
subscriber.putNext((WalletInfo(publicKey: WalletPublicKey(rawValue: key.publicKey), encryptedSecret: encryptedSecretData), wordList))
subscriber.putCompletion()
}, error: { error in
@ -150,13 +154,13 @@ public final class TonInstance {
}
}
fileprivate func importWallet(keychain: TonKeychain, wordList: [String], serverSalt: Data) -> Signal<WalletInfo, ImportWalletInternalError> {
fileprivate func importWallet(keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal<WalletInfo, ImportWalletInternalError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.importKey(withLocalPassword: serverSalt, mnemonicPassword: Data(), wordList: wordList).start(next: { key in
let cancel = ton.importKey(withLocalPassword: localPassword, mnemonicPassword: Data(), wordList: wordList).start(next: { key in
guard let key = key as? TONKey else {
subscriber.putError(.generic)
return
@ -218,8 +222,16 @@ public final class TonInstance {
return
}
subscriber.putNext(state)
}, error: { _ in
subscriber.putError(.generic)
}, error: { error in
if let error = error as? TONError {
if error.text.hasPrefix("LITE_SERVER_") {
subscriber.putError(.network)
} else {
subscriber.putError(.generic)
}
} else {
subscriber.putError(.generic)
}
}, completed: {
subscriber.putCompletion()
})
@ -240,7 +252,7 @@ public final class TonInstance {
}
}
fileprivate func walletLastTransactionId(address: String) -> Signal<WalletTransactionId?, NoError> {
fileprivate func walletLastTransactionId(address: String) -> Signal<WalletTransactionId?, WalletLastTransactionIdError> {
return Signal { subscriber in
let disposable = MetaDisposable()
@ -252,7 +264,16 @@ public final class TonInstance {
return
}
subscriber.putNext(state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:)))
}, error: { _ in
}, error: { error in
if let error = error as? TONError {
if error.text.hasPrefix("ДITE_SERVER_") {
subscriber.putError(.network)
} else {
subscriber.putError(.generic)
}
} else {
subscriber.putError(.generic)
}
}, completed: {
subscriber.putCompletion()
})
@ -278,39 +299,10 @@ public final class TonInstance {
return
}
subscriber.putNext(transactions.map(WalletTransaction.init(tonTransaction:)))
}, error: { _ in
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
}
return disposable
}
}
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)
if error.text.hasPrefix("LITE_SERVER_") {
subscriber.putError(.network)
} else {
subscriber.putError(.generic)
}
@ -330,65 +322,97 @@ public final class TonInstance {
}
}
fileprivate func walletRestoreWords(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<[String], WalletRestoreWordsError> {
return keychain.decrypt(walletInfo.encryptedSecret)
|> mapError { _ -> WalletRestoreWordsError in
return .secretDecryptionFailed
}
|> mapToSignal { decryptedSecret -> Signal<[String], WalletRestoreWordsError> in
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.export(TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret), localPassword: serverSalt).start(next: { wordList in
guard let wordList = wordList as? [String] else {
subscriber.putError(.generic)
return
}
subscriber.putNext(wordList)
}, error: { _ in
fileprivate func sendGramsFromWallet(decryptedSecret: Data, localPassword: 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: localPassword, 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)
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
return
}
subscriber.putCompletion()
}, error: { error in
if let error = error as? TONError {
if error.text.hasPrefix("INVALID_ACCOUNT_ADDRESS") {
subscriber.putError(.invalidAddress)
} else if error.text.hasPrefix("DANGEROUS_TRANSACTION") {
subscriber.putError(.destinationIsNotInitialized)
} else if error.text.hasPrefix("MESSAGE_TOO_LONG") {
subscriber.putError(.messageTooLong)
} else if error.text.hasPrefix("NOT_ENOUGH_FUNDS") {
subscriber.putError(.notEnoughFunds)
} else if error.text.hasPrefix("LITE_SERVER_") {
subscriber.putError(.network)
} else {
subscriber.putError(.generic)
}
} else {
subscriber.putError(.generic)
}
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
return disposable
}
return disposable
}
}
fileprivate func deleteLocalWalletData(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<Never, DeleteLocalWalletDataError> {
return keychain.decrypt(walletInfo.encryptedSecret)
|> mapError { _ -> DeleteLocalWalletDataError in
return .secretDecryptionFailed
}
|> mapToSignal { decryptedSecret -> Signal<Never, DeleteLocalWalletDataError> in
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.delete(TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)).start(next: { _ in
assertionFailure()
}, error: { _ in
fileprivate func walletRestoreWords(publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.export(TONKey(publicKey: publicKey.rawValue, secret: decryptedSecret), localPassword: localPassword).start(next: { wordList in
guard let wordList = wordList as? [String] else {
subscriber.putError(.generic)
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
return
}
subscriber.putNext(wordList)
}, error: { _ in
subscriber.putError(.generic)
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
return disposable
}
return disposable
}
}
fileprivate func deleteAllLocalWalletsData() -> Signal<Never, DeleteAllLocalWalletsDataError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
let cancel = ton.deleteAllKeys().start(next: { _ in
assertionFailure()
}, error: { _ in
subscriber.putError(.generic)
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
cancel?.dispose()
})
}
}
return disposable
}
}
}
@ -508,24 +532,18 @@ public enum CreateWalletError {
case generic
}
public func createWallet(postbox: Postbox, network: Network, tonInstance: TonInstance, keychain: TonKeychain) -> Signal<(WalletInfo, [String]), CreateWalletError> {
return getServerWalletSalt(network: network)
|> mapError { _ -> CreateWalletError in
return .generic
}
|> mapToSignal { serverSalt -> Signal<(WalletInfo, [String]), CreateWalletError> in
return tonInstance.createWallet(keychain: keychain, serverSalt: serverSalt)
|> mapToSignal { walletInfo, wordList -> Signal<(WalletInfo, [String]), CreateWalletError> in
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, exportCompleted: false, state: nil)]
return walletCollection
})
return (walletInfo, wordList)
}
|> castError(CreateWalletError.self)
public func createWallet(postbox: Postbox, tonInstance: TonInstance, keychain: TonKeychain, localPassword: Data) -> Signal<(WalletInfo, [String]), CreateWalletError> {
return tonInstance.createWallet(keychain: keychain, localPassword: localPassword)
|> mapToSignal { walletInfo, wordList -> Signal<(WalletInfo, [String]), CreateWalletError> in
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, exportCompleted: false, state: nil)]
return walletCollection
})
return (walletInfo, wordList)
}
|> castError(CreateWalletError.self)
}
}
@ -552,77 +570,51 @@ public enum ImportWalletError {
case generic
}
public func importWallet(postbox: Postbox, network: Network, tonInstance: TonInstance, keychain: TonKeychain, wordList: [String]) -> Signal<WalletInfo, ImportWalletError> {
return getServerWalletSalt(network: network)
|> mapError { _ -> ImportWalletError in
return .generic
public func importWallet(postbox: Postbox, tonInstance: TonInstance, keychain: TonKeychain, wordList: [String], localPassword: Data) -> Signal<WalletInfo, ImportWalletError> {
return tonInstance.importWallet(keychain: keychain, wordList: wordList, localPassword: localPassword)
|> `catch` { error -> Signal<WalletInfo, ImportWalletError> in
switch error {
case .generic:
return .fail(.generic)
}
}
|> mapToSignal { serverSalt in
return tonInstance.importWallet(keychain: keychain, wordList: wordList, serverSalt: serverSalt)
|> `catch` { error -> Signal<WalletInfo, ImportWalletError> in
switch error {
case .generic:
return .fail(.generic)
}
}
|> mapToSignal { walletInfo -> Signal<WalletInfo, ImportWalletError> in
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, exportCompleted: true, state: nil)]
return walletCollection
})
return walletInfo
}
|> castError(ImportWalletError.self)
|> mapToSignal { walletInfo -> Signal<WalletInfo, ImportWalletError> in
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, exportCompleted: true, state: nil)]
return walletCollection
})
return walletInfo
}
|> castError(ImportWalletError.self)
}
}
public enum DeleteLocalWalletDataError {
public enum DeleteAllLocalWalletsDataError {
case generic
case secretDecryptionFailed
}
public func deleteLocalWalletData(postbox: Postbox, network: Network, tonInstance: TonInstance, keychain: TonKeychain, walletInfo: WalletInfo) -> Signal<Never, DeleteLocalWalletDataError> {
return getServerWalletSalt(network: network)
|> mapError { _ -> DeleteLocalWalletDataError in
return .generic
}
|> mapToSignal { serverSalt -> Signal<Never, DeleteLocalWalletDataError> in
return tonInstance.deleteLocalWalletData(walletInfo: walletInfo, keychain: keychain, serverSalt: serverSalt)
|> then(
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.remove(at: i)
break
}
}
return walletCollection
})
}
|> castError(DeleteLocalWalletDataError.self)
|> ignoreValues
)
}
public func deleteAllLocalWalletsData(postbox: Postbox, network: Network, tonInstance: TonInstance) -> Signal<Never, DeleteAllLocalWalletsDataError> {
return tonInstance.deleteAllLocalWalletsData()
|> then(
postbox.transaction { transaction -> Void in
transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
let walletCollection = WalletCollection(wallets: [])
return walletCollection
})
}
|> castError(DeleteAllLocalWalletsDataError.self)
|> ignoreValues
)
}
public enum WalletRestoreWordsError {
case generic
case secretDecryptionFailed
}
public func walletRestoreWords(network: Network, walletInfo: WalletInfo, tonInstance: TonInstance, keychain: TonKeychain) -> Signal<[String], WalletRestoreWordsError> {
return getServerWalletSalt(network: network)
|> mapError { _ -> WalletRestoreWordsError in
return .generic
}
|> mapToSignal { serverSalt in
return tonInstance.walletRestoreWords(walletInfo: walletInfo, keychain: keychain, serverSalt: serverSalt)
}
public func walletRestoreWords(tonInstance: TonInstance, publicKey: WalletPublicKey, decryptedSecret: Data, localPassword: Data) -> Signal<[String], WalletRestoreWordsError> {
return tonInstance.walletRestoreWords(publicKey: publicKey, decryptedSecret: decryptedSecret, localPassword: localPassword)
}
public struct WalletState: Codable, Equatable {
@ -641,6 +633,7 @@ public func walletAddress(publicKey: WalletPublicKey, tonInstance: TonInstance)
private enum GetWalletStateError {
case generic
case network
}
private func getWalletState(address: String, tonInstance: TonInstance) -> Signal<(WalletState, Int64), GetWalletStateError> {
@ -649,6 +642,7 @@ private func getWalletState(address: String, tonInstance: TonInstance) -> Signal
public enum GetCombinedWalletStateError {
case generic
case network
}
public enum CombinedWalletStateResult {
@ -681,8 +675,19 @@ public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStat
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { address -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
return getWalletState(address: address, tonInstance: tonInstance)
|> mapError { _ -> GetCombinedWalletStateError in
return .generic
|> retryTonRequest(isNetworkError: { error in
if case .network = error {
return true
} else {
return false
}
})
|> mapError { error -> GetCombinedWalletStateError in
if case .network = error {
return .network
} else {
return .generic
}
}
|> mapToSignal { walletState, syncUtime -> Signal<CombinedWalletStateResult, GetCombinedWalletStateError> in
let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError>
@ -690,8 +695,12 @@ public func getCombinedWalletState(postbox: Postbox, subject: CombinedWalletStat
topTransactions = .single(cachedState?.topTransactions ?? [])
} else {
topTransactions = getWalletTransactions(address: address, previousId: nil, tonInstance: tonInstance)
|> mapError { _ -> GetCombinedWalletStateError in
return .generic
|> mapError { error -> GetCombinedWalletStateError in
if case .network = error {
return .network
} else {
return .generic
}
}
}
return topTransactions
@ -743,13 +752,16 @@ public enum SendGramsFromWalletError {
case secretDecryptionFailed
case invalidAddress
case destinationIsNotInitialized
case messageTooLong
case notEnoughFunds
case network
}
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> {
public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, walletInfo: WalletInfo, decryptedSecret: Data, localPassword: 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)
return tonInstance.sendGramsFromWallet(decryptedSecret: decryptedSecret, localPassword: localPassword, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId)
}
}
@ -861,6 +873,7 @@ private extension WalletTransaction {
public enum GetWalletTransactionsError {
case generic
case network
}
public func getWalletTransactions(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> {
@ -884,18 +897,50 @@ public func getWalletTransactions(address: String, previousId: WalletTransaction
}
}
private func retryTonRequest<T, E>(isNetworkError: @escaping (E) -> Bool) -> (Signal<T, E>) -> Signal<T, E> {
return { signal in
return signal
|> retry(retryOnError: isNetworkError, delayIncrement: 0.2, maxDelay: 5.0, maxRetries: 3, onQueue: Queue.concurrentDefaultQueue())
}
}
private enum WalletLastTransactionIdError {
case generic
case network
}
private func getWalletTransactionsOnce(address: String, previousId: WalletTransactionId?, tonInstance: TonInstance) -> Signal<[WalletTransaction], GetWalletTransactionsError> {
let previousIdValue: Signal<WalletTransactionId?, GetWalletTransactionsError>
if let previousId = previousId {
previousIdValue = .single(previousId)
} else {
previousIdValue = tonInstance.walletLastTransactionId(address: address)
|> castError(GetWalletTransactionsError.self)
|> retryTonRequest(isNetworkError: { error in
if case .network = error {
return true
} else {
return false
}
})
|> mapError { error -> GetWalletTransactionsError in
if case .network = error {
return .network
} else {
return .generic
}
}
}
return previousIdValue
|> mapToSignal { previousId in
if let previousId = previousId {
return tonInstance.getWalletTransactions(address: address, previousId: previousId)
|> retryTonRequest(isNetworkError: { error in
if case .network = error {
return true
} else {
return false
}
})
} else {
return .single([])
}

View file

@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "􀊯 2.pdf"
"filename" : "ic_walletupdate.pdf"
}
],
"info" : {

View file

@ -56,6 +56,15 @@ private let keyboardWindowClass: AnyClass? = {
private class ApplicationStatusBarHost: StatusBarHost {
private let application = UIApplication.shared
var isApplicationInForeground: Bool {
switch self.application.applicationState {
case .background:
return false
default:
return true
}
}
var statusBarFrame: CGRect {
return self.application.statusBarFrame
}
@ -71,6 +80,10 @@ private class ApplicationStatusBarHost: StatusBarHost {
self.application.setStatusBarStyle(style, animated: animated)
}
func setStatusBarHidden(_ value: Bool, animated: Bool) {
self.application.setStatusBarHidden(value, with: animated ? .fade : .none)
}
var statusBarWindow: UIView? {
return self.application.value(forKey: "statusBarWindow") as? UIView
}
@ -231,8 +244,6 @@ 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
@ -249,8 +260,6 @@ 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
@ -664,6 +673,7 @@ final class SharedApplicationContext {
#if targetEnvironment(simulator)
tonKeychain = TonKeychain(encryptionPublicKey: {
//return .single(nil)
//return .single("1".data(using: .utf8)!)
return .single(Data())
}, encrypt: { data in
return Signal { subscriber in
@ -701,11 +711,17 @@ final class SharedApplicationContext {
}
}, decrypt: { encryptedData in
return Signal { subscriber in
BuildConfig.decryptApplicationSecret(encryptedData.data, publicKey: encryptedData.publicKey, baseAppBundleId: baseAppBundleId, completion: { result in
BuildConfig.decryptApplicationSecret(encryptedData.data, publicKey: encryptedData.publicKey, baseAppBundleId: baseAppBundleId, completion: { result, cancelled in
if let result = result {
subscriber.putNext(result)
} else {
subscriber.putError(.generic)
let error: TonKeychainDecryptDataError
if cancelled {
error = .cancelled
} else {
error = .generic
}
subscriber.putError(error)
}
subscriber.putCompletion()
})
@ -1259,9 +1275,12 @@ final class SharedApplicationContext {
#endif
}
if UIApplication.shared.isStatusBarHidden {
UIApplication.shared.setStatusBarHidden(false, with: .none)
}
NotificationCenter.default.addObserver(forName: UIWindow.didBecomeHiddenNotification, object: nil, queue: nil, using: { notification in
if UIApplication.shared.isStatusBarHidden {
UIApplication.shared.setStatusBarHidden(false, with: .none)
//UIApplication.shared.setStatusBarHidden(false, with: .none)
}
})
return true

View file

@ -456,6 +456,7 @@ final class AuthorizedApplicationContext {
}, expandAction: { expandData in
if let strongSelf = self {
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId), mode: .overlay)
//chatController.navigation_setNavigationController(strongSelf.rootController)
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(chatController, in: .window(.root), with: ChatControllerOverlayPresentationData(expandData: expandData()))
}
}))

View file

@ -70,6 +70,8 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
self.displayNode = AuthorizationSequenceCodeEntryControllerNode(strings: self.strings, theme: self.theme)
self.displayNodeDidLoad()
self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
self.controllerNode.loginWithCode = { [weak self] code in
self?.continueWithCode(code)
}

View file

@ -74,6 +74,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
self.displayNode = AuthorizationSequencePasswordEntryControllerNode(strings: self.strings, theme: self.theme)
self.displayNodeDidLoad()
self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
self.controllerNode.loginWithCode = { [weak self] _ in
self?.nextPressed()
}

View file

@ -106,6 +106,9 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
self.controllerNode.codeAndNumber = (code, name, number)
}
self.displayNodeDidLoad()
self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
self.controllerNode.selectCountryCode = { [weak self] in
if let strongSelf = self {
let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.strings, theme: strongSelf.theme)
@ -118,8 +121,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
controller.dismissed = {
self?.controllerNode.activateInput()
}
strongSelf.controllerNode.view.endEditing(true)
strongSelf.present(controller, in: .window(.root))
strongSelf.push(controller)
}
}
self.controllerNode.checkPhone = { [weak self] in

View file

@ -93,6 +93,8 @@ final class AuthorizationSequenceSignUpController: ViewController {
})
self.displayNodeDidLoad()
self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
self.controllerNode.signUpWithName = { [weak self] _, _ in
self?.nextPressed()
}

View file

@ -2313,7 +2313,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private func themeAndStringsUpdated() {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
switch self.presentationInterfaceState.mode {
case .standard:
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.deferScreenEdgeGestures = []
case .overlay:
self.statusBar.statusBarStyle = .Hide
self.deferScreenEdgeGestures = [.top]
case .inline:
self.statusBar.statusBarStyle = .Ignore
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
@ -2590,22 +2599,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in
if let strongSelf = self {
let offsetAlpha: CGFloat
if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing {
offsetAlpha = 0.0
} else {
switch offset {
case let .known(offset):
if offset < 40.0 {
offsetAlpha = 0.0
} else {
offsetAlpha = 1.0
}
case .unknown:
offsetAlpha = 1.0
case .none:
switch offset {
case let .known(offset):
if offset < 40.0 {
offsetAlpha = 0.0
}
} else {
offsetAlpha = 1.0
}
case .unknown:
offsetAlpha = 1.0
case .none:
offsetAlpha = 0.0
}
strongSelf.chatDisplayNode.navigateButtons.displayDownButton = !offsetAlpha.isZero
}
}

View file

@ -1903,6 +1903,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
switch self.chatPresentationInterfaceState.mode {
case .standard(previewing: true):
if let result = self.navigateButtons.hitTest(self.view.convert(point, to: self.navigateButtons.view), with: event) {
return result
}
if self.bounds.contains(point) {
return self.historyNode.view
}

View file

@ -38,6 +38,8 @@ final class ChatScheduleTimeController: ViewController {
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (context.sharedContext.presentationData
@ -74,8 +76,6 @@ final class ChatScheduleTimeController: ViewController {
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -15,6 +15,8 @@ public final class NotificationContainerController: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var validLayout: ContainerViewLayout?
public init(context: AccountContext) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -61,7 +63,15 @@ public final class NotificationContainerController: ViewController {
self.controllerNode.displayingItemsUpdated = { [weak self] value in
if let strongSelf = self {
strongSelf.statusBar.statusBarStyle = value ? .Hide : .Ignore
var statusBarHidden = false
if value, let layout = strongSelf.validLayout {
if let statusBarHeight = layout.statusBarHeight, statusBarHeight > 20.0 {
statusBarHidden = false
} else {
statusBarHidden = true
}
}
strongSelf.statusBar.statusBarStyle = statusBarHidden ? .Hide : .Ignore
if value {
strongSelf.deferScreenEdgeGestures = [.top]
} else {
@ -72,6 +82,8 @@ public final class NotificationContainerController: ViewController {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, transition: transition)

View file

@ -68,29 +68,19 @@ final class NotificationContainerControllerNode: ASDisplayNode {
if let (item, topItemNode) = self.topItemAndNode {
if item.groupingKey == key {
self.topItemAndNode = nil
self.displayingItemsUpdated?(false)
topItemNode.animateOut(completion: { [weak self, weak topItemNode] in
topItemNode?.removeFromSupernode()
if let strongSelf = self, strongSelf.topItemAndNode == nil {
strongSelf.displayingItemsUpdated?(false)
}
})
}
}
}
func enqueue(_ item: NotificationItem) {
var updatedDisplayingItems = false
if let (_, topItemNode) = self.topItemAndNode {
topItemNode.animateOut(completion: { [weak self, weak topItemNode] in
topItemNode?.removeFromSupernode()
if let strongSelf = self, strongSelf.topItemAndNode == nil {
strongSelf.displayingItemsUpdated?(false)
}
})
} else {
updatedDisplayingItems = true
}
var useCompactLayout = false
@ -138,9 +128,7 @@ final class NotificationContainerControllerNode: ASDisplayNode {
containerNode.animateIn()
}
if updatedDisplayingItems {
self.displayingItemsUpdated?(true)
}
self.displayingItemsUpdated?(true)
self.resetTimeoutTimer()
}
@ -172,12 +160,9 @@ final class NotificationContainerControllerNode: ASDisplayNode {
if let strongSelf = self {
if let (_, topItemNode) = strongSelf.topItemAndNode {
strongSelf.topItemAndNode = nil
strongSelf.displayingItemsUpdated?(false)
topItemNode.animateOut(completion: { [weak topItemNode] in
topItemNode?.removeFromSupernode()
if let strongSelf = self, strongSelf.topItemAndNode == nil {
strongSelf.displayingItemsUpdated?(false)
}
})
}
}

View file

@ -79,8 +79,6 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {

View file

@ -843,10 +843,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} else {
text = presentationData.strings.Call_StatusBar("").0
}
self.mainWindow?.setForceInCallStatusBar(text)
if let navigationController = self.mainWindow?.viewController as? NavigationController {
navigationController.setForceInCallStatusBar(text)
}
} else {
self.mainWindow?.setForceInCallStatusBar(nil)
if let navigationController = self.mainWindow?.viewController as? NavigationController {
navigationController.setForceInCallStatusBar(nil)
}
}
}

View file

@ -692,6 +692,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return
}
let combinedState: CombinedWalletState?
var isUpdated = false
switch value {
case let .cached(state):
if strongSelf.combinedState != nil {
@ -705,6 +706,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}
combinedState = state
case let .updated(state):
isUpdated = true
strongSelf.loadingIndicator.stopAnimating()
strongSelf.loadingIndicator.isHidden = true
combinedState = state
@ -729,7 +731,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions)
strongSelf.headerNode.isRefreshing = false
if isUpdated {
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), isScrolling: false)
@ -751,7 +755,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.didSetContentReady = true
strongSelf.contentReady.set(.single(true))
}
}, error: { [weak self] _ in
}, error: { [weak self] error in
guard let strongSelf = self else {
return
}
@ -780,7 +784,14 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.contentReady.set(.single(true))
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.Wallet_Info_RefreshErrorTitle, text: strongSelf.presentationData.strings.Wallet_Info_RefreshErrorText, actions: [
let text: String
switch error {
case .generic:
text = strongSelf.presentationData.strings.Wallet_Info_RefreshErrorText
case .network:
text = strongSelf.presentationData.strings.Wallet_Info_RefreshErrorNetworkText
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.Wallet_Info_RefreshErrorTitle, text: text, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})
], actionLayout: .vertical), nil)

View file

@ -83,7 +83,7 @@ final class WalletRefreshNode: ASDisplayNode {
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.image = UIImage(bundleImageName: "Wallet/RefreshIcon")
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Wallet/RefreshIcon"), color: UIColor(white: 0.6, alpha: 1.0))
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(), size: image.size)
}
@ -199,7 +199,7 @@ final class WalletRefreshNode: ASDisplayNode {
let iconSize = self.iconNode.image?.size ?? CGSize(width: 20.0, height: 20.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: 100.0))
let iconSpacing: CGFloat = 5.0
let iconSpacing: CGFloat = 1.0
let contentWidth = iconSize.width + titleSize.width + iconSpacing
let contentOrigin = floor(-contentWidth / 2.0)

View file

@ -104,10 +104,20 @@ public func walletSettingsController(context: AccountContext, tonContext: TonCon
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
presentControllerImpl?(controller, nil)
let _ = (walletRestoreWords(network: context.account.network, walletInfo: walletInfo, tonInstance: tonContext.instance, keychain: tonContext.keychain)
|> deliverOnMainQueue).start(next: { [weak controller] wordList in
controller?.dismiss()
pushControllerImpl?(WalletWordDisplayScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, wordList: wordList, mode: .export, walletCreatedPreloadState: nil))
let _ = (tonContext.keychain.decrypt(walletInfo.encryptedSecret)
|> deliverOnMainQueue).start(next: { [weak controller] decryptedSecret in
let _ = (getServerWalletSalt(network: context.account.network)
|> deliverOnMainQueue).start(next: { serverSalt in
let _ = (walletRestoreWords(tonInstance: tonContext.instance, publicKey: walletInfo.publicKey, decryptedSecret: decryptedSecret, localPassword: serverSalt)
|> deliverOnMainQueue).start(next: { [weak controller] wordList in
controller?.dismiss()
pushControllerImpl?(WalletWordDisplayScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, wordList: wordList, mode: .export, walletCreatedPreloadState: nil))
}, error: { [weak controller] _ in
controller?.dismiss()
})
}, error: { [weak controller] _ in
controller?.dismiss()
})
}, error: { [weak controller] _ in
controller?.dismiss()
})
@ -120,7 +130,7 @@ public func walletSettingsController(context: AccountContext, tonContext: TonCon
actionSheet?.dismissAnimated()
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
presentControllerImpl?(controller, nil)
let _ = (deleteLocalWalletData(postbox: context.account.postbox, network: context.account.network, tonInstance: tonContext.instance, keychain: tonContext.keychain, walletInfo: walletInfo)
let _ = (deleteAllLocalWalletsData(postbox: context.account.postbox, network: context.account.network, tonInstance: tonContext.instance)
|> deliverOnMainQueue).start(error: { [weak controller] _ in
controller?.dismiss()
}, completed: { [weak controller] in

View file

@ -93,17 +93,21 @@ public final class WalletSplashScreen: ViewController {
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
strongSelf.sendGrams(walletInfo: walletInfo, decryptedSecret: decryptedSecret, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: true, randomId: randomId, serverSalt: serverSalt)
}, error: { [weak self] error 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()
if case .cancelled = error {
strongSelf.dismiss()
} else {
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)
@ -129,7 +133,7 @@ public final class WalletSplashScreen: ViewController {
}
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)
let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: 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
@ -138,14 +142,20 @@ public final class WalletSplashScreen: ViewController {
switch error {
case .generic:
text = strongSelf.presentationData.strings.Login_UnknownError
case .network:
text = strongSelf.presentationData.strings.Wallet_Send_NetworkError
case .notEnoughFunds:
text = strongSelf.presentationData.strings.Wallet_Send_ErrorNotEnoughFunds
case .messageTooLong:
text = strongSelf.presentationData.strings.Login_UnknownError
case .invalidAddress:
text = strongSelf.presentationData.strings.Wallet_Send_ErrorInvalidAddress
case .secretDecryptionFailed:
text = strongSelf.presentationData.strings.Wallet_Send_ErrorDecryptionFailed
case .destinationIsNotInitialized:
if !forceIfDestinationNotInitialized {
text = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?"
let controller = textAlertController(context: strongSelf.context, title: "Warning", text: text, actions: [
text = strongSelf.presentationData.strings.Wallet_Send_UninitializedText
let controller = textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Wallet_Send_UninitializedTitle, text: text, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
if let navigationController = strongSelf.navigationController as? NavigationController {
navigationController.popViewController(animated: true)
@ -203,15 +213,7 @@ public final class WalletSplashScreen: ViewController {
switch strongSelf.mode {
case .intro:
let controller = OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .loading(cancelled: nil))
strongSelf.present(controller, in: .window(.root))
let _ = (createWallet(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain)
|> deliverOnMainQueue).start(next: { walletInfo, wordList in
guard let strongSelf = self else {
return
}
controller.dismiss()
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .created(walletInfo, wordList), walletCreatedPreloadState: nil), animated: true)
}, error: { _ in
let displayError: () -> Void = {
guard let strongSelf = self else {
return
}
@ -220,6 +222,22 @@ public final class WalletSplashScreen: ViewController {
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})
], actionLayout: .vertical), in: .window(.root))
}
strongSelf.present(controller, in: .window(.root))
let _ = (getServerWalletSalt(network: strongSelf.context.account.network)
|> deliverOnMainQueue).start(next: { serverSalt in
let _ = (createWallet(postbox: strongSelf.context.account.postbox, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain, localPassword: serverSalt)
|> deliverOnMainQueue).start(next: { walletInfo, wordList in
guard let strongSelf = self else {
return
}
controller.dismiss()
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .created(walletInfo, wordList), walletCreatedPreloadState: nil), animated: true)
}, error: { _ in
displayError()
})
}, error: { _ in
displayError()
})
case let .created(walletInfo, wordList):
if let wordList = wordList {
@ -227,23 +245,56 @@ public final class WalletSplashScreen: ViewController {
} 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: {
let context = strongSelf.context
let tonContext = strongSelf.tonContext
let _ = (strongSelf.tonContext.keychain.decrypt(walletInfo.encryptedSecret)
|> deliverOnMainQueue).start(next: { [weak controller] decryptedSecret in
let _ = (getServerWalletSalt(network: context.account.network)
|> deliverOnMainQueue).start(next: { [weak controller] serverSalt in
let _ = (walletRestoreWords(tonInstance: tonContext.instance, publicKey: walletInfo.publicKey, decryptedSecret: decryptedSecret, localPassword: serverSalt)
|> deliverOnMainQueue).start(next: { wordList in
controller?.dismiss()
guard let strongSelf = self else {
return
}
strongSelf.mode = .created(walletInfo, wordList)
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))
})
], actionLayout: .vertical), in: .window(.root))
}, error: { [weak controller] _ 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))
})
}, error: { [weak controller] error in
controller?.dismiss()
if case .cancelled = error {
} else {
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):
@ -720,16 +771,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
let secondaryActionSize = self.secondaryActionTitleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let contentHeight = iconSize.height + iconSpacing + titleSize.height + titleSpacing + textSize.height
let contentVerticalOrigin = floor((layout.size.height - contentHeight - iconSize.height / 2.0) / 2.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
self.animationNode.updateLayout(size: iconFrame.size)
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
var contentVerticalOrigin = floor((layout.size.height - contentHeight - iconSize.height / 2.0) / 2.0)
let minimalBottomInset: CGFloat = 60.0
let bottomInset = layout.intrinsicInsets.bottom + max(minimalBottomInset, termsSize.height + termsSpacing * 2.0)
@ -740,12 +782,27 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
var maxContentVerticalOrigin = buttonFrame.minY - 12.0 - contentHeight
if !secondaryActionSize.width.isZero {
let secondaryActionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - secondaryActionSize.width) / 2.0), y: buttonFrame.minY - 20.0 - secondaryActionSize.height), size: secondaryActionSize)
transition.updateFrameAdditive(node: self.secondaryActionTitleNode, frame: secondaryActionFrame)
transition.updateFrame(node: self.secondaryActionButtonNode, frame: secondaryActionFrame.insetBy(dx: -10.0, dy: -10.0))
maxContentVerticalOrigin = secondaryActionFrame.minY - 12.0 - contentHeight
}
contentVerticalOrigin = min(contentVerticalOrigin, maxContentVerticalOrigin)
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
self.animationNode.updateLayout(size: iconFrame.size)
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
let termsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsSize.width) / 2.0), y: buttonFrame.maxY + floor((layout.size.height - layout.intrinsicInsets.bottom - buttonFrame.maxY - termsSize.height) / 2.0)), size: termsSize)
transition.updateFrameAdditive(node: self.termsNode, frame: termsFrame)
}

View file

@ -3,7 +3,7 @@ import TelegramStringFormatting
import UrlEscaping
let walletAddressLength: Int = 48
let walletTextLimit: Int = 124
let walletTextLimit: Int = 1024
func formatAddress(_ address: String) -> String {
var address = address

View file

@ -2178,30 +2178,8 @@ public final class WalletWordCheckScreen: ViewController {
], actionLayout: .vertical), in: .window(.root))
return
}
let _ = (importWallet(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain, wordList: enteredWords)
|> deliverOnMainQueue).start(next: { walletInfo in
guard let strongSelf = self else {
return
}
if let navigationController = strongSelf.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
controllers = controllers.filter { controller in
if controller is WalletSplashScreen {
return false
}
if controller is WalletWordDisplayScreen {
return false
}
if controller is WalletWordCheckScreen {
return false
}
return true
}
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
strongSelf.view.endEditing(true)
navigationController.setViewControllers(controllers, animated: true)
}
}, error: { error in
let displayError: () -> Void = {
guard let strongSelf = self else {
return
}
@ -2210,6 +2188,38 @@ public final class WalletWordCheckScreen: ViewController {
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})
], actionLayout: .vertical), in: .window(.root))
}
let _ = (getServerWalletSalt(network: strongSelf.context.account.network)
|> deliverOnMainQueue).start(next: { serverSalt in
let _ = (importWallet(postbox: strongSelf.context.account.postbox, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain, wordList: enteredWords, localPassword: serverSalt)
|> deliverOnMainQueue).start(next: { walletInfo in
guard let strongSelf = self else {
return
}
if let navigationController = strongSelf.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
controllers = controllers.filter { controller in
if controller is WalletSplashScreen {
return false
}
if controller is WalletWordDisplayScreen {
return false
}
if controller is WalletWordCheckScreen {
return false
}
return true
}
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState))
strongSelf.view.endEditing(true)
navigationController.setViewControllers(controllers, animated: true)
}
}, error: { error in
displayError()
})
}, error: { _ in
displayError()
})
}
}, secondaryAction: { [weak self] in

View file

@ -315,6 +315,8 @@ if (NOT CMAKE_CROSSCOMPILING)
GenFif(DEST smartcont/config-code.fif SOURCE smartcont/config-code.fc)
GenFif(DEST smartcont/wallet-code.fif SOURCE smartcont/wallet-code.fc)
GenFif(DEST smartcont/simple-wallet-code.fif SOURCE smartcont/simple-wallet-code.fc)
GenFif(DEST smartcont/highload-wallet-code.fif SOURCE smartcont/highload-wallet-code.fc)
GenFif(DEST smartcont/highload-wallet-v2-code.fif SOURCE smartcont/highload-wallet-v2-code.fc)
GenFif(DEST smartcont/elector-code.fif SOURCE smartcont/elector-code.fc)
endif()
@ -333,3 +335,6 @@ target_link_libraries(dump-block PUBLIC ton_crypto fift-lib ton_block)
if (WINGETOPT_FOUND)
target_link_libraries_system(dump-block wingetopt)
endif()
install(TARGETS fift func RUNTIME DESTINATION bin)
install(DIRECTORY fift/lib/ DESTINATION lib/fift)

View file

@ -8891,6 +8891,7 @@ bool TransactionDescr::skip(vm::CellSlice& cs) const {
&& cs.advance(2);
case trans_split_prepare:
return cs.advance(528)
&& t_Maybe_TrStoragePhase.skip(cs)
&& t_TrComputePhase.skip(cs)
&& t_Maybe_Ref_TrActionPhase.skip(cs)
&& cs.advance(2);
@ -8902,6 +8903,7 @@ bool TransactionDescr::skip(vm::CellSlice& cs) const {
&& cs.advance(1);
case trans_merge_install:
return cs.advance_ext(0x10210)
&& t_Maybe_TrStoragePhase.skip(cs)
&& t_Maybe_TrCreditPhase.skip(cs)
&& t_TrComputePhase.skip(cs)
&& t_Maybe_Ref_TrActionPhase.skip(cs)
@ -8932,6 +8934,7 @@ bool TransactionDescr::validate_skip(vm::CellSlice& cs, bool weak) const {
&& cs.advance(2);
case trans_split_prepare:
return cs.advance(528)
&& t_Maybe_TrStoragePhase.validate_skip(cs, weak)
&& t_TrComputePhase.validate_skip(cs, weak)
&& t_Maybe_Ref_TrActionPhase.validate_skip(cs, weak)
&& cs.advance(2);
@ -8947,6 +8950,7 @@ bool TransactionDescr::validate_skip(vm::CellSlice& cs, bool weak) const {
return cs.fetch_ulong(4) == 7
&& cs.advance(524)
&& t_Transaction.validate_skip_ref(cs, weak)
&& t_Maybe_TrStoragePhase.validate_skip(cs, weak)
&& t_Maybe_TrCreditPhase.validate_skip(cs, weak)
&& t_TrComputePhase.validate_skip(cs, weak)
&& t_Maybe_Ref_TrActionPhase.validate_skip(cs, weak)
@ -8998,7 +9002,7 @@ bool TransactionDescr::cell_unpack_trans_storage(Ref<vm::Cell> cell_ref, Ref<Cel
bool TransactionDescr::unpack(vm::CellSlice& cs, TransactionDescr::Record_trans_tick_tock& data) const {
return cs.fetch_ulong(3) == 1
&& cs.fetch_bool_to(data.is_tock)
&& t_TrStoragePhase.fetch_to(cs, data.storage)
&& t_TrStoragePhase.fetch_to(cs, data.storage_ph)
&& t_TrComputePhase.fetch_to(cs, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.fetch_to(cs, data.action)
&& cs.fetch_bool_to(data.aborted)
@ -9014,6 +9018,7 @@ bool TransactionDescr::cell_unpack(Ref<vm::Cell> cell_ref, TransactionDescr::Rec
bool TransactionDescr::unpack(vm::CellSlice& cs, TransactionDescr::Record_trans_split_prepare& data) const {
return cs.fetch_ulong(4) == 4
&& cs.fetch_subslice_to(524, data.split_info)
&& t_Maybe_TrStoragePhase.fetch_to(cs, data.storage_ph)
&& t_TrComputePhase.fetch_to(cs, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.fetch_to(cs, data.action)
&& cs.fetch_bool_to(data.aborted)
@ -9082,6 +9087,7 @@ bool TransactionDescr::unpack(vm::CellSlice& cs, TransactionDescr::Record_trans_
return cs.fetch_ulong(4) == 7
&& cs.fetch_subslice_to(524, data.split_info)
&& cs.fetch_ref_to(data.prepare_transaction)
&& t_Maybe_TrStoragePhase.fetch_to(cs, data.storage_ph)
&& t_Maybe_TrCreditPhase.fetch_to(cs, data.credit_ph)
&& t_TrComputePhase.fetch_to(cs, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.fetch_to(cs, data.action)
@ -9135,7 +9141,7 @@ bool TransactionDescr::cell_pack_trans_storage(Ref<vm::Cell>& cell_ref, Ref<Cell
bool TransactionDescr::pack(vm::CellBuilder& cb, const TransactionDescr::Record_trans_tick_tock& data) const {
return cb.store_long_bool(1, 3)
&& cb.store_ulong_rchk_bool(data.is_tock, 1)
&& t_TrStoragePhase.store_from(cb, data.storage)
&& t_TrStoragePhase.store_from(cb, data.storage_ph)
&& t_TrComputePhase.store_from(cb, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.store_from(cb, data.action)
&& cb.store_ulong_rchk_bool(data.aborted, 1)
@ -9150,6 +9156,7 @@ bool TransactionDescr::cell_pack(Ref<vm::Cell>& cell_ref, const TransactionDescr
bool TransactionDescr::pack(vm::CellBuilder& cb, const TransactionDescr::Record_trans_split_prepare& data) const {
return cb.store_long_bool(4, 4)
&& cb.append_cellslice_chk(data.split_info, 524)
&& t_Maybe_TrStoragePhase.store_from(cb, data.storage_ph)
&& t_TrComputePhase.store_from(cb, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.store_from(cb, data.action)
&& cb.store_ulong_rchk_bool(data.aborted, 1)
@ -9213,6 +9220,7 @@ bool TransactionDescr::pack(vm::CellBuilder& cb, const TransactionDescr::Record_
return cb.store_long_bool(7, 4)
&& cb.append_cellslice_chk(data.split_info, 524)
&& cb.store_ref_bool(data.prepare_transaction)
&& t_Maybe_TrStoragePhase.store_from(cb, data.storage_ph)
&& t_Maybe_TrCreditPhase.store_from(cb, data.credit_ph)
&& t_TrComputePhase.store_from(cb, data.compute_ph)
&& t_Maybe_Ref_TrActionPhase.store_from(cb, data.action)
@ -9254,7 +9262,7 @@ bool TransactionDescr::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const {
return cs.advance(3)
&& pp.open("trans_tick_tock")
&& pp.fetch_uint_field(cs, 1, "is_tock")
&& pp.field("storage")
&& pp.field("storage_ph")
&& t_TrStoragePhase.print_skip(pp, cs)
&& pp.field("compute_ph")
&& t_TrComputePhase.print_skip(pp, cs)
@ -9268,6 +9276,8 @@ bool TransactionDescr::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const {
&& pp.open("trans_split_prepare")
&& pp.field("split_info")
&& t_SplitMergeInfo.print_skip(pp, cs)
&& pp.field("storage_ph")
&& t_Maybe_TrStoragePhase.print_skip(pp, cs)
&& pp.field("compute_ph")
&& t_TrComputePhase.print_skip(pp, cs)
&& pp.field("action")
@ -9300,6 +9310,8 @@ bool TransactionDescr::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const {
&& t_SplitMergeInfo.print_skip(pp, cs)
&& pp.field("prepare_transaction")
&& t_Transaction.print_ref(pp, cs.fetch_ref())
&& pp.field("storage_ph")
&& t_Maybe_TrStoragePhase.print_skip(pp, cs)
&& pp.field("credit_ph")
&& t_Maybe_TrCreditPhase.print_skip(pp, cs)
&& pp.field("compute_ph")
@ -14089,11 +14101,13 @@ const StoragePrices t_StoragePrices;
//
// code for type `GasLimitsPrices`
//
constexpr unsigned char GasLimitsPrices::cons_tag[2];
constexpr unsigned char GasLimitsPrices::cons_tag[3];
int GasLimitsPrices::get_tag(const vm::CellSlice& cs) const {
switch (cs.bselect(6, 0x180000000000000ULL)) {
switch (cs.bselect(6, 0x1b0000000000000ULL)) {
case 0:
return gas_flat_pfx;
case 2:
return cs.bit_at(6) ? gas_prices_ext : gas_prices;
default:
return -1;
@ -14106,6 +14120,8 @@ int GasLimitsPrices::check_tag(const vm::CellSlice& cs) const {
return cs.prefetch_ulong(8) == 0xdd ? gas_prices : -1;
case gas_prices_ext:
return cs.prefetch_ulong(8) == 0xde ? gas_prices_ext : -1;
case gas_flat_pfx:
return cs.prefetch_ulong(8) == 0xd1 ? gas_flat_pfx : -1;
}
return -1;
}
@ -14116,6 +14132,9 @@ bool GasLimitsPrices::skip(vm::CellSlice& cs) const {
return cs.advance(392);
case gas_prices_ext:
return cs.advance(456);
case gas_flat_pfx:
return cs.advance(136)
&& skip(cs);
}
return false;
}
@ -14128,6 +14147,10 @@ bool GasLimitsPrices::validate_skip(vm::CellSlice& cs, bool weak) const {
case gas_prices_ext:
return cs.fetch_ulong(8) == 0xde
&& cs.advance(448);
case gas_flat_pfx:
return cs.fetch_ulong(8) == 0xd1
&& cs.advance(128)
&& validate_skip(cs, weak);
}
return false;
}
@ -14165,6 +14188,32 @@ bool GasLimitsPrices::cell_unpack(Ref<vm::Cell> cell_ref, GasLimitsPrices::Recor
return unpack(cs, data) && cs.empty_ext();
}
bool GasLimitsPrices::unpack(vm::CellSlice& cs, GasLimitsPrices::Record_gas_flat_pfx& data) const {
return cs.fetch_ulong(8) == 0xd1
&& cs.fetch_uint_to(64, data.flat_gas_limit)
&& cs.fetch_uint_to(64, data.flat_gas_price)
&& fetch_to(cs, data.other);
}
bool GasLimitsPrices::unpack_gas_flat_pfx(vm::CellSlice& cs, unsigned long long& flat_gas_limit, unsigned long long& flat_gas_price, Ref<CellSlice>& other) const {
return cs.fetch_ulong(8) == 0xd1
&& cs.fetch_uint_to(64, flat_gas_limit)
&& cs.fetch_uint_to(64, flat_gas_price)
&& fetch_to(cs, other);
}
bool GasLimitsPrices::cell_unpack(Ref<vm::Cell> cell_ref, GasLimitsPrices::Record_gas_flat_pfx& data) const {
if (cell_ref.is_null()) { return false; }
auto cs = load_cell_slice(std::move(cell_ref));
return unpack(cs, data) && cs.empty_ext();
}
bool GasLimitsPrices::cell_unpack_gas_flat_pfx(Ref<vm::Cell> cell_ref, unsigned long long& flat_gas_limit, unsigned long long& flat_gas_price, Ref<CellSlice>& other) const {
if (cell_ref.is_null()) { return false; }
auto cs = load_cell_slice(std::move(cell_ref));
return unpack_gas_flat_pfx(cs, flat_gas_limit, flat_gas_price, other) && cs.empty_ext();
}
bool GasLimitsPrices::pack(vm::CellBuilder& cb, const GasLimitsPrices::Record_gas_prices& data) const {
return cb.store_long_bool(0xdd, 8)
&& cb.store_ulong_rchk_bool(data.gas_price, 64)
@ -14196,6 +14245,30 @@ bool GasLimitsPrices::cell_pack(Ref<vm::Cell>& cell_ref, const GasLimitsPrices::
return pack(cb, data) && std::move(cb).finalize_to(cell_ref);
}
bool GasLimitsPrices::pack(vm::CellBuilder& cb, const GasLimitsPrices::Record_gas_flat_pfx& data) const {
return cb.store_long_bool(0xd1, 8)
&& cb.store_ulong_rchk_bool(data.flat_gas_limit, 64)
&& cb.store_ulong_rchk_bool(data.flat_gas_price, 64)
&& store_from(cb, data.other);
}
bool GasLimitsPrices::pack_gas_flat_pfx(vm::CellBuilder& cb, unsigned long long flat_gas_limit, unsigned long long flat_gas_price, Ref<CellSlice> other) const {
return cb.store_long_bool(0xd1, 8)
&& cb.store_ulong_rchk_bool(flat_gas_limit, 64)
&& cb.store_ulong_rchk_bool(flat_gas_price, 64)
&& store_from(cb, other);
}
bool GasLimitsPrices::cell_pack(Ref<vm::Cell>& cell_ref, const GasLimitsPrices::Record_gas_flat_pfx& data) const {
vm::CellBuilder cb;
return pack(cb, data) && std::move(cb).finalize_to(cell_ref);
}
bool GasLimitsPrices::cell_pack_gas_flat_pfx(Ref<vm::Cell>& cell_ref, unsigned long long flat_gas_limit, unsigned long long flat_gas_price, Ref<CellSlice> other) const {
vm::CellBuilder cb;
return pack_gas_flat_pfx(cb, flat_gas_limit, flat_gas_price, std::move(other)) && std::move(cb).finalize_to(cell_ref);
}
bool GasLimitsPrices::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const {
switch (get_tag(cs)) {
case gas_prices:
@ -14219,6 +14292,14 @@ bool GasLimitsPrices::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const {
&& pp.fetch_uint_field(cs, 64, "freeze_due_limit")
&& pp.fetch_uint_field(cs, 64, "delete_due_limit")
&& pp.close();
case gas_flat_pfx:
return cs.fetch_ulong(8) == 0xd1
&& pp.open("gas_flat_pfx")
&& pp.fetch_uint_field(cs, 64, "flat_gas_limit")
&& pp.fetch_uint_field(cs, 64, "flat_gas_price")
&& pp.field("other")
&& print_skip(pp, cs)
&& pp.close();
}
return pp.fail("unknown constructor for GasLimitsPrices");
}

View file

@ -3831,24 +3831,25 @@ struct TransactionDescr::Record_trans_ord {
struct TransactionDescr::Record_trans_tick_tock {
typedef TransactionDescr type_class;
bool is_tock; // is_tock : Bool
Ref<CellSlice> storage; // storage : TrStoragePhase
Ref<CellSlice> storage_ph; // storage_ph : TrStoragePhase
Ref<CellSlice> compute_ph; // compute_ph : TrComputePhase
Ref<CellSlice> action; // action : Maybe ^TrActionPhase
bool aborted; // aborted : Bool
bool destroyed; // destroyed : Bool
Record_trans_tick_tock() = default;
Record_trans_tick_tock(bool _is_tock, Ref<CellSlice> _storage, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : is_tock(_is_tock), storage(std::move(_storage)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
Record_trans_tick_tock(bool _is_tock, Ref<CellSlice> _storage_ph, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : is_tock(_is_tock), storage_ph(std::move(_storage_ph)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
};
struct TransactionDescr::Record_trans_split_prepare {
typedef TransactionDescr type_class;
Ref<CellSlice> split_info; // split_info : SplitMergeInfo
Ref<CellSlice> storage_ph; // storage_ph : Maybe TrStoragePhase
Ref<CellSlice> compute_ph; // compute_ph : TrComputePhase
Ref<CellSlice> action; // action : Maybe ^TrActionPhase
bool aborted; // aborted : Bool
bool destroyed; // destroyed : Bool
Record_trans_split_prepare() = default;
Record_trans_split_prepare(Ref<CellSlice> _split_info, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : split_info(std::move(_split_info)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
Record_trans_split_prepare(Ref<CellSlice> _split_info, Ref<CellSlice> _storage_ph, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : split_info(std::move(_split_info)), storage_ph(std::move(_storage_ph)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
};
struct TransactionDescr::Record_trans_split_install {
@ -3873,13 +3874,14 @@ struct TransactionDescr::Record_trans_merge_install {
typedef TransactionDescr type_class;
Ref<CellSlice> split_info; // split_info : SplitMergeInfo
Ref<Cell> prepare_transaction; // prepare_transaction : ^Transaction
Ref<CellSlice> storage_ph; // storage_ph : Maybe TrStoragePhase
Ref<CellSlice> credit_ph; // credit_ph : Maybe TrCreditPhase
Ref<CellSlice> compute_ph; // compute_ph : TrComputePhase
Ref<CellSlice> action; // action : Maybe ^TrActionPhase
bool aborted; // aborted : Bool
bool destroyed; // destroyed : Bool
Record_trans_merge_install() = default;
Record_trans_merge_install(Ref<CellSlice> _split_info, Ref<Cell> _prepare_transaction, Ref<CellSlice> _credit_ph, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : split_info(std::move(_split_info)), prepare_transaction(std::move(_prepare_transaction)), credit_ph(std::move(_credit_ph)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
Record_trans_merge_install(Ref<CellSlice> _split_info, Ref<Cell> _prepare_transaction, Ref<CellSlice> _storage_ph, Ref<CellSlice> _credit_ph, Ref<CellSlice> _compute_ph, Ref<CellSlice> _action, bool _aborted, bool _destroyed) : split_info(std::move(_split_info)), prepare_transaction(std::move(_prepare_transaction)), storage_ph(std::move(_storage_ph)), credit_ph(std::move(_credit_ph)), compute_ph(std::move(_compute_ph)), action(std::move(_action)), aborted(_aborted), destroyed(_destroyed) {}
};
extern const TransactionDescr t_TransactionDescr;
@ -6166,11 +6168,12 @@ extern const StoragePrices t_StoragePrices;
//
struct GasLimitsPrices final : TLB_Complex {
enum { gas_prices, gas_prices_ext };
enum { gas_flat_pfx, gas_prices, gas_prices_ext };
static constexpr int cons_len_exact = 8;
static constexpr unsigned char cons_tag[2] = { 0xdd, 0xde };
static constexpr unsigned char cons_tag[3] = { 0xd1, 0xdd, 0xde };
struct Record_gas_prices;
struct Record_gas_prices_ext;
struct Record_gas_flat_pfx;
bool skip(vm::CellSlice& cs) const override;
bool validate_skip(vm::CellSlice& cs, bool weak = false) const override;
bool unpack(vm::CellSlice& cs, Record_gas_prices& data) const;
@ -6181,6 +6184,14 @@ struct GasLimitsPrices final : TLB_Complex {
bool cell_unpack(Ref<vm::Cell> cell_ref, Record_gas_prices_ext& data) const;
bool pack(vm::CellBuilder& cb, const Record_gas_prices_ext& data) const;
bool cell_pack(Ref<vm::Cell>& cell_ref, const Record_gas_prices_ext& data) const;
bool unpack(vm::CellSlice& cs, Record_gas_flat_pfx& data) const;
bool unpack_gas_flat_pfx(vm::CellSlice& cs, unsigned long long& flat_gas_limit, unsigned long long& flat_gas_price, Ref<CellSlice>& other) const;
bool cell_unpack(Ref<vm::Cell> cell_ref, Record_gas_flat_pfx& data) const;
bool cell_unpack_gas_flat_pfx(Ref<vm::Cell> cell_ref, unsigned long long& flat_gas_limit, unsigned long long& flat_gas_price, Ref<CellSlice>& other) const;
bool pack(vm::CellBuilder& cb, const Record_gas_flat_pfx& data) const;
bool pack_gas_flat_pfx(vm::CellBuilder& cb, unsigned long long flat_gas_limit, unsigned long long flat_gas_price, Ref<CellSlice> other) const;
bool cell_pack(Ref<vm::Cell>& cell_ref, const Record_gas_flat_pfx& data) const;
bool cell_pack_gas_flat_pfx(Ref<vm::Cell>& cell_ref, unsigned long long flat_gas_limit, unsigned long long flat_gas_price, Ref<CellSlice> other) const;
bool print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const override;
std::ostream& print_type(std::ostream& os) const override {
return os << "GasLimitsPrices";
@ -6214,6 +6225,15 @@ struct GasLimitsPrices::Record_gas_prices_ext {
Record_gas_prices_ext(unsigned long long _gas_price, unsigned long long _gas_limit, unsigned long long _special_gas_limit, unsigned long long _gas_credit, unsigned long long _block_gas_limit, unsigned long long _freeze_due_limit, unsigned long long _delete_due_limit) : gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_special_gas_limit), gas_credit(_gas_credit), block_gas_limit(_block_gas_limit), freeze_due_limit(_freeze_due_limit), delete_due_limit(_delete_due_limit) {}
};
struct GasLimitsPrices::Record_gas_flat_pfx {
typedef GasLimitsPrices type_class;
unsigned long long flat_gas_limit; // flat_gas_limit : uint64
unsigned long long flat_gas_price; // flat_gas_price : uint64
Ref<CellSlice> other; // other : GasLimitsPrices
Record_gas_flat_pfx() = default;
Record_gas_flat_pfx(unsigned long long _flat_gas_limit, unsigned long long _flat_gas_price, Ref<CellSlice> _other) : flat_gas_limit(_flat_gas_limit), flat_gas_price(_flat_gas_price), other(std::move(_other)) {}
};
extern const GasLimitsPrices t_GasLimitsPrices;
//
@ -6522,7 +6542,7 @@ extern const ValidatorSignedTempKey t_ValidatorSignedTempKey;
//
struct ConfigParam final : TLB_Complex {
enum { cons32, cons33, cons34, cons35, cons36, cons37, config_mc_block_limits, config_block_limits, cons14, cons0, cons1, cons2, cons3, cons4, cons6, cons7, cons9, cons12, cons15, cons16, cons17, cons18, cons31, cons39, cons28, cons8, cons29, config_mc_gas_prices, config_gas_prices, config_mc_fwd_prices, config_fwd_prices };
enum { cons32, cons33, cons34, cons35, cons36, cons37, config_mc_block_limits, config_block_limits, cons14, cons0, cons1, cons2, cons3, cons4, cons6, cons7, cons9, cons12, cons15, cons16, cons17, cons18, cons31, cons39, cons28, cons8, config_mc_gas_prices, config_gas_prices, cons29, config_mc_fwd_prices, config_fwd_prices };
static constexpr int cons_len_exact = 0;
int m_;
ConfigParam(int m) : m_(m) {}

View file

@ -1143,6 +1143,20 @@ bool TrStoragePhase::validate_skip(vm::CellSlice& cs, bool weak) const {
&& t_AccStatusChange.validate_skip(cs, weak); // status_change:AccStatusChange
}
bool TrStoragePhase::get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const {
return t_Grams.as_integer_skip_to(cs, storage_fees); // storage_fees_collected:Grams
}
bool TrStoragePhase::maybe_get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const {
auto z = cs.fetch_ulong(1);
if (!z) {
storage_fees = td::make_refint(0);
return true;
} else {
return z == 1 && get_storage_fees(cs, storage_fees);
}
}
const TrStoragePhase t_TrStoragePhase;
bool TrCreditPhase::skip(vm::CellSlice& cs) const {
@ -1322,13 +1336,14 @@ bool TransactionDescr::skip(vm::CellSlice& cs) const {
&& t_TrStoragePhase.skip(cs); // storage_ph:TrStoragePhase
case trans_tick_tock:
return cs.advance(4) // trans_tick_tock$001 is_tock:Bool
&& t_TrStoragePhase.skip(cs) // storage:TrStoragePhase
&& t_TrStoragePhase.skip(cs) // storage_ph:TrStoragePhase
&& t_TrComputePhase.skip(cs) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.skip(cs) // action:(Maybe ^TrActionPhase)
&& cs.advance(2); // aborted:Bool destroyed:Bool
case trans_split_prepare:
return cs.advance(4) // trans_split_prepare$0100
&& t_SplitMergeInfo.skip(cs) // split_info:SplitMergeInfo
&& Maybe<TrStoragePhase>{}.skip(cs) // storage_ph:(Maybe TrStoragePhase)
&& t_TrComputePhase.skip(cs) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.skip(cs) // action:(Maybe ^TrActionPhase)
&& cs.advance(2); // aborted:Bool destroyed:Bool
@ -1346,6 +1361,7 @@ bool TransactionDescr::skip(vm::CellSlice& cs) const {
return cs.advance(4) // trans_merge_install$0111
&& t_SplitMergeInfo.skip(cs) // split_info:SplitMergeInfo
&& t_Ref_Transaction.skip(cs) // prepare_transaction:^Transaction
&& Maybe<TrStoragePhase>{}.skip(cs) // storage_ph:(Maybe TrStoragePhase)
&& Maybe<TrCreditPhase>{}.skip(cs) // credit_ph:(Maybe TrCreditPhase)
&& Maybe<TrComputePhase>{}.skip(cs) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.skip(cs) // action:(Maybe ^TrActionPhase)
@ -1370,13 +1386,14 @@ bool TransactionDescr::validate_skip(vm::CellSlice& cs, bool weak) const {
&& t_TrStoragePhase.validate_skip(cs, weak); // storage_ph:TrStoragePhase
case trans_tick_tock:
return cs.advance(4) // trans_tick_tock$001 is_tock:Bool
&& t_TrStoragePhase.validate_skip(cs, weak) // storage:TrStoragePhase
&& t_TrStoragePhase.validate_skip(cs, weak) // storage_ph:TrStoragePhase
&& t_TrComputePhase.validate_skip(cs, weak) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase)
&& cs.advance(2); // aborted:Bool destroyed:Bool
case trans_split_prepare:
return cs.advance(4) // trans_split_prepare$0100
&& t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo
&& Maybe<TrStoragePhase>{}.validate_skip(cs, weak) // storage_ph:(Maybe TrStoragePhase)
&& t_TrComputePhase.validate_skip(cs, weak) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase)
&& cs.advance(2); // aborted:Bool destroyed:Bool
@ -1394,6 +1411,7 @@ bool TransactionDescr::validate_skip(vm::CellSlice& cs, bool weak) const {
return cs.advance(4) // trans_merge_install$0111
&& t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo
&& t_Ref_Transaction.validate_skip(cs, weak) // prepare_transaction:^Transaction
&& Maybe<TrStoragePhase>{}.validate_skip(cs, weak) // storage_ph:(Maybe TrStoragePhase)
&& Maybe<TrCreditPhase>{}.validate_skip(cs, weak) // credit_ph:(Maybe TrCreditPhase)
&& Maybe<TrComputePhase>{}.validate_skip(cs, weak) // compute_ph:TrComputePhase
&& Maybe<RefTo<TrActionPhase>>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase)
@ -1407,6 +1425,53 @@ int TransactionDescr::get_tag(const vm::CellSlice& cs) const {
return (t >= 0 && t <= 7) ? (t == 3 ? 2 : t) : -1;
}
bool TransactionDescr::skip_to_storage_phase(vm::CellSlice& cs, bool& found) const {
found = false;
switch (get_tag(cs)) {
case trans_ord:
return cs.advance(4 + 1) // trans_ord$0000 storage_first:Bool
&& cs.fetch_bool_to(found); // storage_ph:(Maybe TrStoragePhase)
case trans_storage:
return cs.advance(4) // trans_storage$0001
&& (found = true); // storage_ph:TrStoragePhase
case trans_tick_tock:
return cs.advance(4) // trans_tick_tock$001 is_tock:Bool
&& (found = true); // storage_ph:TrStoragePhase
case trans_split_prepare:
return cs.advance(4) // trans_split_prepare$0100
&& t_SplitMergeInfo.skip(cs) // split_info:SplitMergeInfo
&& cs.fetch_bool_to(found); // storage_ph:(Maybe TrStoragePhase)
case trans_split_install:
return true;
case trans_merge_prepare:
return cs.advance(4) // trans_merge_prepare$0110
&& t_SplitMergeInfo.skip(cs) // split_info:SplitMergeInfo
&& (found = true); // storage_ph:TrStoragePhase
case trans_merge_install:
return cs.advance(4) // trans_merge_install$0111
&& t_SplitMergeInfo.skip(cs) // split_info:SplitMergeInfo
&& t_Ref_Transaction.skip(cs) // prepare_transaction:^Transaction
&& cs.fetch_bool_to(found); // storage_ph:(Maybe TrStoragePhase)
}
return false;
}
bool TransactionDescr::get_storage_fees(Ref<vm::Cell> cell, td::RefInt256& storage_fees) const {
if (cell.is_null()) {
return false;
}
auto cs = vm::load_cell_slice(std::move(cell));
bool found;
if (!skip_to_storage_phase(cs, found)) {
return false;
} else if (found) {
return t_TrStoragePhase.get_storage_fees(cs, storage_fees);
} else {
storage_fees = td::make_refint(0);
return true;
}
}
const TransactionDescr t_TransactionDescr;
bool Transaction_aux::skip(vm::CellSlice& cs) const {
@ -1447,6 +1512,32 @@ bool Transaction::validate_skip(vm::CellSlice& cs, bool weak) const {
&& RefTo<TransactionDescr>{}.validate_skip(cs, weak); // description:^TransactionDescr
}
bool Transaction::get_storage_fees(Ref<vm::Cell> cell, td::RefInt256& storage_fees) const {
Ref<vm::Cell> tdescr;
return get_descr(std::move(cell), tdescr) && t_TransactionDescr.get_storage_fees(std::move(tdescr), storage_fees);
}
bool Transaction::get_descr(Ref<vm::Cell> cell, Ref<vm::Cell>& tdescr) const {
if (cell.is_null()) {
return false;
} else {
auto cs = vm::load_cell_slice(std::move(cell));
return cs.is_valid() && get_descr(cs, tdescr) && cs.empty_ext();
}
}
bool Transaction::get_descr(vm::CellSlice& cs, Ref<vm::Cell>& tdescr) const {
return cs.advance(
4 + 256 + 64 + 256 + 64 + 32 +
15) // transaction$0111 account_addr:uint256 lt:uint64 prev_trans_hash:bits256 prev_trans_lt:uint64 now:uint32 outmsg_cnt:uint15
&& t_AccountStatus.skip(cs) // orig_status:AccountStatus
&& t_AccountStatus.skip(cs) // end_status:AccountStatus
&& cs.advance_refs(1) // ^[ in_msg:(Maybe ^Message) out_msgs:(HashmapE 15 ^Message) ]
&& t_CurrencyCollection.skip(cs) // total_fees:CurrencyCollection
&& cs.advance_refs(1) // state_update:^(MERKLE_UPDATE Account)
&& cs.fetch_ref_to(tdescr); // description:^TransactionDescr
}
bool Transaction::get_total_fees(vm::CellSlice&& cs, block::CurrencyCollection& total_fees) const {
return cs.is_valid() && cs.fetch_ulong(4) == 7 // transaction$0111
&&

View file

@ -614,6 +614,8 @@ extern const AccStatusChange t_AccStatusChange;
struct TrStoragePhase final : TLB_Complex {
bool skip(vm::CellSlice& cs) const override;
bool validate_skip(vm::CellSlice& cs, bool weak = false) const override;
bool get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const;
bool maybe_get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const;
};
extern const TrStoragePhase t_TrStoragePhase;
@ -693,6 +695,8 @@ struct TransactionDescr final : TLB_Complex {
bool skip(vm::CellSlice& cs) const override;
bool validate_skip(vm::CellSlice& cs, bool weak = false) const override;
int get_tag(const vm::CellSlice& cs) const override;
bool skip_to_storage_phase(vm::CellSlice& cs, bool& found) const;
bool get_storage_fees(Ref<vm::Cell> cell, td::RefInt256& storage_fees) const;
};
extern const TransactionDescr t_TransactionDescr;
@ -708,6 +712,9 @@ struct Transaction final : TLB_Complex {
bool skip(vm::CellSlice& cs) const override;
bool validate_skip(vm::CellSlice& cs, bool weak = false) const override;
bool get_total_fees(vm::CellSlice&& cs, block::CurrencyCollection& total_fees) const;
bool get_descr(Ref<vm::Cell> cell, Ref<vm::Cell>& tdescr) const;
bool get_descr(vm::CellSlice& cs, Ref<vm::Cell>& tdescr) const;
bool get_storage_fees(Ref<vm::Cell> cell, td::RefInt256& storage_fees) const;
};
extern const Transaction t_Transaction;

View file

@ -368,14 +368,14 @@ std::unique_ptr<MsgProcessedUptoCollection> MsgProcessedUptoCollection::unpack(t
return v && v->valid ? std::move(v) : std::unique_ptr<MsgProcessedUptoCollection>{};
}
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const& {
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const & {
return ton::shard_is_ancestor(shard, other.shard) && mc_seqno >= other.mc_seqno &&
(last_inmsg_lt > other.last_inmsg_lt ||
(last_inmsg_lt == other.last_inmsg_lt && !(last_inmsg_hash < other.last_inmsg_hash)));
}
bool MsgProcessedUpto::contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
ton::BlockSeqno other_mc_seqno) const& {
ton::BlockSeqno other_mc_seqno) const & {
return ton::shard_is_ancestor(shard, other_shard) && mc_seqno >= other_mc_seqno &&
(last_inmsg_lt > other_lt || (last_inmsg_lt == other_lt && !(last_inmsg_hash < other_hash)));
}
@ -552,7 +552,9 @@ bool MsgProcessedUpto::already_processed(const EnqueuedMsgDescr& msg) const {
if (msg.lt_ == last_inmsg_lt && last_inmsg_hash < msg.hash_) {
return false;
}
if (ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
if (msg.same_workchain() && ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
// this branch is needed only for messages generated in the same shard
// (such messages could have been processed without a reference from the masterchain)
// ? enable this branch only if an extra boolean parameter is set ?
return true;
}

View file

@ -145,6 +145,9 @@ struct EnqueuedMsgDescr {
return false;
}
bool unpack(vm::CellSlice& cs);
bool same_workchain() const {
return cur_prefix_.workchain == next_prefix_.workchain;
}
};
using compute_shard_end_lt_func_t = std::function<ton::LogicalTime(ton::AccountIdPrefixFull)>;

View file

@ -325,7 +325,7 @@ trans_ord$0000 credit_first:Bool
trans_storage$0001 storage_ph:TrStoragePhase
= TransactionDescr;
trans_tick_tock$001 is_tock:Bool storage:TrStoragePhase
trans_tick_tock$001 is_tock:Bool storage_ph:TrStoragePhase
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool = TransactionDescr;
//
@ -333,6 +333,7 @@ split_merge_info$_ cur_shard_pfx_len:(## 6)
acc_split_depth:(## 6) this_addr:bits256 sibling_addr:bits256
= SplitMergeInfo;
trans_split_prepare$0100 split_info:SplitMergeInfo
storage_ph:(Maybe TrStoragePhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool
= TransactionDescr;
@ -345,6 +346,7 @@ trans_merge_prepare$0110 split_info:SplitMergeInfo
= TransactionDescr;
trans_merge_install$0111 split_info:SplitMergeInfo
prepare_transaction:^Transaction
storage_ph:(Maybe TrStoragePhase)
credit_ph:(Maybe TrCreditPhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool
@ -609,6 +611,9 @@ gas_prices_ext#de gas_price:uint64 gas_limit:uint64 special_gas_limit:uint64 gas
block_gas_limit:uint64 freeze_due_limit:uint64 delete_due_limit:uint64
= GasLimitsPrices;
gas_flat_pfx#d1 flat_gas_limit:uint64 flat_gas_price:uint64 other:GasLimitsPrices
= GasLimitsPrices;
config_mc_gas_prices#_ GasLimitsPrices = ConfigParam 20;
config_gas_prices#_ GasLimitsPrices = ConfigParam 21;

View file

@ -672,9 +672,56 @@ bool Transaction::prepare_credit_phase() {
return true;
}
bool ComputePhaseConfig::parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit,
td::RefInt256& delete_due_limit) {
return cell.not_null() &&
parse_GasLimitsPrices(vm::load_cell_slice_ref(std::move(cell)), freeze_due_limit, delete_due_limit);
}
bool ComputePhaseConfig::parse_GasLimitsPrices(Ref<vm::CellSlice> cs, td::RefInt256& freeze_due_limit,
td::RefInt256& delete_due_limit) {
if (cs.is_null()) {
return false;
}
block::gen::GasLimitsPrices::Record_gas_flat_pfx flat;
if (tlb::csr_unpack(cs, flat)) {
bool ok = parse_GasLimitsPrices(std::move(flat.other), freeze_due_limit, delete_due_limit);
flat_gas_limit = flat.flat_gas_limit;
flat_gas_price = flat.flat_gas_price;
return ok;
}
flat_gas_limit = flat_gas_price = 0;
auto f = [&](const auto& r, td::uint64 spec_limit) {
gas_limit = r.gas_limit;
special_gas_limit = spec_limit;
gas_credit = r.gas_credit;
gas_price = r.gas_price;
freeze_due_limit = td::RefInt256{true, r.freeze_due_limit};
delete_due_limit = td::RefInt256{true, r.delete_due_limit};
};
block::gen::GasLimitsPrices::Record_gas_prices_ext rec;
if (tlb::csr_unpack(cs, rec)) {
f(rec, rec.special_gas_limit);
} else {
block::gen::GasLimitsPrices::Record_gas_prices rec0;
if (tlb::csr_unpack(std::move(cs), rec0)) {
f(rec0, rec0.gas_limit);
} else {
return false;
}
}
compute_threshold();
return true;
}
void ComputePhaseConfig::compute_threshold() {
gas_price256 = td::RefInt256{true, gas_price};
max_gas_threshold = td::rshift(gas_price256 * gas_limit, 16, 1);
if (gas_limit > flat_gas_limit) {
max_gas_threshold =
td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_refint(flat_gas_price);
} else {
max_gas_threshold = td::make_refint(flat_gas_price);
}
}
td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const {
@ -684,8 +731,11 @@ td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const {
if (nanograms >= max_gas_threshold) {
return gas_limit;
}
auto res = td::div(std::move(nanograms) << 16, gas_price256);
return res->to_long();
if (nanograms < flat_gas_price) {
return 0;
}
auto res = td::div((std::move(nanograms) - flat_gas_price) << 16, gas_price256);
return res->to_long() + flat_gas_limit;
}
td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const {
@ -855,6 +905,16 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
cp.skip_reason = ComputePhase::sk_no_gas;
return true;
}
// Compute gas limits
if (!compute_gas_limits(cp, cfg)) {
compute_phase.reset();
return false;
}
if (!cp.gas_limit && !cp.gas_credit) {
// no gas
cp.skip_reason = ComputePhase::sk_no_gas;
return true;
}
if (in_msg_state.not_null()) {
LOG(DEBUG) << "HASH(in_msg_state) = " << in_msg_state->get_hash().bits().to_hex(256)
<< ", account_state_hash = " << account.state_hash.to_hex();
@ -883,11 +943,6 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
} else if (in_msg_state.not_null()) {
unpack_msg_state(true); // use only libraries
}
// Compute gas limits
if (!compute_gas_limits(cp, cfg)) {
compute_phase.reset();
return false;
}
// initialize VM
Ref<vm::Stack> stack = prepare_vm_stack(cp);
if (stack.is_null()) {

View file

@ -98,6 +98,8 @@ struct ComputePhaseConfig {
td::uint64 gas_limit;
td::uint64 special_gas_limit;
td::uint64 gas_credit;
td::uint64 flat_gas_limit = 0;
td::uint64 flat_gas_price = 0;
static constexpr td::uint64 gas_infty = (1ULL << 63) - 1;
td::RefInt256 gas_price256;
td::RefInt256 max_gas_threshold;
@ -126,6 +128,8 @@ struct ComputePhaseConfig {
Ref<vm::Cell> get_lib_root() const {
return libraries ? libraries->get_root_cell() : Ref<vm::Cell>{};
}
bool parse_GasLimitsPrices(Ref<vm::CellSlice> cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
bool parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
};
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms

View file

@ -79,7 +79,7 @@ class SourceLookup {
if (os_time_) {
return os_time_->now();
}
return static_cast<td::uint32>(td::Time::now());
return static_cast<td::uint32>(td::Clocks::system());
}
protected:

View file

@ -25,7 +25,7 @@
{ bl word 1 { -rot 2 'nop does swap 0 (create) }
} :: 2=:
{ <b swap s, b> } : s>c
{ s>c hash } : shash
{ s>c hashB } : shash
// to be more efficiently re-implemented in C++ in the future
{ dup 0< ' negate if } : abs
{ 2dup > ' swap if } : minmax

View file

@ -764,13 +764,17 @@ void interpret_string_to_bytes(vm::Stack& stack) {
stack.push_bytes(stack.pop_string());
}
void interpret_bytes_hash(vm::Stack& stack) {
void interpret_bytes_hash(vm::Stack& stack, bool as_uint) {
std::string str = stack.pop_bytes();
unsigned char buffer[32];
digest::hash_str<digest::SHA256>(buffer, str.c_str(), str.size());
td::RefInt256 x{true};
x.write().import_bytes(buffer, 32, false);
stack.push_int(std::move(x));
if (as_uint) {
td::RefInt256 x{true};
x.write().import_bytes(buffer, 32, false);
stack.push_int(std::move(x));
} else {
stack.push_bytes(std::string{(char*)buffer, 32});
}
}
void interpret_empty(vm::Stack& stack) {
@ -892,11 +896,15 @@ void interpret_builder_remaining_bitrefs(vm::Stack& stack, int mode) {
}
}
void interpret_cell_hash(vm::Stack& stack) {
void interpret_cell_hash(vm::Stack& stack, bool as_uint) {
auto cell = stack.pop_cell();
td::RefInt256 hash{true};
hash.write().import_bytes(cell->get_hash().as_slice().ubegin(), 32, false);
stack.push_int(std::move(hash));
if (as_uint) {
td::RefInt256 hash{true};
hash.write().import_bytes(cell->get_hash().as_slice().ubegin(), 32, false);
stack.push_int(std::move(hash));
} else {
stack.push_bytes(cell->get_hash().as_slice().str());
}
}
void interpret_store_ref(vm::Stack& stack) {
@ -934,7 +942,9 @@ void interpret_fetch(vm::Stack& stack, int mode) {
auto n = stack.pop_smallint_range(256 + (mode & 1));
auto cs = stack.pop_cellslice();
if (!cs->have(n)) {
stack.push(std::move(cs));
if (mode & 2) {
stack.push(std::move(cs));
}
stack.push_bool(false);
if (!(mode & 4)) {
throw IntError{"end of data while reading integer from cell"};
@ -959,7 +969,9 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) {
unsigned n = stack.pop_smallint_range(127);
auto cs = stack.pop_cellslice();
if (!cs->have(n * 8)) {
stack.push(std::move(cs));
if (mode & 2) {
stack.push(std::move(cs));
}
stack.push_bool(false);
if (!(mode & 4)) {
throw IntError{"end of data while reading byte string from cell"};
@ -970,7 +982,7 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) {
if (mode & 2) {
cs.write().fetch_bytes(tmp, n);
} else {
cs.write().prefetch_bytes(tmp, n);
cs->prefetch_bytes(tmp, n);
}
std::string s{tmp, tmp + n};
if (mode & 1) {
@ -978,7 +990,9 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) {
} else {
stack.push_string(std::move(s));
}
stack.push(std::move(cs));
if (mode & 2) {
stack.push(std::move(cs));
}
if (mode & 4) {
stack.push_bool(true);
}
@ -1009,13 +1023,15 @@ void interpret_cell_remaining(vm::Stack& stack) {
void interpret_fetch_ref(vm::Stack& stack, int mode) {
auto cs = stack.pop_cellslice();
if (!cs->have_refs(1)) {
stack.push(std::move(cs));
if (mode & 2) {
stack.push(std::move(cs));
}
stack.push_bool(false);
if (!(mode & 4)) {
throw IntError{"end of data while reading reference from cell"};
}
} else {
auto cell = (mode & 2) ? cs.write().fetch_ref() : cs.write().prefetch_ref();
auto cell = (mode & 2) ? cs.write().fetch_ref() : cs->prefetch_ref();
if (mode & 2) {
stack.push(std::move(cs));
}
@ -2474,7 +2490,9 @@ void init_words_common(Dictionary& d) {
d.def_stack_word("B>Lu@+ ", std::bind(interpret_bytes_fetch_int, _1, 0x12));
d.def_stack_word("B>Li@+ ", std::bind(interpret_bytes_fetch_int, _1, 0x13));
d.def_stack_word("$>B ", interpret_string_to_bytes);
d.def_stack_word("Bhash ", interpret_bytes_hash);
d.def_stack_word("Bhash ", std::bind(interpret_bytes_hash, _1, true));
d.def_stack_word("Bhashu ", std::bind(interpret_bytes_hash, _1, true));
d.def_stack_word("BhashB ", std::bind(interpret_bytes_hash, _1, false));
// cell manipulation (create, write and modify cells)
d.def_stack_word("<b ", interpret_empty);
d.def_stack_word("i, ", std::bind(interpret_store, _1, true));
@ -2496,7 +2514,9 @@ void init_words_common(Dictionary& d) {
d.def_stack_word("brembits ", std::bind(interpret_builder_remaining_bitrefs, _1, 1));
d.def_stack_word("bremrefs ", std::bind(interpret_builder_remaining_bitrefs, _1, 2));
d.def_stack_word("brembitrefs ", std::bind(interpret_builder_remaining_bitrefs, _1, 3));
d.def_stack_word("hash ", interpret_cell_hash);
d.def_stack_word("hash ", std::bind(interpret_cell_hash, _1, true));
d.def_stack_word("hashu ", std::bind(interpret_cell_hash, _1, true));
d.def_stack_word("hashB ", std::bind(interpret_cell_hash, _1, false));
// cellslice manipulation (read from cells)
d.def_stack_word("<s ", interpret_from_cell);
d.def_stack_word("i@ ", std::bind(interpret_fetch, _1, 1));

View file

@ -628,7 +628,7 @@ bool Op::generate_code_step(Stack& stack) {
stack.opt_show();
StackLayout layout1 = stack.vars();
bool next_empty = next->is_empty();
stack.o << (next_empty ? "WHILEEND:<{" : "WHILE:<{");
stack.o << "WHILE:<{";
stack.o.indent();
stack.forget_const();
block0->generate_code_all(stack);
@ -638,7 +638,7 @@ bool Op::generate_code_step(Stack& stack) {
stack.modified();
stack.o.undent();
Stack stack_copy{stack};
stack.o << (next_empty ? "}>" : "}>DO<{");
stack.o << (next_empty ? "}>DO:" : "}>DO<{");
if (!next_empty) {
stack.o.indent();
}

View file

@ -169,6 +169,11 @@ bool SourceReader::load_line() {
error("line too long");
return false;
}
if (len && cur_line.back() == '\r') {
// CP/M line breaks support
cur_line.pop_back();
--len;
}
loc.text = cur_line;
cur_line_len = (int)len;
loc.line_pos = 0;

View file

@ -15,10 +15,10 @@ cr ."initial basechain state is:" cr dup <s csr. cr
dup dup 31 boc+>B dup Bx. cr
dup "basestate0" +suffix +".boc" tuck B>file
."(Initial basechain state saved to file " type .")" cr
Bhash dup =: basestate0_fhash
Bhashu dup =: basestate0_fhash
."file hash=" dup x. space 256 u>B dup B>base64url type cr
"basestate0" +suffix +".fhash" B>file
hash dup =: basestate0_rhash
hashu dup =: basestate0_rhash
."root hash=" dup x. space 256 u>B dup B>base64url type cr
"basestate0" +suffix +".rhash" B>file
@ -227,10 +227,10 @@ cr cr ."new state is:" cr dup <s csr. cr
dup 31 boc+>B dup Bx. cr
dup "zerostate" +suffix +".boc" tuck B>file
."(Initial masterchain state saved to file " type .")" cr
Bhash dup =: zerostate_fhash
Bhashu dup =: zerostate_fhash
."file hash=" dup x. space 256 u>B dup B>base64url type cr
"zerostate" +suffix +".fhash" B>file
hash dup =: zerostate_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr
hashu dup =: zerostate_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr
"zerostate" +suffix +".rhash" B>file
basestate0_rhash ."Basestate0 root hash=" dup x. space 256 u>B B>base64url type cr
basestate0_fhash ."Basestate0 file hash=" dup x. space 256 u>B B>base64url type cr

View file

@ -15,10 +15,10 @@ cr ."initial basechain state is:" cr dup <s csr. cr
dup dup 31 boc+>B dup Bx. cr
dup "basestate0" +suffix +".boc" tuck B>file
."(Initial basechain state saved to file " type .")" cr
Bhash dup =: basestate0_fhash
Bhashu dup =: basestate0_fhash
."file hash=" dup x. space 256 u>B dup B>base64url type cr
"basestate0" +suffix +".fhash" B>file
hash dup =: basestate0_rhash
hashu dup =: basestate0_rhash
."root hash=" dup x. space 256 u>B dup B>base64url type cr
"basestate0" +suffix +".rhash" B>file
@ -231,10 +231,10 @@ cr cr ."new state is:" cr dup <s csr. cr
dup 31 boc+>B dup Bx. cr
dup "zerostate" +suffix +".boc" tuck B>file
."(Initial masterchain state saved to file " type .")" cr
Bhash dup =: zerostate_fhash
Bhashu dup =: zerostate_fhash
."file hash= " dup X. space 256 u>B dup B>base64url type cr
"zerostate" +suffix +".fhash" B>file
hash dup =: zerostate_rhash ."root hash= " dup X. space 256 u>B dup B>base64url type cr
hashu dup =: zerostate_rhash ."root hash= " dup X. space 256 u>B dup B>base64url type cr
"zerostate" +suffix +".rhash" B>file
basestate0_rhash ."Basestate0 root hash= " dup X. space 256 u>B B>base64url type cr
basestate0_fhash ."Basestate0 file hash= " dup X. space 256 u>B B>base64url type cr

View file

@ -0,0 +1,41 @@
;; Heavy-duty wallet for mass transfers (e.g., for cryptocurrency exchanges)
;; accepts orders for up to 254 internal messages (transfers) in one external message
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
var cs = in_msg;
var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
throw_if(35, valid_until <= now());
var ds = get_data().begin_parse();
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
ds.end_parse();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, subwallet_id == stored_subwallet);
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
var dict = cs~load_dict();
cs.end_parse();
accept_message();
int i = -1;
do {
(i, var cs, var f) = dict.idict_get_next?(16, i);
if (f) {
var mode = cs~load_uint(8);
send_raw_message(cs~load_ref(), mode);
}
} until (~ f);
set_data(begin_cell()
.store_uint(stored_seqno + 1, 32)
.store_uint(stored_subwallet, 32)
.store_uint(public_key, 256)
.end_cell());
}
;; Get methods
int seqno() method_id {
return get_data().begin_parse().preload_uint(32);
}

View file

@ -0,0 +1,79 @@
// automatically generated from `smartcont/stdlib.fc` `smartcont/highload-wallet-code.fc`
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
85143 DECLMETHOD seqno
recv_internal PROC:<{
// in_msg
DROP //
}>
recv_external PROC:<{
// in_msg
9 PUSHPOW2 // in_msg _3=512
LDSLICEX // signature in_msg
DUP // signature in_msg cs
32 LDU // signature in_msg _9 cs
32 LDU // signature in_msg _9 _12 cs
32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs
NOW // signature in_msg subwallet_id valid_until msg_seqno cs _19
s1 s3 XCHG // signature in_msg subwallet_id cs msg_seqno valid_until _19
LEQ // signature in_msg subwallet_id cs msg_seqno _20
35 THROWIF
c4 PUSH // signature in_msg subwallet_id cs msg_seqno _23
CTOS // signature in_msg subwallet_id cs msg_seqno ds
32 LDU // signature in_msg subwallet_id cs msg_seqno _28 ds
32 LDU // signature in_msg subwallet_id cs msg_seqno _28 _31 ds
256 LDU // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key ds
ENDS
s3 s2 XCPU // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet msg_seqno stored_seqno
EQUAL // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet _39
33 THROWIFNOT
s4 s4 XCPU // signature in_msg stored_subwallet cs public_key stored_seqno subwallet_id stored_subwallet
EQUAL // signature in_msg stored_subwallet cs public_key stored_seqno _42
34 THROWIFNOT
s0 s4 XCHG // signature stored_seqno stored_subwallet cs public_key in_msg
HASHSU // signature stored_seqno stored_subwallet cs public_key _45
s0 s5 s5 XC2PU // public_key stored_seqno stored_subwallet cs _45 signature public_key
CHKSIGNU // public_key stored_seqno stored_subwallet cs _46
35 THROWIFNOT
LDDICT // public_key stored_seqno stored_subwallet dict cs
ENDS
ACCEPT
-1 PUSHINT // public_key stored_seqno stored_subwallet dict i=-1
UNTIL:<{
OVER
16 PUSHINT // public_key stored_seqno stored_subwallet dict i dict _57=16
DICTIGETNEXT
NULLSWAPIFNOT
NULLSWAPIFNOT // public_key stored_seqno stored_subwallet dict cs i f
DUP // public_key stored_seqno stored_subwallet dict cs i f f
IF:<{ // public_key stored_seqno stored_subwallet dict cs i f
s0 s2 XCHG // public_key stored_seqno stored_subwallet dict f i cs
8 LDU // public_key stored_seqno stored_subwallet dict f i mode cs
LDREF // public_key stored_seqno stored_subwallet dict f i mode _100 _99
DROP // public_key stored_seqno stored_subwallet dict f i mode _63
SWAP // public_key stored_seqno stored_subwallet dict f i _63 mode
SENDRAWMSG
}>ELSE<{
s2 POP // public_key stored_seqno stored_subwallet dict f i
}>
SWAP // public_key stored_seqno stored_subwallet dict i f
NOT // public_key stored_seqno stored_subwallet dict i _66
}> // public_key stored_seqno stored_subwallet dict i
2DROP // public_key stored_seqno stored_subwallet
SWAP // public_key stored_subwallet stored_seqno
INC // public_key stored_subwallet _68
NEWC // public_key stored_subwallet _68 _69
32 STU // public_key stored_subwallet _71
32 STU // public_key _73
256 STU // _75
ENDC // _76
c4 POP
}>
seqno PROC:<{
//
c4 PUSH // _0
CTOS // _1
32 PLDU // _3
}>
}END>c

View file

@ -0,0 +1,65 @@
;; Heavy-duty wallet for mass transfers (e.g., for cryptocurrency exchanges)
;; accepts orders for up to 254 internal messages (transfers) in one external message
;; this version does not use seqno for replay protection; instead, it remembers all recent query_ids
;; in this way several external messages with different query_id can be sent in parallel
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
var cs = in_msg;
var (subwallet_id, query_id) = (cs~load_uint(32), cs~load_uint(64));
var bound = (now() << 32);
throw_if(35, query_id < bound);
var ds = get_data().begin_parse();
var (stored_subwallet, last_cleaned, public_key, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict());
ds.end_parse();
(_, var found?) = old_queries.udict_get?(64, query_id);
throw_if(32, found?);
throw_unless(34, subwallet_id == stored_subwallet);
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
var dict = cs~load_dict();
cs.end_parse();
accept_message();
int i = -1;
do {
(i, var cs, var f) = dict.idict_get_next?(16, i);
if (f) {
var mode = cs~load_uint(8);
send_raw_message(cs~load_ref(), mode);
}
} until (~ f);
bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
old_queries~udict_set_builder(64, query_id, begin_cell());
var queries = old_queries;
do {
var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64);
f~touch();
if (f) {
f = (i < bound);
}
if (f) {
old_queries = old_queries';
last_cleaned = i;
}
} until (~ f);
set_data(begin_cell()
.store_uint(stored_subwallet, 32)
.store_uint(last_cleaned, 64)
.store_uint(public_key, 256)
.store_dict(old_queries)
.end_cell());
}
;; Get methods
;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
int processed?(int query_id) method_id {
var ds = get_data().begin_parse();
var (_, last_cleaned, _, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict());
ds.end_parse();
(_, var found) = old_queries.udict_get?(64, query_id);
return found ? true : - (query_id <= last_cleaned);
}

View file

@ -0,0 +1,134 @@
// automatically generated from `smartcont/stdlib.fc` `smartcont/highload-wallet-v2-code.fc`
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
117746 DECLMETHOD processed?
recv_internal PROC:<{
// in_msg
DROP //
}>
recv_external PROC:<{
// in_msg
9 PUSHPOW2 // in_msg _3=512
LDSLICEX // signature in_msg
DUP // signature in_msg cs
32 LDU // signature in_msg _8 cs
64 LDU // signature in_msg subwallet_id query_id cs
NOW // signature in_msg subwallet_id query_id cs _15
32 LSHIFT# // signature in_msg subwallet_id query_id cs bound
s2 s0 PUSH2 // signature in_msg subwallet_id query_id cs bound query_id bound
LESS // signature in_msg subwallet_id query_id cs bound _19
35 THROWIF
c4 PUSH // signature in_msg subwallet_id query_id cs bound _22
CTOS // signature in_msg subwallet_id query_id cs bound ds
32 LDU // signature in_msg subwallet_id query_id cs bound _28 ds
64 LDU // signature in_msg subwallet_id query_id cs bound _28 _31 ds
256 LDU // signature in_msg subwallet_id query_id cs bound _28 _31 _34 ds
LDDICT // signature in_msg subwallet_id query_id cs bound stored_subwallet last_cleaned public_key old_queries ds
ENDS
s6 s0 PUSH2
64 PUSHINT // signature in_msg subwallet_id query_id cs bound stored_subwallet last_cleaned public_key old_queries query_id old_queries _42=64
DICTUGET
NULLSWAPIFNOT // signature in_msg subwallet_id query_id cs bound stored_subwallet last_cleaned public_key old_queries _115 _116
NIP // signature in_msg subwallet_id query_id cs bound stored_subwallet last_cleaned public_key old_queries found?
32 THROWIF
s7 s3 XCPU // signature in_msg old_queries query_id cs bound stored_subwallet last_cleaned public_key subwallet_id stored_subwallet
EQUAL // signature in_msg old_queries query_id cs bound stored_subwallet last_cleaned public_key _47
34 THROWIFNOT
s0 s7 XCHG // signature public_key old_queries query_id cs bound stored_subwallet last_cleaned in_msg
HASHSU // signature public_key old_queries query_id cs bound stored_subwallet last_cleaned _50
s0 s8 s7 XC2PU // last_cleaned public_key old_queries query_id cs bound stored_subwallet _50 signature public_key
CHKSIGNU // last_cleaned public_key old_queries query_id cs bound stored_subwallet _51
35 THROWIFNOT
s0 s2 XCHG // last_cleaned public_key old_queries query_id stored_subwallet bound cs
LDDICT // last_cleaned public_key old_queries query_id stored_subwallet bound dict cs
ENDS
ACCEPT
-1 PUSHINT // last_cleaned public_key old_queries query_id stored_subwallet bound dict i=-1
UNTIL:<{
OVER
16 PUSHINT // last_cleaned public_key old_queries query_id stored_subwallet bound dict i dict _62=16
DICTIGETNEXT
NULLSWAPIFNOT
NULLSWAPIFNOT // last_cleaned public_key old_queries query_id stored_subwallet bound dict cs i f
DUP // last_cleaned public_key old_queries query_id stored_subwallet bound dict cs i f f
IF:<{ // last_cleaned public_key old_queries query_id stored_subwallet bound dict cs i f
s0 s2 XCHG // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i cs
8 LDU // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i mode cs
LDREF // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i mode _125 _124
DROP // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i mode _68
SWAP // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i _68 mode
SENDRAWMSG
}>ELSE<{
s2 POP // last_cleaned public_key old_queries query_id stored_subwallet bound dict f i
}>
SWAP // last_cleaned public_key old_queries query_id stored_subwallet bound dict i f
NOT // last_cleaned public_key old_queries query_id stored_subwallet bound dict i _71
}> // last_cleaned public_key old_queries query_id stored_subwallet bound dict i
2DROP // last_cleaned public_key old_queries query_id stored_subwallet bound
38 PUSHPOW2 // last_cleaned public_key old_queries query_id stored_subwallet bound _74
SUB // last_cleaned public_key old_queries query_id stored_subwallet bound
NEWC // last_cleaned public_key old_queries query_id stored_subwallet bound _77
s0 s3 s4 XCHG3
64 PUSHINT // last_cleaned public_key stored_subwallet bound _77 query_id old_queries _78=64
DICTUSETB // last_cleaned public_key stored_subwallet bound old_queries
UNTIL:<{
DUP
64 PUSHINT // last_cleaned public_key stored_subwallet bound old_queries old_queries _85=64
DICTUREMMIN
NULLSWAPIFNOT
NULLSWAPIFNOT // last_cleaned public_key stored_subwallet bound old_queries _126 _128 _127 _129
s2 POP // last_cleaned public_key stored_subwallet bound old_queries old_queries' f i
s1 s0 XCPU // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f f
IF:<{ // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f
DROP // last_cleaned public_key stored_subwallet bound old_queries old_queries' i
s0 s3 PUSH2 // last_cleaned public_key stored_subwallet bound old_queries old_queries' i i bound
LESS // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f
}> // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f
DUP // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f f
IF:<{ // last_cleaned public_key stored_subwallet bound old_queries old_queries' i f
s3 POP
s6 POP // last_cleaned public_key stored_subwallet bound f old_queries
}>ELSE<{
s3 s1 s3 XCHG3
2DROP // last_cleaned public_key stored_subwallet bound f old_queries
}>
SWAP // last_cleaned public_key stored_subwallet bound old_queries f
NOT // last_cleaned public_key stored_subwallet bound old_queries _90
}> // last_cleaned public_key stored_subwallet bound old_queries
NIP // last_cleaned public_key stored_subwallet old_queries
NEWC // last_cleaned public_key stored_subwallet old_queries _91
s1 s2 XCHG // last_cleaned public_key old_queries stored_subwallet _91
32 STU // last_cleaned public_key old_queries _93
s1 s3 XCHG // old_queries public_key last_cleaned _93
64 STU // old_queries public_key _95
256 STU // old_queries _97
STDICT // _98
ENDC // _99
c4 POP
}>
processed? PROC:<{
// query_id
c4 PUSH // query_id _2
CTOS // query_id ds
32 LDU // query_id _29 _28
NIP // query_id ds
64 LDU // query_id _11 ds
256 LDU // query_id _11 _33 _32
NIP // query_id _11 ds
LDDICT // query_id last_cleaned old_queries ds
ENDS
s2 s(-1) PUXC
64 PUSHINT // query_id last_cleaned query_id old_queries _22=64
DICTUGET
NULLSWAPIFNOT // query_id last_cleaned _36 _37
NIP // query_id last_cleaned found
IF:<{ // query_id last_cleaned
2DROP //
TRUE // _24
}>ELSE<{ // query_id last_cleaned
LEQ // _26
NEGATE // _24
}>
}>
}END>c

View file

@ -0,0 +1,69 @@
#!/usr/bin/env fift -s
"TonUtil.fif" include
{ ."usage: " @' $0 type ." <filename-base> <subwallet-id> <order-file> [<savefile>]" cr
."Creates a request with up to 254 orders loaded from <order-file> to high-load v2 (sub)wallet created by new-highload-v2-wallet.fif, with private key loaded from file <filename-base>.pk "
."and address from <filename-base><subwallet-id>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr
."<order-file> is a text file with lines `SEND <dest-addr> <amount>`" cr 1 halt
} : usage
$# dup 3 < swap 4 > or ' usage if
$1 =: file-base
$2 parse-int dup 32 fits ' usage ifnot =: subwallet-id // parse subwallet-id
{ subwallet-id (.) $+ } : +subwallet
$3 =: order-file
def? $4 { @' $4 } { "wallet-query" } cond constant savefile
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
60 constant timeout // external message expires in 60 seconds
file-base +subwallet +".addr" load-address
2dup 2constant wallet_addr
."Source wallet address = " 2dup .addr cr 6 .Addr cr
file-base +".pk" load-keypair nip constant wallet_pk
variable orders dictnew orders !
variable order# order# 0!
// c --
{ <s order# @ dup 254 >= abort"more than 254 orders"
orders @ 16 udict!+ not abort"cannot add order to dictionary"
orders ! order# 1+!
} : add-order
// b body -- b'
{ tuck <s 2dup s-fits? not rot over 1 i, -rot
{ drop swap ref, } { s, nip } cond
} : append-msg-body
// ng wc addr bounce body -- c
{ <b b{01} s, rot 1 i, b{000100} s, 2swap addr, rot Gram,
0 9 64 32 + + 1+ u, swap append-msg-body b>
} : create-int-msg
// ng wc addr bnc --
{ ."Transferring " 3 roll .GR ."to account "
-rot 2dup 4 pick 7 + .Addr ." = " .addr ." bounce=" . cr
} : .transfer
// addr$ ng -- c
{ swap parse-smc-addr // ng wc addr bnc
2over 2over .transfer
<b 0 32 u, b> create-int-msg
} : create-simple-transfer
// c m -- c'
{ <b swap 8 u, swap ref, b> } : create-order
// addr$ ng --
{ create-simple-transfer send-mode create-order add-order } : send
{ bl word bl word $>GR send } : SEND
// parse order file
order-file include
// create external message
now timeout + 32 << <b orders @ dict, b> hashu 32 1<<1- and + =: query_id
<b subwallet-id 32 i, query_id 64 u, orders @ dict, b>
dup ."signing message: " <s csr. cr
dup hashu wallet_pk ed25519_sign_uint
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
swap B, swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
."Query_id is " query_id dup . ."= 0x" X. cr
savefile +".boc" tuck B>file
."(Saved to file " type .")" cr

View file

@ -0,0 +1,68 @@
#!/usr/bin/env fift -s
"TonUtil.fif" include
{ ."usage: " @' $0 type ." <filename-base> <subwallet-id> <seqno> <order-file> [<savefile>]" cr
."Creates a request with up to 254 orders loaded from <order-file> to high-load (sub)wallet created by new-highload-wallet.fif, with private key loaded from file <filename-base>.pk "
."and address from <filename-base><subwallet-id>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr
."<order-file> is a text file with lines `SEND <dest-addr> <amount>`" cr 1 halt
} : usage
$# dup 4 < swap 5 > or ' usage if
$1 =: file-base
$2 parse-int dup 32 fits ' usage ifnot =: subwallet-id // parse subwallet-id
{ subwallet-id (.) $+ } : +subwallet
$3 parse-int =: seqno
$4 =: order-file
def? $5 { @' $5 } { "wallet-query" } cond constant savefile
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
60 constant timeout // external message expires in 60 seconds
file-base +subwallet +".addr" load-address
2dup 2constant wallet_addr
."Source wallet address = " 2dup .addr cr 6 .Addr cr
file-base +".pk" load-keypair nip constant wallet_pk
variable orders dictnew orders !
variable order# order# 0!
// c --
{ <s order# @ dup 254 >= abort"more than 254 orders"
orders @ 16 udict!+ not abort"cannot add order to dictionary"
orders ! order# 1+!
} : add-order
// b body -- b'
{ tuck <s 2dup s-fits? not rot over 1 i, -rot
{ drop swap ref, } { s, nip } cond
} : append-msg-body
// ng wc addr bounce body -- c
{ <b b{01} s, rot 1 i, b{000100} s, 2swap addr, rot Gram,
0 9 64 32 + + 1+ u, swap append-msg-body b>
} : create-int-msg
// ng wc addr bnc --
{ ."Transferring " 3 roll .GR ."to account "
-rot 2dup 4 pick 7 + .Addr ." = " .addr ." bounce=" . cr
} : .transfer
// addr$ ng -- c
{ swap parse-smc-addr // ng wc addr bnc
2over 2over .transfer
<b 0 32 u, b> create-int-msg
} : create-simple-transfer
// c m -- c'
{ <b swap 8 u, swap ref, b> } : create-order
// addr$ ng --
{ create-simple-transfer send-mode create-order add-order } : send
{ bl word bl word $>GR send } : SEND
// parse order file
order-file include
// create external message
<b subwallet-id 32 i, now timeout + 32 u, seqno 32 u, orders @ dict, b>
dup ."signing message: " <s csr. cr
dup hashu wallet_pk ed25519_sign_uint
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
swap B, swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
savefile +".boc" tuck B>file
."(Saved to file " type .")" cr

View file

@ -0,0 +1,47 @@
#!/usr/bin/env fift -s
"TonUtil.fif" include
"Asm.fif" include
{ ."usage: " @' $0 type ." <workchain-id> <subwallet-id> [<filename-base>]" cr
."Creates a new v2 high-load wallet in the specified workchain, with the controlling private key saved to or loaded from <filename-base>.pk "
."('new-wallet.pk' by default)" cr
."<subwallet-id> is the 32-bit identifier of this subwallet among all controlled by the same private key" cr 1 halt
} : usage
$# 2- -2 and ' usage if
$1 parse-workchain-id =: wc // set workchain id from command line argument
$2 parse-int dup =: subwallet-id // parse subwallet-id
32 fits ' usage ifnot
{ subwallet-id (.) $+ } : +subwallet
def? $3 { @' $3 } { "new-wallet" } cond constant file-base
65536 constant timeout // init query times out in 65536 seconds
."Creating new v2 high-load wallet in workchain " wc .
."with subwallet id " subwallet-id . cr
// Create new high-load wallet; source code included from `highload-wallet-v2-code.fif`
"highload-wallet-v2-code.fif" include
// code
<b subwallet-id 32 i, 0 64 u,
file-base +".pk" load-generate-keypair
constant wallet_pk
B, false 1 i,
b> // data
null // no libraries
<b b{0011} s, 3 roll ref, rot ref, swap dict, b> // create StateInit
dup ."StateInit: " <s csr. cr
dup hashu wc swap 2dup 2constant wallet_addr
."new wallet address = " 2dup .addr cr
2dup file-base +subwallet +".addr" save-address-verbose
."Non-bounceable address (for init): " 2dup 7 .Addr cr
."Bounceable address (for later access): " 6 .Addr cr
now timeout + 32 << 1- dup =: query_id
."Init query_id is " dup . ."(0x" X._ .")" cr
<b subwallet-id 32 i, query_id 64 u, false 1 i, b>
dup ."signing message: " <s csr. cr
dup hashu wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
dup ."External message for initialization is " <s csr. cr
2 boc+>B dup Bx. cr
file-base +subwallet +"-query.boc" tuck B>file
."(Saved wallet creating query to file " type .")" cr

Some files were not shown because too many files have changed in this diff Show more